activestorage-cloudinary-service 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: