activestorage-resumable 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +81 -0
- data/Rakefile +19 -0
- data/app/assets/javascripts/activestorage-resumable.js +7853 -0
- data/app/controllers/active_storage/resumable_uploads_controller.rb +26 -0
- data/app/javascript/activestorage-resumable/helpers.js +51 -0
- data/app/javascript/activestorage-resumable/index.js +11 -0
- data/app/javascript/activestorage-resumable/resumable_blob_record.js +73 -0
- data/app/javascript/activestorage-resumable/resumable_blob_upload.js +56 -0
- data/app/javascript/activestorage-resumable/resumable_file_checksum.js +66 -0
- data/app/javascript/activestorage-resumable/resumable_upload.js +48 -0
- data/app/javascript/activestorage-resumable/resumable_upload_controller.js +69 -0
- data/app/javascript/activestorage-resumable/resumable_uploads_controller.js +50 -0
- data/app/javascript/activestorage-resumable/ujs-resumable.js +86 -0
- data/config/routes.rb +8 -0
- data/db/migrate/20190825001409_add_resumable_url_to_active_storage_blobs.rb +5 -0
- data/lib/actionview_extensions/helpers/form_tag_helper.rb +20 -0
- data/lib/activestorage/resumable.rb +17 -0
- data/lib/activestorage/resumable/engine.rb +9 -0
- data/lib/activestorage/resumable/version.rb +7 -0
- data/lib/activestorage_extensions/blob.rb +37 -0
- data/lib/activestorage_extensions/service/gcs_service.rb +32 -0
- metadata +107 -0
@@ -0,0 +1,86 @@
|
|
1
|
+
import { ResumableUploadsController } from "./resumable_uploads_controller"
|
2
|
+
import { findElement } from "./helpers"
|
3
|
+
|
4
|
+
const processingAttribute = "data-resumable-uploads-processing"
|
5
|
+
const submitButtonsByForm = new WeakMap
|
6
|
+
let started = false
|
7
|
+
|
8
|
+
export function start() {
|
9
|
+
if (!started) {
|
10
|
+
started = true
|
11
|
+
document.addEventListener("click", didClick, true)
|
12
|
+
document.addEventListener("submit", didSubmitForm)
|
13
|
+
document.addEventListener("ajax:before", didSubmitRemoteElement)
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
function didClick(event) {
|
18
|
+
const { target } = event
|
19
|
+
if ((target.tagName == "INPUT" || target.tagName == "BUTTON") && target.type == "submit" && target.form) {
|
20
|
+
submitButtonsByForm.set(target.form, target)
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
function didSubmitForm(event) {
|
25
|
+
handleFormSubmissionEvent(event)
|
26
|
+
}
|
27
|
+
|
28
|
+
function didSubmitRemoteElement(event) {
|
29
|
+
if (event.target.tagName == "FORM") {
|
30
|
+
handleFormSubmissionEvent(event)
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
function handleFormSubmissionEvent(event) {
|
35
|
+
const form = event.target
|
36
|
+
|
37
|
+
if (form.hasAttribute(processingAttribute)) {
|
38
|
+
event.preventDefault()
|
39
|
+
return
|
40
|
+
}
|
41
|
+
|
42
|
+
const controller = new ResumableUploadsController(form)
|
43
|
+
const { inputs } = controller
|
44
|
+
|
45
|
+
if (inputs.length) {
|
46
|
+
event.preventDefault()
|
47
|
+
form.setAttribute(processingAttribute, "")
|
48
|
+
inputs.forEach(disable)
|
49
|
+
controller.start(error => {
|
50
|
+
form.removeAttribute(processingAttribute)
|
51
|
+
if (error) {
|
52
|
+
inputs.forEach(enable)
|
53
|
+
} else {
|
54
|
+
submitForm(form)
|
55
|
+
}
|
56
|
+
})
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
function submitForm(form) {
|
61
|
+
let button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit], button[type=submit]")
|
62
|
+
|
63
|
+
if (button) {
|
64
|
+
const { disabled } = button
|
65
|
+
button.disabled = false
|
66
|
+
button.focus()
|
67
|
+
button.click()
|
68
|
+
button.disabled = disabled
|
69
|
+
} else {
|
70
|
+
button = document.createElement("input")
|
71
|
+
button.type = "submit"
|
72
|
+
button.style.display = "none"
|
73
|
+
form.appendChild(button)
|
74
|
+
button.click()
|
75
|
+
form.removeChild(button)
|
76
|
+
}
|
77
|
+
submitButtonsByForm.delete(form)
|
78
|
+
}
|
79
|
+
|
80
|
+
function disable(input) {
|
81
|
+
input.disabled = true
|
82
|
+
}
|
83
|
+
|
84
|
+
function enable(input) {
|
85
|
+
input.disabled = false
|
86
|
+
}
|
data/config/routes.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Rails.application.routes.draw do
|
4
|
+
scope ActiveStorage.routes_prefix do
|
5
|
+
post '/resumable_uploads' => 'active_storage/resumable_uploads#create', as: :rails_resumable_uploads
|
6
|
+
put '/resumable_uploads/:signed_blob_id' => 'active_storage/resumable_uploads#update', as: :update_rails_resumable_upload
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ActiveSupport.on_load(:action_view) do
|
4
|
+
module ActionView
|
5
|
+
module Helpers
|
6
|
+
module FormTagHelper
|
7
|
+
private
|
8
|
+
|
9
|
+
def convert_direct_upload_option_to_url(options)
|
10
|
+
if options.delete(:direct_upload) && main_app.respond_to?(:rails_direct_uploads_url)
|
11
|
+
options['data-direct-upload-url'] = main_app.rails_direct_uploads_url
|
12
|
+
elsif options.delete(:resumable_upload) && main_app.respond_to?(:rails_resumable_uploads_url)
|
13
|
+
options['data-resumable-upload-url'] = main_app.rails_resumable_uploads_url
|
14
|
+
end
|
15
|
+
options
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'activestorage/resumable/engine'
|
4
|
+
require 'activestorage_extensions/service/gcs_service'
|
5
|
+
require 'activestorage_extensions/blob'
|
6
|
+
require 'actionview_extensions/helpers/form_tag_helper'
|
7
|
+
|
8
|
+
module ActiveStorage
|
9
|
+
module Resumable
|
10
|
+
mattr_accessor :upload_origin_url
|
11
|
+
@@upload_origin_url = ENV['UPLOAD_ORIGIN_URL']
|
12
|
+
|
13
|
+
def self.configure
|
14
|
+
yield self
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ActiveSupport.on_load(:active_storage_blob) do
|
4
|
+
class ActiveStorage::Blob
|
5
|
+
before_create do
|
6
|
+
self.resumable_url = service.url_for_resumable_upload(key, content_type)
|
7
|
+
end
|
8
|
+
|
9
|
+
scope :resumable, -> { unattached.where.not(resumable_url: nil) }
|
10
|
+
scope :active_resumable, -> { resumable.where('active_storage_blobs.created_at >= ?', 7.days.ago) }
|
11
|
+
scope :expired_resumable, -> { resumable.where('active_storage_blobs.created_at < ?', 7.days.ago) }
|
12
|
+
|
13
|
+
def self.find_or_create_before_resumable_upload!(filename:, byte_size:, checksum:, content_type: nil, metadata: nil)
|
14
|
+
ActiveStorage::Blob.expired_resumable.destroy_all
|
15
|
+
find_or_create_by! filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type,
|
16
|
+
metadata: metadata
|
17
|
+
end
|
18
|
+
|
19
|
+
def uploaded_bytes
|
20
|
+
return unless resumable_url
|
21
|
+
|
22
|
+
@uploaded_bytes ||= begin
|
23
|
+
uri = URI(resumable_url)
|
24
|
+
req = Net::HTTP::Put.new(resumable_url, 'Content-Range': "bytes */#{byte_size}")
|
25
|
+
http = Net::HTTP.new(uri.hostname, uri.port)
|
26
|
+
http.use_ssl = true
|
27
|
+
res = http.request(req)
|
28
|
+
res['Range'].split('-').last.to_i + 1
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def service_url_for_resumable_upload(expires_in: ActiveStorage.service_urls_expire_in)
|
33
|
+
service.url_for_resumable_upload key, expires_in: expires_in, content_type: content_type,
|
34
|
+
content_length: byte_size, checksum: checksum
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
ActiveSupport.on_load(:after_initialize) do
|
4
|
+
require 'active_storage/service/gcs_service'
|
5
|
+
|
6
|
+
module ActiveStorage
|
7
|
+
class Service::GCSService
|
8
|
+
def url_for_resumable_upload(key, content_type, **)
|
9
|
+
instrument :url, key: key do |payload|
|
10
|
+
signed_url = bucket.signed_url(key, method: 'PUT', content_type: content_type, version: :v4)
|
11
|
+
|
12
|
+
uri = URI.parse(signed_url)
|
13
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
14
|
+
https.use_ssl = true
|
15
|
+
|
16
|
+
headers = {
|
17
|
+
'Origin': ActiveStorage::Resumable.upload_origin_url,
|
18
|
+
'x-goog-resumable': 'start'
|
19
|
+
}
|
20
|
+
request = Net::HTTP::Put.new(uri.request_uri, headers)
|
21
|
+
response = https.request(request)
|
22
|
+
|
23
|
+
generated_url = response['location']
|
24
|
+
|
25
|
+
payload[:url] = generated_url
|
26
|
+
|
27
|
+
generated_url
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activestorage-resumable
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kadu Diógenes
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-10-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activestorage
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.2.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.2.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sqlite3
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Adds resumable support for ActiveStorage.
|
56
|
+
email:
|
57
|
+
- kadu@fnix.com.br
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- MIT-LICENSE
|
63
|
+
- README.md
|
64
|
+
- Rakefile
|
65
|
+
- app/assets/javascripts/activestorage-resumable.js
|
66
|
+
- app/controllers/active_storage/resumable_uploads_controller.rb
|
67
|
+
- app/javascript/activestorage-resumable/helpers.js
|
68
|
+
- app/javascript/activestorage-resumable/index.js
|
69
|
+
- app/javascript/activestorage-resumable/resumable_blob_record.js
|
70
|
+
- app/javascript/activestorage-resumable/resumable_blob_upload.js
|
71
|
+
- app/javascript/activestorage-resumable/resumable_file_checksum.js
|
72
|
+
- app/javascript/activestorage-resumable/resumable_upload.js
|
73
|
+
- app/javascript/activestorage-resumable/resumable_upload_controller.js
|
74
|
+
- app/javascript/activestorage-resumable/resumable_uploads_controller.js
|
75
|
+
- app/javascript/activestorage-resumable/ujs-resumable.js
|
76
|
+
- config/routes.rb
|
77
|
+
- db/migrate/20190825001409_add_resumable_url_to_active_storage_blobs.rb
|
78
|
+
- lib/actionview_extensions/helpers/form_tag_helper.rb
|
79
|
+
- lib/activestorage/resumable.rb
|
80
|
+
- lib/activestorage/resumable/engine.rb
|
81
|
+
- lib/activestorage/resumable/version.rb
|
82
|
+
- lib/activestorage_extensions/blob.rb
|
83
|
+
- lib/activestorage_extensions/service/gcs_service.rb
|
84
|
+
homepage: https://github.com/fnix/activestorage-resumable
|
85
|
+
licenses:
|
86
|
+
- MIT
|
87
|
+
metadata: {}
|
88
|
+
post_install_message:
|
89
|
+
rdoc_options: []
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
requirements: []
|
103
|
+
rubygems_version: 3.0.3
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Adds resumable support for ActiveStorage.
|
107
|
+
test_files: []
|