asset_cloud 2.7.0 → 2.7.2
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 +4 -4
- data/.github/workflows/ci.yml +42 -0
- data/.github/workflows/cla.yml +22 -0
- data/.rubocop.yml +3 -1
- data/Gemfile +5 -3
- data/History.md +8 -0
- data/README.rdoc +3 -4
- data/Rakefile +18 -16
- data/asset_cloud.gemspec +19 -18
- data/dev.yml +1 -1
- data/lib/asset_cloud/asset.rb +17 -13
- data/lib/asset_cloud/asset_extension.rb +27 -15
- data/lib/asset_cloud/base.rb +77 -72
- data/lib/asset_cloud/bucket.rb +5 -2
- data/lib/asset_cloud/buckets/active_record_bucket.rb +16 -14
- data/lib/asset_cloud/buckets/blackhole_bucket.rb +2 -0
- data/lib/asset_cloud/buckets/bucket_chain.rb +38 -31
- data/lib/asset_cloud/buckets/file_system_bucket.rb +14 -15
- data/lib/asset_cloud/buckets/gcs_bucket.rb +6 -8
- data/lib/asset_cloud/buckets/invalid_bucket.rb +9 -6
- data/lib/asset_cloud/buckets/memory_bucket.rb +7 -4
- data/lib/asset_cloud/buckets/s3_bucket.rb +11 -8
- data/lib/asset_cloud/buckets/versioned_memory_bucket.rb +4 -2
- data/lib/asset_cloud/callbacks.rb +9 -5
- data/lib/asset_cloud/free_key_locator.rb +6 -6
- data/lib/asset_cloud/metadata.rb +11 -7
- data/lib/asset_cloud/validations.rb +9 -5
- data/lib/asset_cloud.rb +23 -21
- data/spec/active_record_bucket_spec.rb +27 -26
- data/spec/asset_cloud/metadata_spec.rb +4 -2
- data/spec/asset_extension_spec.rb +17 -16
- data/spec/asset_spec.rb +27 -21
- data/spec/base_spec.rb +93 -92
- data/spec/blackhole_bucket_spec.rb +12 -11
- data/spec/bucket_chain_spec.rb +61 -56
- data/spec/bucket_spec.rb +6 -5
- data/spec/callbacks_spec.rb +52 -32
- data/spec/file_system_spec.rb +25 -24
- data/spec/find_free_key_spec.rb +16 -17
- data/spec/gcs_bucket_remote_spec.rb +23 -22
- data/spec/gcs_bucket_spec.rb +48 -60
- data/spec/memory_bucket_spec.rb +12 -11
- data/spec/mock_s3_interface.rb +17 -6
- data/spec/remote_s3_bucket_spec.rb +31 -28
- data/spec/s3_bucket_spec.rb +19 -17
- data/spec/spec_helper.rb +8 -7
- data/spec/validations_spec.rb +13 -12
- data/spec/versioned_memory_bucket_spec.rb +11 -10
- metadata +10 -33
- data/.github/probots.yml +0 -2
- data/.rubocop_todo.yml +0 -326
- data/.travis.yml +0 -12
data/lib/asset_cloud/base.rb
CHANGED
@@ -1,42 +1,44 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "uri/rfc2396_parser"
|
4
4
|
|
5
|
+
module AssetCloud
|
5
6
|
class IllegalPath < StandardError
|
6
7
|
end
|
7
8
|
|
8
9
|
class Base
|
9
10
|
cattr_accessor :logger
|
10
11
|
|
11
|
-
VALID_PATHS =
|
12
|
+
VALID_PATHS = %r{\A
|
12
13
|
(
|
13
|
-
(\w)
|
14
|
-
|
|
14
|
+
(\w) # Filename can be a single letter or underscore
|
15
|
+
| # OR it is many and follows the below rules
|
15
16
|
(
|
16
|
-
(\.?[\w\[\]\(\)\-\@])
|
17
|
+
(\.?[\w\[\]\(\)\-\@]) # It can start with a dot but it must have a following character
|
17
18
|
(
|
18
|
-
[\w\[\]\(\)\-\@]
|
19
|
+
[\w\[\]\(\)\-\@] # You can have a letter without any following conditions
|
19
20
|
|
|
20
|
-
[\ ][\w\[\]\(\)\-\@\.]
|
21
|
+
[\ ][\w\[\]\(\)\-\@\.] # If there is a space you need to have a normal letter afterward or a dot
|
21
22
|
|
|
22
|
-
[
|
23
|
+
[/][\w\[\]\(\)\-\@] # If there is a slash you need to have a normal letter afterward
|
23
24
|
|
|
24
|
-
[
|
25
|
+
[/][\.][\w\[\]\(\)\-\@] # Though a slash could be followed by a dot
|
26
|
+
# so long as there is a normal letter afterward
|
25
27
|
|
|
26
|
-
[\.]+[\w\[\]\(\)\-\@]+
|
27
|
-
)*
|
28
|
+
[\.]+[\w\[\]\(\)\-\@]+ # One or more dots must be followed by one (or more) normal letters
|
29
|
+
)* # Zero to many of these combinations.
|
28
30
|
)
|
29
|
-
)\z
|
30
|
-
MATCH_BUCKET =
|
31
|
+
)\z}x
|
32
|
+
MATCH_BUCKET = %r{^(\w+)(/|$)}
|
31
33
|
|
32
34
|
URI_PARSER = URI::RFC2396_Parser.new
|
33
35
|
|
34
36
|
attr_accessor :url, :root
|
35
37
|
|
36
38
|
class_attribute :root_bucket_class
|
37
|
-
self.root_bucket_class =
|
39
|
+
self.root_bucket_class = "AssetCloud::FileSystemBucket"
|
38
40
|
class_attribute :root_asset_class
|
39
|
-
self.root_asset_class
|
41
|
+
self.root_asset_class = "AssetCloud::Asset"
|
40
42
|
|
41
43
|
class_attribute :bucket_classes
|
42
44
|
self.bucket_classes = {}.freeze
|
@@ -45,56 +47,68 @@ module AssetCloud
|
|
45
47
|
class_attribute :asset_extension_classes
|
46
48
|
self.asset_extension_classes = {}.freeze
|
47
49
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
50
|
+
class << self
|
51
|
+
def bucket(*args)
|
52
|
+
asset_class = if args.last.is_a?(Hash)
|
53
|
+
convert_to_class_name_if_possible(args.pop[:asset_class])
|
54
|
+
end
|
52
55
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
56
|
+
bucket_class = if args.last.is_a?(Class)
|
57
|
+
convert_to_class_name_if_possible(args.pop)
|
58
|
+
else
|
59
|
+
raise ArgumentError, "requires a bucket class"
|
60
|
+
end
|
58
61
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
62
|
+
if (bucket_name = args.first)
|
63
|
+
self.bucket_classes = bucket_classes.merge(bucket_name.to_sym => bucket_class).freeze
|
64
|
+
self.asset_classes = asset_classes.merge(bucket_name.to_sym => asset_class).freeze if asset_class
|
65
|
+
else
|
66
|
+
self.root_bucket_class = bucket_class
|
67
|
+
if asset_class
|
68
|
+
raise ArgumentError, "asset_class on the root bucket cannot be a proc" if asset_class.is_a?(Proc)
|
69
|
+
|
70
|
+
self.root_asset_class = asset_class
|
71
|
+
end
|
67
72
|
end
|
68
73
|
end
|
69
|
-
end
|
70
74
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
+
def asset_extensions(*args)
|
76
|
+
opts = args.last.is_a?(Hash) ? args.pop.slice(:only, :except) : {}
|
77
|
+
opts.each do |k, v|
|
78
|
+
opts[k] = [v].flatten.map(&:to_sym)
|
79
|
+
end
|
80
|
+
|
81
|
+
args.each do |klass|
|
82
|
+
klass = convert_to_class_name_if_possible(klass)
|
83
|
+
self.asset_extension_classes = asset_extension_classes.merge(klass => opts).freeze
|
84
|
+
end
|
75
85
|
end
|
76
86
|
|
77
|
-
|
78
|
-
|
79
|
-
|
87
|
+
private
|
88
|
+
|
89
|
+
def convert_to_class_name_if_possible(klass)
|
90
|
+
if klass.is_a?(Class) && klass.name.present?
|
91
|
+
klass.name
|
92
|
+
else
|
93
|
+
klass
|
94
|
+
end
|
80
95
|
end
|
81
96
|
end
|
82
97
|
|
83
98
|
def buckets
|
84
99
|
@buckets ||= Hash.new do |hash, key|
|
85
|
-
if klass = self.class.bucket_classes[key]
|
86
|
-
|
87
|
-
else
|
88
|
-
hash[key] = nil
|
100
|
+
hash[key] = if (klass = self.class.bucket_classes[key])
|
101
|
+
constantize_if_necessary(klass).new(self, key)
|
89
102
|
end
|
90
103
|
end
|
91
104
|
end
|
92
105
|
|
93
|
-
def initialize(root, url =
|
94
|
-
@root
|
106
|
+
def initialize(root, url = "/")
|
107
|
+
@root = root
|
108
|
+
@url = url
|
95
109
|
end
|
96
110
|
|
97
|
-
def url_for(key, options={})
|
111
|
+
def url_for(key, options = {})
|
98
112
|
File.join(@url, URI_PARSER.escape(key))
|
99
113
|
end
|
100
114
|
|
@@ -140,13 +154,13 @@ module AssetCloud
|
|
140
154
|
end
|
141
155
|
|
142
156
|
def build(key, value = nil, &block)
|
143
|
-
logger
|
157
|
+
logger&.info { " [#{self.class.name}] Building asset #{key}" }
|
144
158
|
asset_class_for(key).new(self, key, value, Metadata.non_existing, &block)
|
145
159
|
end
|
146
160
|
|
147
161
|
def write(key, value)
|
148
162
|
check_key_for_errors(key)
|
149
|
-
logger
|
163
|
+
logger&.info { " [#{self.class.name}] Writing #{value.size} bytes to #{key}" }
|
150
164
|
|
151
165
|
bucket_for(key).write(key, value)
|
152
166
|
end
|
@@ -158,25 +172,25 @@ module AssetCloud
|
|
158
172
|
end
|
159
173
|
|
160
174
|
def read(key)
|
161
|
-
logger
|
175
|
+
logger&.info { " [#{self.class.name}] Reading from #{key}" }
|
162
176
|
|
163
177
|
bucket_for(key).read(key)
|
164
178
|
end
|
165
179
|
|
166
180
|
def stat(key)
|
167
|
-
logger
|
181
|
+
logger&.info { " [#{self.class.name}] Statting #{key}" }
|
168
182
|
|
169
183
|
bucket_for(key).stat(key)
|
170
184
|
end
|
171
185
|
|
172
186
|
def ls(key)
|
173
|
-
logger
|
187
|
+
logger&.info { " [#{self.class.name}] Listing objects in #{key}" }
|
174
188
|
|
175
189
|
bucket_for(key).ls(key)
|
176
190
|
end
|
177
191
|
|
178
192
|
def exist?(key)
|
179
|
-
if fp = stat(key)
|
193
|
+
if (fp = stat(key))
|
180
194
|
fp.exist?
|
181
195
|
else
|
182
196
|
false
|
@@ -188,7 +202,7 @@ module AssetCloud
|
|
188
202
|
end
|
189
203
|
|
190
204
|
def delete(key)
|
191
|
-
logger
|
205
|
+
logger&.info { " [#{self.class.name}] Deleting #{key}" }
|
192
206
|
|
193
207
|
bucket_for(key).delete(key)
|
194
208
|
end
|
@@ -211,17 +225,17 @@ module AssetCloud
|
|
211
225
|
# versioning
|
212
226
|
|
213
227
|
def read_version(key, version)
|
214
|
-
logger
|
228
|
+
logger&.info { " [#{self.class.name}] Reading from #{key} at version #{version}" }
|
215
229
|
bucket_for(key).read_version(key, version)
|
216
230
|
end
|
217
231
|
|
218
232
|
def versions(key)
|
219
|
-
logger
|
233
|
+
logger&.info { " [#{self.class.name}] Getting all versions for #{key}" }
|
220
234
|
bucket_for(key).versions(key)
|
221
235
|
end
|
222
236
|
|
223
237
|
def version_details(key)
|
224
|
-
logger
|
238
|
+
logger&.info { " [#{self.class.name}] Getting all version details for #{key}" }
|
225
239
|
bucket_for(key).version_details(key)
|
226
240
|
end
|
227
241
|
|
@@ -239,40 +253,31 @@ module AssetCloud
|
|
239
253
|
klasses = extensions.keys.select do |ext|
|
240
254
|
opts = extensions[ext]
|
241
255
|
(opts.key?(:only) ? opts[:only].include?(bucket) : true) &&
|
242
|
-
|
256
|
+
(opts.key?(:except) ? !opts[:except].include?(bucket) : true)
|
243
257
|
end
|
244
|
-
klasses.map {|klass| constantize_if_necessary(klass)}
|
258
|
+
klasses.map { |klass| constantize_if_necessary(klass) }
|
245
259
|
end
|
246
260
|
|
247
261
|
protected
|
248
262
|
|
249
263
|
def bucket_symbol_for_key(key)
|
250
|
-
|
264
|
+
Regexp.last_match(1).to_sym if key =~ MATCH_BUCKET
|
251
265
|
end
|
252
266
|
|
253
267
|
def root_bucket
|
254
|
-
@default_bucket ||= constantize_if_necessary(self.class.root_bucket_class).new(self,
|
268
|
+
@default_bucket ||= constantize_if_necessary(self.class.root_bucket_class).new(self, "")
|
255
269
|
end
|
256
270
|
|
257
271
|
def constantize_if_necessary(klass)
|
258
272
|
klass.is_a?(Class) ? klass : klass.constantize
|
259
273
|
end
|
260
274
|
|
261
|
-
def self.convert_to_class_name_if_possible(klass)
|
262
|
-
if klass.is_a?(Class) && klass.name.present?
|
263
|
-
klass.name
|
264
|
-
else
|
265
|
-
klass
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
275
|
def check_key_for_errors(key)
|
270
276
|
raise IllegalPath, "key cannot be empty" if key.blank?
|
271
277
|
raise IllegalPath, "#{key.inspect} contains illegal characters" unless supports?(key)
|
272
278
|
rescue => e
|
273
|
-
logger
|
279
|
+
logger&.info { " [#{self.class.name}] bad key #{e.message}" }
|
274
280
|
raise
|
275
281
|
end
|
276
|
-
|
277
282
|
end
|
278
283
|
end
|
data/lib/asset_cloud/bucket.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AssetCloud
|
2
4
|
class AssetNotFoundError < StandardError
|
3
|
-
def initialize(key, version=nil)
|
5
|
+
def initialize(key, version = nil)
|
4
6
|
super(version ? "Could not find version #{version} of asset #{key}" : "Could not find asset #{key}")
|
5
7
|
end
|
6
8
|
end
|
@@ -10,7 +12,8 @@ module AssetCloud
|
|
10
12
|
attr_accessor :cloud
|
11
13
|
|
12
14
|
def initialize(cloud, name)
|
13
|
-
@cloud
|
15
|
+
@cloud = cloud
|
16
|
+
@name = name
|
14
17
|
end
|
15
18
|
|
16
19
|
def ls(key = nil)
|
@@ -1,35 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AssetCloud
|
2
4
|
class ActiveRecordBucket < AssetCloud::Bucket
|
3
5
|
class_attribute :key_attribute, :value_attribute
|
4
|
-
self.key_attribute =
|
5
|
-
self.value_attribute =
|
6
|
+
self.key_attribute = "key"
|
7
|
+
self.value_attribute = "value"
|
6
8
|
|
7
|
-
def ls(key=name)
|
8
|
-
col = records.connection.quote_column_name(
|
9
|
-
records.all(:
|
10
|
-
cloud[r.send(
|
9
|
+
def ls(key = name)
|
10
|
+
col = records.connection.quote_column_name(key_attribute)
|
11
|
+
records.all(conditions: ["#{col} LIKE ?", "#{key}%"]).map do |r|
|
12
|
+
cloud[r.send(key_attribute)]
|
11
13
|
end
|
12
14
|
end
|
13
15
|
|
14
16
|
def read(key)
|
15
|
-
find_record!(key).send(
|
17
|
+
find_record!(key).send(value_attribute)
|
16
18
|
end
|
17
19
|
|
18
20
|
def write(key, value)
|
19
|
-
record = records.send("find_or_initialize_by_#{
|
20
|
-
record.send("#{
|
21
|
+
record = records.send("find_or_initialize_by_#{key_attribute}", key.to_s)
|
22
|
+
record.send("#{value_attribute}=", value)
|
21
23
|
record.save!
|
22
24
|
end
|
23
25
|
|
24
26
|
def delete(key)
|
25
|
-
if record = find_record(key)
|
27
|
+
if (record = find_record(key))
|
26
28
|
record.destroy
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
30
32
|
def stat(key)
|
31
|
-
if record = find_record(key)
|
32
|
-
AssetCloud::Metadata.new(true, record.send(
|
33
|
+
if (record = find_record(key))
|
34
|
+
AssetCloud::Metadata.new(true, record.send(value_attribute).size, record.created_at, record.updated_at)
|
33
35
|
else
|
34
36
|
AssetCloud::Metadata.new(false)
|
35
37
|
end
|
@@ -47,11 +49,11 @@ module AssetCloud
|
|
47
49
|
end
|
48
50
|
|
49
51
|
def find_record(key)
|
50
|
-
records.first(:
|
52
|
+
records.first(conditions: { key_attribute => key.to_s })
|
51
53
|
end
|
52
54
|
|
53
55
|
def find_record!(key)
|
54
|
-
find_record(key)
|
56
|
+
find_record(key) || raise(AssetCloud::AssetNotFoundError, key)
|
55
57
|
end
|
56
58
|
end
|
57
59
|
end
|
@@ -1,61 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AssetCloud
|
2
4
|
class BucketChain < Bucket
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
class << self
|
6
|
+
# returns a new Bucket class which writes to each given Bucket
|
7
|
+
# but only uses the first one for reading
|
8
|
+
def chain(*klasses)
|
9
|
+
Class.new(self) do
|
10
|
+
attr_reader :chained_buckets
|
11
|
+
|
12
|
+
define_method "initialize" do |cloud, name|
|
13
|
+
super(cloud, name)
|
14
|
+
@chained_buckets = klasses.map { |klass| klass.new(cloud, name) }
|
15
|
+
end
|
11
16
|
end
|
12
17
|
end
|
13
18
|
end
|
14
19
|
|
15
|
-
def ls(key=nil)
|
16
|
-
first_possible_bucket {|b| b.ls(key)}
|
20
|
+
def ls(key = nil)
|
21
|
+
first_possible_bucket { |b| b.ls(key) }
|
17
22
|
end
|
23
|
+
|
18
24
|
def read(key)
|
19
|
-
first_possible_bucket {|b| b.read(key)}
|
25
|
+
first_possible_bucket { |b| b.read(key) }
|
20
26
|
end
|
21
|
-
|
22
|
-
|
27
|
+
|
28
|
+
def stat(key = nil)
|
29
|
+
first_possible_bucket { |b| b.stat(key) }
|
23
30
|
end
|
31
|
+
|
24
32
|
def read_version(key, version)
|
25
|
-
first_possible_bucket {|b| b.read_version(key, version)}
|
33
|
+
first_possible_bucket { |b| b.read_version(key, version) }
|
26
34
|
end
|
35
|
+
|
27
36
|
def versions(key)
|
28
|
-
first_possible_bucket {|b| b.versions(key)}
|
37
|
+
first_possible_bucket { |b| b.versions(key) }
|
29
38
|
end
|
30
39
|
|
31
40
|
def write(key, data)
|
32
|
-
every_bucket_with_transaction_on_key(key) {|b| b.write(key, data)}
|
41
|
+
every_bucket_with_transaction_on_key(key) { |b| b.write(key, data) }
|
33
42
|
end
|
43
|
+
|
34
44
|
def delete(key)
|
35
|
-
every_bucket_with_transaction_on_key(key) {|b| b.delete(key)}
|
45
|
+
every_bucket_with_transaction_on_key(key) { |b| b.delete(key) }
|
36
46
|
end
|
37
47
|
|
38
|
-
def
|
39
|
-
@chained_buckets.any? {|b| b.respond_to?(sym)}
|
48
|
+
def respond_to_missing?(sym, *)
|
49
|
+
@chained_buckets.any? { |b| b.respond_to?(sym) }
|
40
50
|
end
|
51
|
+
|
41
52
|
def method_missing(sym, *args)
|
42
|
-
first_possible_bucket {|b| b.send(sym, *args)}
|
53
|
+
first_possible_bucket { |b| b.send(sym, *args) }
|
43
54
|
end
|
44
55
|
|
45
56
|
private
|
46
57
|
|
47
58
|
def first_possible_bucket(&block)
|
48
59
|
@chained_buckets.each do |bucket|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
nil
|
53
|
-
end
|
60
|
+
return yield(bucket)
|
61
|
+
rescue NoMethodError, NotImplementedError
|
62
|
+
nil
|
54
63
|
end
|
55
64
|
end
|
56
65
|
|
57
|
-
def every_bucket_with_transaction_on_key(key, i=0, &block)
|
58
|
-
return unless bucket = @chained_buckets[i]
|
66
|
+
def every_bucket_with_transaction_on_key(key, i = 0, &block)
|
67
|
+
return unless (bucket = @chained_buckets[i])
|
59
68
|
|
60
69
|
old_value = begin
|
61
70
|
bucket.read(key)
|
@@ -65,8 +74,8 @@ module AssetCloud
|
|
65
74
|
result = yield(bucket)
|
66
75
|
|
67
76
|
begin
|
68
|
-
every_bucket_with_transaction_on_key(key, i+1, &block)
|
69
|
-
|
77
|
+
every_bucket_with_transaction_on_key(key, i + 1, &block)
|
78
|
+
result
|
70
79
|
rescue StandardError => e
|
71
80
|
if old_value
|
72
81
|
bucket.write(key, old_value)
|
@@ -76,7 +85,5 @@ module AssetCloud
|
|
76
85
|
raise e
|
77
86
|
end
|
78
87
|
end
|
79
|
-
|
80
88
|
end
|
81
|
-
|
82
89
|
end
|
@@ -1,27 +1,29 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
+
module AssetCloud
|
3
4
|
class FileSystemBucket < Bucket
|
4
|
-
|
5
5
|
def ls(key = nil)
|
6
6
|
objects = []
|
7
|
-
base_path = File.join(path_for(key),
|
7
|
+
base_path = File.join(path_for(key), "*")
|
8
8
|
|
9
9
|
Dir.glob(base_path).each do |f|
|
10
10
|
next unless File.file?(f)
|
11
|
-
|
11
|
+
|
12
|
+
objects.push(cloud[relative_path_for(f)])
|
12
13
|
end
|
13
14
|
objects
|
14
15
|
end
|
15
16
|
|
16
17
|
def read(key)
|
17
18
|
File.read(path_for(key))
|
18
|
-
rescue Errno::ENOENT
|
19
|
+
rescue Errno::ENOENT
|
19
20
|
raise AssetCloud::AssetNotFoundError, key
|
20
21
|
end
|
21
22
|
|
22
23
|
def delete(key)
|
23
24
|
File.delete(path_for(key))
|
24
25
|
rescue Errno::ENOENT
|
26
|
+
nil
|
25
27
|
end
|
26
28
|
|
27
29
|
def write(key, data)
|
@@ -32,12 +34,10 @@ module AssetCloud
|
|
32
34
|
end
|
33
35
|
|
34
36
|
def stat(key)
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
Metadata.new(false)
|
40
|
-
end
|
37
|
+
stat = File.stat(path_for(key))
|
38
|
+
Metadata.new(true, stat.size, stat.ctime, stat.mtime)
|
39
|
+
rescue Errno::ENOENT
|
40
|
+
Metadata.new(false)
|
41
41
|
end
|
42
42
|
|
43
43
|
protected
|
@@ -53,11 +53,11 @@ module AssetCloud
|
|
53
53
|
private
|
54
54
|
|
55
55
|
def remove_full_path_regexp
|
56
|
-
@regexp ||=
|
56
|
+
@regexp ||= %r{^#{path}/}
|
57
57
|
end
|
58
58
|
|
59
59
|
def relative_path_for(f)
|
60
|
-
f.sub(remove_full_path_regexp,
|
60
|
+
f.sub(remove_full_path_regexp, "")
|
61
61
|
end
|
62
62
|
|
63
63
|
def execute_in_full_path(key, &block)
|
@@ -71,10 +71,9 @@ module AssetCloud
|
|
71
71
|
|
72
72
|
begin
|
73
73
|
yield(path)
|
74
|
-
rescue Errno::ENOENT
|
74
|
+
rescue Errno::ENOENT
|
75
75
|
raise if retried
|
76
76
|
|
77
|
-
directory = File.dirname(path)
|
78
77
|
FileUtils.mkdir_p(File.dirname(path))
|
79
78
|
retried = true
|
80
79
|
retry
|
@@ -31,12 +31,10 @@ module AssetCloud
|
|
31
31
|
end
|
32
32
|
|
33
33
|
def stat(key)
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
Metadata.new(false)
|
39
|
-
end
|
34
|
+
file = find_by_key!(key)
|
35
|
+
Metadata.new(true, file.size, file.created_at, file.updated_at)
|
36
|
+
rescue AssetCloud::AssetNotFoundError
|
37
|
+
Metadata.new(false)
|
40
38
|
end
|
41
39
|
|
42
40
|
private
|
@@ -47,11 +45,11 @@ module AssetCloud
|
|
47
45
|
|
48
46
|
def absolute_key(key = nil)
|
49
47
|
if key.to_s.starts_with?(path_prefix)
|
50
|
-
|
48
|
+
key
|
51
49
|
else
|
52
50
|
args = [path_prefix]
|
53
51
|
args << key.to_s if key
|
54
|
-
args.join(
|
52
|
+
args.join("/")
|
55
53
|
end
|
56
54
|
end
|
57
55
|
|
@@ -1,28 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AssetCloud
|
2
4
|
class InvalidBucketError < StandardError
|
3
5
|
end
|
4
6
|
|
5
7
|
class InvalidBucket < Bucket
|
6
|
-
|
8
|
+
ERROR = "No such namespace: %s"
|
9
|
+
private_constant :ERROR
|
7
10
|
|
8
11
|
def ls(namespace)
|
9
|
-
raise InvalidBucketError,
|
12
|
+
raise InvalidBucketError, ERROR % namespace
|
10
13
|
end
|
11
14
|
|
12
15
|
def read(key)
|
13
|
-
raise InvalidBucketError,
|
16
|
+
raise InvalidBucketError, ERROR % key
|
14
17
|
end
|
15
18
|
|
16
19
|
def write(key, data)
|
17
|
-
raise InvalidBucketError,
|
20
|
+
raise InvalidBucketError, ERROR % key
|
18
21
|
end
|
19
22
|
|
20
23
|
def delete(key)
|
21
|
-
raise InvalidBucketError,
|
24
|
+
raise InvalidBucketError, ERROR % key
|
22
25
|
end
|
23
26
|
|
24
27
|
def stat(key)
|
25
|
-
raise InvalidBucketError,
|
28
|
+
raise InvalidBucketError, ERROR % key
|
26
29
|
end
|
27
30
|
end
|
28
31
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module AssetCloud
|
2
4
|
class MemoryBucket < Bucket
|
3
5
|
def initialize(*args)
|
@@ -5,16 +7,17 @@ module AssetCloud
|
|
5
7
|
@memory = {}
|
6
8
|
end
|
7
9
|
|
8
|
-
def ls(prefix=nil)
|
10
|
+
def ls(prefix = nil)
|
9
11
|
results = []
|
10
|
-
@memory.each do |k,
|
12
|
+
@memory.each do |k, _v|
|
11
13
|
results.push(cloud[k]) if prefix.nil? || k.starts_with?(prefix)
|
12
14
|
end
|
13
15
|
results
|
14
16
|
end
|
15
17
|
|
16
18
|
def read(key)
|
17
|
-
raise AssetCloud::AssetNotFoundError, key unless @memory.
|
19
|
+
raise AssetCloud::AssetNotFoundError, key unless @memory.key?(key)
|
20
|
+
|
18
21
|
@memory[key]
|
19
22
|
end
|
20
23
|
|
@@ -29,7 +32,7 @@ module AssetCloud
|
|
29
32
|
end
|
30
33
|
|
31
34
|
def stat(key)
|
32
|
-
return Metadata.non_existing unless @memory.
|
35
|
+
return Metadata.non_existing unless @memory.key?(key)
|
33
36
|
|
34
37
|
Metadata.new(true, read(key).size)
|
35
38
|
end
|