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 +4 -1
- data/Gemfile.lock +1 -1
- data/README.md +14 -7
- data/app/controllers/s3_multipart/uploads_controller.rb +4 -4
- data/app/models/s3_multipart/upload.rb +23 -2
- data/javascripts/s3mp.js +3 -5
- data/javascripts/upload.js +12 -12
- data/lib/generators/s3_multipart/templates/uploader.rb +7 -4
- data/lib/s3_multipart/engine.rb +1 -1
- data/lib/s3_multipart/railtie.rb +5 -1
- data/lib/s3_multipart/uploader/validations.rb +6 -2
- data/lib/s3_multipart/version.rb +2 -2
- data/package.json +2 -2
- data/spec/integration/uploads_controller_spec.rb +2 -2
- data/spec/internal/app/uploaders/multipart/video_uploader.rb +4 -1
- data/spec/unit/upload_controller_spec.rb +12 -6
- data/vendor/assets/javascripts/s3_multipart/lib.js +15 -15
- data/vendor/assets/javascripts/s3_multipart/lib.min.js +1 -1
- metadata +3 -3
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
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(
|
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
|
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
|
-
*
|
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
|
-
|
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 =
|
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
|
+
};
|
data/javascripts/upload.js
CHANGED
@@ -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
|
29
|
+
} else { // greater than 5 mb (S3 does not allow multipart uploads < 5 mb)
|
30
30
|
num_segs = 2;
|
31
31
|
pipes = 2;
|
32
|
-
}
|
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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
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
|
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
|
data/lib/s3_multipart/engine.rb
CHANGED
data/lib/s3_multipart/railtie.rb
CHANGED
@@ -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
|
data/lib/s3_multipart/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module S3Multipart
|
2
|
-
VERSION = "0.0.
|
3
|
-
end
|
2
|
+
VERSION = "0.0.8"
|
3
|
+
end
|
data/package.json
CHANGED
@@ -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
|
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
|
-
|
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
|
-
|
23
|
-
|
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
|
-
|
27
|
+
GenericUploader.attach :video
|
28
28
|
S3Multipart::Upload.new.respond_to?(:video).should be_true
|
29
29
|
end
|
30
30
|
|
31
|
-
|
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
|
371
|
+
} else { // greater than 5 mb (S3 does not allow multipart uploads < 5 mb)
|
373
372
|
num_segs = 2;
|
374
373
|
pipes = 2;
|
375
|
-
}
|
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
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
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):
|
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.
|
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-
|
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.
|
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
|