activestorage-openstack 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
  SHA256:
3
- metadata.gz: 679f589974232f4e10a8668beca5099273e6f5a1f2595eb2f4d3298cef4a4dcf
4
- data.tar.gz: b67c2107534d6208a65b867e98559738ce2bb8ec32854f0b1582f33cf9d03929
3
+ metadata.gz: 983a4b66788932c6fa1e5af33b72ba32385e5b2e9b3664e353d6a8bdc30ea70a
4
+ data.tar.gz: 54a1176eb547289bd43dd8510cdc235c9e257cb2a208024744e9651b54ac2df4
5
5
  SHA512:
6
- metadata.gz: bdc68784986e569e426386d2f9da663766ea9b0ab56b577d85fb9d1fbb4094f322a361017900b51b9886c6d5e2b29fd1cddf35cc218f182b4344cff5e62ae45f
7
- data.tar.gz: f27590fc1462ed17c7681032dbc84734ce7a98b65fa839bd9e2a843d7dab390949f8c680d75d32076b9fac3e1d0a1c0f7d782c3db31e380ffa85977e216b7e4a
6
+ metadata.gz: d0653477a4bae7e3d31cc81400c440906939a574e3ad6e07cbd545fe9c9574edd08cff487b8071d5cba9f103691069cd17ac7f65eef6b76ae510a47c7e4dd024
7
+ data.tar.gz: 2f4c2d22fb3f5087870b44a2348c2a8bd8dcda8d373f690202a642670fd37f543f5869b2a629fe649f2a401de7691424865f50d6af0dc1856f5efc544a33acee
data/README.md CHANGED
@@ -57,6 +57,12 @@ the `openstack_temp_url_key` in your configuration is mandatory for generating U
57
57
 
58
58
  The next version of this plugin, will add a rails generator, or expose a method that would use the built-in method from `Fog::OpenStack::Real` to generate the key.
59
59
 
60
+ ## `ActiveStorage::Openstack`'s Content-Type handling
61
+
62
+ OpenStack Swift handles the Content-Type of an object differently from other object storage services. You cannot overwrite the Content-Type via a temp URL. This gem will try very hard to set the right Content-Type for an object at object creation (either via server upload or direct upload) but this is wrong in some edge cases (e.g. you use direct upload and the browser provides a wrong mime type).
63
+
64
+ For these edge cases `ActiveStorage::Blob::Identifiable` downloads the first 4K of a file, identifies the content type and saves the result in the database. For `ActiveStorage::Openstack` we also need to update the Content-Type of the object. This is done automatically with a little monkey patch.
65
+
60
66
  ## Testing
61
67
  First, run `bundle` to install the gem dependencies (both development and production)
