s3_multipart 0.0.10.4 → 0.0.10.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: d98ae2406219c5a736d33bf323dfb675f2649e18
4
+ data.tar.gz: 790a0d7b0d2d42765c05474851eaffd773845a10
5
+ SHA512:
6
+ metadata.gz: c5aec821b693decc4f4d86a4ef4a7fa000dfe3e3a051dc7567c42de585da0a97d466d7193b9b68cfec34e88f40ef62149959f9cacd7ea3b594bf39ad9b093c13
7
+ data.tar.gz: 3885d74fa41187e425cd4273d73bad593221937368efa238592efa5a31c24b2f781e479db623af403f4c97c940fed06335a2060151bf3a0e72f09a987bfcacfc
data/README.md CHANGED
@@ -6,6 +6,8 @@ Multipart uploading allows files to be split into many chunks and uploaded in pa
6
6
 
7
7
  ## What's New
8
8
 
9
+ **0.0.10.5** - See pull request [16](https://github.com/maxgillett/s3_multipart/pull/16) and [18](https://github.com/maxgillett/s3_multipart/pull/18) for detailed changes.
10
+
9
11
  **0.0.10.4** - Fixed a race condition that led to incorrect upload progress feedback.
10
12
 
11
13
  **0.0.10.3** - Fixed a bug that prevented 5-10mb files from being uploaded correctly.
@@ -8,8 +8,9 @@ module S3Multipart
8
8
  response = upload.to_json
9
9
  rescue FileTypeError, FileSizeError => e
10
10
  response = {error: e.message}
11
- rescue
12
- response = {error: 'There was an error initiating the upload'}
11
+ rescue => e
12
+ logger.error "EXC: #{e.message}"
13
+ response = { error: t("s3_multipart.errors.create") }
13
14
  ensure
14
15
  render :json => response
15
16
  end
@@ -26,8 +27,9 @@ module S3Multipart
26
27
  def sign_batch
27
28
  begin
28
29
  response = Upload.sign_batch(params)
29
- rescue
30
- response = {error: 'There was an error in processing your upload'}
30
+ rescue => e
31
+ logger.error "EXC: #{e.message}"
32
+ response = {error: t("s3_multipart.errors.update")}
31
33
  ensure
32
34
  render :json => response
33
35
  end
@@ -36,8 +38,9 @@ module S3Multipart
36
38
  def sign_part
37
39
  begin
38
40
  response = Upload.sign_part(params)
39
- rescue
40
- response = {error: 'There was an error in processing your upload'}
41
+ rescue => e
42
+ logger.error "EXC: #{e.message}"
43
+ response = {error: t("s3_multipart.errors.update")}
41
44
  ensure
42
45
  render :json => response
43
46
  end
@@ -49,8 +52,9 @@ module S3Multipart
49
52
  upload = Upload.find_by_upload_id(params[:upload_id])
50
53
  upload.update_attributes(location: response[:location])
51
54
  upload.execute_callback(:complete, session)
52
- rescue
53
- response = {error: 'There was an error completing the upload'}
55
+ rescue => e
56
+ logger.error "EXC: #{e.message}"
57
+ response = {error: t("s3_multipart.errors.complete")}
54
58
  ensure
55
59
  render :json => response
56
60
  end
@@ -1,8 +1,8 @@
1
1
  module S3Multipart
2
2
  class Upload < ::ActiveRecord::Base
3
3
  extend S3Multipart::TransferHelpers
4
+ include ActionView::Helpers::NumberHelper
4
5
 
5
- attr_accessible :key, :upload_id, :name, :location, :uploader, :size
6
6
  before_create :validate_file_type, :validate_file_size
7
7
 
8
8
  def self.create(params)
@@ -17,7 +17,7 @@ module S3Multipart
17
17
  when :begin
18
18
  controller.on_begin_callback.call(self, session) if controller.on_begin_callback
19
19
  when :complete
20
- controller.on_complete_callback.call(self, session) if controller.on_begin_callback
20
+ controller.on_complete_callback.call(self, session) if controller.on_complete_callback
21
21
  end
22
22
  end
23
23
 
@@ -26,16 +26,24 @@ module S3Multipart
26
26
  def validate_file_size
27
27
  size = self.size
28
28
  limits = deserialize(self.uploader).size_limits
29
- raise FileSizeError, "File size is too small" if limits[:min] > size
30
- raise FileSizeError, "File size is too large" if limits[:max] < size
29
+
30
+ if limits.present?
31
+ if limits.key?(:min) && limits[:min] > size
32
+ raise FileSizeError, I18n.t("s3_multipart.errors.limits.min", min: number_to_human_size(limits[:min]))
33
+ end
34
+
35
+ if limits.key?(:max) && limits[:max] < size
36
+ raise FileSizeError, I18n.t("s3_multipart.errors.limits.max", max: number_to_human_size(limits[:max]))
37
+ end
38
+ end
31
39
  end
32
40
 
33
41
  def validate_file_type
34
42
  ext = self.name.match(/\.([a-zA-Z0-9]+)$/)[1]
35
- controller = deserialize(self.uploader)
43
+ types = deserialize(self.uploader).file_types
36
44
 
37
- if !controller.file_types.include?(ext)
38
- raise FileTypeError, "File type not supported"
45
+ unless types.blank? || types.include?(ext)
46
+ raise FileTypeError, I18n.t("s3_multipart.errors.types", types: upload.deserialize(upload.uploader).file_types.join(", "))
39
47
  end
40
48
  end
41
49
 
@@ -0,0 +1,10 @@
1
+ en:
2
+ s3_multipart:
3
+ errors:
4
+ limits:
5
+ min: "Please choose a larger file (min %{min})"
6
+ max: "Please choose a smaller file (max %{max})"
7
+ types: "Please choose a different format (allowed types: %{types})"
8
+ create: "There was an error initiating the upload"
9
+ update: "There was an error processing the upload"
10
+ complete: "There was an error completing the upload"
@@ -5,43 +5,45 @@ module S3Multipart
5
5
  module TransferHelpers
6
6
 
7
7
  def initiate(options)
8
- real_name = options[:object_name]
9
- unique_name = UUID.generate + real_name.match(/.[A-Za-z0-9]+$/)[0] # clean this up later
10
- url = "/#{unique_name}?uploads"
8
+ url = "/#{unique_name(options)}?uploads"
11
9
 
12
10
  headers = {content_type: options[:content_type]}
13
- headers[:authorization], headers[:date] = sign_request verb: 'POST', url: url, content_type: options[:content_type]
11
+ headers.merge!(options[:headers]) if options.key?(:headers)
12
+ headers[:authorization], headers[:date] = sign_request verb: 'POST',
13
+ url: url,
14
+ content_type: options[:content_type],
15
+ headers: options[:headers]
14
16
 
15
- response = Http.post url, {headers: headers}
17
+ response = Http.post url, headers: headers
16
18
  parsed_response_body = XmlSimple.xml_in(response.body)
17
19
 
18
- return { "key" => parsed_response_body["Key"][0],
19
- "upload_id" => parsed_response_body["UploadId"][0],
20
- "name" => real_name }
20
+ { "key" => parsed_response_body["Key"][0],
21
+ "upload_id" => parsed_response_body["UploadId"][0],
22
+ "name" => options[:object_name] }
21
23
  end
22
24
 
23
25
  def sign_batch(options)
24
- parts = options[:content_lengths].split('-').each_with_index.map do |len, i|
26
+ parts = options[:content_lengths].to_s.split('-').each_with_index.map do |len, i|
25
27
  sign_part(options.merge!({content_length: len, part_number: i+1}))
26
28
  end
27
29
  end
28
30
 
29
31
  def sign_part(options)
30
32
  url = "/#{options[:object_name]}?partNumber=#{options[:part_number]}&uploadId=#{options[:upload_id]}"
31
- authorization, date = sign_request verb: 'PUT', url: url, content_length: options[:content_length]
33
+ authorization, date = sign_request verb: 'PUT', url: URI.escape(url), content_length: options[:content_length]
32
34
 
33
- return {authorization: authorization, date: date}
35
+ { authorization: authorization, date: date }
34
36
  end
35
37
 
36
38
  def complete(options)
37
39
  options[:content_type] = "application/xml"
38
40
 
39
- url = "/#{options[:object_name]}?uploadId=#{options[:upload_id]}"
41
+ url = URI.escape("/#{options[:object_name]}?uploadId=#{options[:upload_id]}")
40
42
 
41
43
  body = format_part_list_in_xml(options)
42
44
  headers = { content_type: options[:content_type],
43
45
  content_length: options[:content_length] }
44
-
46
+
45
47
  headers[:authorization], headers[:date] = sign_request verb: 'POST', url: url, content_type: options[:content_type]
46
48
 
47
49
  response = Http.post url, {headers: headers, body: body}
@@ -55,19 +57,49 @@ module S3Multipart
55
57
  end
56
58
 
57
59
  def sign_request(options)
58
- #options.default = ""
59
- time = Time.now.strftime("%a, %d %b %Y %T %Z")
60
+ time = Time.now.utc.strftime("%a, %d %b %Y %T %Z")
61
+ [calculate_authorization_hash(time, options), time]
62
+ end
63
+
64
+ def unique_name(options)
65
+ url = [UUID.generate, options[:object_name]].join("/")
66
+ controller = S3Multipart::Uploader.deserialize(options[:uploader])
60
67
 
61
- return [calculate_authorization_hash(time, options), time]
68
+ if controller.mount_point && defined?(CarrierWaveDirect)
69
+ uploader = controller.model.to_s.classify.constantize.new.send(controller.mount_point)
70
+
71
+ if uploader.class.ancestors.include?(CarrierWaveDirect::Uploader)
72
+ url = uploader.key.sub(/#{Regexp.escape(CarrierWaveDirect::Uploader::FILENAME_WILDCARD)}\z/, options[:object_name])
73
+ end
74
+ end
75
+
76
+ URI.escape(url)
62
77
  end
63
78
 
64
79
  private
65
80
 
66
81
  def calculate_authorization_hash(time, options)
67
82
  date = String.new(time)
68
- date.insert(0, "\nx-amz-date:") if from_upload_part?(options) && options[:parts].nil?
83
+ request_parts = [ options[:verb],
84
+ "", # optional content md5
85
+ options[:content_type]]
86
+
87
+ headers = options[:headers] || {}
88
+
89
+ if from_upload_part?(options) && options[:parts].nil?
90
+ request_parts << "" # skip date as it's present as an x-amz- header
91
+ headers["x-amz-date"] = date
92
+ else
93
+ request_parts << date
94
+ end
95
+
96
+ if headers.present?
97
+ canonicalized_headers = headers.keys.sort.inject([]) {|array,k| array.push "#{k}:#{headers[k]}"}.join("\n")
98
+ request_parts << canonicalized_headers
99
+ end
69
100
 
70
- unsigned_request = "#{options[:verb]}\n\n#{options[:content_type]}\n#{date}\n/#{Config.instance.bucket_name}#{options[:url]}"
101
+ request_parts << "/#{Config.instance.bucket_name}#{options[:url]}"
102
+ unsigned_request = request_parts.join("\n")
71
103
  signature = Base64.strict_encode64(OpenSSL::HMAC.digest('sha1', Config.instance.s3_secret_key, unsigned_request))
72
104
 
73
105
  authorization = "AWS" + " " + Config.instance.s3_access_key + ":" + signature
@@ -27,13 +27,18 @@ module S3Multipart
27
27
  include S3Multipart::Uploader::Callbacks
28
28
  include S3Multipart::Uploader::Validations
29
29
 
30
+ attr_accessor :mount_point, :model
31
+
30
32
  def self.extended(klass)
31
33
  Uploader.controllers[klass.to_s.to_sym] = Digest::SHA1.hexdigest(klass.to_s)
32
34
  end
33
35
 
34
- def attach(model)
36
+ def attach(model, options = {})
37
+ self.mount_point = options.delete(:using)
38
+ self.model = model
39
+
35
40
  S3Multipart::Upload.class_eval do
36
- has_one(model)
41
+ has_one(model, options)
37
42
  end
38
43
  end
39
44
 
@@ -1,5 +1,5 @@
1
1
  module S3Multipart
2
- VERSION = "0.0.10.4"
2
+ VERSION = "0.0.10.5"
3
3
  BREAKING_CHANGES = {
4
4
  :"0.0.10.2" => 'Modifications made to the database table used by the gem are now handled by migrations. If you are upgrading versions, run `rails g s3_multipart:install_new_migrations` followed by `rake db:migrate`. Fresh installs do not require subsequent migrations. The current version must now also be passed in to the gem\'s configuration function to alert you of breaking changes. This is done by setting a revision yml variable. See the section regarding the aws.yml file in the readme section below (just before "Getting Started").'
5
5
  }
@@ -19,6 +19,7 @@ function S3MP(options) {
19
19
  , S3MP = this;
20
20
 
21
21
  _.extend(this, options);
22
+ this.headers = _.object(_.map(options.headers, function(v,k) { return ["x-amz-" + k.toLowerCase(), v] }));
22
23
 
23
24
  this.uploadList = [];
24
25
 
@@ -32,7 +33,7 @@ function S3MP(options) {
32
33
  var i = [];
33
34
  function beginUpload(pipes, uploadObj) {
34
35
  var key = uploadObj.key
35
- , num_parts = uploadObj.parts.length
36
+ , num_parts = uploadObj.parts.length;
36
37
 
37
38
  if (typeof i[key] === "undefined") {
38
39
  i[key] = 0;
@@ -61,6 +62,7 @@ function S3MP(options) {
61
62
  var parts, i, ETag;
62
63
 
63
64
  parts = uploadObj.parts;
65
+ finished_part.status = "complete";
64
66
 
65
67
  // Append the ETag (in the response header) to the ETags array
66
68
  ETag = finished_part.xhr.getResponseHeader("ETag");
@@ -155,12 +157,6 @@ function S3MP(options) {
155
157
  }
156
158
 
157
159
  _.each(files, function(file, key) {
158
- if (file.size < 5000000) {
159
- return S3MP.onError({name: "FileSizeError", message: "File size is too small"})
160
- // This should still work. The multipart API just can't be used b/c Amazon doesn't allow
161
- // multipart file uploads that are less than 5 mb in size.
162
- }
163
-
164
160
  var upload = new Upload(file, S3MP, key);
165
161
  S3MP.uploadList.push(upload);
166
162
  upload.init();
@@ -175,6 +171,7 @@ S3MP.prototype.initiateMultipart = function(upload, cb) {
175
171
  body = JSON.stringify({ object_name : upload.name,
176
172
  content_type : upload.type,
177
173
  content_size : upload.size,
174
+ headers : this.headers,
178
175
  uploader : $(this.fileInputElement).data("uploader")
179
176
  });
180
177
 
@@ -190,7 +187,7 @@ S3MP.prototype.signPartRequests = function(id, object_name, upload_id, parts, cb
190
187
  return memo + "-" + part.size;
191
188
  }, parts[0].size);
192
189
 
193
- url = "s3_multipart/uploads/"+id;
190
+ url = "/s3_multipart/uploads/"+id;
194
191
  body = JSON.stringify({ object_name : object_name,
195
192
  upload_id : upload_id,
196
193
  content_lengths : content_lengths
@@ -203,7 +200,7 @@ S3MP.prototype.signPartRequests = function(id, object_name, upload_id, parts, cb
203
200
  S3MP.prototype.completeMultipart = function(uploadObj, cb) {
204
201
  var url, body, xhr;
205
202
 
206
- url = 's3_multipart/uploads/'+uploadObj.id;
203
+ url = '/s3_multipart/uploads/'+uploadObj.id;
207
204
  body = JSON.stringify({ object_name : uploadObj.object_name,
208
205
  upload_id : uploadObj.upload_id,
209
206
  content_length : uploadObj.size,
@@ -224,7 +221,7 @@ S3MP.prototype.deliverRequest = function(xhr, body, cb) {
224
221
  if (response.error) {
225
222
  return self.onError({
226
223
  name: "ServerResponse",
227
- message: "The server responded with an error"
224
+ message: response.error
228
225
  });
229
226
  }
230
227
  cb(response);
@@ -406,12 +403,8 @@ function Upload(file, o, key) {
406
403
 
407
404
  upload.signPartRequests(id, object_name, upload_id, parts, function(response) {
408
405
  _.each(parts, function(part, key) {
409
- var xhr = part.xhr;
410
-
411
- xhr.open('PUT', 'http://'+upload.bucket+'.s3.amazonaws.com/'+object_name+'?partNumber='+part.num+'&uploadId='+upload_id, true);
412
- xhr.setRequestHeader('x-amz-date', response[key].date);
413
- xhr.setRequestHeader('Authorization', response[key].authorization);
414
-
406
+ part.date = response[key].date;
407
+ part.auth = response[key].authorization;
415
408
  // Notify handler that an xhr request has been opened
416
409
  upload.handler.beginUpload(pipes, upload);
417
410
  });
@@ -433,6 +426,7 @@ function UploadPart(blob, key, upload) {
433
426
  this.size = blob.size;
434
427
  this.blob = blob;
435
428
  this.num = key;
429
+ this.upload = upload;
436
430
 
437
431
  this.xhr = xhr = upload.createXhrRequest();
438
432
  xhr.onload = function() {
@@ -442,7 +436,7 @@ function UploadPart(blob, key, upload) {
442
436
  upload.handler.onError(upload, part);
443
437
  };
444
438
  xhr.upload.onprogress = _.throttle(function(e) {
445
- if (upload.inprogress[key] != 0) {
439
+ if (e.lengthComputable) {
446
440
  upload.inprogress[key] = e.loaded;
447
441
  }
448
442
  }, 1000);
@@ -450,6 +444,10 @@ function UploadPart(blob, key, upload) {
450
444
  };
451
445
 
452
446
  UploadPart.prototype.activate = function() {
447
+ this.xhr.open('PUT', 'http://'+this.upload.bucket+'.s3.amazonaws.com/'+this.upload.object_name+'?partNumber='+this.num+'&uploadId='+this.upload.upload_id, true);
448
+ this.xhr.setRequestHeader('x-amz-date', this.date);
449
+ this.xhr.setRequestHeader('Authorization', this.auth);
450
+
453
451
  this.xhr.send(this.blob);
454
452
  this.status = "active";
455
453
  };
@@ -463,4 +461,4 @@ return S3MP;
463
461
 
464
462
  }());
465
463
 
466
- }(this));
464
+ }(this));
metadata CHANGED
@@ -1,52 +1,46 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: s3_multipart
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10.4
5
- prerelease:
4
+ version: 0.0.10.5
6
5
  platform: ruby
7
6
  authors:
8
7
  - Max Gillett
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-04-07 00:00:00.000000000 Z
11
+ date: 2014-04-19 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: uuid
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ! '>='
17
+ - - '>='
20
18
  - !ruby/object:Gem::Version
21
19
  version: 2.3.6
22
20
  type: :runtime
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ! '>='
24
+ - - '>='
28
25
  - !ruby/object:Gem::Version
29
26
  version: 2.3.6
30
27
  - !ruby/object:Gem::Dependency
31
28
  name: xml-simple
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ! '>='
31
+ - - '>='
36
32
  - !ruby/object:Gem::Version
37
33
  version: 1.1.2
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ! '>='
38
+ - - '>='
44
39
  - !ruby/object:Gem::Version
45
40
  version: 1.1.2
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: combustion
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
45
  - - ~>
52
46
  - !ruby/object:Gem::Version
@@ -54,7 +48,6 @@ dependencies:
54
48
  type: :development
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
52
  - - ~>
60
53
  - !ruby/object:Gem::Version
@@ -62,84 +55,74 @@ dependencies:
62
55
  - !ruby/object:Gem::Dependency
63
56
  name: rails
64
57
  requirement: !ruby/object:Gem::Requirement
65
- none: false
66
58
  requirements:
67
- - - ! '>='
59
+ - - '>='
68
60
  - !ruby/object:Gem::Version
69
61
  version: '0'
70
62
  type: :development
71
63
  prerelease: false
72
64
  version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
65
  requirements:
75
- - - ! '>='
66
+ - - '>='
76
67
  - !ruby/object:Gem::Version
77
68
  version: '0'
78
69
  - !ruby/object:Gem::Dependency
79
70
  name: sqlite3
80
71
  requirement: !ruby/object:Gem::Requirement
81
- none: false
82
72
  requirements:
83
- - - ! '>='
73
+ - - '>='
84
74
  - !ruby/object:Gem::Version
85
75
  version: '0'
86
76
  type: :development
87
77
  prerelease: false
88
78
  version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
79
  requirements:
91
- - - ! '>='
80
+ - - '>='
92
81
  - !ruby/object:Gem::Version
93
82
  version: '0'
94
83
  - !ruby/object:Gem::Dependency
95
84
  name: rspec
96
85
  requirement: !ruby/object:Gem::Requirement
97
- none: false
98
86
  requirements:
99
- - - ! '>='
87
+ - - '>='
100
88
  - !ruby/object:Gem::Version
101
89
  version: '0'
102
90
  type: :development
103
91
  prerelease: false
104
92
  version_requirements: !ruby/object:Gem::Requirement
105
- none: false
106
93
  requirements:
107
- - - ! '>='
94
+ - - '>='
108
95
  - !ruby/object:Gem::Version
109
96
  version: '0'
110
97
  - !ruby/object:Gem::Dependency
111
98
  name: rspec-rails
112
99
  requirement: !ruby/object:Gem::Requirement
113
- none: false
114
100
  requirements:
115
- - - ! '>='
101
+ - - '>='
116
102
  - !ruby/object:Gem::Version
117
103
  version: '0'
118
104
  type: :development
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
107
  requirements:
123
- - - ! '>='
108
+ - - '>='
124
109
  - !ruby/object:Gem::Version
125
110
  version: '0'
126
111
  - !ruby/object:Gem::Dependency
127
112
  name: capybara
128
113
  requirement: !ruby/object:Gem::Requirement
129
- none: false
130
114
  requirements:
131
- - - ! '>='
115
+ - - '>='
132
116
  - !ruby/object:Gem::Version
133
117
  version: '0'
134
118
  type: :development
135
119
  prerelease: false
136
120
  version_requirements: !ruby/object:Gem::Requirement
137
- none: false
138
121
  requirements:
139
- - - ! '>='
122
+ - - '>='
140
123
  - !ruby/object:Gem::Version
141
124
  version: '0'
142
- description: ! 'See github for installation and configuration '
125
+ description: 'See github for installation and configuration '
143
126
  email:
144
127
  - max.gillett@gmail.com
145
128
  executables: []
@@ -156,6 +139,7 @@ files:
156
139
  - app/controllers/s3_multipart/application_controller.rb
157
140
  - app/controllers/s3_multipart/uploads_controller.rb
158
141
  - app/models/s3_multipart/upload.rb
142
+ - config/locales/s3_multipart.en.yml
159
143
  - config/routes.rb
160
144
  - grunt.js
161
145
  - javascripts/footer.js
@@ -281,26 +265,25 @@ files:
281
265
  - vendor/assets/javascripts/s3_multipart/lib.min.js
282
266
  homepage: https://github.com/maxgillett/s3_multipart
283
267
  licenses: []
268
+ metadata: {}
284
269
  post_install_message:
285
270
  rdoc_options: []
286
271
  require_paths:
287
272
  - lib
288
273
  required_ruby_version: !ruby/object:Gem::Requirement
289
- none: false
290
274
  requirements:
291
- - - ! '>='
275
+ - - '>='
292
276
  - !ruby/object:Gem::Version
293
277
  version: '0'
294
278
  required_rubygems_version: !ruby/object:Gem::Requirement
295
- none: false
296
279
  requirements:
297
- - - ! '>='
280
+ - - '>='
298
281
  - !ruby/object:Gem::Version
299
282
  version: '0'
300
283
  requirements: []
301
284
  rubyforge_project:
302
- rubygems_version: 1.8.25
285
+ rubygems_version: 2.0.3
303
286
  signing_key:
304
- specification_version: 3
287
+ specification_version: 4
305
288
  summary: Upload directly to S3 using multipart uploading
306
289
  test_files: []