dis 1.3.0 → 1.3.1
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/README.md +30 -0
- data/lib/dis/layer.rb +58 -12
- data/lib/dis/model/data.rb +68 -21
- data/lib/dis/model.rb +26 -9
- data/lib/dis/storage.rb +39 -0
- data/lib/dis/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cbda8b2db5161c2dd8357c1f06102a27dfc4ff3e1a3eb79974b29abb6fa27c4d
|
|
4
|
+
data.tar.gz: 4cd6907621cae900a48f021f2bd0976d5721cfaa1a4a368af0f54e728c572543
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2dc2ee898c2864535927cd472c46c6f7d7db082e1adfe95b4366b9a7679af772ff64c26475dcab2257a49284663170269d03627dd69416fe87a89ee6be8b3491
|
|
7
|
+
data.tar.gz: 5c43189d589f280df560496e3fe37ef50ec19217fd031e93f551439e3ce97804d5e71a33b53acda960e96e0a5fe1855fa1ac8caca8881599810b1e790f56d582
|
data/README.md
CHANGED
|
@@ -104,6 +104,36 @@ class DocumentsController < ApplicationController
|
|
|
104
104
|
end
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
+
### Accessing the data
|
|
108
|
+
|
|
109
|
+
Which accessor you want depends on how long the data needs to stay
|
|
110
|
+
valid.
|
|
111
|
+
|
|
112
|
+
`data` returns the content as a binary string.
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
document.data # => "foobar"
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`with_data_file` yields a path, for tools that want a file name rather
|
|
119
|
+
than the bytes. It is valid for the duration of the block, so resolve
|
|
120
|
+
anything lazy before returning.
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
document.with_data_file { |path| Vips::Image.new_from_file(path.to_s).avg }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`open_data` returns an open file, valid until you close it — even if
|
|
127
|
+
the content is deleted or evicted from a cache layer meanwhile. Use it
|
|
128
|
+
when the reader outlives the current call stack, such as a response
|
|
129
|
+
body handed to the web server.
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
document.open_data { |file| file.read }
|
|
133
|
+
|
|
134
|
+
file = document.open_data # caller closes it
|
|
135
|
+
```
|
|
136
|
+
|
|
107
137
|
## Layers
|
|
108
138
|
|
|
109
139
|
The underlying storage consists of one or more layers. Each layer
|
data/lib/dis/layer.rb
CHANGED
|
@@ -213,25 +213,45 @@ module Dis
|
|
|
213
213
|
result = debug_log("Get #{type}/#{key} from #{name}") do
|
|
214
214
|
dir.files.get(key_component(type, key))
|
|
215
215
|
end
|
|
216
|
-
|
|
216
|
+
refresh_cache_mtime(local_file_path(type, key)) if result && cache?
|
|
217
217
|
result
|
|
218
218
|
end
|
|
219
219
|
|
|
220
|
+
# Writes the contents of a file into the given IO. Local layers
|
|
221
|
+
# copy from disk; remote layers stream in chunks, never holding
|
|
222
|
+
# the whole body in memory.
|
|
223
|
+
#
|
|
224
|
+
# @param type [String] the type scope
|
|
225
|
+
# @param key [String] the content hash
|
|
226
|
+
# @param io [IO] the destination to write to
|
|
227
|
+
# @return [Boolean] true if the file was found and written
|
|
228
|
+
def stream(type, key, io)
|
|
229
|
+
local = local_file_path(type, key)
|
|
230
|
+
if local
|
|
231
|
+
copy_local(local, io)
|
|
232
|
+
return true
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
dir = directory(type, key)
|
|
236
|
+
return false unless dir
|
|
237
|
+
|
|
238
|
+
debug_log("Stream #{type}/#{key} from #{name}") do
|
|
239
|
+
!fetch_chunks(dir, type, key, io).nil?
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
220
243
|
# Returns the absolute file path for a locally stored file, or
|
|
221
244
|
# nil if the provider is not local or the file does not exist.
|
|
245
|
+
# On cache layers this counts as a read and may refresh the
|
|
246
|
+
# file's mtime.
|
|
222
247
|
#
|
|
223
248
|
# @param type [String] the type scope
|
|
224
249
|
# @param key [String] the content hash
|
|
225
250
|
# @return [String, nil]
|
|
226
251
|
def file_path(type, key)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
File.join(
|
|
231
|
-
connection.local_root,
|
|
232
|
-
directory_component(type, key),
|
|
233
|
-
key_component(type, key)
|
|
234
|
-
)
|
|
252
|
+
path = local_file_path(type, key)
|
|
253
|
+
refresh_cache_mtime(path) if cache?
|
|
254
|
+
path
|
|
235
255
|
end
|
|
236
256
|
|
|
237
257
|
# Deletes a file from the store.
|
|
@@ -292,9 +312,35 @@ module Dis
|
|
|
292
312
|
mtime: file.mtime, size: file.size }
|
|
293
313
|
end
|
|
294
314
|
|
|
295
|
-
def
|
|
296
|
-
|
|
297
|
-
|
|
315
|
+
def local_file_path(type, key)
|
|
316
|
+
return unless connection.respond_to?(:local_root)
|
|
317
|
+
return unless exists?(type, key)
|
|
318
|
+
|
|
319
|
+
File.join(
|
|
320
|
+
connection.local_root,
|
|
321
|
+
directory_component(type, key),
|
|
322
|
+
key_component(type, key)
|
|
323
|
+
)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def refresh_cache_mtime(path)
|
|
327
|
+
return unless path
|
|
328
|
+
return unless File.mtime(path) < 1.minute.ago
|
|
329
|
+
|
|
330
|
+
FileUtils.touch(path)
|
|
331
|
+
rescue Errno::ENOENT
|
|
332
|
+
nil
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def copy_local(path, io)
|
|
336
|
+
refresh_cache_mtime(path) if cache?
|
|
337
|
+
IO.copy_stream(path, io)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def fetch_chunks(dir, type, key, io)
|
|
341
|
+
dir.files.get(key_component(type, key)) do |chunk, _rem, _total|
|
|
342
|
+
io.write(chunk)
|
|
343
|
+
end
|
|
298
344
|
end
|
|
299
345
|
|
|
300
346
|
def directory_component(_type, _key)
|
data/lib/dis/model/data.rb
CHANGED
|
@@ -93,37 +93,42 @@ module Dis
|
|
|
93
93
|
Dis::Storage.store(storage_type, raw)
|
|
94
94
|
end
|
|
95
95
|
|
|
96
|
-
# Clears cached data
|
|
97
|
-
#
|
|
98
|
-
# will re-fetch from storage.
|
|
96
|
+
# Clears cached data, allowing it to be garbage collected.
|
|
97
|
+
# Subsequent calls to +read+ will re-fetch from storage.
|
|
99
98
|
#
|
|
100
99
|
# @return [void]
|
|
101
100
|
def reset_read_cache!
|
|
102
101
|
@read = nil
|
|
103
|
-
return unless @tempfile
|
|
104
|
-
|
|
105
|
-
@tempfile.close!
|
|
106
|
-
@tempfile = nil
|
|
107
102
|
end
|
|
108
103
|
|
|
109
|
-
#
|
|
110
|
-
#
|
|
104
|
+
# Yields a path to the data, removing any temporary copy
|
|
105
|
+
# afterwards.
|
|
111
106
|
#
|
|
112
|
-
# @
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
# @yieldparam path [Pathname] path to the data
|
|
108
|
+
# @return [Object] the return value of the block
|
|
109
|
+
def with_file
|
|
110
|
+
path = local_path
|
|
111
|
+
return yield(Pathname.new(path)) if path
|
|
112
|
+
|
|
113
|
+
file = materialize
|
|
114
|
+
begin
|
|
115
|
+
yield(Pathname.new(file.path))
|
|
116
|
+
ensure
|
|
117
|
+
close_and_unlink(file)
|
|
118
|
+
end
|
|
115
119
|
end
|
|
116
120
|
|
|
117
|
-
#
|
|
121
|
+
# Returns the data as an open file. Any temporary copy is
|
|
122
|
+
# unlinked first, leaving the kernel to reclaim it on close.
|
|
118
123
|
#
|
|
119
|
-
# @return [
|
|
120
|
-
def
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
124
|
+
# @return [File] an open file, positioned at the start
|
|
125
|
+
def open
|
|
126
|
+
path = local_path
|
|
127
|
+
return File.open(path, "rb") if path
|
|
128
|
+
|
|
129
|
+
file = materialize
|
|
130
|
+
File.unlink(file.path)
|
|
131
|
+
file
|
|
127
132
|
end
|
|
128
133
|
|
|
129
134
|
protected
|
|
@@ -185,6 +190,48 @@ module Dis
|
|
|
185
190
|
Dis::Storage.file_path(storage_type, content_hash)
|
|
186
191
|
end
|
|
187
192
|
|
|
193
|
+
# Writes the data to a new temporary file. The caller owns it.
|
|
194
|
+
def materialize
|
|
195
|
+
file = Tempfile.create
|
|
196
|
+
file.binmode
|
|
197
|
+
fill(file)
|
|
198
|
+
file.rewind
|
|
199
|
+
file
|
|
200
|
+
rescue StandardError
|
|
201
|
+
close_and_unlink(file) if file
|
|
202
|
+
raise
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def fill(file)
|
|
206
|
+
if raw?
|
|
207
|
+
write_raw_to(file)
|
|
208
|
+
else
|
|
209
|
+
Dis::Storage.get_file(storage_type, content_hash, file)
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def write_raw_to(file)
|
|
214
|
+
if raw.respond_to?(:read)
|
|
215
|
+
rewind_raw
|
|
216
|
+
IO.copy_stream(raw, file)
|
|
217
|
+
rewind_raw
|
|
218
|
+
else
|
|
219
|
+
file.write(raw)
|
|
220
|
+
end
|
|
221
|
+
file.flush
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def rewind_raw
|
|
225
|
+
raw.rewind if raw.respond_to?(:rewind)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def close_and_unlink(file)
|
|
229
|
+
file.close unless file.closed?
|
|
230
|
+
File.unlink(file.path)
|
|
231
|
+
rescue Errno::ENOENT
|
|
232
|
+
nil
|
|
233
|
+
end
|
|
234
|
+
|
|
188
235
|
attr_reader :raw
|
|
189
236
|
end
|
|
190
237
|
end
|
data/lib/dis/model.rb
CHANGED
|
@@ -151,19 +151,36 @@ module Dis
|
|
|
151
151
|
dis_set :filename, file.original_filename
|
|
152
152
|
end
|
|
153
153
|
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
# the
|
|
154
|
+
# Yields the path to a file containing the data, for tools that
|
|
155
|
+
# need a file name rather than the bytes. The path is valid for
|
|
156
|
+
# the duration of the block only, so resolve anything lazy before
|
|
157
|
+
# returning.
|
|
157
158
|
#
|
|
158
|
-
# @
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
# @yieldparam path [Pathname] path to the data
|
|
160
|
+
# @return [Object] the return value of the block
|
|
161
|
+
#
|
|
162
|
+
# @example
|
|
163
|
+
# document.with_data_file { |path| Vips::Image.new_from_file(path.to_s).avg }
|
|
164
|
+
def with_data_file(&)
|
|
165
|
+
dis_data.with_file(&)
|
|
161
166
|
end
|
|
162
167
|
|
|
163
|
-
# Returns the data as
|
|
168
|
+
# Returns the data as an open, read-only file, or yields it and
|
|
169
|
+
# closes it afterwards. The data stays readable until the file is
|
|
170
|
+
# closed, even if the content is deleted or evicted meanwhile.
|
|
164
171
|
#
|
|
165
|
-
# @
|
|
166
|
-
|
|
172
|
+
# @yieldparam file [File] an open file, positioned at the start
|
|
173
|
+
# @return [File, Object] the open file, or the block's value
|
|
174
|
+
def open_data
|
|
175
|
+
file = dis_data.open
|
|
176
|
+
return file unless block_given?
|
|
177
|
+
|
|
178
|
+
begin
|
|
179
|
+
yield file
|
|
180
|
+
ensure
|
|
181
|
+
file.close unless file.closed?
|
|
182
|
+
end
|
|
183
|
+
end
|
|
167
184
|
|
|
168
185
|
private
|
|
169
186
|
|
data/lib/dis/storage.rb
CHANGED
|
@@ -166,6 +166,29 @@ module Dis
|
|
|
166
166
|
nil
|
|
167
167
|
end
|
|
168
168
|
|
|
169
|
+
# Streams the contents of a file into the given file, fetching
|
|
170
|
+
# from the first layer that has it. Backfills faster layers if
|
|
171
|
+
# the content had to be fetched from further down.
|
|
172
|
+
#
|
|
173
|
+
# @param type [String] the type scope
|
|
174
|
+
# @param key [String] the content hash
|
|
175
|
+
# @param file [File] the destination, must respond to +path+
|
|
176
|
+
# @return [File] the file that was written to, positioned at
|
|
177
|
+
# the start
|
|
178
|
+
# @raise [Dis::Errors::NoLayersError] if no layers are configured
|
|
179
|
+
# @raise [Dis::Errors::NotFoundError] if the file is not found
|
|
180
|
+
def get_file(type, key, file)
|
|
181
|
+
require_layers!
|
|
182
|
+
fetch_count = 0
|
|
183
|
+
found = layers.detect do |layer|
|
|
184
|
+
fetch_count += 1
|
|
185
|
+
stream_from_layer(layer, type, key, file)
|
|
186
|
+
end
|
|
187
|
+
raise Dis::Errors::NotFoundError unless found
|
|
188
|
+
|
|
189
|
+
finalize_fetched_file(type, file, fetch_count)
|
|
190
|
+
end
|
|
191
|
+
|
|
169
192
|
# Deletes a file from all layers. Kicks off a
|
|
170
193
|
# {Dis::Jobs::Delete} job if any delayed layers are defined.
|
|
171
194
|
#
|
|
@@ -304,6 +327,22 @@ module Dis
|
|
|
304
327
|
end
|
|
305
328
|
end
|
|
306
329
|
|
|
330
|
+
def finalize_fetched_file(type, file, fetch_count)
|
|
331
|
+
file.flush
|
|
332
|
+
backfill!(type, file) if fetch_count > 1
|
|
333
|
+
file.rewind
|
|
334
|
+
file
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def stream_from_layer(layer, type, key, file)
|
|
338
|
+
file.truncate(0)
|
|
339
|
+
file.rewind
|
|
340
|
+
layer.stream(type, key, file)
|
|
341
|
+
rescue StandardError => e
|
|
342
|
+
report_layer_error(e, layer:, type:, key:)
|
|
343
|
+
false
|
|
344
|
+
end
|
|
345
|
+
|
|
307
346
|
def fetch_from_layer(layer, type, key)
|
|
308
347
|
layer.get(type, key)
|
|
309
348
|
rescue StandardError => e
|
data/lib/dis/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dis
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Inge Jørgensen
|
|
@@ -150,7 +150,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
150
150
|
- !ruby/object:Gem::Version
|
|
151
151
|
version: '0'
|
|
152
152
|
requirements: []
|
|
153
|
-
rubygems_version: 4.0.
|
|
153
|
+
rubygems_version: 4.0.10
|
|
154
154
|
specification_version: 4
|
|
155
155
|
summary: A file store for your Rails app
|
|
156
156
|
test_files: []
|