s3_multipart 0.0.8 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -109,6 +109,9 @@ class VideoUploader < ApplicationController
109
109
  # Only accept certain file types. Expects an array of valid extensions.
110
110
  accept %w(wmv avi mp4 mkv mov mpeg)
111
111
 
112
+ # Define the minimum and maximum allowed file sizes (in bytes)
113
+ limit min: 5*1000*1000, max: 2*1000*1000*1000
114
+
112
115
  # Takes in a block that will be evaluated when the upload has been
113
116
  # successfully initiated. The block will be passed an instance of
114
117
  # the upload object as well as the session hash when the callback is made.
@@ -141,7 +144,7 @@ To add the multipart uploader to a view, insert the following:
141
144
 
142
145
  ```ruby
143
146
  <%= multipart_uploader_form(input_name: 'uploader',
144
- uploader: 'VideoUploader'
147
+ uploader: 'VideoUploader',
145
148
  button_class: 'submit-button',
146
149
  button_text: 'Upload selected videos',
147
150
  html: %Q{<button class="upload-button">Select videos</button>}) %>
@@ -210,12 +213,16 @@ S3_Multipart is very much a work in progress. If you squash a bug, make enhancem
210
213
 
211
214
  The library is working on the latest version of IE, Firefox, Safari, and Chrome. Tests for over 100 browsers are currently being conducted.
212
215
 
216
+ ## What's New
217
+
218
+ **0.0.9** - File type and size validations are now specified in the upload controller. Untested support for browsers that lack the FileBlob API
219
+
213
220
  ## To Do
214
221
 
215
222
  * ~~If the FileBlob API is not supported on page load, the uploader should just send one giant chunk~~ (DONE)
216
223
  * Handle network errors in the javascript client library
217
224
  * ~~File type validations~~ (DONE)
218
- * File size validations
225
+ * ~~File size validations~~ (DONE)
219
226
  * More and better tests
220
227
  * More browser testing
221
228
  * Roll file signing and initiation into one request
@@ -6,7 +6,9 @@ module S3Multipart
6
6
  upload = Upload.create(params)
7
7
  upload.execute_callback(:begin, session)
8
8
  response = upload.to_json
9
- rescue
9
+ rescue FileTypeError, FileSizeError => e
10
+ response = {error: e.message}
11
+ rescue
10
12
  response = {error: 'There was an error initiating the upload'}
11
13
  ensure
12
14
  render :json => response
@@ -2,12 +2,12 @@ module S3Multipart
2
2
  class Upload < ::ActiveRecord::Base
3
3
  extend S3Multipart::TransferHelpers
4
4
 
5
- attr_accessible :key, :upload_id, :name, :location, :uploader
6
- #before_create :validate_mime_types
5
+ attr_accessible :key, :upload_id, :name, :location, :uploader, :size
6
+ before_create :validate_file_type, :validate_file_size
7
7
 
8
8
  def self.create(params)
9
9
  response = initiate(params)
10
- super(key: response["key"], upload_id: response["upload_id"], name: response["name"], uploader: params["uploader"])
10
+ super(key: response["key"], upload_id: response["upload_id"], name: response["name"], uploader: params["uploader"], size: params["content_size"])
11
11
  end
12
12
 
13
13
  def execute_callback(stage, session)
@@ -23,12 +23,19 @@ module S3Multipart
23
23
 
24
24
  private
25
25
 
26
- def validate_mime_types(upload)
27
- ext = upload.name.match(/\.([a-zA-Z0-9]+)$/)[1]
28
- controller = deserialize(upload.uploader)
26
+ def validate_file_size
27
+ size = self.size
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
31
+ end
32
+
33
+ def validate_file_type
34
+ ext = self.name.match(/\.([a-zA-Z0-9]+)$/)[1]
35
+ controller = deserialize(self.uploader)
29
36
 
30
37
  if !controller.file_types.include?(ext)
31
- return false
38
+ raise FileTypeError, "File type not supported"
32
39
  end
33
40
  end
34
41
 
data/javascripts/s3mp.js CHANGED
@@ -160,6 +160,7 @@ S3MP.prototype.initiateMultipart = function(upload, cb) {
160
160
  url = '/s3_multipart/uploads';
161
161
  body = JSON.stringify({ object_name : upload.name,
162
162
  content_type : upload.type,
163
+ content_size : upload.size,
163
164
  uploader : $(this.fileInputElement).data("uploader")
164
165
  });
165
166
 
@@ -8,6 +8,9 @@ class <%= model_constant %>Uploader < ApplicationController
8
8
  # Only accept certain file types. Expects an array of valid extensions.
9
9
  accept %w(wmv avi mp4 mkv mov mpeg)
10
10
 
11
+ # Define the minimum and maximum allowed file sizes (in bytes)
12
+ limit min: 5*1000*1000, max: 2*1000*1000*1000
13
+
11
14
  # Takes in a block that will be evaluated when the upload has been
12
15
  # successfully initiated. The block will be passed an instance of
13
16
  # the upload object as well as the session hashwhen the callback is made.
@@ -7,8 +7,9 @@ class CreateS3MultipartUploads < ActiveRecord::Migration
7
7
  t.string :key
8
8
  t.string :name
9
9
  t.string :uploader
10
+ t.integer :size
10
11
 
11
12
  t.timestamps
12
13
  end
13
14
  end
14
- end
15
+ end
@@ -15,7 +15,7 @@ if defined?(Rails)
15
15
  # Load all of the upload controllers in app/uploaders/multipart
16
16
  initializer "s3_multipart.load_upload_controllers" do
17
17
  begin
18
- uploaders = Dir.entries(Rails.root.join('app', 'uploaders', 'multipart').to_s).keep_if {|n| n =~ /[uploader]/}
18
+ uploaders = Dir.entries(Rails.root.join('app', 'uploaders', 'multipart').to_s).keep_if {|n| n =~ /uploader\.rb$/}
19
19
  uploaders.each do |uploader|
20
20
  require "#{Rails.root.join('app', 'uploaders', 'multipart')}/#{uploader}"
21
21
  end
@@ -2,12 +2,16 @@ module S3Multipart
2
2
  module Uploader
3
3
  module Validations
4
4
 
5
- attr_accessor :file_types
5
+ attr_accessor :file_types, :size_limits
6
6
 
7
7
  def accept(types)
8
8
  self.file_types = types
9
9
  end
10
10
 
11
+ def limit(sizes)
12
+ self.size_limits = sizes
13
+ end
14
+
11
15
  end
12
16
  end
13
17
  end
@@ -16,7 +16,6 @@ module S3Multipart
16
16
  controllers[controller.to_s.to_sym]
17
17
  end
18
18
 
19
- # What is wrong with this method?
20
19
  def self.deserialize(digest)
21
20
  controllers.key(digest).to_s.constantize
22
21
  end
@@ -1,3 +1,3 @@
1
1
  module S3Multipart
2
- VERSION = "0.0.8"
2
+ VERSION = "0.0.9"
3
3
  end
data/lib/s3_multipart.rb CHANGED
@@ -18,6 +18,9 @@ module S3Multipart
18
18
 
19
19
  end
20
20
 
21
+ class FileTypeError < StandardError; end
22
+ class FileSizeError < StandardError; end
23
+
21
24
  end
22
25
 
23
26
  require 's3_multipart/config'
@@ -25,4 +28,4 @@ require 's3_multipart/railtie'
25
28
  require 's3_multipart/engine'
26
29
  require 's3_multipart/http/net_http'
27
30
  require 's3_multipart/uploader'
28
- require 's3_multipart/transfer_helpers'
31
+ require 's3_multipart/transfer_helpers'
@@ -8,6 +8,9 @@ class VideoUploader < ApplicationController
8
8
  # Only accept certain file types. Expects an array of valid extensions.
9
9
  accept %w(wmv avi mp4 mkv mov mpeg)
10
10
 
11
+ # Define the minimum and maximum allowed file sizes (in bytes)
12
+ limit min: 5*1000*1000, max: 2*1000*1000*1000
13
+
11
14
  # Takes in a block that will be evaluated when the upload has been
12
15
  # successfully initiated. The block will be passed an instance of
13
16
  # the upload object when the callback is made.
@@ -5,6 +5,7 @@ ActiveRecord::Schema.define do
5
5
  t.string "key"
6
6
  t.string "name"
7
7
  t.string "uploader"
8
+ t.integer "size"
8
9
  t.datetime "created_at", :null => false
9
10
  t.datetime "updated_at", :null => false
10
11
  end
@@ -174,6 +174,7 @@ S3MP.prototype.initiateMultipart = function(upload, cb) {
174
174
  url = '/s3_multipart/uploads';
175
175
  body = JSON.stringify({ object_name : upload.name,
176
176
  content_type : upload.type,
177
+ content_size : upload.size,
177
178
  uploader : $(this.fileInputElement).data("uploader")
178
179
  });
179
180
 
@@ -1 +1 @@
1
- (function(e){e.S3MP=function(){function t(t){var r,i=[],s=this;_.extend(this,t),this.uploadList=[],this.handler={beginUpload:function(){function t(t,n){var r=n.key,i=n.parts.length;typeof e[r]=="undefined"&&(e[r]=0),e[r]++;if(e[r]===i){for(var o=0;o<t;o++)n.parts[o].activate();s.handler.startProgressTimer(r),s.onStart(n)}}var e=[];return t}(),onError:function(e,t){},onPartSuccess:function(e,t){var n,r,i;n=e.parts,i=t.xhr.getResponseHeader("ETag"),e.Etags.push({ETag:i.replace(/\"/g,""),partNum:t.num}),e.uploaded+=t.size,e.inprogress[t.num]=0,r=_.indexOf(n,t),n.splice(r,1),n.length&&(r=_.findIndex(n,function(e,t,n){if(e.status!=="active")return!0}),r!==-1&&n[r].activate()),n.length||this.onComplete(e)},onComplete:function(e){var t=_.indexOf(s.uploadList,e);this.clearProgressTimer(t),s.completeMultipart(e,function(t){t.location&&s.onComplete(e)})},onProgress:function(e,t,n,r,i){s.onProgress(e,t,n,r,i)},startProgressTimer:function(){var t=[],n=function(n){i[n]=e.setInterval(function(){var e,r,i,o,u;typeof t[n]=="undefined"&&(t[n]=0),e=s.uploadList[n],r=e.size,i=e.uploaded,_.each(e.inprogress,function(e){i+=e}),o=i/r*100,u=i-t[n],t[n]=i,e.handler.onProgress(n,r,i,o,u)},1e3)};return n}(),clearProgressTimer:function(t){e.clearInterval(i[t])}},this.fileSelector?r=$(this.fileSelector).get(0).files:r=this.fileList,_.each(r,function(e,t){if(e.size<5e6)return s.onError({name:"FileSizeError",message:"File size is too small"});var r=new n(e,s,t);s.uploadList.push(r),r.init()})}function n(e,t,n){function i(){var t,i,s,o,u,a,f,l,c;t=this,this.key=n,this.file=e,this.name=e.name,this.size=e.size,this.type=e.type,this.Etags=[],this.inprogress=[],this.uploaded=0,this.status="",this.size>1e9?(num_segs=100,l=10):this.size>5e8?(num_segs=50,l=5):this.size>1e8?(num_segs=20,l=5):(num_segs=2,l=2),a=_.range(num_segs+1),f=_.map(a,function(t){return Math.round(t*(e.size/num_segs))}),t.sliceBlob=="Unsupported"?this.parts=[new r(e,0,t)]:(this.parts=_.map(f,function(n,i){return c=t.sliceBlob(e,n,f[i+1]),new r(c,i+1,t)}),this.parts.pop()),this.init=function(){t.initiateMultipart(t,function(e){var n=t.id=e.id,r=t.upload_id=e.upload_id,i=t.object_name=e.key,s=t.parts;t.signPartRequests(n,i,r,s,function(e){_.each(s,function(n,s){var o=n.xhr;o.open("PUT","http://"+t.bucket+".s3.amazonaws.com/"+i+"?partNumber="+n.num+"&uploadId="+r,!0),o.setRequestHeader("x-amz-date",e[s].date),o.setRequestHeader("Authorization",e[s].authorization),t.handler.beginUpload(l,t)})})})}}return i.prototype=t,new i}function r(e,t,n){var r,i;r=this,this.size=e.size,this.blob=e,this.num=t,this.xhr=i=n.createXhrRequest(),i.onload=function(){n.handler.onPartSuccess(n,r)},i.onerror=function(){n.handler.onError(n,r)},i.upload.onprogress=_.throttle(function(e){n.inprogress[t]=e.loaded},1e3)}return _.mixin({findIndex:function(e,t){for(var n=0;n<e.length;n++)if(t(e[n],n,e))return n;return-1}}),t.prototype.initiateMultipart=function(e,t){var n,r,i;n="/s3_multipart/uploads",r=JSON.stringify({object_name:e.name,content_type:e.type,uploader:$(this.fileInputElement).data("uploader")}),i=this.createXhrRequest("POST",n),this.deliverRequest(i,r,t)},t.prototype.signPartRequests=function(e,t,n,r,i){var s,o,u,a;s=_.reduce(_.rest(r),function(e,t){return e+"-"+t.size},r[0].size),o="s3_multipart/uploads/"+e,u=JSON.stringify({object_name:t,upload_id:n,content_lengths:s}),a=this.createXhrRequest("PUT",o),this.deliverRequest(a,u,i)},t.prototype.completeMultipart=function(e,t){var n,r,i;n="s3_multipart/uploads/"+e.id,r=JSON.stringify({object_name:e.object_name,upload_id:e.upload_id,content_length:e.size,parts:e.Etags}),i=this.createXhrRequest("PUT",n),this.deliverRequest(i,r,t)},t.prototype.deliverRequest=function(e,t,n){var r=this;e.onload=function(){response=JSON.parse(this.responseText);if(response.error)return r.onError({name:"ServerResponse",message:"The server responded with an error"});n(response)},e.onerror=function(){},e.setRequestHeader("Content-Type","application/json"),e.setRequestHeader("X-CSRF-Token",$('meta[name="csrf-token"]').attr("content")),e.send(t)},t.prototype.createXhrRequest=function(){var e;return typeof XMLHttpRequest.constructor=="function"?e=XMLHttpRequest:typeof XDomainRequest!="undefined"?e=XDomainRequest:e=null,function(t,n,r,i){var s,o,i=!0;return s=Array.prototype.slice.call(arguments),typeof s[0]=="undefined"&&(r=null,i=!1),o=new e,i&&o.open(t,n,!0),o.onreadystatechange=r,o}}(),t.prototype.sliceBlob=function(){try{var e=new Blob}catch(t){return"Unsupported"}return e.slice?function(e,t,n){return e.slice(t,n)}:e.mozSlice?function(e,t,n){return e.mozSlice(t,n)}:e.webkitSlice?function(e,t,n){return e.webkitSlice(t,n)}:"Unsupported"}(),t.prototype._returnUploadObj=function(e){var t=_.find(this.uploadList,function(t){return t.key===e});return t},t.prototype.cancel=function(e){var t,n;t=this._returnUploadObj(e),n=_.indexOf(this.uploadList,t),this.uploadList.splice(n,n+1),this.onCancel()},t.prototype.pause=function(e){var t=this._returnUploadObj(e);_.each(t.parts,function(e,t,n){e.status=="active"&&e.pause()}),this.onPause()},t.prototype.resume=function(e){var t=this._returnUploadObj(e);_.each(t.parts,function(e,t,n){e.status=="paused"&&e.activate()}),this.onResume()},r.prototype.activate=function(){this.xhr.send(this.blob),this.status="active"},r.prototype.pause=function(){this.xhr.abort(),this.status="paused"},t}()})(this);
1
+ (function(e){e.S3MP=function(){function t(t){var r,i=[],s=this;_.extend(this,t),this.uploadList=[],this.handler={beginUpload:function(){function t(t,n){var r=n.key,i=n.parts.length;typeof e[r]=="undefined"&&(e[r]=0),e[r]++;if(e[r]===i){for(var o=0;o<t;o++)n.parts[o].activate();s.handler.startProgressTimer(r),s.onStart(n)}}var e=[];return t}(),onError:function(e,t){},onPartSuccess:function(e,t){var n,r,i;n=e.parts,i=t.xhr.getResponseHeader("ETag"),e.Etags.push({ETag:i.replace(/\"/g,""),partNum:t.num}),e.uploaded+=t.size,e.inprogress[t.num]=0,r=_.indexOf(n,t),n.splice(r,1),n.length&&(r=_.findIndex(n,function(e,t,n){if(e.status!=="active")return!0}),r!==-1&&n[r].activate()),n.length||this.onComplete(e)},onComplete:function(e){var t=_.indexOf(s.uploadList,e);this.clearProgressTimer(t),s.completeMultipart(e,function(t){t.location&&s.onComplete(e)})},onProgress:function(e,t,n,r,i){s.onProgress(e,t,n,r,i)},startProgressTimer:function(){var t=[],n=function(n){i[n]=e.setInterval(function(){var e,r,i,o,u;typeof t[n]=="undefined"&&(t[n]=0),e=s.uploadList[n],r=e.size,i=e.uploaded,_.each(e.inprogress,function(e){i+=e}),o=i/r*100,u=i-t[n],t[n]=i,e.handler.onProgress(n,r,i,o,u)},1e3)};return n}(),clearProgressTimer:function(t){e.clearInterval(i[t])}},this.fileSelector?r=$(this.fileSelector).get(0).files:r=this.fileList,_.each(r,function(e,t){if(e.size<5e6)return s.onError({name:"FileSizeError",message:"File size is too small"});var r=new n(e,s,t);s.uploadList.push(r),r.init()})}function n(e,t,n){function i(){var t,i,s,o,u,a,f,l,c;t=this,this.key=n,this.file=e,this.name=e.name,this.size=e.size,this.type=e.type,this.Etags=[],this.inprogress=[],this.uploaded=0,this.status="",this.size>1e9?(num_segs=100,l=10):this.size>5e8?(num_segs=50,l=5):this.size>1e8?(num_segs=20,l=5):(num_segs=2,l=2),a=_.range(num_segs+1),f=_.map(a,function(t){return Math.round(t*(e.size/num_segs))}),t.sliceBlob=="Unsupported"?this.parts=[new r(e,0,t)]:(this.parts=_.map(f,function(n,i){return c=t.sliceBlob(e,n,f[i+1]),new r(c,i+1,t)}),this.parts.pop()),this.init=function(){t.initiateMultipart(t,function(e){var n=t.id=e.id,r=t.upload_id=e.upload_id,i=t.object_name=e.key,s=t.parts;t.signPartRequests(n,i,r,s,function(e){_.each(s,function(n,s){var o=n.xhr;o.open("PUT","http://"+t.bucket+".s3.amazonaws.com/"+i+"?partNumber="+n.num+"&uploadId="+r,!0),o.setRequestHeader("x-amz-date",e[s].date),o.setRequestHeader("Authorization",e[s].authorization),t.handler.beginUpload(l,t)})})})}}return i.prototype=t,new i}function r(e,t,n){var r,i;r=this,this.size=e.size,this.blob=e,this.num=t,this.xhr=i=n.createXhrRequest(),i.onload=function(){n.handler.onPartSuccess(n,r)},i.onerror=function(){n.handler.onError(n,r)},i.upload.onprogress=_.throttle(function(e){n.inprogress[t]=e.loaded},1e3)}return _.mixin({findIndex:function(e,t){for(var n=0;n<e.length;n++)if(t(e[n],n,e))return n;return-1}}),t.prototype.initiateMultipart=function(e,t){var n,r,i;n="/s3_multipart/uploads",r=JSON.stringify({object_name:e.name,content_type:e.type,content_size:e.size,uploader:$(this.fileInputElement).data("uploader")}),i=this.createXhrRequest("POST",n),this.deliverRequest(i,r,t)},t.prototype.signPartRequests=function(e,t,n,r,i){var s,o,u,a;s=_.reduce(_.rest(r),function(e,t){return e+"-"+t.size},r[0].size),o="s3_multipart/uploads/"+e,u=JSON.stringify({object_name:t,upload_id:n,content_lengths:s}),a=this.createXhrRequest("PUT",o),this.deliverRequest(a,u,i)},t.prototype.completeMultipart=function(e,t){var n,r,i;n="s3_multipart/uploads/"+e.id,r=JSON.stringify({object_name:e.object_name,upload_id:e.upload_id,content_length:e.size,parts:e.Etags}),i=this.createXhrRequest("PUT",n),this.deliverRequest(i,r,t)},t.prototype.deliverRequest=function(e,t,n){var r=this;e.onload=function(){response=JSON.parse(this.responseText);if(response.error)return r.onError({name:"ServerResponse",message:"The server responded with an error"});n(response)},e.onerror=function(){},e.setRequestHeader("Content-Type","application/json"),e.setRequestHeader("X-CSRF-Token",$('meta[name="csrf-token"]').attr("content")),e.send(t)},t.prototype.createXhrRequest=function(){var e;return typeof XMLHttpRequest.constructor=="function"?e=XMLHttpRequest:typeof XDomainRequest!="undefined"?e=XDomainRequest:e=null,function(t,n,r,i){var s,o,i=!0;return s=Array.prototype.slice.call(arguments),typeof s[0]=="undefined"&&(r=null,i=!1),o=new e,i&&o.open(t,n,!0),o.onreadystatechange=r,o}}(),t.prototype.sliceBlob=function(){try{var e=new Blob}catch(t){return"Unsupported"}return e.slice?function(e,t,n){return e.slice(t,n)}:e.mozSlice?function(e,t,n){return e.mozSlice(t,n)}:e.webkitSlice?function(e,t,n){return e.webkitSlice(t,n)}:"Unsupported"}(),t.prototype._returnUploadObj=function(e){var t=_.find(this.uploadList,function(t){return t.key===e});return t},t.prototype.cancel=function(e){var t,n;t=this._returnUploadObj(e),n=_.indexOf(this.uploadList,t),this.uploadList.splice(n,n+1),this.onCancel()},t.prototype.pause=function(e){var t=this._returnUploadObj(e);_.each(t.parts,function(e,t,n){e.status=="active"&&e.pause()}),this.onPause()},t.prototype.resume=function(e){var t=this._returnUploadObj(e);_.each(t.parts,function(e,t,n){e.status=="paused"&&e.activate()}),this.onResume()},r.prototype.activate=function(){this.xhr.send(this.blob),this.status="active"},r.prototype.pause=function(){this.xhr.abort(),this.status="paused"},t}()})(this);
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: s3_multipart
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.8
4
+ version: 0.0.9
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-24 00:00:00.000000000 Z
12
+ date: 2013-03-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: uuid
@@ -297,7 +297,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
297
297
  version: '0'
298
298
  requirements: []
299
299
  rubyforge_project:
300
- rubygems_version: 1.8.24
300
+ rubygems_version: 1.8.25
301
301
  signing_key:
302
302
  specification_version: 3
303
303
  summary: Upload directly to S3 using multipart uploading