62
68
  ```bash
@@ -1,6 +1,18 @@
1
1
  module ActiveStorage
2
2
  module Openstack
3
3
  class Railtie < ::Rails::Railtie
4
+ initializer "active_storage_openstack.blob" do
5
+ ActiveSupport.on_load(:active_storage_blob) do |klass|
6
+ klass.after_commit do |blob|
7
+ # overwrite content type if identification run and
8
+ # the service responds to change_content_type
9
+ if blob.identified? && !blob.content_type.blank? &&
10
+ blob.service.respond_to?(:change_content_type)
11
+ blob.service.change_content_type(blob.key, blob.content_type)
12
+ end
13
+ end
14
+ end
15
+ end
4
16
  end
5
17
  end
6
18
  end
@@ -1,5 +1,5 @@
1
1
  module ActiveStorage
2
2
  module Openstack
3
- VERSION = '0.1.0'
3
+ VERSION = '0.2.0'
4
4
  end
5
5
  end
@@ -16,7 +16,10 @@ module ActiveStorage
16
16
 
17
17
  def upload(key, io, checksum: nil)
18
18
  instrument :upload, key: key, checksum: checksum do
19
- params = {}.merge(etag: convert_base64digest_to_hexdigest(checksum))
19
+ params = {
20
+ 'Content-Type' => guess_content_type(io),
21
+ 'ETag' => convert_base64digest_to_hexdigest(checksum)
22
+ }
20
23
  begin
21
24
  client.put_object(container, key, io, params)
22
25
  rescue Excon::Error::UnprocessableEntity
@@ -28,7 +31,7 @@ module ActiveStorage
28
31
  def download(key, &block)
29
32
  if block_given?
30
33
  instrument :streaming_download, key: key do
31
- object_for(key, &block).body
34
+ object_for(key, &block)
32
35
  end
33
36
  else
34
37
  instrument :download, key: key do
@@ -39,17 +42,21 @@ module ActiveStorage
39
42
 
40
43
  def download_chunk(key, range)
41
44
  instrument :download_chunk, key: key, range: range do
42
- object_for(key).body[range]
45
+ chunk_buffer = []
46
+
47
+ object_for(key) do |chunk|
48
+ chunk_buffer << chunk
49
+ end
50
+
51
+ chunk_buffer.join[range]
43
52
  end
44
53
  end
45
54
 
46
55
  def delete(key)
47
56
  instrument :delete, key: key do
48
- begin
49
- client.delete_object(container, key)
50
- rescue Fog::Storage::OpenStack::NotFound
51
- false
52
- end
57
+ client.delete_object(container, key)
58
+ rescue Fog::Storage::OpenStack::NotFound
59
+ false
53
60
  end
54
61
  end
55
62
 
@@ -65,31 +72,28 @@ module ActiveStorage
65
72
 
66
73
  def exist?(key)
67
74
  instrument :exist, key: key do |payload|
68
- begin
69
- answer = object_for(key)
70
- payload[:exist] = answer
71
- rescue Fog::Storage::OpenStack::NotFound
72
- payload[:exist] = false
73
- end
75
+ answer = object_for(key)
76
+ payload[:exist] = answer.present?
77
+ rescue Fog::Storage::OpenStack::NotFound
78
+ payload[:exist] = false
74
79
  end
75
80
  end
76
81
 
77
- def url(key, expires_in:, disposition:, filename:, content_type:)
82
+ def url(key, expires_in:, disposition:, filename:, **)
78
83
  instrument :url, key: key do |payload|
79
84
  expire_at = unix_timestamp_expires_at(expires_in)
80
- generated_url = client.get_object_https_url(container,
81
- key,
82
- expire_at,
83
- disposition: disposition,
84
- filename: filename,
85
- content_type: content_type)
85
+ generated_url = client.get_object_https_url(container, key, expire_at)
86
+ generated_url += '&inline' if disposition.to_s != 'attachment'
87
+ generated_url += "&filename=#{Fog::OpenStack.escape(filename.to_s)}" unless filename.nil?
88
+ # unfortunately OpenStack Swift cannot overwrite the content type of an object via a temp url
89
+ # so we just ignore the content_type argument here
86
90
  payload[:url] = generated_url
87
91
 
88
92
  generated_url
89
93
  end
90
94
  end
91
95
 
92
- def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:)
96
+ def url_for_direct_upload(key, expires_in:, **)
93
97
  instrument :url, key: key do |payload|
94
98
  expire_at = unix_timestamp_expires_at(expires_in)
95
99
  generated_url = client.create_temp_url(container,
@@ -97,10 +101,7 @@ module ActiveStorage
97
101
  expire_at,
98
102
  'PUT',
99
103
  port: 443,
100
- scheme: "https",
101
- content_type: content_type,
102
- content_length: content_length,
103
- etag: convert_base64digest_to_hexdigest(checksum))
104
+ scheme: 'https')
104
105
 
105
106
  payload[:url] = generated_url
106
107
 
@@ -108,24 +109,33 @@ module ActiveStorage
108
109
  end
109
110
  end
110
111
 
111
- def headers_for_direct_upload(key, content_type:, content_length:, checksum:)
112
+ def headers_for_direct_upload(_key, content_type:, checksum:, **)
112
113
  {
113
114
  'Content-Type' => content_type,
114
- 'Etag' => convert_base64digest_to_hexdigest(checksum),
115
- 'Content-Length' => content_length
115
+ 'ETag' => convert_base64digest_to_hexdigest(checksum)
116
116
  }
117
117
  end
118
118
 
119
- private
119
+ # Non-standard method to change the content type of an existing object
120
+ def change_content_type(key, content_type)
121
+ client.post_object(container,
122
+ key,
123
+ 'Content-Type' => content_type)
124
+ true
125
+ rescue Fog::Storage::OpenStack::NotFound
126
+ false
127
+ end
128
+
129
+ private
120
130
 
121
131
  def object_for(key, &block)
122
132
  client.get_object(container, key, &block)
123
133
  end
124
134
 
125
135
  # ActiveStorage sends a `Digest::MD5.base64digest` checksum
126
- # OpenStack expects a `Digest::MD5.hexdigest` Etag
136
+ # OpenStack expects a `Digest::MD5.hexdigest` ETag
127
137
  def convert_base64digest_to_hexdigest(base64digest)
128
- base64digest.unpack('m0').first.unpack('H*').first if base64digest
138
+ base64digest&.unpack1('m0')&.unpack1('H*')
129
139
  end
130
140
 
131
141
  def unix_timestamp_expires_at(seconds_from_now)
@@ -136,6 +146,10 @@ module ActiveStorage
136
146
  " bytes=#{range.begin}-#{range.exclude_end? ? range.end - 1 : range.end}"
137
147
  end
138
148
 
139
-
149
+ def guess_content_type(io)
150
+ Marcel::MimeType.for io,
151
+ name: io.try(:original_filename),
152
+ declared_type: io.try(:content_type)
153
+ end
140
154
  end
141
155
  end
@@ -0,0 +1,2 @@
1
+ require "active_storage/openstack/version"
2
+ require "active_storage/openstack/railtie"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activestorage-openstack
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
  - Chedli Bourguiba
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-23 00:00:00.000000000 Z
11
+ date: 2018-09-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fog-openstack
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: marcel
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: sqlite3
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -76,10 +90,10 @@ files:
76
90
  - MIT-LICENSE
77
91
  - README.md
78
92
  - Rakefile
79
- - lib/active_storage/openstack.rb
80
93
  - lib/active_storage/openstack/railtie.rb
81
94
  - lib/active_storage/openstack/version.rb
82
95
  - lib/active_storage/service/open_stack_service.rb
96
+ - lib/activestorage-openstack.rb
83
97
  - lib/tasks/activestorage/openstack_tasks.rake
84
98
  homepage: https://github.com/chaadow/activestorage-openstack
85
99
  licenses:
@@ -1,7 +0,0 @@
1
- require "active_storage/openstack/railtie"
2
-
3
- module ActiveStorage
4
- module Openstack
5
- # Your code goes here...
6
- end
7
- end