s3_multipart 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -14,4 +14,7 @@ _SpecRunner.html
14
14
 
15
15
  /node_modules
16
16
 
17
- /spec/internal/db/combustion_test.sqlite3
17
+ /spec/internal/db/combustion_test.sqlite3
18
+
19
+ *.swp
20
+ *.swo
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- s3_multipart (0.0.6)
4
+ s3_multipart (0.0.8)
5
5
  uuid (>= 2.3.6)
6
6
  xml-simple (>= 1.1.2)
7
7
 
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  The S3 Multipart gem brings direct multipart uploading to S3 to Rails. Data is piped from the client straight to Amazon S3 and a server-side callback is run when the upload is complete.
4
4
 
5
- Multipart uploading allows files to be split into many chunks and uploaded in parallel or succession (or both). This can result in dramatically increased upload speeds for the client and allows for the pausing and resuming of uploads. For a more complete overview of multipart uploading as it applies to S3, see the documentation [here](http://docs.amazonwebservices.com/AmazonS3/latest/dev/mpuoverview.html).
5
+ Multipart uploading allows files to be split into many chunks and uploaded in parallel or succession (or both). This can result in dramatically increased upload speeds for the client and allows for the pausing and resuming of uploads. For a more complete overview of multipart uploading as it applies to S3, see the documentation [here](http://docs.amazonwebservices.com/AmazonS3/latest/dev/mpuoverview.html). Read more about the philosophy behind the gem on the Bitcast [blog](http://blog.bitcast.io/post/43001057745/direct-multipart-uploads-to-s3-in-rails).
6
6
 
7
7
  ## Setup
8
8
 
@@ -106,6 +106,9 @@ class VideoUploader < ApplicationController
106
106
  # relationship between the internal upload model and the given model.
107
107
  attach :video
108
108
 
109
+ # Only accept certain file types. Expects an array of valid extensions.
110
+ accept %w(wmv avi mp4 mkv mov mpeg)
111
+
109
112
  # Takes in a block that will be evaluated when the upload has been
110
113
  # successfully initiated. The block will be passed an instance of
111
114
  # the upload object as well as the session hash when the callback is made.
@@ -137,15 +140,14 @@ The generator also creates the migration to add this functionality, so make sure
137
140
  To add the multipart uploader to a view, insert the following:
138
141
 
139
142
  ```ruby
140
- <%= multipart_uploader_form(types: ['video/mpeg'],
141
- input_name: 'uploader',
143
+ <%= multipart_uploader_form(input_name: 'uploader',
142
144
  uploader: 'VideoUploader'
143
145
  button_class: 'submit-button',
144
146
  button_text: 'Upload selected videos',
145
147
  html: %Q{<button class="upload-button">Select videos</button>}) %>
146
148
  ```
147
149
 
148
- The `multipart_uploader_form` function is a view helper, and generates the necessary input elements. It takes in a array of allowed MIME types and a string of html to be interpolated between the generated file input element and submit button. It also expects an upload controller (as a string or constant) to be passed in with the 'uploader' option. This links the upload form with the callbacks specified in the given controller.
150
+ The `multipart_uploader_form` function is a view helper, and generates the necessary input elements. It takes in a string of html to be interpolated between the generated file input element and submit button. It also expects an upload controller (as a string or constant) to be passed in with the 'uploader' option. This links the upload form with the callbacks specified in the given controller.
149
151
 
150
152
  The code above outputs this:
151
153
 
@@ -204,11 +206,16 @@ To re-build the javascript library, run `grunt concat` and to minify, `grunt min
204
206
 
205
207
  S3_Multipart is very much a work in progress. If you squash a bug, make enhancements, or write more tests, please submit a pull request.
206
208
 
209
+ ## Browser Compatibility
210
+
211
+ The library is working on the latest version of IE, Firefox, Safari, and Chrome. Tests for over 100 browsers are currently being conducted.
212
+
207
213
  ## To Do
208
214
 
209
- * If the FileBlob API is not supported on page load, the uploader should just send one giant chunk
215
+ * ~~If the FileBlob API is not supported on page load, the uploader should just send one giant chunk~~ (DONE)
210
216
  * Handle network errors in the javascript client library
211
- * Modular validations (checking file size and type)
217
+ * ~~File type validations~~ (DONE)
218
+ * File size validations
212
219
  * More and better tests
213
220
  * More browser testing
214
- * Roll file signing and initiation into one request
221
+ * Roll file signing and initiation into one request
@@ -1,11 +1,11 @@
1
1
  module S3Multipart
2
2
  class UploadsController < ApplicationController
3
+
3
4
  def create
4
5
  begin
5
- response = Upload.initiate(params)
6
- upload = Upload.create(key: response["key"], upload_id: response["upload_id"], name: response["name"], uploader: params["uploader"])
7
- response["id"] = upload["id"]
6
+ upload = Upload.create(params)
8
7
  upload.execute_callback(:begin, session)
8
+ response = upload.to_json
9
9
  rescue
10
10
  response = {error: 'There was an error initiating the upload'}
11
11
  ensure
@@ -55,4 +55,4 @@ module S3Multipart
55
55
  end
56
56
 
57
57
  end
58
- end
58
+ end
@@ -3,9 +3,15 @@ module S3Multipart
3
3
  extend S3Multipart::TransferHelpers
4
4
 
5
5
  attr_accessible :key, :upload_id, :name, :location, :uploader
6
+ #before_create :validate_mime_types
7
+
8
+ def self.create(params)
9
+ response = initiate(params)
10
+ super(key: response["key"], upload_id: response["upload_id"], name: response["name"], uploader: params["uploader"])
11
+ end
6
12
 
7
13
  def execute_callback(stage, session)
8
- controller = S3Multipart::Uploader.deserialize(uploader)
14
+ controller = deserialize(uploader)
9
15
 
10
16
  case stage
11
17
  when :begin
@@ -15,5 +21,20 @@ module S3Multipart
15
21
  end
16
22
  end
17
23
 
24
+ private
25
+
26
+ def validate_mime_types(upload)
27
+ ext = upload.name.match(/\.([a-zA-Z0-9]+)$/)[1]
28
+ controller = deserialize(upload.uploader)
29
+
30
+ if !controller.file_types.include?(ext)
31
+ return false
32
+ end
33
+ end
34
+
35
+ def deserialize(uploader)
36
+ S3Multipart::Uploader.deserialize(uploader)
37
+ end
38
+
18
39
  end
19
- end
40
+ end
data/javascripts/s3mp.js CHANGED
@@ -141,12 +141,10 @@ function S3MP(options) {
141
141
  }
142
142
 
143
143
  _.each(files, function(file, key) {
144
- //Do validation for each file before creating a new upload object
145
- // if (!file.type.match(/video/)) {
146
- // return false;
147
- // }
148
144
  if (file.size < 5000000) {
149
145
  return S3MP.onError({name: "FileSizeError", message: "File size is too small"})
146
+ // This should still work. The multipart API just can't be used b/c Amazon doesn't allow
147
+ // multipart file uploads that are less than 5 mb in size.
150
148
  }
151
149
 
152
150
  var upload = new Upload(file, S3MP, key);
@@ -326,4 +324,4 @@ S3MP.prototype.resume = function(key) {
326
324
  });
327
325
 
328
326
  this.onResume();
329
- };
327
+ };
@@ -26,25 +26,25 @@ function Upload(file, o, key) {
26
26
  } else if (this.size > 100000000) { // greater than 100 mb
27
27
  num_segs = 20;
28
28
  pipes = 5;
29
- } else if (this.size > 5000000) { // greater than 5 mb (S3 does not allow multipart uploads < 5 mb)
29
+ } else { // greater than 5 mb (S3 does not allow multipart uploads < 5 mb)
30
30
  num_segs = 2;
31
31
  pipes = 2;
32
- } else {
33
- num_segs = 10;
34
- pipes = 3;
35
- }
32
+ }
36
33
 
37
34
  chunk_segs = _.range(num_segs + 1);
38
35
  chunk_lens = _.map(chunk_segs, function(seg) {
39
36
  return Math.round(seg * (file.size/num_segs));
40
37
  });
41
38
 
42
- this.parts = _.map(chunk_lens, function(len, i) {
43
- blob = upload.sliceBlob(file, len, chunk_lens[i+1]);
44
- return new UploadPart(blob, i+1, upload);
45
- });
46
-
47
- this.parts.pop(); // Remove the empty blob at the end of the array
39
+ if (upload.sliceBlob == "Unsupported") {
40
+ this.parts = [new UploadPart(file, 0, upload)];
41
+ } else {
42
+ this.parts = _.map(chunk_lens, function(len, i) {
43
+ blob = upload.sliceBlob(file, len, chunk_lens[i+1]);
44
+ return new UploadPart(blob, i+1, upload);
45
+ });
46
+ this.parts.pop(); // Remove the empty blob at the end of the array
47
+ }
48
48
 
49
49
  // init function will initiate the multipart upload, sign all the parts, and
50
50
  // start uploading some parts in parallel
@@ -73,4 +73,4 @@ function Upload(file, o, key) {
73
73
  // Inherit the properties and prototype methods of the passed in S3MP instance object
74
74
  Upload.prototype = o;
75
75
  return new Upload();
76
- }
76
+ }
@@ -5,9 +5,12 @@ class <%= model_constant %>Uploader < ApplicationController
5
5
  # relationship between the internal upload model and the given model.
6
6
  attach :<%= model %>
7
7
 
8
+ # Only accept certain file types. Expects an array of valid extensions.
9
+ accept %w(wmv avi mp4 mkv mov mpeg)
10
+
8
11
  # Takes in a block that will be evaluated when the upload has been
9
12
  # successfully initiated. The block will be passed an instance of
10
- # the upload object when the callback is made.
13
+ # the upload object as well as the session hashwhen the callback is made.
11
14
  #
12
15
  # The following attributes are available on the upload object:
13
16
  # - key: A randomly generated unique key to replace the file
@@ -17,13 +20,13 @@ class <%= model_constant %>Uploader < ApplicationController
17
20
  # - location: The location of the file on S3. Available only to the
18
21
  # upload object passed into the on_complete callback
19
22
  #
20
- on_begin do |upload|
23
+ on_begin do |upload, session|
21
24
  # Code to be evaluated when upload begins.
22
25
  end
23
26
 
24
27
  # See above comment. Called when the upload has successfully completed
25
- on_complete do |upload|
28
+ on_complete do |upload, session|
26
29
  # Code to be evaluated when upload completes
27
30
  end
28
31
 
29
- end
32
+ end
@@ -2,4 +2,4 @@ module S3Multipart
2
2
  class Engine < Rails::Engine
3
3
  isolate_namespace S3Multipart
4
4
  end
5
- end
5
+ end
@@ -8,6 +8,10 @@ if defined?(Rails)
8
8
  end
9
9
  end
10
10
 
11
+ initializer "s3_multipart.active_record" do
12
+ ActiveRecord::Base.include_root_in_json = false
13
+ end
14
+
11
15
  # Load all of the upload controllers in app/uploaders/multipart
12
16
  initializer "s3_multipart.load_upload_controllers" do
13
17
  begin
@@ -22,4 +26,4 @@ if defined?(Rails)
22
26
 
23
27
  end
24
28
  end
25
- end
29
+ end
@@ -2,8 +2,12 @@ module S3Multipart
2
2
  module Uploader
3
3
  module Validations
4
4
 
5
- # To do
5
+ attr_accessor :file_types
6
+
7
+ def accept(types)
8
+ self.file_types = types
9
+ end
6
10
 
7
11
  end
8
12
  end
9
- end
13
+ end
@@ -1,3 +1,3 @@
1
1
  module S3Multipart
2
- VERSION = "0.0.7"
3
- end
2
+ VERSION = "0.0.8"
3
+ end
data/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "s3_multipart",
3
- "version": "0.0.6",
3
+ "version": "0.0.8",
4
4
  "devDependencies": {
5
5
  "grunt": "latest",
6
6
  "grunt-contrib-uglify": "latest",
7
7
  "grunt-jasmine-runner": "latest"
8
8
  }
9
- }
9
+ }
@@ -3,8 +3,8 @@ require "digest/sha1"
3
3
 
4
4
  describe "Uploads controller" do
5
5
  it "should create an upload" do
6
- post '/s3_multipart/uploads', {object_name: "example_object", content_type: "video/x-ms-wmv", uploader: Digest::SHA1.hexdigest("VideoUploader")}
6
+ post '/s3_multipart/uploads', {object_name: "example_object.wmv", content_type: "video/x-ms-wmv", uploader: Digest::SHA1.hexdigest("VideoUploader")}
7
7
  parsed_body = JSON.parse(response.body)
8
8
  parsed_body.should_not eq({"error"=>"There was an error initiating the upload"})
9
9
  end
10
- end
10
+ end
@@ -5,6 +5,9 @@ class VideoUploader < ApplicationController
5
5
  # relationship between the internal upload model and the given model.
6
6
  attach :video
7
7
 
8
+ # Only accept certain file types. Expects an array of valid extensions.
9
+ accept %w(wmv avi mp4 mkv mov mpeg)
10
+
8
11
  # Takes in a block that will be evaluated when the upload has been
9
12
  # successfully initiated. The block will be passed an instance of
10
13
  # the upload object when the callback is made.
@@ -29,4 +32,4 @@ class VideoUploader < ApplicationController
29
32
  # Code to be evaluated when upload completes
30
33
  end
31
34
 
32
- end
35
+ end
@@ -3,13 +3,13 @@ require 'spec_helper.rb'
3
3
  describe "An upload controller" do
4
4
 
5
5
  before(:all) do
6
- class Uploader
6
+ class GenericUploader
7
7
  extend S3Multipart::Uploader::Core
8
8
  end
9
9
  end
10
10
 
11
11
  it "should set up callbacks" do
12
- Uploader.class_eval do
12
+ GenericUploader.class_eval do
13
13
  on_begin do |upload|
14
14
  "Upload has begun"
15
15
  end
@@ -19,13 +19,19 @@ describe "An upload controller" do
19
19
  end
20
20
  end
21
21
 
22
- Uploader.on_begin_callback.call.should eql("Upload has begun")
23
- Uploader.on_complete_callback.call.should eql("Upload has completed")
22
+ GenericUploader.on_begin_callback.call.should eql("Upload has begun")
23
+ GenericUploader.on_complete_callback.call.should eql("Upload has completed")
24
24
  end
25
25
 
26
26
  it "should attach a model to the uploader" do
27
- Uploader.attach :video
27
+ GenericUploader.attach :video
28
28
  S3Multipart::Upload.new.respond_to?(:video).should be_true
29
29
  end
30
30
 
31
- end
31
+ it "should store the allowed file types" do
32
+ exts = %w(wmv avi mp4 mkv mov mpeg)
33
+ GenericUploader.accept(exts)
34
+ GenericUploader.file_types.should eql(exts)
35
+ end
36
+
37
+ end
@@ -155,12 +155,10 @@ function S3MP(options) {
155
155
  }
156
156
 
157
157
  _.each(files, function(file, key) {
158
- //Do validation for each file before creating a new upload object
159
- // if (!file.type.match(/video/)) {
160
- // return false;
161
- // }
162
158
  if (file.size < 5000000) {
163
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.
164
162
  }
165
163
 
166
164
  var upload = new Upload(file, S3MP, key);
@@ -341,6 +339,7 @@ S3MP.prototype.resume = function(key) {
341
339
 
342
340
  this.onResume();
343
341
  };
342
+
344
343
  // Upload constructor
345
344
  function Upload(file, o, key) {
346
345
  function Upload() {
@@ -369,25 +368,25 @@ function Upload(file, o, key) {
369
368
  } else if (this.size > 100000000) { // greater than 100 mb
370
369
  num_segs = 20;
371
370
  pipes = 5;
372
- } else if (this.size > 5000000) { // greater than 5 mb (S3 does not allow multipart uploads < 5 mb)
371
+ } else { // greater than 5 mb (S3 does not allow multipart uploads < 5 mb)
373
372
  num_segs = 2;
374
373
  pipes = 2;
375
- } else {
376
- num_segs = 10;
377
- pipes = 3;
378
- }
374
+ }
379
375
 
380
376
  chunk_segs = _.range(num_segs + 1);
381
377
  chunk_lens = _.map(chunk_segs, function(seg) {
382
378
  return Math.round(seg * (file.size/num_segs));
383
379
  });
384
380
 
385
- this.parts = _.map(chunk_lens, function(len, i) {
386
- blob = upload.sliceBlob(file, len, chunk_lens[i+1]);
387
- return new UploadPart(blob, i+1, upload);
388
- });
389
-
390
- this.parts.pop(); // Remove the empty blob at the end of the array
381
+ if (upload.sliceBlob == "Unsupported") {
382
+ this.parts = [new UploadPart(file, 0, upload)];
383
+ } else {
384
+ this.parts = _.map(chunk_lens, function(len, i) {
385
+ blob = upload.sliceBlob(file, len, chunk_lens[i+1]);
386
+ return new UploadPart(blob, i+1, upload);
387
+ });
388
+ this.parts.pop(); // Remove the empty blob at the end of the array
389
+ }
391
390
 
392
391
  // init function will initiate the multipart upload, sign all the parts, and
393
392
  // start uploading some parts in parallel
@@ -417,6 +416,7 @@ function Upload(file, o, key) {
417
416
  Upload.prototype = o;
418
417
  return new Upload();
419
418
  }
419
+
420
420
  // Upload part constructor
421
421
  function UploadPart(blob, key, upload) {
422
422
  var part, xhr;
@@ -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):this.size>5e6?(num_segs=2,l=2):(num_segs=10,l=3),a=_.range(num_segs+1),f=_.map(a,function(t){return Math.round(t*(e.size/num_segs))}),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,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.7
4
+ version: 0.0.8
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-15 00:00:00.000000000 Z
12
+ date: 2013-02-24 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.25
300
+ rubygems_version: 1.8.24
301
301
  signing_key:
302
302
  specification_version: 3
303
303
  summary: Upload directly to S3 using multipart uploading