shrine-uploadcare 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +133 -0
- data/lib/shrine/storage/uploadcare.rb +181 -0
- data/shrine-uploadcare.gemspec +23 -0
- metadata +133 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8ea3771f54e872cfdf9a254fc21c824fe0926d6e
|
4
|
+
data.tar.gz: 87e4f1c355642caca1d86e0411eb09c9b2c496c9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e013043a5c1717069c41aa4f17cb84c2de9eca70dd0b1c1f6a261748d975a6ebd4cc886d3b61fafcfda81cd8755bfa69b9f56f8f076139d581e1e2da6a8ddde3
|
7
|
+
data.tar.gz: a0e69937e1c7b275fd537ae56dddb8180275b76ecf74991ac36a622242b5065959c5ef2afd7d12be7b3d5e351ccf15da52adc5fff563f62e898f4082fd69bd2f
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Janko Marohnić
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
# Shrine::Storage::Uploadcare
|
2
|
+
|
3
|
+
Provides [Uploadcare] storage for [Shrine].
|
4
|
+
|
5
|
+
Uploadcare offers file storage with a CDN and on-demand image processing, along
|
6
|
+
with an advanced HTML widget for direct uploads.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem "shrine-uploadcare"
|
12
|
+
```
|
13
|
+
|
14
|
+
## Usage
|
15
|
+
|
16
|
+
```rb
|
17
|
+
require "shrine"
|
18
|
+
require "shrine/storage/uploadcare"
|
19
|
+
|
20
|
+
uploadcare_options = {
|
21
|
+
public_key: "...",
|
22
|
+
private_key: "...",
|
23
|
+
}
|
24
|
+
|
25
|
+
Shrine.storages = {
|
26
|
+
cache: Shrine::Storage::Uploadcare.new(**uploadcare_options),
|
27
|
+
store: Shrine::Storage::Uploadcare.new(**uploadcare_options),
|
28
|
+
}
|
29
|
+
```
|
30
|
+
|
31
|
+
### Direct uploads
|
32
|
+
|
33
|
+
Uploadcare supports uploading files directly to the service, freeing your
|
34
|
+
application from accepting file uploads. The easiest way to do that is by using
|
35
|
+
Uploadcare's [HTML widget], you can see how it can be used in the
|
36
|
+
shrine-uploadcare [demo app].
|
37
|
+
|
38
|
+
[Secure file uploads] are also supported, you can generate signatures with
|
39
|
+
`#presign` on the storage, or using the [direct_upload plugin] with `presign:
|
40
|
+
true`.
|
41
|
+
|
42
|
+
### URL operations
|
43
|
+
|
44
|
+
You can generate Uploadcare's [URL operations] by passing options to `#url`:
|
45
|
+
|
46
|
+
```rb
|
47
|
+
photo.image_url(resize: "200x")
|
48
|
+
photo.image_url(crop: ["200x300", :center])
|
49
|
+
```
|
50
|
+
|
51
|
+
### Upload options
|
52
|
+
|
53
|
+
You can add upload options using the upload_options plugin or using
|
54
|
+
`:upload_options` on the storage:
|
55
|
+
|
56
|
+
```rb
|
57
|
+
Shrine::Storage::Cloudinary.new(upload_options: {...}, **uploadcare_options)
|
58
|
+
```
|
59
|
+
|
60
|
+
### Storing information
|
61
|
+
|
62
|
+
You can have all Uploadcare file information saved in the uploaded file's
|
63
|
+
metadata:
|
64
|
+
|
65
|
+
```rb
|
66
|
+
Shrine::Storage::Uploadcare.new(store_info: true, **uploadcare_options)
|
67
|
+
```
|
68
|
+
```rb
|
69
|
+
user = User.create(avatar: image_file)
|
70
|
+
user.avatar.metadata["uploadcare"] #=>
|
71
|
+
# {
|
72
|
+
# "type" => "file",
|
73
|
+
# "result" => {
|
74
|
+
# "original_file_url" => "http://www.ucarecdn.com/d1d2dc43-4904-4783-bb4d-fbcf64264e63/image.png",
|
75
|
+
# "image_info" => {
|
76
|
+
# "height" => 45,
|
77
|
+
# "width" => 91,
|
78
|
+
# "geo_location" => null,
|
79
|
+
# "datetime_original" => null,
|
80
|
+
# "format" => "PNG"
|
81
|
+
# },
|
82
|
+
# "mime_type" => "image/png",
|
83
|
+
# "is_ready" => true,
|
84
|
+
# "url" => "https://api.uploadcare.com/files/d1d2dc43-4904-4783-bb4d-fbcf64264e63/",
|
85
|
+
# "uuid" => "d1d2dc43-4904-4783-bb4d-fbcf64264e63",
|
86
|
+
# "original_filename" => "image.png",
|
87
|
+
# "datetime_uploaded" => "2014-09-09T16:48:57.284Z",
|
88
|
+
# "size" => 12952,
|
89
|
+
# "is_image" => null,
|
90
|
+
# "datetime_stored" => "2014-09-09T16:48:57.291Z",
|
91
|
+
# "datetime_removed" => null,
|
92
|
+
# "source" => "/03ccf9ab-f266-43fb-973d-a6529c55c2ae/"
|
93
|
+
# }
|
94
|
+
# }
|
95
|
+
```
|
96
|
+
|
97
|
+
### Clearing storage
|
98
|
+
|
99
|
+
You can delete all files from the Uploadcare storage in the same way as you do
|
100
|
+
with other storages:
|
101
|
+
|
102
|
+
```rb
|
103
|
+
uploadcare = Shrine::Storage::Uploadcare.new(**options)
|
104
|
+
# ...
|
105
|
+
uploadcare.clear!
|
106
|
+
```
|
107
|
+
|
108
|
+
## Contributing
|
109
|
+
|
110
|
+
Firstly you need to create an `.env` file with Uploadcare credentials:
|
111
|
+
|
112
|
+
```sh
|
113
|
+
# .env
|
114
|
+
UPLOADCARE_PUBLIC_KEY="..."
|
115
|
+
UPLOADCARE_SECRET_KEY="..."
|
116
|
+
```
|
117
|
+
|
118
|
+
Afterwards you can run the tests:
|
119
|
+
|
120
|
+
```sh
|
121
|
+
$ rake test
|
122
|
+
```
|
123
|
+
## License
|
124
|
+
|
125
|
+
[MIT](http://opensource.org/licenses/MIT)
|
126
|
+
|
127
|
+
[Uploadcare]: https://uploadcare.com/
|
128
|
+
[Shrine]: https://github.com/janko-m/shrine
|
129
|
+
[HTML widget]: https://uploadcare.com/documentation/widget/
|
130
|
+
[demo app]: /demo
|
131
|
+
[Secure file uploads]: https://uploadcare.com/documentation/upload/#secure-uploads
|
132
|
+
[direct_upload plugin]: http://shrinerb.com/rdoc/classes/Shrine/Plugins/DirectUpload.html
|
133
|
+
[URL operations]: https://uploadcare.com/documentation/cdn/
|
@@ -0,0 +1,181 @@
|
|
1
|
+
require "shrine"
|
2
|
+
require "uploadcare"
|
3
|
+
require "down"
|
4
|
+
require "net/http"
|
5
|
+
require "uri"
|
6
|
+
require "digest"
|
7
|
+
|
8
|
+
class Shrine
|
9
|
+
module Storage
|
10
|
+
class Uploadcare
|
11
|
+
Error = Class.new(StandardError)
|
12
|
+
|
13
|
+
attr_reader :uploadcare
|
14
|
+
|
15
|
+
def initialize(store_info: false, upload_options: {}, **options)
|
16
|
+
@uploadcare = ::Uploadcare::Api.new(api_version: "0.5", **options)
|
17
|
+
@store_info = store_info
|
18
|
+
@upload_options = upload_options
|
19
|
+
end
|
20
|
+
|
21
|
+
def upload(io, id, shrine_metadata: {}, **upload_options)
|
22
|
+
result = _upload(io, id, shrine_metadata: shrine_metadata, **upload_options)
|
23
|
+
update_metadata!(shrine_metadata, result)
|
24
|
+
update_id!(id, result)
|
25
|
+
end
|
26
|
+
|
27
|
+
def download(id)
|
28
|
+
Down.download(url(id))
|
29
|
+
end
|
30
|
+
|
31
|
+
def open(id)
|
32
|
+
Down.open(url(id))
|
33
|
+
end
|
34
|
+
|
35
|
+
def read(id)
|
36
|
+
Net::HTTP.get(URI(url(id)))
|
37
|
+
end
|
38
|
+
|
39
|
+
def exists?(id)
|
40
|
+
response = api_client.get("/files/#{id}/")
|
41
|
+
!!response.body["datetime_stored"]
|
42
|
+
rescue ::Uploadcare::Error::RequestError::NotFound
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete(id)
|
47
|
+
api_client.delete("/files/#{id}/storage/")
|
48
|
+
end
|
49
|
+
|
50
|
+
def multi_delete(ids)
|
51
|
+
ids, rest = ids.take(100), Array(ids[100..-1])
|
52
|
+
response = api_client.delete("/files/storage/") do |request|
|
53
|
+
request.body = ids.to_json
|
54
|
+
request.headers["Content-Type"] = "application/json"
|
55
|
+
end
|
56
|
+
multi_delete(rest) unless rest.empty?
|
57
|
+
end
|
58
|
+
|
59
|
+
def url(id, **options)
|
60
|
+
operations = options.to_a.map { |operation| operation.flatten.join("/") }
|
61
|
+
file(id, operations).cdn_url(true)
|
62
|
+
end
|
63
|
+
|
64
|
+
def clear!
|
65
|
+
response = api_client.get("/files/", stored: true, limit: 1000)
|
66
|
+
loop do
|
67
|
+
uuids = response.body["results"].map { |result| result.fetch("uuid") }
|
68
|
+
multi_delete(uuids) unless uuids.empty?
|
69
|
+
return if (next_url = response.body["next"]).nil?
|
70
|
+
response = api_client.get(URI(next_url).request_uri)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def presign(id = nil, **options)
|
75
|
+
expire = Time.now.to_i + (options[:expires_in] || 60*60)
|
76
|
+
secret_key = uploadcare.options[:private_key]
|
77
|
+
|
78
|
+
signature = Digest::MD5.hexdigest(secret_key + expire.to_s)
|
79
|
+
|
80
|
+
fields = {
|
81
|
+
UPLOADCARE_PUB_KEY: uploadcare.options[:public_key],
|
82
|
+
signature: signature,
|
83
|
+
expire: expire,
|
84
|
+
}
|
85
|
+
|
86
|
+
url = upload_client.url_prefix + "base/"
|
87
|
+
|
88
|
+
Struct.new(:url, :fields).new(url, fields)
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
def file(id, operations = [])
|
94
|
+
::Uploadcare::Api::File.new(uploadcare, id, operations: operations)
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def _upload(io, id, **options)
|
100
|
+
if uploadcare_file?(io)
|
101
|
+
store(io, id, **options)
|
102
|
+
else
|
103
|
+
create(io, id, **options)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def store(io, id, **options)
|
108
|
+
response = api_client.put "/files/#{io.id}/storage/"
|
109
|
+
response.body
|
110
|
+
end
|
111
|
+
|
112
|
+
def create(io, id, **options)
|
113
|
+
if remote_file?(io)
|
114
|
+
create_from_url(io, id, **options)
|
115
|
+
else
|
116
|
+
create_from_file(io, id, **options)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def create_from_url(io, id, shrine_metadata: {}, **upload_options)
|
121
|
+
options = {source_url: io.url, store: 1, pub_key: uploadcare.options[:public_key]}
|
122
|
+
options.update(@upload_options).update(upload_options)
|
123
|
+
response = upload_client.post "/from_url/", options
|
124
|
+
token = response.body.fetch("token")
|
125
|
+
|
126
|
+
loop do
|
127
|
+
response = upload_client.get "/from_url/status/", token: token
|
128
|
+
raise Error, response.body["error"] if response.body["status"] == "error"
|
129
|
+
break response.body if response.body["status"] == "success"
|
130
|
+
sleep 0.5
|
131
|
+
end
|
132
|
+
rescue ::Uploadcare::Error::RequestError::Forbidden => error
|
133
|
+
raise Error, "You must allow \"automatic file storing\" in project settings"
|
134
|
+
end
|
135
|
+
|
136
|
+
def create_from_file(io, id, shrine_metadata: {}, **upload_options)
|
137
|
+
options = {UPLOADCARE_PUB_KEY: uploadcare.options[:public_key], UPLOADCARE_STORE: 1}
|
138
|
+
options.update(@upload_options).update(upload_options)
|
139
|
+
io = Faraday::UploadIO.new(io, shrine_metadata["mime_type"], shrine_metadata["filename"])
|
140
|
+
io.instance_eval { def length; size; end } # hack for multipart-post
|
141
|
+
response = upload_client.post "/base/", file: io, **options
|
142
|
+
{"uuid" => response.body.fetch("file")}
|
143
|
+
rescue ::Uploadcare::Error::RequestError::Forbidden => error
|
144
|
+
raise Error, "You must allow \"automatic file storing\" in project settings"
|
145
|
+
end
|
146
|
+
|
147
|
+
def api_client
|
148
|
+
uploadcare.instance_variable_get("@api_connection")
|
149
|
+
end
|
150
|
+
|
151
|
+
def upload_client
|
152
|
+
uploadcare.instance_variable_get("@upload_connection")
|
153
|
+
end
|
154
|
+
|
155
|
+
def update_metadata!(metadata, result)
|
156
|
+
retrieved_metadata = {
|
157
|
+
"mime_type" => result["mime_type"],
|
158
|
+
"width" => result["image_info"] && result["image_info"]["width"],
|
159
|
+
"height" => result["image_info"] && result["image_info"]["height"],
|
160
|
+
"size" => result["size"],
|
161
|
+
}
|
162
|
+
retrieved_metadata.reject! { |key, value| value.nil? }
|
163
|
+
retrieved_metadata["uploadcare"] = result if @store_info
|
164
|
+
|
165
|
+
metadata.update(retrieved_metadata)
|
166
|
+
end
|
167
|
+
|
168
|
+
def update_id!(id, result)
|
169
|
+
id.replace(result.fetch("uuid"))
|
170
|
+
end
|
171
|
+
|
172
|
+
def uploadcare_file?(io)
|
173
|
+
io.is_a?(UploadedFile) && io.storage.is_a?(Storage::Uploadcare)
|
174
|
+
end
|
175
|
+
|
176
|
+
def remote_file?(io)
|
177
|
+
io.is_a?(UploadedFile) && io.url.to_s =~ /^ftp:|^https?:/
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Gem::Specification.new do |gem|
|
2
|
+
gem.name = "shrine-uploadcare"
|
3
|
+
gem.version = "0.1.0"
|
4
|
+
|
5
|
+
gem.required_ruby_version = ">= 2.1"
|
6
|
+
|
7
|
+
gem.summary = "Provides Uploadcare storage for Shrine."
|
8
|
+
gem.homepage = "https://github.com/janko-m/shrine-uploadcare"
|
9
|
+
gem.authors = ["Janko Marohnić"]
|
10
|
+
gem.email = ["janko.marohnic@gmail.com"]
|
11
|
+
gem.license = "MIT"
|
12
|
+
|
13
|
+
gem.files = Dir["README.md", "LICENSE.txt", "lib/**/*.rb", "*.gemspec"]
|
14
|
+
gem.require_path = "lib"
|
15
|
+
|
16
|
+
gem.add_dependency "shrine", "~> 2.0"
|
17
|
+
gem.add_dependency "uploadcare-ruby", "~> 1.0.5"
|
18
|
+
gem.add_dependency "down", ">= 2.3.3"
|
19
|
+
|
20
|
+
gem.add_development_dependency "rake"
|
21
|
+
gem.add_development_dependency "minitest"
|
22
|
+
gem.add_development_dependency "dotenv"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,133 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: shrine-uploadcare
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Janko Marohnić
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-06-25 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: shrine
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '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: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: uploadcare-ruby
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.0.5
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.0.5
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: down
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.3.3
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.3.3
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: dotenv
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
- janko.marohnic@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- LICENSE.txt
|
105
|
+
- README.md
|
106
|
+
- lib/shrine/storage/uploadcare.rb
|
107
|
+
- shrine-uploadcare.gemspec
|
108
|
+
homepage: https://github.com/janko-m/shrine-uploadcare
|
109
|
+
licenses:
|
110
|
+
- MIT
|
111
|
+
metadata: {}
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '2.1'
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubyforge_project:
|
128
|
+
rubygems_version: 2.5.1
|
129
|
+
signing_key:
|
130
|
+
specification_version: 4
|
131
|
+
summary: Provides Uploadcare storage for Shrine.
|
132
|
+
test_files: []
|
133
|
+
has_rdoc:
|