activestorage-cloudinary-service 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2eb62018264e48d4b9f1dc376cfd401f8d215645
4
- data.tar.gz: bc69c0c0b7367845416aeb0aa67e1bd5087175ed
3
+ metadata.gz: 2ed3e755146252107a2682fbd64e330be4ad5da2
4
+ data.tar.gz: dd6ea8202085b611a75cf588c3acd499a632975b
5
5
  SHA512:
6
- metadata.gz: 3332fda9e60d98e76fa3c84bd7d148118811537bcf0bf5c806ec6ffb366f72a4450ab6b9696362085eeaa16324b798e8c48a3d81d365e47b5ca2a2bd55ea396b
7
- data.tar.gz: 4c92345d5ca888864a208ea6d51a38547f846ac90e67df9b4d8d3d78149c5a3c14f0ed73a85eeb83099924df86f790c6cb42e3a04c242ff4867c76f13c5d439b
6
+ metadata.gz: 9b82f66786e2c685b88b4470a536b8a4d1d97578b61eb0f973a6508b44fb7bce0a406555456a806dc6cbd4791f7ff217f0cbe1a746b5670d323db13f69901c67
7
+ data.tar.gz: 2fc0f43b8c67469a7b658bf8ec9b9b5d8a5759f29de7b906ddde69fb23f294b4acf35734dabce0e5e71fd80d256ed05ac78eefb8bab7b577413b1f280e9c9ade
@@ -0,0 +1,44 @@
1
+ version: 2
2
+ jobs:
3
+ build:
4
+ working_directory: ~/0sc/activestorage-cloudinary-service
5
+ parallelism: 1
6
+ shell: /bin/bash --login
7
+ environment:
8
+ CIRCLE_ARTIFACTS: /tmp/circleci-artifacts
9
+ CIRCLE_TEST_REPORTS: /tmp/circleci-test-results
10
+ docker:
11
+ - image: circleci/ruby:2.4.2-jessie
12
+ steps:
13
+ - checkout
14
+ - run: mkdir -p $CIRCLE_ARTIFACTS $CIRCLE_TEST_REPORTS
15
+ - restore_cache:
16
+ keys:
17
+ # This branch if available
18
+ - v1-dep-{{ .Branch }}-
19
+ # Default branch if not
20
+ - v1-dep-master-
21
+ # Any branch if there are none on the default branch - this should be unnecessary if you have your default branch configured correctly
22
+ - v1-dep-
23
+ - run: echo -e "export RAILS_ENV=test\nexport RACK_ENV=test" >> $BASH_ENV
24
+ - run: 'bundle check --path=vendor/bundle || bundle install --path=vendor/bundle
25
+ --jobs=4 --retry=3'
26
+ - save_cache:
27
+ key: v1-dep-{{ .Branch }}-{{ epoch }}
28
+ paths:
29
+ # This is a broad list of cache paths to include many possible development environments
30
+ # You can probably delete some of these entries
31
+ - vendor/bundle
32
+ - ~/.bundle
33
+ - run:
34
+ command: bundle exec rspec --require spec_helper --format progress spec
35
+ environment:
36
+ RAILS_ENV: test
37
+ RACK_ENV: test
38
+ - store_test_results:
39
+ path: /tmp/circleci-test-results
40
+ # Save artifacts
41
+ - store_artifacts:
42
+ path: /tmp/circleci-artifacts
43
+ - store_artifacts:
44
+ path: /tmp/circleci-test-results
@@ -0,0 +1 @@
1
+ 2.4.2
data/Gemfile CHANGED
@@ -5,4 +5,5 @@ git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
5
5
  # Specify your gem's dependencies in active_storage-cloudinary_service.gemspec
6
6
  gemspec
7
7
 
