ocfl-tools 0.9.14 → 0.9.15
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/VERSION +1 -1
- data/lib/ocfl_tools/ocfl_errors.rb +59 -9
- data/lib/ocfl_tools/ocfl_inventory.rb +22 -9
- data/lib/ocfl_tools/ocfl_object.rb +17 -13
- data/lib/ocfl_tools/ocfl_validator.rb +75 -59
- data/lib/ocfl_tools/utils_file.rb +29 -12
- data/lib/ocfl_tools/utils_inventory.rb +1 -1
- data/ocfl-tools.gemspec +1 -1
- metadata +9 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 817dfd488b3cb6f05522175c68cba6385c76007950dc03af3162579517a1a653
|
|
4
|
+
data.tar.gz: 402eb44681f151b91e1403ed5fa289c632f62480c1f3199f7e8f468ca6f4e567
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a8a98f2d7bfab21babf2103857d970ad8d8e6e2cb1b6131b39f654568f161055ab4b4c567c2360d0d772bf709bb791d8125df0eff161de07eafec04d1c2fdffa
|
|
7
|
+
data.tar.gz: a8e692758b075775d124939bb533761b56f03b5b5d79a2ed3f916105f61b2361c1cd024f49886d554fd2578ac7e768c155c703ff85238b399a6b204047782476
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.9.
|
|
1
|
+
0.9.15
|
|
@@ -1,21 +1,71 @@
|
|
|
1
1
|
module OcflTools
|
|
2
2
|
module Errors
|
|
3
|
+
# See this for a nice model.
|
|
4
|
+
# https://github.com/ryanb/cancan/blob/master/lib/cancan/exceptions.rb
|
|
5
|
+
class ValidationError < StandardError
|
|
6
|
+
attr_accessor :details
|
|
7
|
+
attr_accessor :msg
|
|
8
|
+
def initialize(msg: "A validation error has occured.", details: {} )
|
|
9
|
+
@msg = msg
|
|
10
|
+
@details = details
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# For bad client requests
|
|
15
|
+
class ClientError < StandardError; end
|
|
16
|
+
|
|
17
|
+
class SyntaxError < StandardError
|
|
18
|
+
def initialize(msg="Generic syntax error.")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class UnableToLoadInventoryFile < StandardError
|
|
23
|
+
def initialize(msg="Requested inventory file failed to load. See downstream errors for details.")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
### Client errors (you asked for the wrong stuff)
|
|
28
|
+
class RequestedKeyNotFound < ClientError
|
|
29
|
+
# You ask for key 'foo', but you are dumb and key 'foo' is not in the spec.
|
|
30
|
+
def initialize(msg="Requested key not found in provided inventory.json.")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class RequestedFileNotFound < ClientError
|
|
35
|
+
def initialize(msg="Requested file does not exist.")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class RequestedDirectoryNotFound < ClientError
|
|
40
|
+
def initialize(msg="Requested directory does not exist.")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class FileMissingFromVersionState < ClientError
|
|
45
|
+
def initialize(msg="The requested file cannot be found in the provided version state block.")
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class FileDigestMismatch < ClientError
|
|
50
|
+
def initialize(msg="The requested file already exists in inventory with different digest.")
|
|
51
|
+
end
|
|
52
|
+
end
|
|
3
53
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
54
|
+
# You asked for version -1, or version 44c.
|
|
55
|
+
class NonCompliantValue < ClientError
|
|
56
|
+
def initialize(msg="Value provided is outside of specification bounds.")
|
|
7
57
|
end
|
|
8
58
|
end
|
|
9
59
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
60
|
+
### Validation errors (stuff that MUST be true, per spec, is not)
|
|
61
|
+
class RequiredKeyNotFound < ValidationError
|
|
62
|
+
# key 'foo' is in the spec and should be in the inventory. Fail if not.
|
|
63
|
+
def initialize(msg="Required key not found in provided inventory.json.")
|
|
13
64
|
end
|
|
14
65
|
end
|
|
15
66
|
|
|
16
|
-
class
|
|
17
|
-
def initialize(msg="
|
|
18
|
-
super
|
|
67
|
+
class CannotEditPreviousVersion < ValidationError
|
|
68
|
+
def initialize(msg="Previous version state blocks are considered read-only.")
|
|
19
69
|
end
|
|
20
70
|
end
|
|
21
71
|
|
|
@@ -54,9 +54,13 @@ module OcflTools
|
|
|
54
54
|
begin
|
|
55
55
|
JSON.parse(File.read(file))
|
|
56
56
|
rescue JSON::ParserError
|
|
57
|
-
raise OcflTools::Errors::
|
|
58
|
-
rescue
|
|
59
|
-
raise "
|
|
57
|
+
raise OcflTools::Errors::ValidationError, details: { "E211" => ["#{file} is not valid JSON."] }
|
|
58
|
+
rescue Errno::ENOENT
|
|
59
|
+
# raise OcflTools::Errors::Error215, "expected inventory file #{file} not found!"
|
|
60
|
+
raise OcflTools::Errors::ValidationError, details: { "E215" => ["expected inventory file #{file} not found!"] }
|
|
61
|
+
# rescue Errno::EACCES Don't think we need to explicitly raise file permissions; let StdErr take it.
|
|
62
|
+
rescue StandardError => e
|
|
63
|
+
raise "An unknown error occured reading file #{file}: #{e}" # catch/encapsulate any FileIO issues
|
|
60
64
|
end
|
|
61
65
|
end
|
|
62
66
|
|
|
@@ -67,22 +71,31 @@ module OcflTools
|
|
|
67
71
|
import_hash = read_json(file)
|
|
68
72
|
|
|
69
73
|
# REQUIRED keys; raise exception if not found.
|
|
74
|
+
e216_errors = []
|
|
75
|
+
e217_errors = []
|
|
76
|
+
error_hash = {}
|
|
70
77
|
[ 'id', 'head', 'type', 'digestAlgorithm', 'manifest', 'versions' ].each do | key |
|
|
71
78
|
unless import_hash.key?(key)
|
|
72
|
-
|
|
79
|
+
e216_errors << "Required key #{key} not found in #{file}"
|
|
80
|
+
error_hash["E216"] = e216_errors # we'll keep updating this value as new errors are recorded.
|
|
73
81
|
end
|
|
74
|
-
if import_hash[key].empty?
|
|
75
|
-
|
|
82
|
+
if import_hash.key?(key) && import_hash[key].empty?
|
|
83
|
+
# If the key exists but it's empty, that's also a problem!
|
|
84
|
+
e217_errors << "Required key #{key} in #{file} must contain a value"
|
|
85
|
+
error_hash["E217"] = e217_errors
|
|
76
86
|
end
|
|
77
87
|
end
|
|
88
|
+
# Raise a problem if we have anything in error_hash.
|
|
89
|
+
if error_hash.size > 0
|
|
90
|
+
raise OcflTools::Errors::ValidationError, details: error_hash
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# The actual values of these keys may be hot garbage, but it's not ocfl_inventory's place to check.
|
|
78
94
|
|
|
79
95
|
@id = import_hash['id']
|
|
80
96
|
@head = import_hash['head']
|
|
81
97
|
@type = import_hash['type']
|
|
82
98
|
@digestAlgorithm = import_hash['digestAlgorithm']
|
|
83
|
-
# if import_hash.key?('contentDirectory')
|
|
84
|
-
# @contentDirectory = import_hash['contentDirectory']
|
|
85
|
-
# end
|
|
86
99
|
@manifest = import_hash['manifest']
|
|
87
100
|
@versions = import_hash['versions']
|
|
88
101
|
# Optional keys - contentDirectory and fixity block.
|
|
@@ -52,7 +52,7 @@ module OcflTools
|
|
|
52
52
|
# @note will raise an exception if you attempt to query a non-existent version.
|
|
53
53
|
def set_version_message(version, message)
|
|
54
54
|
unless @versions.key?(OcflTools::Utils.version_int_to_string(version))
|
|
55
|
-
raise "Version #{version} does not yet exist!"
|
|
55
|
+
raise OcflTools::Errors::RequestedKeyNotFound, "Version #{version} does not yet exist!"
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
@versions[OcflTools::Utils.version_int_to_string(version)]['message'] = message
|
|
@@ -64,7 +64,7 @@ module OcflTools
|
|
|
64
64
|
# @note will raise an exception if you attempt to query a non-existent version.
|
|
65
65
|
def get_version_message(version)
|
|
66
66
|
unless @versions.key?(OcflTools::Utils.version_int_to_string(version))
|
|
67
|
-
raise "Version #{version} does not yet exist!"
|
|
67
|
+
raise OcflTools::Errors::RequestedKeyNotFound, "Version #{version} does not yet exist!"
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
@versions[OcflTools::Utils.version_int_to_string(version)]['message']
|
|
@@ -76,7 +76,7 @@ module OcflTools
|
|
|
76
76
|
# @note will raise an exception if you attempt to query a non-existent version.
|
|
77
77
|
def set_version_created(version, created)
|
|
78
78
|
unless @versions.key?(OcflTools::Utils.version_int_to_string(version))
|
|
79
|
-
raise "Version #{version} does not yet exist!"
|
|
79
|
+
raise OcflTools::Errors::RequestedKeyNotFound, "Version #{version} does not yet exist!"
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
@versions[OcflTools::Utils.version_int_to_string(version)]['created'] = created
|
|
@@ -88,7 +88,7 @@ module OcflTools
|
|
|
88
88
|
# @note will raise an exception if you attempt to query a non-existent version.
|
|
89
89
|
def get_version_created(version)
|
|
90
90
|
unless @versions.key?(OcflTools::Utils.version_int_to_string(version))
|
|
91
|
-
raise "Version #{version} does not yet exist!"
|
|
91
|
+
raise OcflTools::Errors::RequestedKeyNotFound, "Version #{version} does not yet exist!"
|
|
92
92
|
end
|
|
93
93
|
|
|
94
94
|
@versions[OcflTools::Utils.version_int_to_string(version)]['created']
|
|
@@ -100,7 +100,7 @@ module OcflTools
|
|
|
100
100
|
# @note will raise an exception if you attempt to query a nonexistent version.
|
|
101
101
|
def set_version_user(version, user)
|
|
102
102
|
unless @versions.key?(OcflTools::Utils.version_int_to_string(version))
|
|
103
|
-
raise "Version #{version} does not yet exist!"
|
|
103
|
+
raise OcflTools::Errors::RequestedKeyNotFound, "Version #{version} does not yet exist!"
|
|
104
104
|
end
|
|
105
105
|
|
|
106
106
|
@versions[OcflTools::Utils.version_int_to_string(version)]['user'] = user
|
|
@@ -112,7 +112,7 @@ module OcflTools
|
|
|
112
112
|
# @note will raise an exception if you attempt to query a nonexistent version.
|
|
113
113
|
def get_version_user(version)
|
|
114
114
|
unless @versions.key?(OcflTools::Utils.version_int_to_string(version))
|
|
115
|
-
raise "Version #{version} does not yet exist!"
|
|
115
|
+
raise OcflTools::Errors::RequestedKeyNotFound, "Version #{version} does not yet exist!"
|
|
116
116
|
end
|
|
117
117
|
|
|
118
118
|
@versions[OcflTools::Utils.version_int_to_string(version)]['user']
|
|
@@ -184,7 +184,7 @@ module OcflTools
|
|
|
184
184
|
my_state = get_state(version)
|
|
185
185
|
|
|
186
186
|
unless version == version_id_list.max
|
|
187
|
-
raise "Can't edit prior versions! Only version #{version_id_list.max} can be modified now."
|
|
187
|
+
raise OcflTools::Errors::CannotEditPreviousVersion, "Can't edit prior versions! Only version #{version_id_list.max} can be modified now."
|
|
188
188
|
end
|
|
189
189
|
|
|
190
190
|
# if the key is not in the manifest, assume that we meant to add it.
|
|
@@ -205,7 +205,7 @@ module OcflTools
|
|
|
205
205
|
# If so; fail. We don't do implicit / soft adds. You want that, be explict: do an update_file instead.
|
|
206
206
|
existing_files = get_files(version)
|
|
207
207
|
if existing_files.key?(file)
|
|
208
|
-
raise
|
|
208
|
+
raise OcflTools::Errors::FileDigestMismatch, "#{file} already exists with different digest in version #{version}. Consider update instead."
|
|
209
209
|
end
|
|
210
210
|
|
|
211
211
|
# if it's not in State already, just add it.
|
|
@@ -256,7 +256,7 @@ module OcflTools
|
|
|
256
256
|
# Does Digest exist in @manifest? Fail if not.
|
|
257
257
|
# Doe fixityAlgorithm exist as a key in @fixity? Add if not.
|
|
258
258
|
unless @manifest.key?(digest) == true
|
|
259
|
-
raise "Unable to find digest #{digest} in manifest!"
|
|
259
|
+
raise OcflTools::Errors::RequestedKeyNotFound, "Unable to find digest #{digest} in manifest!"
|
|
260
260
|
end
|
|
261
261
|
|
|
262
262
|
filepaths = @manifest[digest]
|
|
@@ -289,7 +289,7 @@ module OcflTools
|
|
|
289
289
|
my_state = get_state(version) # Creates version & copies state from prior version if doesn't exist.
|
|
290
290
|
|
|
291
291
|
unless version == version_id_list.max
|
|
292
|
-
raise "Can't edit prior versions! Only version #{
|
|
292
|
+
raise OcflTools::Errors::CannotEditPreviousVersion, "Can't edit prior versions! Only version #{version_id_list.max} can be modified now."
|
|
293
293
|
end
|
|
294
294
|
|
|
295
295
|
my_digest = get_digest(file, version)
|
|
@@ -364,7 +364,7 @@ module OcflTools
|
|
|
364
364
|
end
|
|
365
365
|
# Now see if the requested file is actually here.
|
|
366
366
|
unless my_files.key?(file)
|
|
367
|
-
raise "Get_digest can't find requested file in
|
|
367
|
+
raise OcflTools::Errors::FileMissingFromVersionState, "Get_digest can't find requested file #{file} in version #{version}."
|
|
368
368
|
end
|
|
369
369
|
|
|
370
370
|
my_files[file]
|
|
@@ -377,7 +377,7 @@ module OcflTools
|
|
|
377
377
|
# @note If a (n-1) version exists in the object, and the requested version does not yet exist, this method will copy that version's state block into the new version.
|
|
378
378
|
def get_version(version)
|
|
379
379
|
unless version > 0
|
|
380
|
-
raise "
|
|
380
|
+
raise OcflTools::Errors::NonCompliantValue, "Requested value '#{version}' for object version does not comply with specification."
|
|
381
381
|
end
|
|
382
382
|
if @versions.key?(OcflTools::Utils.version_int_to_string(version))
|
|
383
383
|
@versions[OcflTools::Utils.version_int_to_string(version)]
|
|
@@ -414,11 +414,15 @@ module OcflTools
|
|
|
414
414
|
# @param [Hash] hash use this hash for the content of the new OCFL version block.
|
|
415
415
|
def set_version(version, hash)
|
|
416
416
|
# SAN Check to make sure passed Hash has all expected keys.
|
|
417
|
+
e216_errors = []
|
|
417
418
|
%w[created message user state].each do |key|
|
|
418
419
|
if hash.key?(key) == false
|
|
419
|
-
|
|
420
|
+
e216_errors << "version #{version} hash block is missing required #{key} key."
|
|
420
421
|
end
|
|
421
422
|
end
|
|
423
|
+
if e216_errors.size > 0
|
|
424
|
+
raise OcflTools::Errors::ValidationError, details: { "E216" => e216_errors }
|
|
425
|
+
end
|
|
422
426
|
@versions[OcflTools::Utils.version_int_to_string(version)] = hash
|
|
423
427
|
end
|
|
424
428
|
end
|
|
@@ -63,7 +63,14 @@ module OcflTools
|
|
|
63
63
|
# @return {OcflTools::OcflResults} of event results
|
|
64
64
|
def verify_fixity(inventory_file: "#{@ocfl_object_root}/inventory.json", digest: 'md5')
|
|
65
65
|
# Gets the appropriate fixity block, calls compare_hash_checksums
|
|
66
|
-
|
|
66
|
+
|
|
67
|
+
begin
|
|
68
|
+
@inventory = load_inventory(inventory_file)
|
|
69
|
+
rescue OcflTools::Errors::ValidationError
|
|
70
|
+
@my_results.error('E210', 'verify_fixity', "Unable to process inventory file #{inventory_file}.")
|
|
71
|
+
return @my_results
|
|
72
|
+
end
|
|
73
|
+
|
|
67
74
|
# Since fixity blocks are not required to be complete, we just validate what's there.
|
|
68
75
|
# So get the fixity block, flip it, expand it, checksum it against the same files on disk.
|
|
69
76
|
|
|
@@ -121,9 +128,9 @@ module OcflTools
|
|
|
121
128
|
return @my_results
|
|
122
129
|
end
|
|
123
130
|
|
|
124
|
-
|
|
125
|
-
@inventory
|
|
126
|
-
|
|
131
|
+
begin
|
|
132
|
+
@inventory = load_inventory(inventory_file)
|
|
133
|
+
rescue OcflTools::Errors::ValidationError
|
|
127
134
|
@my_results.error('E210', 'verify_fixity', "Unable to process inventory file #{inventory_file}.")
|
|
128
135
|
return @my_results
|
|
129
136
|
end
|
|
@@ -260,9 +267,9 @@ module OcflTools
|
|
|
260
267
|
return @my_results
|
|
261
268
|
end
|
|
262
269
|
|
|
263
|
-
|
|
264
|
-
@inventory =
|
|
265
|
-
|
|
270
|
+
begin
|
|
271
|
+
@inventory = load_inventory(inventory_file)
|
|
272
|
+
rescue OcflTools::Errors::ValidationError
|
|
266
273
|
@my_results.error('E210', 'verify_checksums', "Unable to process inventory file #{inventory_file}.")
|
|
267
274
|
return @my_results
|
|
268
275
|
end
|
|
@@ -312,7 +319,8 @@ module OcflTools
|
|
|
312
319
|
# 1. use get_version_format to determine the format used for version directories.
|
|
313
320
|
# If we can't deduce it by inspection of the object_root, ERROR and try and process using site-wide defaults.
|
|
314
321
|
if get_version_format == false
|
|
315
|
-
error
|
|
322
|
+
@my_results.error('E111', 'verify_structure', 'OCFL unable to determine version format by inspection of directories.')
|
|
323
|
+
@error = true
|
|
316
324
|
end
|
|
317
325
|
|
|
318
326
|
object_root_dirs = []
|
|
@@ -339,12 +347,13 @@ module OcflTools
|
|
|
339
347
|
# 2b. What's the highest version we should find here?
|
|
340
348
|
# 2c. What should our contentDirectory value be?
|
|
341
349
|
if File.exist? "#{@ocfl_object_root}/inventory.json"
|
|
342
|
-
|
|
350
|
+
begin
|
|
351
|
+
@inventory = load_inventory("#{@ocfl_object_root}/inventory.json")
|
|
343
352
|
json_digest = OcflTools::Utils::Inventory.get_digestAlgorithm("#{@ocfl_object_root}/inventory.json")
|
|
344
353
|
contentDirectory = OcflTools::Utils::Inventory.get_contentDirectory("#{@ocfl_object_root}/inventory.json")
|
|
345
354
|
expect_head = OcflTools::Utils::Inventory.get_value("#{@ocfl_object_root}/inventory.json", 'head')
|
|
346
355
|
file_checks << "inventory.json.#{json_digest}"
|
|
347
|
-
|
|
356
|
+
rescue OcflTools::Errors::ValidationError
|
|
348
357
|
# We couldn't load up the inventory; use site defaults.
|
|
349
358
|
contentDirectory = OcflTools.config.content_directory
|
|
350
359
|
json_digest = OcflTools.config.digest_algorithm
|
|
@@ -444,7 +453,17 @@ module OcflTools
|
|
|
444
453
|
object_root_dirs.delete('extensions')
|
|
445
454
|
end
|
|
446
455
|
|
|
447
|
-
|
|
456
|
+
begin
|
|
457
|
+
version_directories = OcflTools::Utils::Files.get_version_directories(@ocfl_object_root)
|
|
458
|
+
rescue OcflTools::Errors::ValidationError => e
|
|
459
|
+
e.details.each do | code, messages |
|
|
460
|
+
messages.each do | msg |
|
|
461
|
+
@my_results.error(code, 'verify_structure', msg)
|
|
462
|
+
end
|
|
463
|
+
end
|
|
464
|
+
# If we actually throw a validation error, we can't proceed: no version directories found!
|
|
465
|
+
return @my_results
|
|
466
|
+
end
|
|
448
467
|
|
|
449
468
|
remaining_dirs = object_root_dirs - version_directories
|
|
450
469
|
|
|
@@ -466,7 +485,7 @@ module OcflTools
|
|
|
466
485
|
# just that they're valid version dir names, sorted in ascending order, and they exist.
|
|
467
486
|
if version_directories.include? expected_directory
|
|
468
487
|
# Could verbose log this here.
|
|
469
|
-
# @my_results.
|
|
488
|
+
# @my_results.info('I200', 'verify_sructure', "Expected version directory #{expected_directory} found.")
|
|
470
489
|
else
|
|
471
490
|
@my_results.error('E013', 'verify_structure', "Expected version directory #{expected_directory} missing from directory list #{version_directories} ")
|
|
472
491
|
error = true
|
|
@@ -499,7 +518,8 @@ module OcflTools
|
|
|
499
518
|
# 9. Warn if inventory.json and sidecar are not present in version directory.
|
|
500
519
|
file_checks = []
|
|
501
520
|
if File.exist? "#{@ocfl_object_root}/#{ver}/inventory.json"
|
|
502
|
-
|
|
521
|
+
begin
|
|
522
|
+
@inventory = load_inventory("#{@ocfl_object_root}/#{ver}/inventory.json")
|
|
503
523
|
json_digest = OcflTools::Utils::Inventory.get_digestAlgorithm("#{@ocfl_object_root}/#{ver}/inventory.json")
|
|
504
524
|
file_checks << 'inventory.json'
|
|
505
525
|
file_checks << "inventory.json.#{json_digest}"
|
|
@@ -508,12 +528,14 @@ module OcflTools
|
|
|
508
528
|
@my_results.error('E111', 'verify_structure', "contentDirectory value #{versionContentDirectory} in version #{ver} does not match expected contentDirectory value #{contentDirectory}.")
|
|
509
529
|
error = true
|
|
510
530
|
end
|
|
511
|
-
|
|
531
|
+
rescue OcflTools::Errors::ValidationError
|
|
532
|
+
# We couldn't load up the inventory; use site defaults.
|
|
533
|
+
# We should also record the error in @my_results?
|
|
512
534
|
json_digest = OcflTools.config.digest_algorithm
|
|
513
535
|
file_checks << 'inventory.json'
|
|
514
536
|
file_checks << "inventory.json.#{json_digest}"
|
|
537
|
+
error = true
|
|
515
538
|
end
|
|
516
|
-
|
|
517
539
|
else
|
|
518
540
|
file_checks << 'inventory.json' # We look for it, even though we know we won't find it, so we can log the omission.
|
|
519
541
|
file_checks << 'inventory.json.sha512' # We look for it, even though we know we won't find it, so we can log the omission.
|
|
@@ -557,12 +579,6 @@ module OcflTools
|
|
|
557
579
|
# 11. WARN if a contentDirectory exists, but is empty.
|
|
558
580
|
if version_dirs.include? contentDirectory
|
|
559
581
|
version_dirs.delete(contentDirectory)
|
|
560
|
-
# if Dir.empty?(contentDirectory)
|
|
561
|
-
# @my_results.warn('W102', 'verify_structure', "OCFL 3.3.1 version #{ver} contentDirectory should not be empty.")
|
|
562
|
-
# end
|
|
563
|
-
# else
|
|
564
|
-
# # Informational message that contentDir does not exist. Not necssarily a problem!
|
|
565
|
-
# @my_results.info('I101', 'verify_structure', "OCFL 3.3.1 version #{ver} does not contain a contentDirectory.")
|
|
566
582
|
end
|
|
567
583
|
|
|
568
584
|
# 12. Warn if any directories other than the expected 'content' directory are found in the version directory.
|
|
@@ -587,11 +603,8 @@ module OcflTools
|
|
|
587
603
|
# @return {OcflTools::OcflResults} of verify events
|
|
588
604
|
def verify_directory(version)
|
|
589
605
|
# start by getting version format and directories.
|
|
590
|
-
|
|
591
|
-
@version_format = OcflTools::Utils::Files.get_version_format(@ocfl_object_root)
|
|
592
|
-
end
|
|
606
|
+
get_version_format # sets @version_format, one way or another.
|
|
593
607
|
|
|
594
|
-
# result = OcflTools.config.version_format % version.to_i
|
|
595
608
|
version_name = @version_format % version.to_i
|
|
596
609
|
# Make sure this directory actually exists.
|
|
597
610
|
unless File.directory?("#{@ocfl_object_root}/#{version_name}")
|
|
@@ -653,44 +666,38 @@ module OcflTools
|
|
|
653
666
|
def verify_inventory(inventory_file = "#{@ocfl_object_root}/inventory.json")
|
|
654
667
|
# Load up the object with ocfl_inventory, push it through ocfl_verify.
|
|
655
668
|
@my_results ||= OcflTools::OcflResults.new
|
|
656
|
-
#
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
end
|
|
667
|
-
else
|
|
668
|
-
@my_results.error('E215', 'verify_inventory', "Expected inventory file #{inventory_file} not found.")
|
|
669
|
+
# If inventory_file does not exist, load_inventory will throw and log an E215.
|
|
670
|
+
begin
|
|
671
|
+
@inventory = load_inventory(inventory_file)
|
|
672
|
+
@inventory = OcflTools::OcflInventory.new.from_file(inventory_file)
|
|
673
|
+
@verify = OcflTools::OcflVerify.new(@inventory)
|
|
674
|
+
@verify.check_all # creates & returns @results object from OcflVerify
|
|
675
|
+
# This could be OcflTools::Errors::ValidationError now.
|
|
676
|
+
rescue OcflTools::Errors::ValidationError
|
|
677
|
+
# I don't think we need to throw this E210 any more.
|
|
678
|
+
@my_results.error('E210', 'verify_inventory', "Unable to process inventory file #{inventory_file}.")
|
|
669
679
|
return @my_results
|
|
670
680
|
end
|
|
671
681
|
end
|
|
672
682
|
|
|
673
683
|
private
|
|
674
684
|
# load up an inventory file and handle any errors.
|
|
675
|
-
# Returns
|
|
685
|
+
# Returns an inventory file is syntatically correct; false if otherwise.
|
|
676
686
|
def load_inventory(inventory_file)
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
@my_results.error('E217', 'load_inventory', "#{e} in #{inventory_file}")
|
|
692
|
-
return false
|
|
693
|
-
end
|
|
687
|
+
@my_results ||= OcflTools::OcflResults.new
|
|
688
|
+
OcflTools::OcflInventory.new.from_file(inventory_file)
|
|
689
|
+
# The generic 'something went wrong but I don't know what'; not sure if we should keep this.
|
|
690
|
+
rescue RuntimeError => e
|
|
691
|
+
@my_results.error('E210', 'load_inventory', "#{e}")
|
|
692
|
+
raise
|
|
693
|
+
rescue OcflTools::Errors::ValidationError => e
|
|
694
|
+
e.details.each do | code, messages |
|
|
695
|
+
# code is a string, messages is an array.
|
|
696
|
+
messages.each do | msg |
|
|
697
|
+
@my_results.error(code, 'load_inventory', msg)
|
|
698
|
+
end
|
|
699
|
+
end
|
|
700
|
+
raise # re-raise the error.
|
|
694
701
|
end
|
|
695
702
|
|
|
696
703
|
# Compares the state blocks for all versions across all inventories in the object,
|
|
@@ -784,13 +791,22 @@ module OcflTools
|
|
|
784
791
|
|
|
785
792
|
def get_version_format
|
|
786
793
|
begin
|
|
794
|
+
@my_results ||= OcflTools::OcflResults.new
|
|
787
795
|
@version_format ||= OcflTools::Utils::Files.get_version_format(@ocfl_object_root)
|
|
788
796
|
@my_results.ok('O111', 'version_format', 'OCFL conforming first version directory found.')
|
|
789
|
-
return
|
|
790
|
-
rescue
|
|
797
|
+
return @version_format
|
|
798
|
+
rescue OcflTools::Errors::ValidationError => e
|
|
799
|
+
# OcflTools::Utils::Files.get_version_format doesn't set errors, so capture them here.
|
|
800
|
+
e.details.each do | code, messages |
|
|
801
|
+
# code is a string, messages is an array.
|
|
802
|
+
messages.each do | msg |
|
|
803
|
+
@my_results.error(code, 'load_inventory', msg)
|
|
804
|
+
end
|
|
805
|
+
end
|
|
806
|
+
# Add on another error explaining how we got here.
|
|
791
807
|
@my_results.error('E111', 'version_format', 'OCFL unable to determine version format by inspection of directories.')
|
|
792
|
-
@version_format = OcflTools.config.version_format
|
|
793
808
|
@my_results.warn('W111', 'version_format', "Attempting to process using default value: #{OcflTools.config.version_format}")
|
|
809
|
+
@version_format = OcflTools.config.version_format
|
|
794
810
|
return false
|
|
795
811
|
end
|
|
796
812
|
end
|
|
@@ -52,7 +52,8 @@ module OcflTools
|
|
|
52
52
|
# Given an object root directory, deduce and return the version directories by inspecting disk.
|
|
53
53
|
def self.get_version_directories(object_root_dir)
|
|
54
54
|
unless Dir.exist?(object_root_dir) == true
|
|
55
|
-
|
|
55
|
+
# This is a client error.
|
|
56
|
+
raise OcflTools::Errors::RequestedDirectoryNotFound, "#{object_root_dir} does not exist!"
|
|
56
57
|
end
|
|
57
58
|
|
|
58
59
|
object_root_dirs = []
|
|
@@ -62,7 +63,8 @@ module OcflTools
|
|
|
62
63
|
object_root_dirs << file if File.directory? file
|
|
63
64
|
end
|
|
64
65
|
if object_root_dirs.empty?
|
|
65
|
-
|
|
66
|
+
# This is a validation error.
|
|
67
|
+
raise OcflTools::Errors::ValidationError, details: { "E100" => ["#{object_root_dir} is empty!"] }
|
|
66
68
|
end
|
|
67
69
|
|
|
68
70
|
# Needs to call get version_format method here.
|
|
@@ -71,15 +73,18 @@ module OcflTools
|
|
|
71
73
|
version_directories << i
|
|
72
74
|
end
|
|
73
75
|
end
|
|
74
|
-
|
|
75
|
-
|
|
76
|
+
# This is a validation error; we expect to find matching version directories.
|
|
77
|
+
if version_directories.empty?
|
|
78
|
+
raise OcflTools::Errors::ValidationError, details: { "E013" => ["#{object_root_dir} must contain at least one identifiable version directory."] }
|
|
79
|
+
end
|
|
76
80
|
version_directories.sort! # sort it, to be nice.
|
|
77
81
|
end
|
|
78
82
|
|
|
79
83
|
# Given an object_root_directory, deduce the format used to describe version directories.
|
|
80
84
|
def self.get_version_format(object_root_dir)
|
|
81
85
|
unless Dir.exist?(object_root_dir) == true
|
|
82
|
-
|
|
86
|
+
# This is a client error.
|
|
87
|
+
raise OcflTools::Errors::RequestedDirectoryNotFound, "#{object_root_dir} does not exist!"
|
|
83
88
|
end
|
|
84
89
|
|
|
85
90
|
# Get all directories starting with 'v', sort them.
|
|
@@ -98,14 +103,16 @@ module OcflTools
|
|
|
98
103
|
first_version.slice!(0, 1) # cut the leading 'v' from the string.
|
|
99
104
|
if first_version.length == 1 # A length of 1 for the first version implies 'v1'
|
|
100
105
|
unless first_version.to_i == 1
|
|
101
|
-
|
|
106
|
+
# This is a validation error; there must be a v1 directory.
|
|
107
|
+
# E015 "OCFL 3.5.3 Expected version sequence not found. Expected version #{count}, found version #{my_versions[count]}."
|
|
108
|
+
raise OcflTools::Errors::ValidationError, details: { "E015" => ["Expected version 1 not found. Found version #{first_version.to_i} instead."] }
|
|
102
109
|
end
|
|
103
110
|
|
|
104
111
|
version_format = 'v%d'
|
|
105
112
|
else
|
|
106
113
|
# Make sure this is Integer 1.
|
|
107
114
|
unless first_version.to_i == 1
|
|
108
|
-
raise
|
|
115
|
+
raise OcflTools::Errors::ValidationError, details: { "E015" => ["Expected version 1 not found. Found version #{first_version.to_i} instead."] }
|
|
109
116
|
end
|
|
110
117
|
|
|
111
118
|
version_format = "v%0#{first_version.length}d"
|
|
@@ -170,24 +177,34 @@ module OcflTools
|
|
|
170
177
|
# g_v_d returns a sorted array already. Reverse it, so we start with highest version.
|
|
171
178
|
my_versions = OcflTools::Utils::Files.get_version_directories(object_root_dir).reverse
|
|
172
179
|
case
|
|
180
|
+
# Return the inventory file in the highest version dir, if it exists.
|
|
173
181
|
when File.exist?("#{object_root_dir}/#{my_versions[0]}/inventory.json")
|
|
174
182
|
return "#{object_root_dir}/#{my_versions[0]}/inventory.json"
|
|
183
|
+
# Otherwise, return the inventory file in the root, if it exists.
|
|
175
184
|
when File.exist?("#{object_root_dir}/inventory.json")
|
|
176
185
|
return "#{object_root_dir}/inventory.json"
|
|
177
186
|
else
|
|
178
|
-
#
|
|
187
|
+
# We don't have a highest-version inventory, and we don't have a root inventory.
|
|
188
|
+
# This is a problem! But there might be an inventory file in a non-highest-version dir.
|
|
189
|
+
# Quit out here if there was only 1 version directory (We've already checked this)
|
|
179
190
|
unless my_versions.size > 1
|
|
180
|
-
|
|
191
|
+
# This is a validation error; no inventory files found.
|
|
192
|
+
raise OcflTools::Errors::ValidationError, details: { "E215" => ["OCFL 3.1 Expected inventory file not found in #{object_root_dir} or discovered version directories."] }
|
|
181
193
|
end
|
|
182
194
|
|
|
183
|
-
my_versions.delete_at(0) # drop the first element.
|
|
195
|
+
my_versions.delete_at(0) # drop the first element (we've already checked it).
|
|
184
196
|
my_versions.each do |v|
|
|
197
|
+
# Return the highest version inventory file we find.
|
|
198
|
+
# Note, this is technically a non-compliant object root, but we
|
|
199
|
+
# want to return a result if we have one - we might be attempting object
|
|
200
|
+
# recovery and something is better than nothing.
|
|
185
201
|
if File.exist?("#{object_root_dir}/#{v}/inventory.json")
|
|
186
202
|
return "#{object_root_dir}/#{v}/inventory.json"
|
|
187
203
|
end
|
|
188
204
|
end
|
|
189
|
-
# If we get here, no inventory file found
|
|
190
|
-
|
|
205
|
+
# If we get here, no inventory file found in any version dirs or the object root.
|
|
206
|
+
# This is a validation error.
|
|
207
|
+
raise OcflTools::Errors::ValidationError, details: { "E215" => ["OCFL 3.1 Expected inventory file not found in #{object_root_dir} or discovered version directories."] }
|
|
191
208
|
end
|
|
192
209
|
end
|
|
193
210
|
end
|
|
@@ -12,7 +12,7 @@ module OcflTools
|
|
|
12
12
|
# @return [String or nil] the value of the requested key, or nil if not found.
|
|
13
13
|
def self.get_value(inventory_file, key)
|
|
14
14
|
unless %w[contentDirectory digestAlgorithm head type id].include?(key)
|
|
15
|
-
raise "#{key} is not a valid OCFL inventory header key"
|
|
15
|
+
raise OcflTools::Errors::RequestedKeyNotFound, "#{key} is not a valid OCFL inventory header key"
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
inventory = OcflTools::OcflInventory.new.from_file(inventory_file)
|
data/ocfl-tools.gemspec
CHANGED
|
@@ -8,10 +8,10 @@ Gem::Specification.new do |gem|
|
|
|
8
8
|
gem.homepage = 'https://github.com/sul-dlss-labs/OCFL-Tools'
|
|
9
9
|
gem.licenses = ['Apache-2.0']
|
|
10
10
|
|
|
11
|
+
# Don't include fixtures in the gem.
|
|
11
12
|
gem.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
12
13
|
f.match(%r{^(test|spec|features)/})
|
|
13
14
|
end
|
|
14
|
-
# gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
|
|
15
15
|
|
|
16
16
|
gem.add_runtime_dependency 'anyway_config', '~> 1.0'
|
|
17
17
|
gem.add_runtime_dependency 'fileutils', '~> 1.3'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ocfl-tools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.9.
|
|
4
|
+
version: 0.9.15
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Julian M. Morley
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2020-
|
|
11
|
+
date: 2020-04-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: anyway_config
|
|
@@ -42,22 +42,22 @@ dependencies:
|
|
|
42
42
|
name: json
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - ">="
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: 2.2.0
|
|
48
45
|
- - "~>"
|
|
49
46
|
- !ruby/object:Gem::Version
|
|
50
47
|
version: '2.2'
|
|
48
|
+
- - ">="
|
|
49
|
+
- !ruby/object:Gem::Version
|
|
50
|
+
version: 2.2.0
|
|
51
51
|
type: :runtime
|
|
52
52
|
prerelease: false
|
|
53
53
|
version_requirements: !ruby/object:Gem::Requirement
|
|
54
54
|
requirements:
|
|
55
|
-
- - ">="
|
|
56
|
-
- !ruby/object:Gem::Version
|
|
57
|
-
version: 2.2.0
|
|
58
55
|
- - "~>"
|
|
59
56
|
- !ruby/object:Gem::Version
|
|
60
57
|
version: '2.2'
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: 2.2.0
|
|
61
61
|
- !ruby/object:Gem::Dependency
|
|
62
62
|
name: pry-byebug
|
|
63
63
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -183,7 +183,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
183
183
|
- !ruby/object:Gem::Version
|
|
184
184
|
version: '0'
|
|
185
185
|
requirements: []
|
|
186
|
-
rubygems_version: 3.
|
|
186
|
+
rubygems_version: 3.1.2
|
|
187
187
|
signing_key:
|
|
188
188
|
specification_version: 4
|
|
189
189
|
summary: Tools to create, manipulate and write Oxford Common File Layout (OCFL) preservation
|