store-digest 0.1.3 → 0.3.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 +4 -4
- data/TODO.org +37 -0
- data/lib/store/digest/driver/lmdb.rb +2 -0
- data/lib/store/digest/driver.rb +19 -0
- data/lib/store/digest/meta/lmdb.rb +1025 -357
- data/lib/store/digest/meta.rb +178 -5
- data/lib/store/digest/object.rb +144 -39
- data/lib/store/digest/version.rb +1 -1
- data/lib/store/digest.rb +28 -23
- data/store-digest.gemspec +11 -9
- metadata +42 -18
data/lib/store/digest/meta.rb
CHANGED
|
@@ -1,7 +1,180 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
# grab the symbols so we don't have to indent
|
|
2
|
+
require_relative 'version'
|
|
3
|
+
|
|
4
|
+
# This is an abstract module for metadata operations. All required
|
|
5
|
+
# methods are defined, and raise {NotImplementedError}.
|
|
6
|
+
module Store::Digest::Meta
|
|
7
|
+
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
PRIMARY = :"sha-256"
|
|
11
|
+
|
|
12
|
+
DIGESTS = {
|
|
13
|
+
md5: 16,
|
|
14
|
+
"sha-1": 20,
|
|
15
|
+
"sha-256": 32,
|
|
16
|
+
"sha-384": 48,
|
|
17
|
+
"sha-512": 64,
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
INTS = %i[size ctime mtime ptime dtime flags].map do |k|
|
|
21
|
+
[k, :to_i]
|
|
22
|
+
end.to_h.freeze
|
|
23
|
+
|
|
24
|
+
protected
|
|
25
|
+
|
|
26
|
+
# This method is run on initialization to bootstrap or otherwise
|
|
27
|
+
# verify the integrity of the database.
|
|
28
|
+
#
|
|
29
|
+
# @param options [Hash] whatever the parameters entail; it's your
|
|
30
|
+
# driver lol
|
|
31
|
+
#
|
|
32
|
+
# @return [void]
|
|
33
|
+
#
|
|
34
|
+
def setup **options
|
|
35
|
+
raise NotImplementedError
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Set/add an individual object's metadata to the database.
|
|
39
|
+
#
|
|
40
|
+
# @param obj [Store::Digest::Object] the object to store
|
|
41
|
+
# @param preserve [false, true] flag to preserve modification time
|
|
42
|
+
#
|
|
43
|
+
# @return [void]
|
|
44
|
+
#
|
|
45
|
+
def set_meta obj, preserve: false
|
|
46
|
+
raise NotImplementedError
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Retrieve a hash of the metadata stored for the given object.
|
|
50
|
+
#
|
|
51
|
+
# @param obj [#to_h, URI::ni] something hash-like which has keys that
|
|
52
|
+
# correspond to the identifiers for the digest algorithms, or
|
|
53
|
+
# otherwise a `ni:` URI.
|
|
54
|
+
#
|
|
55
|
+
# @return [Hash]
|
|
56
|
+
#
|
|
57
|
+
def get_meta obj, preserve: false
|
|
58
|
+
raise NotImplementedError
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Remove the metadata from the database and return it.
|
|
62
|
+
#
|
|
63
|
+
# @see #get_meta
|
|
64
|
+
#
|
|
65
|
+
# @param obj [#to_h, URI::ni] The object/identifier
|
|
66
|
+
#
|
|
67
|
+
# @return [Hash] The eliminated metadata.
|
|
68
|
+
#
|
|
69
|
+
def remove_meta obj, preserve: false
|
|
70
|
+
raise NotImplementedError
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Mark the object's record as "deleted" (but do not actually delete
|
|
74
|
+
# it) and return the updated record.
|
|
75
|
+
#
|
|
76
|
+
# @see #get_meta
|
|
77
|
+
#
|
|
78
|
+
# @param obj [#to_h, URI::ni] The object/identifier
|
|
79
|
+
#
|
|
80
|
+
# @return [Hash] The updated metadata.
|
|
81
|
+
#
|
|
82
|
+
# @return [Hash]
|
|
83
|
+
#
|
|
84
|
+
def mark_meta_deleted obj
|
|
85
|
+
raise NotImplementedError
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Retrieve storage statistics from the database itself.
|
|
89
|
+
#
|
|
90
|
+
# @return [Hash] global stats for the database.
|
|
91
|
+
#
|
|
92
|
+
def meta_get_stats
|
|
93
|
+
raise NotImplementedError
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
public
|
|
97
|
+
|
|
98
|
+
# Wrap the block in a transaction.
|
|
99
|
+
#
|
|
100
|
+
# @param block [Proc] whatever you pass into the transaction.
|
|
101
|
+
#
|
|
102
|
+
# @return [Object] whatever the block returns.
|
|
103
|
+
#
|
|
104
|
+
def transaction &block
|
|
105
|
+
raise NotImplementedError
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Return the set of algorithms initialized in the database.
|
|
109
|
+
#
|
|
110
|
+
# @return [Array] the algorithms
|
|
111
|
+
#
|
|
112
|
+
def algorithms
|
|
113
|
+
raise NotImplementedError
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Return the primary digest algorithm.
|
|
117
|
+
#
|
|
118
|
+
# @return [Symbol] the primary algorithm
|
|
119
|
+
#
|
|
120
|
+
def primary
|
|
121
|
+
raise NotImplementedError
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Return the number of objects in the database.
|
|
125
|
+
#
|
|
126
|
+
# @return [Integer]
|
|
127
|
+
#
|
|
128
|
+
def objects
|
|
129
|
+
raise NotImplementedError
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Return the number of objects whose payloads are deleted but are
|
|
133
|
+
# still on record.
|
|
134
|
+
#
|
|
135
|
+
# @return [Integer]
|
|
136
|
+
#
|
|
137
|
+
def deleted
|
|
138
|
+
raise NotImplementedError
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Return the number of bytes stored in the database (notwithstanding
|
|
142
|
+
# the database itself).
|
|
143
|
+
#
|
|
144
|
+
# @return [Integer]
|
|
145
|
+
#
|
|
146
|
+
def bytes
|
|
147
|
+
raise NotImplementedError
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Return a list of objects matching the given criteria. The result
|
|
151
|
+
# set will be the intersection of all supplied parameters. `:type`,
|
|
152
|
+
# `:charset`, `:encoding`, and `:language` are treated like discrete
|
|
153
|
+
# sets, while the rest of the parameters are treated like ranges
|
|
154
|
+
# (two-element arrays). Single values will be coerced into arrays;
|
|
155
|
+
# single range values will be interpreted as an inclusive lower
|
|
156
|
+
# bound. To bound only at the top, use a two-element array with its
|
|
157
|
+
# first value `nil`, like so: `size: [nil, 31337]`. The sorting
|
|
158
|
+
# criteria are the symbols of the other parameters.
|
|
159
|
+
#
|
|
160
|
+
# @param type [nil, String, #to_a]
|
|
161
|
+
# @param charset [nil, String, #to_a]
|
|
162
|
+
# @param encoding [nil, String, #to_a]
|
|
163
|
+
# @param language [nil, String, #to_a]
|
|
164
|
+
# @param size [nil, Integer, #to_a] byte size range
|
|
165
|
+
# @param ctime [nil, Time, DateTime, #to_a] creation time range
|
|
166
|
+
# @param mtime [nil, Time, DateTime, #to_a] modification time range
|
|
167
|
+
# @param ptime [nil, Time, DateTime, #to_a] medatata property change range
|
|
168
|
+
# @param dtime [nil, Time, DateTime, #to_a] deletion time range
|
|
169
|
+
# @param sort [nil, Symbol, #to_a] sorting criteria
|
|
170
|
+
#
|
|
171
|
+
# @return [Array] the list
|
|
172
|
+
#
|
|
173
|
+
def list type: nil, charset: nil, encoding: nil, language: nil,
|
|
174
|
+
size: nil, ctime: nil, mtime: nil, ptime: nil, dtime: nil, sort: nil
|
|
175
|
+
raise NotImplementedError
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
class CorruptStateError < RuntimeError
|
|
6
179
|
end
|
|
7
180
|
end
|
data/lib/store/digest/object.rb
CHANGED
|
@@ -3,23 +3,41 @@ require 'store/digest/version'
|
|
|
3
3
|
require 'uri'
|
|
4
4
|
require 'uri/ni'
|
|
5
5
|
require 'mimemagic'
|
|
6
|
-
require 'mimemagic/overlay'
|
|
7
6
|
|
|
8
7
|
class MimeMagic
|
|
9
8
|
# XXX erase this when these methods get added
|
|
10
|
-
unless
|
|
9
|
+
unless singleton_class.method_defined? :parents
|
|
11
10
|
def self.parents type
|
|
12
|
-
TYPES.fetch(type, [nil,[]])[1].map { |t| new t }.uniq
|
|
11
|
+
TYPES.fetch(type.to_s, [nil,[]])[1].map { |t| new t }.uniq
|
|
13
12
|
end
|
|
14
13
|
end
|
|
15
14
|
|
|
16
|
-
unless
|
|
17
|
-
def
|
|
18
|
-
|
|
15
|
+
unless method_defined? :parents
|
|
16
|
+
def parents
|
|
17
|
+
out = TYPES.fetch(type.to_s.downcase, [nil, []])[1].map do |x|
|
|
18
|
+
self.class.new x
|
|
19
|
+
end
|
|
20
|
+
# add this unless we're it
|
|
21
|
+
out << self.class.new('application/octet-stream') if
|
|
22
|
+
out.empty? and type.downcase != 'application/octet-stream'
|
|
23
|
+
|
|
24
|
+
out.uniq
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
unless method_defined? :lineage
|
|
29
|
+
def lineage
|
|
30
|
+
([self] + parents.map { |t| t.lineage }.flatten).uniq
|
|
19
31
|
end
|
|
20
32
|
end
|
|
21
33
|
|
|
22
|
-
unless
|
|
34
|
+
unless method_defined? :descendant_of?
|
|
35
|
+
def descendant_of? type
|
|
36
|
+
lineage.map(&:type).include? type.to_s.downcase
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
unless singleton_class.method_defined? :binary?
|
|
23
41
|
def self.binary? thing
|
|
24
42
|
sample = nil
|
|
25
43
|
|
|
@@ -42,15 +60,80 @@ class MimeMagic
|
|
|
42
60
|
end
|
|
43
61
|
end
|
|
44
62
|
|
|
45
|
-
unless
|
|
63
|
+
unless singleton_class.method_defined? :default_type
|
|
46
64
|
def self.default_type thing
|
|
47
65
|
new self.binary?(thing) ? 'application/octet-stream' : 'text/plain'
|
|
48
66
|
end
|
|
49
67
|
end
|
|
50
68
|
end
|
|
51
69
|
|
|
70
|
+
# Store entry object class.
|
|
71
|
+
#
|
|
52
72
|
class Store::Digest::Object
|
|
53
73
|
|
|
74
|
+
# These is a struct for the bank of flags, with a couple of extra
|
|
75
|
+
# methods for parsing
|
|
76
|
+
#
|
|
77
|
+
Flags = Struct.new(
|
|
78
|
+
'Flags',
|
|
79
|
+
:type_checked, :type_valid, :charset_checked, :charset_valid,
|
|
80
|
+
:encoding_checked, :encoding_valid, :syntax_checked, :syntax_valid, :cache
|
|
81
|
+
) do |name|
|
|
82
|
+
|
|
83
|
+
# Initialize a struct of flags from arbitrary input
|
|
84
|
+
#
|
|
85
|
+
# @param arg [Store::Digest::Object::Flags, Integer, #to_h, #to_a]
|
|
86
|
+
#
|
|
87
|
+
# @return [Store::Digest::Object::Flags]
|
|
88
|
+
#
|
|
89
|
+
def self.from arg
|
|
90
|
+
# get the length since we use it in a few places
|
|
91
|
+
len = self.members.size
|
|
92
|
+
|
|
93
|
+
if arg.is_a? Integer
|
|
94
|
+
tmp = arg.digits(2).first(len)
|
|
95
|
+
elsif arg.is_a? self
|
|
96
|
+
# noop
|
|
97
|
+
return arg
|
|
98
|
+
elsif arg.is_a? Hash
|
|
99
|
+
tmp = arg.slice(*self.members).transform_values do |v|
|
|
100
|
+
!!(v && v != 0)
|
|
101
|
+
end
|
|
102
|
+
return self.[](**tmp)
|
|
103
|
+
elsif arg.respond_to? :to_a
|
|
104
|
+
tmp = arg.to_a.first(len)
|
|
105
|
+
else
|
|
106
|
+
raise ArgumentError, 'Input must be an integer or array'
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# append these
|
|
110
|
+
tmp += [false] * (len - tmp.size) if tmp.size < len
|
|
111
|
+
|
|
112
|
+
# make sure these are true/false
|
|
113
|
+
tmp.map! { |b| !!(b && b != 0) }
|
|
114
|
+
|
|
115
|
+
# we do this because `new` doesn't do this
|
|
116
|
+
self.[](*tmp)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Turn an arbitrary {Array} back into an {Integer}.
|
|
120
|
+
#
|
|
121
|
+
# @param array [Array]
|
|
122
|
+
#
|
|
123
|
+
# @return [Integer]
|
|
124
|
+
#
|
|
125
|
+
def self.to_i array
|
|
126
|
+
array.to_a.reverse.reduce(0) { |acc, b| (acc << 1) | (b ? 1 : 0) }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# wish there was a cleaner way to do derive individual instance
|
|
130
|
+
# methods from class methods
|
|
131
|
+
begin
|
|
132
|
+
cm = self.method :to_i
|
|
133
|
+
define_method(:to_i) { cm.call self.to_a }
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
54
137
|
private
|
|
55
138
|
|
|
56
139
|
SAMPLE = 2**13 # must be big enough to detect ooxml
|
|
@@ -88,13 +171,14 @@ class Store::Digest::Object
|
|
|
88
171
|
ENCODING_VALID = 1 << 5
|
|
89
172
|
SYNTAX_CHECKED = 1 << 6
|
|
90
173
|
SYNTAX_VALID = 1 << 7
|
|
174
|
+
IS_CACHE = 1 << 8
|
|
91
175
|
|
|
92
176
|
LABELS = {
|
|
93
177
|
size: 'Size (Bytes)',
|
|
94
178
|
ctime: 'Added to Store',
|
|
95
179
|
mtime: 'Last Modified',
|
|
96
180
|
ptime: 'Properties Modified',
|
|
97
|
-
dtime: 'Deleted',
|
|
181
|
+
dtime: 'Deleted (Expires)',
|
|
98
182
|
type: 'Content Type',
|
|
99
183
|
language: '(Natural) Language',
|
|
100
184
|
charset: 'Character Set',
|
|
@@ -132,7 +216,7 @@ class Store::Digest::Object
|
|
|
132
216
|
|
|
133
217
|
# Create a new object, naively recording whatever is handed
|
|
134
218
|
#
|
|
135
|
-
# @note use {.scan} or {#scan} to populate
|
|
219
|
+
# @note use {.scan} or {#scan} to populate
|
|
136
220
|
#
|
|
137
221
|
# @param content [IO, String, Proc, File, Pathname, ...] some content
|
|
138
222
|
# @param digests [Hash] the digests ascribed to the content
|
|
@@ -148,7 +232,9 @@ class Store::Digest::Object
|
|
|
148
232
|
# @param flags [Integer] validation state flags
|
|
149
233
|
# @param strict [true, false] raise an error on bad input
|
|
150
234
|
# @param fresh [true, false] assert "freshness" of object vis-a-vis the store
|
|
235
|
+
#
|
|
151
236
|
# @return [Store::Digest::Object] the object in question
|
|
237
|
+
#
|
|
152
238
|
def initialize content = nil, digests: {}, size: 0,
|
|
153
239
|
type: 'application/octet-stream', charset: nil, language: nil,
|
|
154
240
|
encoding: nil, ctime: nil, mtime: nil, ptime: nil, dtime: nil,
|
|
@@ -211,19 +297,17 @@ class Store::Digest::Object
|
|
|
211
297
|
instance_variable_set "@#{k}", v
|
|
212
298
|
end
|
|
213
299
|
|
|
214
|
-
#
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
instance_variable_set "@#{k}", v
|
|
226
|
-
end
|
|
300
|
+
# set the flags
|
|
301
|
+
@flags = Flags.from(flags || 0)
|
|
302
|
+
|
|
303
|
+
@size = case size
|
|
304
|
+
when nil then 0
|
|
305
|
+
when Numeric
|
|
306
|
+
raise ArgumentError, 'size must be non-negative' if size < 0
|
|
307
|
+
size.to_i
|
|
308
|
+
else
|
|
309
|
+
raise ArgumentError, 'size must be nil or Numeric'
|
|
310
|
+
end
|
|
227
311
|
|
|
228
312
|
# the following can be strings or symbols:
|
|
229
313
|
TOKENS.keys.each do |k|
|
|
@@ -274,7 +358,7 @@ class Store::Digest::Object
|
|
|
274
358
|
|
|
275
359
|
# sane default for mtime
|
|
276
360
|
@mtime = coerce_time(mtime || @mtime ||
|
|
277
|
-
(content.respond_to?(:mtime) ? content.mtime : Time.now), :mtime)
|
|
361
|
+
(content.respond_to?(:mtime) ? content.mtime : Time.now(in: ?Z)), :mtime)
|
|
278
362
|
|
|
279
363
|
# eh, *some* code reuse
|
|
280
364
|
b = binding
|
|
@@ -321,15 +405,21 @@ class Store::Digest::Object
|
|
|
321
405
|
[k, URI::NI.compute(v, algorithm: k).freeze]
|
|
322
406
|
end.to_h.freeze
|
|
323
407
|
|
|
408
|
+
# ensure there is the most generic of possible types
|
|
409
|
+
type ||= 'application/octet-stream'.freeze
|
|
410
|
+
|
|
324
411
|
# obtain the sampled content type
|
|
325
412
|
ts = MimeMagic.by_magic(sample) || MimeMagic.default_type(sample)
|
|
326
413
|
if content.respond_to? :path
|
|
327
414
|
# may as well use the path if it's available and more specific
|
|
328
|
-
ps = MimeMagic.by_path(content.path)
|
|
415
|
+
ps = MimeMagic.by_path(content.path.to_s)
|
|
329
416
|
# XXX the need to do ts.to_s is a bug in mimemagic
|
|
330
|
-
ts = ps if ps and ps.
|
|
417
|
+
ts = ps if ps and ps.descendant_of?(ts.to_s)
|
|
331
418
|
end
|
|
332
|
-
|
|
419
|
+
|
|
420
|
+
# set the type to ts if it is more specific
|
|
421
|
+
@type = ts.descendant_of?(type.to_s) ? ts.to_s.freeze :
|
|
422
|
+
type.to_s.dup.downcase.freeze
|
|
333
423
|
|
|
334
424
|
self
|
|
335
425
|
end
|
|
@@ -337,9 +427,14 @@ class Store::Digest::Object
|
|
|
337
427
|
# Determine (or set) whether the object is "fresh", i.e. whether it
|
|
338
428
|
# is new (or restored), or had been previously been in the store.
|
|
339
429
|
#
|
|
340
|
-
# @
|
|
341
|
-
|
|
342
|
-
|
|
430
|
+
# @return [true, false]
|
|
431
|
+
#
|
|
432
|
+
def fresh?
|
|
433
|
+
!!@fresh
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
def fresh= state
|
|
437
|
+
@fresh = !!state
|
|
343
438
|
end
|
|
344
439
|
|
|
345
440
|
# Return the algorithms used in the object.
|
|
@@ -385,53 +480,63 @@ class Store::Digest::Object
|
|
|
385
480
|
!@digests.empty?
|
|
386
481
|
end
|
|
387
482
|
|
|
483
|
+
# Returns whether the object is cache.
|
|
484
|
+
#
|
|
485
|
+
# @return [false, true]
|
|
486
|
+
#
|
|
487
|
+
def cache?
|
|
488
|
+
!!@flags.cache
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
# XXX i'm keeping these as-is for now
|
|
492
|
+
|
|
388
493
|
# Returns true if the content type has been checked.
|
|
389
494
|
# @return [false, true]
|
|
390
495
|
def type_checked?
|
|
391
|
-
0 != @flags & TYPE_CHECKED
|
|
496
|
+
0 != @flags.to_i & TYPE_CHECKED
|
|
392
497
|
end
|
|
393
498
|
|
|
394
499
|
# Returns true if the content type has been checked _and_ is valid.
|
|
395
500
|
# @return [false, true]
|
|
396
501
|
def type_valid?
|
|
397
|
-
0 != @flags & (TYPE_CHECKED|TYPE_VALID)
|
|
502
|
+
0 != @flags.to_i & (TYPE_CHECKED|TYPE_VALID)
|
|
398
503
|
end
|
|
399
504
|
|
|
400
505
|
# Returns true if the character set has been checked.
|
|
401
506
|
# @return [false, true]
|
|
402
507
|
def charset_checked?
|
|
403
|
-
0 != @flags & CHARSET_CHECKED
|
|
508
|
+
0 != @flags.to_i & CHARSET_CHECKED
|
|
404
509
|
end
|
|
405
510
|
|
|
406
511
|
# Returns true if the character set has been checked _and_ is valid.
|
|
407
512
|
# @return [false, true]
|
|
408
513
|
def charset_valid?
|
|
409
|
-
0 != @flags & (CHARSET_CHECKED|CHARSET_VALID)
|
|
514
|
+
0 != @flags.to_i & (CHARSET_CHECKED|CHARSET_VALID)
|
|
410
515
|
end
|
|
411
516
|
|
|
412
517
|
# Returns true if the content encoding (e.g. gzip, deflate) has
|
|
413
518
|
# been checked.
|
|
414
519
|
# @return [false, true]
|
|
415
520
|
def encoding_checked?
|
|
416
|
-
0 != @flags & ENCODING_CHECKED
|
|
521
|
+
0 != @flags.to_i & ENCODING_CHECKED
|
|
417
522
|
end
|
|
418
523
|
|
|
419
524
|
# Returns true if the content encoding has been checked _and_ is valid.
|
|
420
525
|
# @return [false, true]
|
|
421
526
|
def encoding_valid?
|
|
422
|
-
0 != @flags & (ENCODING_CHECKED|ENCODING_VALID)
|
|
527
|
+
0 != @flags.to_i & (ENCODING_CHECKED|ENCODING_VALID)
|
|
423
528
|
end
|
|
424
529
|
|
|
425
530
|
# Returns true if the blob's syntax has been checked.
|
|
426
531
|
# @return [false, true]
|
|
427
532
|
def syntax_checked?
|
|
428
|
-
0 != @flags & SYNTAX_CHECKED
|
|
533
|
+
0 != @flags.to_i & SYNTAX_CHECKED
|
|
429
534
|
end
|
|
430
535
|
|
|
431
536
|
# Returns true if the blob's syntax has been checked _and_ is valid.
|
|
432
537
|
# @return [false, true]
|
|
433
538
|
def syntax_valid?
|
|
434
|
-
0 != @flags & (SYNTAX_CHECKED|SYNTAX_VALID)
|
|
539
|
+
0 != @flags.to_i & (SYNTAX_CHECKED|SYNTAX_VALID)
|
|
435
540
|
end
|
|
436
541
|
|
|
437
542
|
%i[ctime mtime ptime dtime].each do |k|
|
|
@@ -488,7 +593,7 @@ class Store::Digest::Object
|
|
|
488
593
|
# now the validation statuses
|
|
489
594
|
out << "Validation:\n"
|
|
490
595
|
FLAG.each_index do |i|
|
|
491
|
-
x = flags >> (3 - i) & 3
|
|
596
|
+
x = flags.to_i >> (3 - i) & 3
|
|
492
597
|
out << (" %-16s: %s\n" % [FLAG[i], STATE[x]])
|
|
493
598
|
end
|
|
494
599
|
|
data/lib/store/digest/version.rb
CHANGED
data/lib/store/digest.rb
CHANGED
|
@@ -67,7 +67,7 @@ class Store::Digest
|
|
|
67
67
|
|
|
68
68
|
extend driver
|
|
69
69
|
|
|
70
|
-
#
|
|
70
|
+
#
|
|
71
71
|
setup(**options)
|
|
72
72
|
end
|
|
73
73
|
|
|
@@ -79,8 +79,8 @@ class Store::Digest
|
|
|
79
79
|
|
|
80
80
|
# alias_method :inspect, :to_s
|
|
81
81
|
|
|
82
|
-
# Add an object to the store. Takes pretty much anything that
|
|
83
|
-
#
|
|
82
|
+
# Add an object to the store. Takes pretty much anything that makes
|
|
83
|
+
# sense to throw at it.
|
|
84
84
|
#
|
|
85
85
|
# @note Prefabricated {Store::Digest::Object} instances will be
|
|
86
86
|
# rescanned.
|
|
@@ -96,15 +96,19 @@ class Store::Digest
|
|
|
96
96
|
# @param mtime [Time] the modification time, if not "now"
|
|
97
97
|
# @param strict [true, false] strict checking on metadata input
|
|
98
98
|
# @param preserve [false, true] preserve existing modification time
|
|
99
|
+
#
|
|
99
100
|
# @return [Store::Digest::Object] The (potentially pre-existing) entry
|
|
101
|
+
#
|
|
100
102
|
def add obj, type: nil, charset: nil, language: nil, encoding: nil,
|
|
101
103
|
mtime: nil, strict: true, preserve: false
|
|
102
104
|
return unless obj
|
|
103
|
-
|
|
105
|
+
|
|
106
|
+
transaction do # |txn|
|
|
104
107
|
obj = coerce_object obj, type: type, charset: charset,
|
|
105
108
|
language: language, encoding: encoding, mtime: mtime, strict: strict
|
|
106
109
|
raise ArgumentError, 'We need something to store!' unless obj.content?
|
|
107
110
|
|
|
111
|
+
# this method is helicoptered in
|
|
108
112
|
tmp = temp_blob
|
|
109
113
|
|
|
110
114
|
# XXX this is stupid; figure out a better way to do this
|
|
@@ -121,6 +125,7 @@ class Store::Digest
|
|
|
121
125
|
|
|
122
126
|
# set_meta will return nil if there is no difference in what is set
|
|
123
127
|
if h = set_meta(obj, preserve: preserve)
|
|
128
|
+
# warn h.inspect
|
|
124
129
|
# replace the object
|
|
125
130
|
|
|
126
131
|
content = obj.content
|
|
@@ -135,30 +140,34 @@ class Store::Digest
|
|
|
135
140
|
|
|
136
141
|
# now settle the blob into storage
|
|
137
142
|
settle_blob obj[primary].digest, tmp, mtime: obj.mtime
|
|
138
|
-
#txn.commit
|
|
139
143
|
else
|
|
140
144
|
tmp.close
|
|
141
145
|
tmp.unlink
|
|
142
146
|
|
|
147
|
+
# warn "got here lolol"
|
|
148
|
+
|
|
143
149
|
# eh just do this
|
|
144
150
|
obj = get obj
|
|
145
|
-
obj.fresh
|
|
151
|
+
obj.fresh = false # object is not fresh since we already have it
|
|
146
152
|
end
|
|
147
153
|
|
|
148
154
|
obj
|
|
149
|
-
|
|
155
|
+
end
|
|
150
156
|
end
|
|
151
157
|
|
|
152
158
|
# Retrieve an object from the store.
|
|
153
|
-
#
|
|
159
|
+
#
|
|
160
|
+
# @param obj [URI, Store::Digest::Object]
|
|
161
|
+
#
|
|
162
|
+
# @return [Store::Digest::Object, nil]
|
|
154
163
|
def get obj
|
|
155
|
-
|
|
164
|
+
transaction readonly: true do
|
|
156
165
|
obj = coerce_object obj
|
|
157
|
-
h = get_meta(obj)
|
|
158
|
-
|
|
159
|
-
|
|
166
|
+
if h = get_meta(obj) # bail if this does not exist
|
|
167
|
+
b = get_blob h[:digests][primary].digest # may be nil
|
|
168
|
+
Store::Digest::Object.new b, **h
|
|
169
|
+
end
|
|
160
170
|
end
|
|
161
|
-
transaction(&body)
|
|
162
171
|
end
|
|
163
172
|
|
|
164
173
|
# Remove an object from the store, optionally "forgetting" it ever existed.
|
|
@@ -171,23 +180,19 @@ class Store::Digest
|
|
|
171
180
|
obj.scan digests: algorithms, blocksize: 2**20
|
|
172
181
|
end
|
|
173
182
|
|
|
174
|
-
# remove
|
|
175
|
-
meta = nil
|
|
183
|
+
# remove or mark metadata entry as deleted and remove blob
|
|
176
184
|
transaction do
|
|
177
|
-
meta = forget ? remove_meta(obj) : mark_meta_deleted(obj)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
if blob = remove_blob(meta[:digests][primary].digest)
|
|
182
|
-
return Store::Digest::Object.new blob, **meta
|
|
185
|
+
if meta = forget ? remove_meta(obj) : mark_meta_deleted(obj)
|
|
186
|
+
if blob = remove_blob(meta[:digests][primary].digest)
|
|
187
|
+
Store::Digest::Object.new blob, **meta
|
|
188
|
+
end
|
|
183
189
|
end
|
|
184
190
|
end
|
|
185
|
-
nil
|
|
186
191
|
end
|
|
187
192
|
|
|
188
193
|
# Remove an object from the store and "forget" it ever existed,
|
|
189
194
|
# i.e., purge it from the metadata.
|
|
190
|
-
#
|
|
195
|
+
#
|
|
191
196
|
def forget obj
|
|
192
197
|
remove obj, forget: true
|
|
193
198
|
end
|
data/store-digest.gemspec
CHANGED
|
@@ -11,12 +11,12 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.license = 'Apache-2.0'
|
|
12
12
|
spec.homepage = 'https://github.com/doriantaylor/rb-store-digest'
|
|
13
13
|
spec.summary = 'Lightweight, multi-digest content-addressable store'
|
|
14
|
-
spec.description =
|
|
14
|
+
spec.description = <<~DESC
|
|
15
15
|
DESC
|
|
16
16
|
|
|
17
17
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
|
18
18
|
`git ls-files -z`.split("\x0").reject do |f|
|
|
19
|
-
f.match(%r{^(test|spec|features)/})
|
|
19
|
+
f.match(%r{^(test|spec|features)/})
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
spec.bindir = 'exe'
|
|
@@ -24,16 +24,18 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
spec.require_paths = ['lib']
|
|
25
25
|
|
|
26
26
|
# ruby
|
|
27
|
-
spec.required_ruby_version = '
|
|
27
|
+
spec.required_ruby_version = '>= 3.0'
|
|
28
28
|
|
|
29
29
|
# dev/test dependencies
|
|
30
|
-
spec.add_development_dependency 'bundler', '
|
|
31
|
-
spec.add_development_dependency 'rake', '
|
|
32
|
-
spec.add_development_dependency 'rspec', '
|
|
30
|
+
spec.add_development_dependency 'bundler', '>= 2.1'
|
|
31
|
+
spec.add_development_dependency 'rake', '>= 13.0'
|
|
32
|
+
spec.add_development_dependency 'rspec', '>= 3.9'
|
|
33
33
|
|
|
34
34
|
# stuff we use
|
|
35
|
-
spec.add_runtime_dependency '
|
|
36
|
-
spec.add_runtime_dependency '
|
|
37
|
-
spec.add_runtime_dependency '
|
|
35
|
+
spec.add_runtime_dependency 'base64', '~> 0.3' # stop it complaining
|
|
36
|
+
spec.add_runtime_dependency 'base32', '~> 0.3', '>= 0.3.2'
|
|
37
|
+
spec.add_runtime_dependency 'lmdb', '~> 0.7', '>= 0.7.1' # my hacks
|
|
38
|
+
# spec.add_runtime_dependency 'mimemagic', '>= 0.4.3', '< 0.5'
|
|
39
|
+
spec.add_runtime_dependency 'mimemagic', '>= 0.4.3'
|
|
38
40
|
spec.add_runtime_dependency 'uri-ni', '>= 0.1.4'
|
|
39
41
|
end
|