8
- gem 'cloudinary', require: false
8
+ gem 'activestorage', '~> 5.2.0', require: false
9
+ gem 'cloudinary', '~> 1.8.2', require: false
@@ -1,24 +1,82 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- activestorage-cloudinary-service (0.1.0)
4
+ activestorage-cloudinary-service (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
+ actionpack (5.2.0)
10
+ actionview (= 5.2.0)
11
+ activesupport (= 5.2.0)
12
+ rack (~> 2.0)
13
+ rack-test (>= 0.6.3)
14
+ rails-dom-testing (~> 2.0)
15
+ rails-html-sanitizer (~> 1.0, >= 1.0.2)
16
+ actionview (5.2.0)
17
+ activesupport (= 5.2.0)
18
+ builder (~> 3.1)
19
+ erubi (~> 1.4)
20
+ rails-dom-testing (~> 2.0)
21
+ rails-html-sanitizer (~> 1.0, >= 1.0.3)
22
+ activemodel (5.2.0)
23
+ activesupport (= 5.2.0)
24
+ activerecord (5.2.0)
25
+ activemodel (= 5.2.0)
26
+ activesupport (= 5.2.0)
27
+ arel (>= 9.0)
28
+ activestorage (5.2.0)
29
+ actionpack (= 5.2.0)
30
+ activerecord (= 5.2.0)
31
+ marcel (~> 0.3.1)
32
+ activesupport (5.2.0)
33
+ concurrent-ruby (~> 1.0, >= 1.0.2)
34
+ i18n (>= 0.7, < 2)
35
+ minitest (~> 5.1)
36
+ tzinfo (~> 1.1)
37
+ arel (9.0.0)
9
38
  aws_cf_signer (0.1.3)
39
+ builder (3.2.3)
10
40
  cloudinary (1.8.2)
11
41
  aws_cf_signer
12
42
  rest-client
43
+ coderay (1.1.2)
44
+ concurrent-ruby (1.0.5)
45
+ crass (1.0.4)
13
46
  diff-lcs (1.3)
14
47
  domain_name (0.5.20170404)
15
48
  unf (>= 0.0.5, < 1.0.0)
49
+ erubi (1.7.1)
16
50
  http-cookie (1.0.3)
17
51
  domain_name (~> 0.5)
52
+ i18n (1.0.1)
53
+ concurrent-ruby (~> 1.0)
54
+ loofah (2.2.2)
55
+ crass (~> 1.0.2)
56
+ nokogiri (>= 1.5.9)
57
+ marcel (0.3.2)
58
+ mimemagic (~> 0.3.2)
59
+ method_source (0.9.0)
18
60
  mime-types (3.1)
19
61
  mime-types-data (~> 3.2015)
20
62
  mime-types-data (3.2016.0521)
63
+ mimemagic (0.3.2)
64
+ mini_portile2 (2.3.0)
65
+ minitest (5.11.3)
21
66
  netrc (0.11.0)
67
+ nokogiri (1.8.4)
68
+ mini_portile2 (~> 2.3.0)
69
+ pry (0.11.3)
70
+ coderay (~> 1.1.0)
71
+ method_source (~> 0.9.0)
72
+ rack (2.0.5)
73
+ rack-test (1.1.0)
74
+ rack (>= 1.0, < 3)
75
+ rails-dom-testing (2.0.3)
76
+ activesupport (>= 4.2.0)
77
+ nokogiri (>= 1.6)
78
+ rails-html-sanitizer (1.0.4)
79
+ loofah (~> 2.2, >= 2.2.2)
22
80
  rake (10.5.0)
23
81
  rest-client (2.0.2)
24
82
  http-cookie (>= 1.0.2, < 2.0)
@@ -37,6 +95,9 @@ GEM
37
95
  diff-lcs (>= 1.2.0, < 2.0)
38
96
  rspec-support (~> 3.7.0)
39
97
  rspec-support (3.7.0)
98
+ thread_safe (0.3.6)
99
+ tzinfo (1.2.5)
100
+ thread_safe (~> 0.1)
40
101
  unf (0.1.4)
41
102
  unf_ext
42
103
  unf_ext (0.0.7.4)
@@ -45,11 +106,13 @@ PLATFORMS
45
106
  ruby
46
107
 
47
108
  DEPENDENCIES
109
+ activestorage (~> 5.2.0)
48
110
  activestorage-cloudinary-service!
49
111
  bundler (~> 1.16)
50
- cloudinary
112
+ cloudinary (~> 1.8.2)
113
+ pry (~> 0.11.3)
51
114
  rake (~> 10.0)
52
- rspec
115
+ rspec (~> 3.7.0)
53
116
 
54
117
  BUNDLED WITH
55
- 1.16.0
118
+ 1.16.2
data/README.md CHANGED
@@ -64,11 +64,6 @@ Currently, active_storage client-side upload doesn't work with Cloudinary. This
64
64
 
65
65
  Nevertheless, the necessary ground work for this is set and once either active_storage is updated to support more request type or Cloudinary enables support for `PUT` request method, it should work fine.
66
66
 
67
- ## Development
68
-
69
- After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
70
-
71
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
72
67
 
73
68
  ## Contributing
74
69
 
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.require_paths = ['lib']
22
22
 
23
23
  spec.add_development_dependency 'bundler', '~> 1.16'
24
+ spec.add_development_dependency 'pry', '~> 0.11.3'
24
25
  spec.add_development_dependency 'rake', '~> 10.0'
25
- spec.add_development_dependency 'rspec'
26
- spec.add_development_dependency 'cloudinary'
26
+ spec.add_development_dependency 'rspec', '~> 3.7.0'
27
27
  end
@@ -1,11 +1,15 @@
1
1
  require 'cloudinary'
2
- require 'open-uri'
2
+ require_relative 'download_utils'
3
3
 
4
4
  module ActiveStorage
5
+ # Wraps the Cloudinary as an Active Storage service.
6
+ # See ActiveStorage::Service for the generic API documentation that applies to all services.
5
7
  class Service::CloudinaryService < Service
8
+ include DownloadUtils
9
+
6
10
  # FIXME: implement setup for private resource type
7
11
  # FIXME: allow configuration via cloudinary url
8
- def initialize(cloud_name:, api_key:, api_secret:, options: {})
12
+ def initialize(cloud_name:, api_key:, api_secret:, **options)
9
13
  options.merge!(
10
14
  cloud_name: cloud_name,
11
15
  api_key: api_key,
@@ -17,28 +21,32 @@ module ActiveStorage
17
21
 
18
22
  def upload(key, io, checksum: nil)
19
23
  instrument :upload, key: key, checksum: checksum do
20
- Cloudinary::Uploader.upload(io, public_id: key)
24
+ Cloudinary::Uploader.upload(io, public_id: key, resource_type: 'auto')
21
25
  end
22
26
  end
23
27
 
24
28
  # Return the content of the file at the +key+.
25
- def download(key)
26
- tmp_file = open(url_for_public_id(key))
29
+ def download(key, &block)
27
30
  if block_given?
28
31
  instrument :streaming_download, key: key do
29
- File.open(tmp_file, 'rb') do |file|
30
- while (data = file.read(64.kilobytes))
31
- yield data
32
- end
33
- end
32
+ source = cloudinary_url_for_key(key)
33
+ stream_download(source, &block)
34
34
  end
35
35
  else
36
36
  instrument :download, key: key do
37
- File.binread tmp_file
37
+ Cloudinary::Downloader.download(key)
38
38
  end
39
39
  end
40
40
  end
41
41
 
42
+ # Return the partial content in the byte +range+ of the file at the +key+.
43
+ def download_chunk(key, range)
44
+ instrument :download_chunk, key: key, range: range do
45
+ source = cloudinary_url_for_key(key)
46
+ download_range(source, range)
47
+ end
48
+ end
49
+
42
50
  # Delete the file at the +key+.
43
51
  def delete(key)
44
52
  instrument :delete, key: key do
@@ -49,9 +57,7 @@ module ActiveStorage
49
57
  # Delete files at keys starting with the +prefix+.
50
58
  def delete_prefixed(prefix)
51
59
  instrument :delete_prefixed, prefix: prefix do
52
- find_resources_with_public_id_prefix(prefix).each do |resource|
53
- delete_resource_with_public_id(resource['public_id'])
54
- end
60
+ Cloudinary::Api.delete_resources_by_prefix(prefix)
55
61
  end
56
62
  end
57
63
 
@@ -87,9 +93,15 @@ module ActiveStorage
87
93
  expires_in: expires_in,
88
94
  content_type: content_type,
89
95
  content_length: content_length,
90
- checksum: checksum
96
+ checksum: checksum,
97
+ resource_type: 'auto'
91
98
  }
92
- direct_upload_url_for_public_id(key, options)
99
+
100
+ # FIXME: Cloudinary Ruby SDK does't expose an api for signed upload url
101
+ # The expected url is similar to the private_download_url
102
+ # with download replaced with upload
103
+ signed_download_url_for_public_id(key, options)
104
+ .sub(/download/, 'upload')
93
105
  end
94
106
  end
95
107
 
@@ -108,49 +120,48 @@ module ActiveStorage
108
120
  Cloudinary::Api.resources_by_ids(public_id).fetch('resources')
109
121
  end
110
122
 
111
- def find_resources_with_public_id_prefix(prefix)
112
- Cloudinary::Api.resources(type: :upload, prefix: prefix).fetch('resources')
113
- end
114
-
115
123
  def delete_resource_with_public_id(public_id)
116
124
  Cloudinary::Uploader.destroy(public_id)
117
125
  end
118
126
 
119
- def url_for_public_id(public_id)
120
- Cloudinary::Api.resource(public_id)['secure_url']
121
- end
122
-
123
- # FIXME: Cloudinary Ruby SDK does't expose an api for signed upload url
124
- # The expected url is similar to the private_download_url
125
- # with download replaced with upload
126
- def direct_upload_url_for_public_id(public_id, options)
127
- # allow the server to auto detect the resource_type
128
- options[:resource_type] ||= 'auto'
129
- signed_download_url_for_public_id(public_id, options).sub(/download/, 'upload')
130
- end
131
-
132
127
  def signed_download_url_for_public_id(public_id, options)
133
- options[:resource_type] ||= resource_type(options[:content_type])
128
+ extension = resource_format(options[:filename])
129
+ options[:resource_type] ||= resource_type(extension)
130
+
134
131
  Cloudinary::Utils.private_download_url(
135
- public_id,
136
- resource_format(options),
132
+ finalize_public_id(public_id, extension, options[:resource_type]),
133
+ extension,
137
134
  signed_url_options(options)
138
135
  )
139
136
  end
140
137
 
138
+ # TODO: for assets of type raw,
139
+ # cloudinary request the extension to be part of the public_id
140
+ def finalize_public_id(public_id, extension, resource_type)
141
+ return public_id unless resource_type == 'raw'
142
+ public_id + '.' + extension
143
+ end
144
+
141
145
  def signed_url_options(options)
142
146
  {
143
- resource_type: (options[:resource_type] || 'auto'),
147
+ resource_type: (options[:resource_type] || 'image'),
144
148
  type: (options[:type] || 'upload'),
145
- attachment: (options[:attachment] == :attachment),
149
+ attachment: (options[:disposition]&.to_sym == :attachment),
146
150
  expires_at: (Time.now + options[:expires_in])
147
151
  }
148
152
  end
149
153
 
150
- def resource_format(_options); end
154
+ def resource_format(filename)
155
+ extension = filename&.extension_with_delimiter || ''
156
+ extension.sub('.', '')
157
+ end
158
+
159
+ def resource_type(extension)
160
+ Cloudinary::Utils.resource_type_for_format(extension)
161
+ end
151
162
 
152
- def resource_type(content_type)
153
- content_type.sub(%r{/.*$}, '')
163
+ def cloudinary_url_for_key(key)
164
+ Cloudinary::Utils.cloudinary_url(key)
154
165
  end
155
166
  end
156
167
  end
@@ -0,0 +1,50 @@
1
+ require 'net/http'
2
+ require 'openssl'
3
+
4
+ module DownloadUtils
5
+ def stream_download(source, chunk_size = 5_242_880)
6
+ url = URI.parse(source)
7
+ http, req = setup_connection(url)
8
+
9
+ content_length = http.request_head(url).content_length
10
+ upper_limit = content_length + (content_length % chunk_size)
11
+ offset = 0
12
+
13
+ http.start do |agent|
14
+ while offset < upper_limit
15
+ lim = (offset + chunk_size)
16
+ # QUESTION: is it relevant to set the last chunk
17
+ # to the exact remaining bytes
18
+ # lim = content_length if lim > content_length
19
+ req.range = (offset..lim)
20
+
21
+ chunk = agent.request(req).body
22
+ yield chunk.force_encoding(Encoding::BINARY)
23
+
24
+ offset += chunk_size + 1
25
+ end
26
+ end
27
+ end
28
+
29
+ def download_range(source, range)
30
+ url = URI.parse(source)
31
+ http, req = setup_connection(url)
32
+ req.range = range
33
+
34
+ chunk = http.start { |agent| agent.request(req).body }
35
+ chunk.force_encoding(Encoding::BINARY)
36
+ end
37
+
38
+ private
39
+
40
+ def setup_connection(url)
41
+ http = Net::HTTP.new(url.host, url.port)
42
+ req = Net::HTTP::Get.new(url.request_uri)
43
+
44
+ if url.port == 443
45
+ http.use_ssl = true
46
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
47
+ end
48
+ [http, req]
49
+ end
50
+ end
@@ -1,5 +1,5 @@
1
1
  module ActiveStorage
2
2
  module CloudinaryService
3
- VERSION = '0.1.0'.freeze
3
+ VERSION = '0.2.0'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activestorage-cloudinary-service
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Osmond Oscar
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-12-19 00:00:00.000000000 Z
11
+ date: 2018-07-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -25,47 +25,47 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.16'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake
28
+ name: pry
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: 0.11.3
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: 0.11.3
41
41
  - !ruby/object:Gem::Dependency
42
- name: rspec
42
+ name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - ">="
45
+ - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '10.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - ">="
52
+ - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '10.0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: cloudinary
56
+ name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - ">="
59
+ - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: 3.7.0
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - ">="
66
+ - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: 3.7.0
69
69
  description:
70
70
  email:
71
71
  - oskarromero3@gmail.com
@@ -73,8 +73,10 @@ executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
75
  files:
76
+ - ".circleci/config.yml"
76
77
  - ".gitignore"
77
78
  - ".rspec"
79
+ - ".ruby-version"
78
80
  - CODE_OF_CONDUCT.md
79
81
  - Gemfile
80
82
  - Gemfile.lock
@@ -85,6 +87,7 @@ files:
85
87
  - bin/console
86
88
  - bin/setup
87
89
  - lib/active_storage/service/cloudinary_service.rb
90
+ - lib/active_storage/service/download_utils.rb
88
91
  - lib/active_storage/service/version.rb
89
92
  homepage: https://github.com/0sc/activestorage-cloudinary-service
90
93
  licenses: