s3_multipart 0.0.4 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +11 -0
- data/Gemfile +2 -2
- data/Gemfile.lock +8 -1
- data/README.md +150 -42
- data/Rakefile +10 -0
- data/app/controllers/s3_multipart/application_controller.rb +6 -3
- data/app/controllers/s3_multipart/uploads_controller.rb +30 -29
- data/app/models/s3_multipart/upload.rb +13 -5
- data/grunt.js +44 -0
- data/javascripts/footer.js +5 -0
- data/javascripts/header.js +14 -0
- data/javascripts/libs/underscore.js +1 -0
- data/javascripts/s3mp.js +329 -0
- data/javascripts/upload.js +76 -0
- data/javascripts/uploadpart.js +32 -0
- data/lib/generators/s3_multipart/install_generator.rb +30 -0
- data/lib/generators/s3_multipart/templates/add_uploader_column_to_model.rb +7 -0
- data/lib/generators/s3_multipart/templates/aws.yml +4 -0
- data/lib/generators/s3_multipart/templates/configuration_initializer.rb +8 -0
- data/lib/generators/s3_multipart/templates/uploader.rb +29 -0
- data/{db/migrate/20110727184726_create_s3_multipart_uploads.rb → lib/generators/s3_multipart/templates/uploads_table_migration.rb} +2 -0
- data/lib/generators/s3_multipart/uploader_generator.rb +33 -0
- data/lib/s3_multipart/action_view_helpers/form_helper.rb +2 -1
- data/lib/s3_multipart/config.rb +12 -0
- data/lib/s3_multipart/http/net_http.rb +6 -6
- data/lib/s3_multipart/railtie.rb +12 -0
- data/lib/s3_multipart/transfer_helpers.rb +92 -0
- data/lib/s3_multipart/uploader/callbacks.rb +17 -0
- data/lib/s3_multipart/uploader/validations.rb +9 -0
- data/lib/s3_multipart/uploader.rb +27 -68
- data/lib/s3_multipart/version.rb +1 -1
- data/lib/s3_multipart.rb +4 -42
- data/package.json +9 -0
- data/s3_multipart.gemspec +6 -3
- data/spec/integration/uploads_controller_spec.rb +2 -1
- data/spec/internal/app/assets/javascripts/application.js +148 -63
- data/spec/internal/app/assets/stylesheets/application.css.scss +384 -0
- data/spec/internal/app/assets/stylesheets/font-awesome.scss +499 -0
- data/spec/internal/app/controllers/application_controller.rb +4 -1
- data/spec/internal/app/controllers/pages_controller.rb +3 -7
- data/spec/internal/app/models/user.rb +4 -0
- data/spec/internal/app/models/video.rb +5 -0
- data/spec/internal/app/uploaders/multipart/video_uploader.rb +32 -0
- data/spec/internal/app/views/pages/upload.html.erb +38 -4
- data/spec/internal/config/routes.rb +1 -1
- data/spec/internal/db/schema.rb +22 -14
- data/spec/internal/public/fonts/FontAwesome.otf +0 -0
- data/spec/internal/public/fonts/fontawesome-webfont.eot +0 -0
- data/spec/internal/public/fonts/fontawesome-webfont.ttf +0 -0
- data/spec/internal/public/fonts/fontawesome-webfont.woff +0 -0
- data/spec/internal/tmp/cache/sass/077f66d4d9153cfd737b81ab3f4c5d5858b0db3b/_grid-background.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_appearance.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_background-clip.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_background-origin.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_background-size.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_border-radius.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_box-shadow.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_box-sizing.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_box.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_columns.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_filter.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_font-face.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_hyphenation.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_images.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_inline-block.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_opacity.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_regions.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_shared.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_text-shadow.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_transform.scssc +0 -0
- data/spec/internal/tmp/cache/sass/1925334270d3f17f5ea2c4e86092ee0214484038/_transition.scssc +0 -0
- data/spec/internal/tmp/cache/sass/351072894b53bf1a2d2af4bd57c177c805cf063e/_contrast.scssc +0 -0
- data/spec/internal/tmp/cache/sass/49ecd6d86aba13e15e515be708bbfd8bb67a011a/_css3.scssc +0 -0
- data/spec/internal/tmp/cache/sass/49ecd6d86aba13e15e515be708bbfd8bb67a011a/_support.scssc +0 -0
- data/spec/internal/tmp/cache/sass/49ecd6d86aba13e15e515be708bbfd8bb67a011a/_typography.scssc +0 -0
- data/spec/internal/tmp/cache/sass/49ecd6d86aba13e15e515be708bbfd8bb67a011a/_utilities.scssc +0 -0
- data/spec/internal/tmp/cache/sass/633bc5f017268b81a60f921876c980c4adcebb74/_base.scssc +0 -0
- data/spec/internal/tmp/cache/sass/633bc5f017268b81a60f921876c980c4adcebb74/_sprite-img.scssc +0 -0
- data/spec/internal/tmp/cache/sass/68dbce8ac3037e0cd797c9b5c6aa131096361144/_utilities.scssc +0 -0
- data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_clearfix.scssc +0 -0
- data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_float.scssc +0 -0
- data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_hacks.scssc +0 -0
- data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_min.scssc +0 -0
- data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_reset.scssc +0 -0
- data/spec/internal/tmp/cache/sass/7722315031801f2a146090aa73380dfd28539145/_tag-cloud.scssc +0 -0
- data/spec/internal/tmp/cache/sass/775792c99f07f64e98296a8dc8c9d2c74a28fd8a/_reset.scssc +0 -0
- data/spec/internal/tmp/cache/sass/8b4f1545a124f2498699911561d1ec6302c9de56/_ellipsis.scssc +0 -0
- data/spec/internal/tmp/cache/sass/8b4f1545a124f2498699911561d1ec6302c9de56/_force-wrap.scssc +0 -0
- data/spec/internal/tmp/cache/sass/8b4f1545a124f2498699911561d1ec6302c9de56/_nowrap.scssc +0 -0
- data/spec/internal/tmp/cache/sass/8b4f1545a124f2498699911561d1ec6302c9de56/_replacement.scssc +0 -0
- data/spec/internal/tmp/cache/sass/abd68615441476dbd8e9fdc1606210305b3177fd/_bullets.scssc +0 -0
- data/spec/internal/tmp/cache/sass/abd68615441476dbd8e9fdc1606210305b3177fd/_horizontal-list.scssc +0 -0
- data/spec/internal/tmp/cache/sass/abd68615441476dbd8e9fdc1606210305b3177fd/_inline-block-list.scssc +0 -0
- data/spec/internal/tmp/cache/sass/abd68615441476dbd8e9fdc1606210305b3177fd/_inline-list.scssc +0 -0
- data/spec/internal/tmp/cache/sass/b1bb3e2fbcb5c6a71ea2273c6dcb7c8967d2a058/_alternating-rows-and-columns.scssc +0 -0
- data/spec/internal/tmp/cache/sass/b1bb3e2fbcb5c6a71ea2273c6dcb7c8967d2a058/_borders.scssc +0 -0
- data/spec/internal/tmp/cache/sass/b1bb3e2fbcb5c6a71ea2273c6dcb7c8967d2a058/_scaffolding.scssc +0 -0
- data/spec/internal/tmp/cache/sass/bc7f11d6e8f07a22f545deb35760cfc7830f3369/application.css.scssc +0 -0
- data/spec/internal/tmp/cache/sass/bc7f11d6e8f07a22f545deb35760cfc7830f3369/font-awesome.scssc +0 -0
- data/spec/internal/tmp/cache/sass/c9342b40bf8bc7bf64ce587860fb065cbf6d2ca4/_color.scssc +0 -0
- data/spec/internal/tmp/cache/sass/c9342b40bf8bc7bf64ce587860fb065cbf6d2ca4/_general.scssc +0 -0
- data/spec/internal/tmp/cache/sass/c9342b40bf8bc7bf64ce587860fb065cbf6d2ca4/_sprites.scssc +0 -0
- data/spec/internal/tmp/cache/sass/c9342b40bf8bc7bf64ce587860fb065cbf6d2ca4/_tables.scssc +0 -0
- data/spec/internal/tmp/cache/sass/d1da036a47062a9f69ce0327962c00aa77d2ea44/_hover-link.scssc +0 -0
- data/spec/internal/tmp/cache/sass/d1da036a47062a9f69ce0327962c00aa77d2ea44/_link-colors.scssc +0 -0
- data/spec/internal/tmp/cache/sass/d1da036a47062a9f69ce0327962c00aa77d2ea44/_unstyled-link.scssc +0 -0
- data/spec/internal/tmp/cache/sass/d559b7bf0c5e0c0d23f2f34bfc21b4817fb38463/_links.scssc +0 -0
- data/spec/internal/tmp/cache/sass/d559b7bf0c5e0c0d23f2f34bfc21b4817fb38463/_lists.scssc +0 -0
- data/spec/internal/tmp/cache/sass/d559b7bf0c5e0c0d23f2f34bfc21b4817fb38463/_text.scssc +0 -0
- data/spec/internal/tmp/cache/sass/d559b7bf0c5e0c0d23f2f34bfc21b4817fb38463/_vertical_rhythm.scssc +0 -0
- data/spec/internal/tmp/cache/sass/e2e5b9bb57a9d1b018b9f546bc8b16f6b2a627a9/_compass.scssc +0 -0
- data/spec/internal/tmp/cache/sass/e654955d4502954a409bf21071c0d98eb4ac2e9f/_utilities.scssc +0 -0
- data/spec/javascripts/UploadSpec.js +75 -0
- data/spec/javascripts/helpers/SpecHelper.js +9 -0
- data/spec/javascripts/support/jasmine.yml +84 -0
- data/spec/unit/upload_controller_spec.rb +31 -0
- data/spec/{requests/uploader_spec.rb → unit/upload_spec.rb} +9 -10
- data/spec/unit/uploader_module_spec.rb +31 -0
- data/vendor/assets/javascripts/s3_multipart/lib.js +456 -0
- data/vendor/assets/javascripts/s3_multipart/lib.min.js +1 -0
- metadata +103 -12
- data/.rspec +0 -1
- data/lib/s3_multipart/uploader/config.rb +0 -15
- data/spec/internal/app/assets/stylesheets/application.css +0 -0
- data/spec/internal/db/combustion_test.sqlite3 +0 -0
- data/vendor/assets/javascripts/s3_multipart/index.js +0 -1
- data/vendor/assets/javascripts/s3_multipart/s3_multipart.js +0 -478
@@ -0,0 +1,92 @@
|
|
1
|
+
module S3Multipart
|
2
|
+
|
3
|
+
# Collection of methods to be mixed in to the Upload class.
|
4
|
+
# Handle all communication with Amazon S3 servers
|
5
|
+
module TransferHelpers
|
6
|
+
|
7
|
+
def initiate(options)
|
8
|
+
real_name = options[:object_name]
|
9
|
+
unique_name = UUID.generate + real_name.match(/.[A-Za-z]+$/)[0] # clean this up later
|
10
|
+
url = "/#{unique_name}?uploads"
|
11
|
+
|
12
|
+
headers = {content_type: options[:content_type]}
|
13
|
+
headers[:authorization], headers[:date] = sign_request verb: 'POST', url: url, content_type: options[:content_type]
|
14
|
+
|
15
|
+
response = Http.post url, {headers: headers}
|
16
|
+
parsed_response_body = XmlSimple.xml_in(response.body)
|
17
|
+
|
18
|
+
return { "key" => parsed_response_body["Key"][0],
|
19
|
+
"upload_id" => parsed_response_body["UploadId"][0],
|
20
|
+
"name" => real_name }
|
21
|
+
end
|
22
|
+
|
23
|
+
def sign_batch(options)
|
24
|
+
parts = options[:content_lengths].split('-').each_with_index.map do |len, i|
|
25
|
+
sign_part(options.merge!({content_length: len, part_number: i+1}))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def sign_part(options)
|
30
|
+
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]
|
32
|
+
|
33
|
+
return {authorization: authorization, date: date}
|
34
|
+
end
|
35
|
+
|
36
|
+
def complete(options)
|
37
|
+
options[:content_type] = "application/xml"
|
38
|
+
|
39
|
+
url = "/#{options[:object_name]}?uploadId=#{options[:upload_id]}"
|
40
|
+
|
41
|
+
body = format_part_list_in_xml(options)
|
42
|
+
headers = { content_type: options[:content_type],
|
43
|
+
content_length: options[:content_length] }
|
44
|
+
|
45
|
+
headers[:authorization], headers[:date] = sign_request verb: 'POST', url: url, content_type: options[:content_type]
|
46
|
+
|
47
|
+
response = Http.post url, {headers: headers, body: body}
|
48
|
+
parsed_response_body = XmlSimple.xml_in(response.body)
|
49
|
+
|
50
|
+
begin
|
51
|
+
return { location: parsed_response_body["Location"][0] }
|
52
|
+
rescue NoMethodError
|
53
|
+
return { error: "Upload does not exist"} if parsed_response_body["Message"].first.match("The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def sign_request(options)
|
58
|
+
#options.default = ""
|
59
|
+
time = Time.now.strftime("%a, %d %b %Y %T %Z")
|
60
|
+
|
61
|
+
return [calculate_authorization_hash(time, options), time]
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def calculate_authorization_hash(time, options)
|
67
|
+
date = String.new(time)
|
68
|
+
date.insert(0, "\nx-amz-date:") if from_upload_part?(options) && options[:parts].nil?
|
69
|
+
|
70
|
+
unsigned_request = "#{options[:verb]}\n\n#{options[:content_type]}\n#{date}\n/#{Config.instance.bucket_name}#{options[:url]}"
|
71
|
+
signature = Base64.strict_encode64(OpenSSL::HMAC.digest('sha1', Config.instance.s3_secret_key, unsigned_request))
|
72
|
+
|
73
|
+
authorization = "AWS" + " " + Config.instance.s3_access_key + ":" + signature
|
74
|
+
end
|
75
|
+
|
76
|
+
def from_upload_part?(options)
|
77
|
+
options[:content_length].to_s.match(/^[0-9]+$/) ? true : false
|
78
|
+
end
|
79
|
+
|
80
|
+
def format_part_list_in_xml(options)
|
81
|
+
hash = Hash["Part", ""];
|
82
|
+
hash["Part"] = options[:parts].map do |part|
|
83
|
+
{ "PartNumber" => part[:partNum], "ETag" => part[:ETag] }
|
84
|
+
end
|
85
|
+
hash["Part"].sort_by! {|obj| obj["PartNumber"]}
|
86
|
+
|
87
|
+
XmlSimple.xml_out(hash, { :RootName => "CompleteMultipartUpload", :AttrPrefix => true })
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module S3Multipart
|
2
|
+
module Uploader
|
3
|
+
module Callbacks
|
4
|
+
|
5
|
+
attr_accessor :on_begin_callback, :on_complete_callback
|
6
|
+
|
7
|
+
def on_begin(&block)
|
8
|
+
self.on_begin_callback = block
|
9
|
+
end
|
10
|
+
|
11
|
+
def on_complete(&block)
|
12
|
+
self.on_complete_callback = block
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,85 +1,44 @@
|
|
1
|
+
require "s3_multipart/uploader/callbacks"
|
2
|
+
require "s3_multipart/uploader/validations"
|
3
|
+
require 'active_support/core_ext/string'
|
4
|
+
require "digest/sha1"
|
5
|
+
|
1
6
|
module S3Multipart
|
2
7
|
module Uploader
|
3
|
-
def initiate(options)
|
4
|
-
url = "/#{options[:object_name]}?uploads"
|
5
|
-
|
6
|
-
headers = {content_type: options[:content_type]}
|
7
|
-
headers[:authorization], headers[:date] = sign_request verb: 'POST', url: url, content_type: options[:content_type]
|
8
|
-
|
9
|
-
response = Http.post url, {headers: headers}
|
10
|
-
parsed_response_body = XmlSimple.xml_in(response.body)
|
11
8
|
|
12
|
-
|
13
|
-
|
14
|
-
"name" => options[:object_name] }
|
9
|
+
class << self
|
10
|
+
attr_accessor :controllers
|
15
11
|
end
|
16
12
|
|
17
|
-
|
18
|
-
parts = options[:content_lengths].split('-').each_with_index.map do |len, i|
|
19
|
-
sign_part(options.merge!({content_length: len, part_number: i+1}))
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def sign_part(options)
|
24
|
-
url = "/#{options[:object_name]}?partNumber=#{options[:part_number]}&uploadId=#{options[:upload_id]}"
|
25
|
-
authorization, date = sign_request verb: 'PUT', url: url, content_length: options[:content_length]
|
26
|
-
|
27
|
-
return {authorization: authorization, date: date}
|
28
|
-
end
|
29
|
-
|
30
|
-
def complete(options)
|
31
|
-
options[:content_type] = "application/xml"
|
32
|
-
|
33
|
-
url = "/#{options[:object_name]}?uploadId=#{options[:upload_id]}"
|
13
|
+
self.controllers = {}
|
34
14
|
|
35
|
-
|
36
|
-
|
37
|
-
content_length: options[:content_length] }
|
38
|
-
|
39
|
-
headers[:authorization], headers[:date] = sign_request verb: 'POST', url: url, content_type: options[:content_type]
|
40
|
-
|
41
|
-
response = Http.post url, {headers: headers, body: body}
|
42
|
-
response.body
|
43
|
-
parsed_response_body = XmlSimple.xml_in(response.body)
|
44
|
-
|
45
|
-
begin
|
46
|
-
return { location: parsed_response_body["Location"][0] }
|
47
|
-
rescue NoMethodError
|
48
|
-
return { error: "Upload does not exist"} if parsed_response_body["Message"].first.match("The specified upload does not exist. The upload ID may be invalid, or the upload may have been aborted or completed.")
|
49
|
-
end
|
15
|
+
def self.serialize(controller)
|
16
|
+
controllers[controller.to_s.to_sym]
|
50
17
|
end
|
51
18
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
return [calculate_authorization_hash(time, options), time]
|
19
|
+
# What is wrong with this method?
|
20
|
+
def self.deserialize(digest)
|
21
|
+
controllers.key(digest).to_s.constantize
|
57
22
|
end
|
58
23
|
|
59
|
-
|
24
|
+
# Generated multipart upload controllers (which reside in the app/uploaders/multipart
|
25
|
+
# directory in the Rails application) extend this module.
|
26
|
+
module Core
|
60
27
|
|
61
|
-
|
62
|
-
|
63
|
-
date.insert(0, "\nx-amz-date:") if from_upload_part?(options) && options[:parts].nil?
|
28
|
+
include S3Multipart::Uploader::Callbacks
|
29
|
+
include S3Multipart::Uploader::Validations
|
64
30
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
authorization = "AWS" + " " + Config.instance.s3_access_key + ":" + signature
|
69
|
-
end
|
70
|
-
|
71
|
-
def from_upload_part?(options)
|
72
|
-
options[:content_length].to_s.match(/^[0-9]+$/) ? true : false
|
73
|
-
end
|
31
|
+
def self.extended(klass)
|
32
|
+
Uploader.controllers[klass.to_s.to_sym] = Digest::SHA1.hexdigest(klass.to_s)
|
33
|
+
end
|
74
34
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
35
|
+
def attach(model)
|
36
|
+
S3Multipart::Upload.class_eval do
|
37
|
+
has_one(model)
|
38
|
+
end
|
79
39
|
end
|
80
|
-
hash["Part"].sort_by! {|obj| obj["PartNumber"]}
|
81
40
|
|
82
|
-
XmlSimple.xml_out(hash, { :RootName => "CompleteMultipartUpload", :AttrPrefix => true })
|
83
41
|
end
|
84
|
-
|
42
|
+
|
43
|
+
end
|
85
44
|
end
|
data/lib/s3_multipart/version.rb
CHANGED
data/lib/s3_multipart.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
|
-
require 'active_record'
|
3
|
+
# require 'active_record'
|
4
4
|
require 'xmlsimple'
|
5
5
|
require 'uuid'
|
6
6
|
|
@@ -8,16 +8,8 @@ module S3Multipart
|
|
8
8
|
|
9
9
|
class << self
|
10
10
|
|
11
|
-
# Syntax:
|
12
|
-
#
|
13
|
-
# S3_Multipart.configure do |config|
|
14
|
-
# config.s3_access_key = ''
|
15
|
-
# config.s3_secret_key = ''
|
16
|
-
# config.bucket_name = ''
|
17
|
-
# end
|
18
|
-
|
19
11
|
def configure(&block)
|
20
|
-
S3Multipart::
|
12
|
+
S3Multipart::Config.configure(block)
|
21
13
|
end
|
22
14
|
|
23
15
|
def remove_unfinished_uploads(seconds=60*60*24*10)
|
@@ -26,41 +18,11 @@ module S3Multipart
|
|
26
18
|
|
27
19
|
end
|
28
20
|
|
29
|
-
module ActionControllerHelpers
|
30
|
-
|
31
|
-
module AttachUploader
|
32
|
-
def self.on_begin(&block)
|
33
|
-
S3Multipart::Upload.class_eval do
|
34
|
-
self.on_begin_callback = block
|
35
|
-
def on_begin
|
36
|
-
Upload.on_begin_callback.call(self)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
def self.on_complete(&block)
|
42
|
-
S3Multipart::Upload.class_eval do
|
43
|
-
self.on_complete_callback = block
|
44
|
-
def on_complete
|
45
|
-
Upload.on_complete_callback.call(self)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def attach_uploader
|
52
|
-
return AttachUploader
|
53
|
-
end
|
54
|
-
|
55
|
-
end
|
56
|
-
|
57
21
|
end
|
58
22
|
|
23
|
+
require 's3_multipart/config'
|
59
24
|
require 's3_multipart/railtie'
|
60
25
|
require 's3_multipart/engine'
|
61
26
|
require 's3_multipart/http/net_http'
|
62
27
|
require 's3_multipart/uploader'
|
63
|
-
require 's3_multipart/
|
64
|
-
|
65
|
-
|
66
|
-
ActionController::Base.send(:include, S3Multipart::ActionControllerHelpers)
|
28
|
+
require 's3_multipart/transfer_helpers'
|
data/package.json
ADDED
data/s3_multipart.gemspec
CHANGED
@@ -2,15 +2,21 @@
|
|
2
2
|
|
3
3
|
$:.push File.expand_path("../lib", __FILE__)
|
4
4
|
require 's3_multipart/version'
|
5
|
+
require 'date'
|
5
6
|
|
6
7
|
Gem::Specification.new do |s|
|
7
8
|
s.name = "s3_multipart"
|
9
|
+
s.date = Date.today
|
8
10
|
s.version = S3Multipart::VERSION
|
9
11
|
s.authors = ["Max Gillett"]
|
10
12
|
s.email = ["max.gillett@gmail.com"]
|
11
13
|
s.homepage = "https://github.com/maxgillett/s3_multipart"
|
12
14
|
s.summary = %q{Upload directly to S3 using multipart uploading}
|
13
15
|
s.description = %q{See github for installation and configuration }
|
16
|
+
s.extra_rdoc_files = ["README.md"]
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.require_paths = ["lib"]
|
14
20
|
|
15
21
|
s.add_dependency "uuid", ">= 2.3.6"
|
16
22
|
s.add_dependency "xml-simple", ">= 1.1.2"
|
@@ -21,7 +27,4 @@ Gem::Specification.new do |s|
|
|
21
27
|
s.add_development_dependency 'rspec'
|
22
28
|
s.add_development_dependency 'rspec-rails'
|
23
29
|
s.add_development_dependency 'capybara'
|
24
|
-
|
25
|
-
s.files = `git ls-files`.split("\n")
|
26
|
-
s.require_paths = ["lib"]
|
27
30
|
end
|
@@ -1,8 +1,9 @@
|
|
1
1
|
require 'spec_helper.rb'
|
2
|
+
require "digest/sha1"
|
2
3
|
|
3
4
|
describe "Uploads controller" do
|
4
5
|
it "should create an upload" do
|
5
|
-
post '/s3_multipart/uploads', {object_name: "example_object", content_type: "video/x-ms-wmv"}
|
6
|
+
post '/s3_multipart/uploads', {object_name: "example_object", content_type: "video/x-ms-wmv", uploader: Digest::SHA1.hexdigest("VideoUploader")}
|
6
7
|
parsed_body = JSON.parse(response.body)
|
7
8
|
parsed_body.should_not eq({"error"=>"There was an error initiating the upload"})
|
8
9
|
end
|
@@ -12,83 +12,168 @@
|
|
12
12
|
//
|
13
13
|
//= require underscore
|
14
14
|
//= require_tree .
|
15
|
-
//= require
|
15
|
+
//= require jquery.ui.progressbar
|
16
|
+
//= require s3_multipart/lib
|
16
17
|
|
17
18
|
$(function() {
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
19
|
+
var file_list, s3mp;
|
20
|
+
|
21
|
+
$(".submit-button").click(function() {
|
22
|
+
s3mp = new window.S3MP({
|
23
|
+
bucket: 'bitcast-bucket',
|
24
|
+
fileInputElement: "#uploader",
|
25
|
+
fileList: file_list,
|
26
|
+
onStart: function(upload) {
|
27
|
+
var id = upload.id
|
28
|
+
, key = upload.key;
|
29
|
+
|
30
|
+
// Insert the upload details form if only one file upload is going on
|
31
|
+
if (file_list.length === 1) {
|
32
|
+
// $.ajax({
|
33
|
+
// url: "/videos/"+id+"/settings",
|
34
|
+
// cache: false,
|
35
|
+
// success: function(html){
|
36
|
+
// $(".upload-form").append(html);
|
37
|
+
// $('.edit_video').bind('ajax:success', function(evt, data, status, xhr){
|
38
|
+
// alert("Video updated successfully");
|
39
|
+
// })
|
40
|
+
// }
|
41
|
+
// });
|
39
42
|
}
|
40
|
-
|
41
|
-
|
43
|
+
|
44
|
+
// Hide the upload button + list, and insert the progress bar
|
45
|
+
$(".upload-wrapper, .upload-list").hide();
|
46
|
+
$(".upload-list").after('<div class="progress-bar-'+key+'"></div>')
|
47
|
+
$(".progress-bar-"+key).progressbar({ max: 100 })
|
48
|
+
.after('<div class="progress-bar-info progress-bar-info-'+key+'"><span class="name">'+upload.name+'</span><span class="speed"></span></div>');
|
49
|
+
|
50
|
+
console.log("File "+key+" has started uploading")
|
51
|
+
},
|
52
|
+
onComplete: function(upload) {
|
53
|
+
$('.progress-bar-'+upload.key).progressbar({ value: 100 });
|
54
|
+
$('.progress-bar-info-'+upload.key)
|
55
|
+
.find(".speed").html("100% ("+(upload.size/1000000).toFixed(1)+" MB of "+(upload.size/1000000).toFixed(1)+" MB)");
|
56
|
+
|
57
|
+
console.log("File "+upload.key+" successfully uploaded")
|
58
|
+
},
|
59
|
+
onPause: function(key) {
|
60
|
+
console.log("File "+key+" has been paused")
|
61
|
+
},
|
62
|
+
onResume: function(key) {
|
63
|
+
console.log("File "+key+" has been resumed")
|
64
|
+
},
|
65
|
+
onCancel: function(key) {
|
66
|
+
console.log("File upload "+key+" was canceled")
|
67
|
+
},
|
68
|
+
onError: function() {
|
69
|
+
console.log("There was an error")
|
70
|
+
},
|
71
|
+
onProgress: function(key, size, done, percent, speed) {
|
72
|
+
$('.progress-bar-'+key).progressbar({ value: percent });
|
73
|
+
$('.progress-bar-info-'+key)
|
74
|
+
.find(".speed").html(percent.toFixed(1)+"% ("+(done/1000000).toFixed(1)+" MB of "+(size/1000000).toFixed(1)+" MB) at "+(speed/1000).toFixed(0)+" kbps");
|
75
|
+
console.log("File %d is %f percent done (%f of %f total) and uploading at %s", key, percent, done, size, speed);
|
76
|
+
}
|
42
77
|
});
|
78
|
+
});
|
79
|
+
|
80
|
+
// Empty array to store all the files (cannot store in FileList b/c it is read only)
|
81
|
+
file_list = [];
|
82
|
+
|
83
|
+
// Code to handle upload buttons + the upload list
|
84
|
+
(function() {
|
85
|
+
var uploader, cbs;
|
86
|
+
|
87
|
+
// Reference to uploader file element
|
88
|
+
uploader = document.getElementById("uploader");
|
89
|
+
|
90
|
+
// Callback functions
|
91
|
+
cbs = {
|
92
|
+
|
93
|
+
moveFileInputEl: _.throttle(function(e) {
|
94
|
+
var offset = { left: $(uploader).parent().offset().left, top: $(uploader).parent().offset().top };
|
95
|
+
uploader.style.left = e.pageX - offset.left - 100 + "px";
|
96
|
+
uploader.style.top = e.pageY - offset.top - 5 + "px";
|
97
|
+
},50),
|
98
|
+
|
99
|
+
addActiveClass: function() {
|
100
|
+
$(".upload-button").addClass("active");
|
101
|
+
},
|
43
102
|
|
103
|
+
removeActiveClass: function() {
|
104
|
+
$(".upload-button").removeClass("active");
|
105
|
+
},
|
44
106
|
|
45
|
-
|
46
|
-
|
107
|
+
updateFileList: function() {
|
108
|
+
var upload_list, clone, size, num;
|
47
109
|
|
48
|
-
|
110
|
+
$("#uploader, .upload-button").hide();
|
111
|
+
$(".submit-button").show();
|
49
112
|
|
50
|
-
|
51
|
-
|
52
|
-
// uploader.style.left = e.pageX-280 +"px";
|
53
|
-
// uploader.style.top = "0px";
|
54
|
-
// },10);
|
55
|
-
// mouseover_fn = function() {
|
56
|
-
// $(".upload-button").addClass("active");
|
57
|
-
// };
|
58
|
-
// mouseleave_fn = function() {
|
59
|
-
// $(".upload-button").removeClass("active");
|
60
|
-
// }
|
61
|
-
// change_fn = function() {
|
62
|
-
// var upload_list, clone, size;
|
113
|
+
upload_list = $(".upload-list")
|
114
|
+
upload_list.show();
|
63
115
|
|
64
|
-
|
65
|
-
|
116
|
+
_.each($("#uploader").get(0).files, function(val, key, list) {
|
117
|
+
file_list.push(val);
|
118
|
+
|
119
|
+
size = (val.size/1000000).toFixed(1);
|
120
|
+
|
121
|
+
if (upload_list.find("li").length === 2 && upload_list.find("li").data("num") === undefined) {
|
122
|
+
clone = upload_list.find("li:first");
|
123
|
+
} else {
|
124
|
+
clone = upload_list.find("li:first").clone()
|
125
|
+
}
|
126
|
+
|
127
|
+
clone.find(".name").text(val.name);
|
128
|
+
clone.find(".size").text(size + " MB");
|
129
|
+
|
130
|
+
clone.insertBefore(".upload-list ul .select-another-video").attr("data-num", key);
|
131
|
+
});
|
132
|
+
|
133
|
+
num = file_list.length
|
134
|
+
if (num > 1) {
|
135
|
+
$(".upload-list .total").text(num+" files");
|
136
|
+
}
|
137
|
+
},
|
138
|
+
|
139
|
+
removeFile: function(e) {
|
140
|
+
var li = $(this).parent();
|
141
|
+
file_list[li.data("num")] = null;
|
142
|
+
li.remove();
|
143
|
+
|
144
|
+
// Update "total" span element
|
145
|
+
var num = _.without(file_list, null).length;
|
146
|
+
if (num > 1) {
|
147
|
+
$(".upload-list .total").text(num+" files");
|
148
|
+
}
|
149
|
+
if (num == 1) {
|
150
|
+
$(".upload-list .total").text("1 file");
|
151
|
+
}
|
152
|
+
},
|
66
153
|
|
67
|
-
|
68
|
-
|
154
|
+
pauseAll: function(e) {
|
155
|
+
_.each(file_list, function(file, key) {
|
156
|
+
s3mp.pause(key);
|
157
|
+
});
|
158
|
+
},
|
69
159
|
|
70
|
-
|
71
|
-
|
160
|
+
resumeAll: function(e) {
|
161
|
+
_.each(file_list, function(file, key) {
|
162
|
+
s3mp.resume(key);
|
163
|
+
});
|
164
|
+
}
|
72
165
|
|
73
|
-
|
74
|
-
// clone = upload_list.find("li");
|
75
|
-
// } else {
|
76
|
-
// clone = upload_list.find("li:first").clone()
|
77
|
-
// }
|
166
|
+
}
|
78
167
|
|
79
|
-
|
80
|
-
|
168
|
+
$(".upload-wrapper")
|
169
|
+
.mouseover(cbs.addActiveClass)
|
170
|
+
.mouseleave(cbs.removeActiveClass)
|
171
|
+
.mousemove(cbs.moveFileInputEl)
|
172
|
+
.live('change',cbs.updateFileList);
|
81
173
|
|
82
|
-
|
83
|
-
// });
|
84
|
-
// }
|
174
|
+
$(".upload-list").on("click", ".delete", cbs.removeFile);
|
85
175
|
|
86
|
-
|
87
|
-
// .mouseover(mouseover_fn)
|
88
|
-
// .mouseleave(mouseleave_fn)
|
89
|
-
// .mousemove(mousemove_fn)
|
90
|
-
// .live('change',change_fn);
|
176
|
+
})();
|
91
177
|
|
92
|
-
|
178
|
+
});
|
93
179
|
|
94
|
-
});
|