moab-versioning 4.2.0 → 4.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/moab/bagger.rb +17 -19
- data/lib/moab/config.rb +5 -6
- data/lib/moab/deposit_bag_validator.rb +2 -4
- data/lib/moab/exceptions.rb +0 -5
- data/lib/moab/file_group.rb +13 -17
- data/lib/moab/file_group_difference.rb +15 -19
- data/lib/moab/file_group_difference_subset.rb +2 -4
- data/lib/moab/file_instance.rb +3 -8
- data/lib/moab/file_instance_difference.rb +2 -6
- data/lib/moab/file_inventory.rb +18 -22
- data/lib/moab/file_inventory_difference.rb +3 -7
- data/lib/moab/file_manifestation.rb +3 -6
- data/lib/moab/file_signature.rb +45 -33
- data/lib/moab/signature_catalog.rb +13 -16
- data/lib/moab/signature_catalog_entry.rb +3 -7
- data/lib/moab/storage_object.rb +28 -31
- data/lib/moab/storage_object_validator.rb +28 -70
- data/lib/moab/storage_object_version.rb +47 -50
- data/lib/moab/storage_repository.rb +17 -21
- data/lib/moab/storage_services.rb +12 -14
- data/lib/moab/utc_time.rb +18 -19
- data/lib/moab/verification_result.rb +26 -37
- data/lib/moab/version_metadata.rb +1 -5
- data/lib/moab/version_metadata_entry.rb +2 -6
- data/lib/moab/version_metadata_event.rb +2 -7
- data/lib/serializer/manifest.rb +6 -10
- data/lib/serializer/serializable.rb +23 -27
- data/lib/stanford/active_fedora_object.rb +0 -4
- data/lib/stanford/content_inventory.rb +49 -52
- data/lib/stanford/dor_metadata.rb +5 -8
- data/lib/stanford/storage_object_validator.rb +1 -2
- data/lib/stanford/storage_repository.rb +0 -4
- data/lib/stanford/storage_services.rb +6 -10
- metadata +2 -2
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'moab'
|
2
2
|
|
3
3
|
module Moab
|
4
|
-
|
5
4
|
# A file-level entry in a digital object's {SignatureCatalog}.
|
6
5
|
# It has a child {FileSignature} element that identifies the file's contents (the bytestream)
|
7
6
|
# along with data that specfies the SDR storage location that was used to preserve a single file instance.
|
@@ -14,20 +13,19 @@ module Moab
|
|
14
13
|
# @note Copyright (c) 2012 by The Board of Trustees of the Leland Stanford Junior University.
|
15
14
|
# All rights reserved. See {file:LICENSE.rdoc} for details.
|
16
15
|
class SignatureCatalogEntry < Serializer::Serializable
|
17
|
-
|
18
16
|
include HappyMapper
|
19
17
|
|
20
18
|
# The name of the XML element used to serialize this objects data
|
21
19
|
tag 'entry'
|
22
20
|
|
23
21
|
# (see Serializable#initialize)
|
24
|
-
def initialize(opts={})
|
22
|
+
def initialize(opts = {})
|
25
23
|
super(opts)
|
26
24
|
end
|
27
25
|
|
28
26
|
# @attribute
|
29
27
|
# @return [Integer] The ordinal version number
|
30
|
-
attribute :version_id, Integer, :tag => 'originalVersion', :key => true, :on_save => Proc.new {|n| n.to_s}
|
28
|
+
attribute :version_id, Integer, :tag => 'originalVersion', :key => true, :on_save => Proc.new { |n| n.to_s }
|
31
29
|
|
32
30
|
# @attribute
|
33
31
|
# @return [String] The name of the file group
|
@@ -53,9 +51,7 @@ module Moab
|
|
53
51
|
# @api internal
|
54
52
|
# @return [String] Returns the storage path to a file, relative to the object storage home directory
|
55
53
|
def storage_path
|
56
|
-
File.join(StorageObject.version_dirname(version_id),'data', group_id, path)
|
54
|
+
File.join(StorageObject.version_dirname(version_id), 'data', group_id, path)
|
57
55
|
end
|
58
|
-
|
59
56
|
end
|
60
|
-
|
61
57
|
end
|
data/lib/moab/storage_object.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'moab'
|
2
2
|
|
3
3
|
module Moab
|
4
|
-
|
5
4
|
# A class to represent a digital object's repository storage location
|
6
5
|
# and methods for
|
7
6
|
# * packaging a bag for ingest of a new object version to the repository
|
@@ -18,7 +17,6 @@ module Moab
|
|
18
17
|
# @note Copyright (c) 2012 by The Board of Trustees of the Leland Stanford Junior University.
|
19
18
|
# All rights reserved. See {file:LICENSE.rdoc} for details.
|
20
19
|
class StorageObject
|
21
|
-
|
22
20
|
# @return [String] The digital object ID (druid)
|
23
21
|
attr_accessor :digital_object_id
|
24
22
|
|
@@ -30,7 +28,7 @@ module Moab
|
|
30
28
|
|
31
29
|
# @param object_id [String] The digital object identifier
|
32
30
|
# @param object_dir [Pathname,String] The location of the object's storage home directory
|
33
|
-
def initialize(object_id, object_dir, mkpath=false)
|
31
|
+
def initialize(object_id, object_dir, mkpath = false)
|
34
32
|
@digital_object_id = object_id
|
35
33
|
@object_pathname = Pathname.new(object_dir)
|
36
34
|
initialize_storage if mkpath
|
@@ -44,7 +42,7 @@ module Moab
|
|
44
42
|
# @api external
|
45
43
|
# @return [void] Create the directory for the digital object home unless it already exists
|
46
44
|
def initialize_storage
|
47
|
-
|
45
|
+
@object_pathname.mkpath
|
48
46
|
end
|
49
47
|
|
50
48
|
# @return [Pathname] The absolute location of the area in which bags are deposited
|
@@ -61,20 +59,20 @@ module Moab
|
|
61
59
|
# @param bag_dir [Pathname,String] The location of the bag to be ingested
|
62
60
|
# @return [void] Ingest a new object version contained in a bag into this objects storage area
|
63
61
|
# @example {include:file:spec/features/storage/ingest_spec.rb}
|
64
|
-
def ingest_bag(bag_dir=deposit_bag_pathname)
|
62
|
+
def ingest_bag(bag_dir = deposit_bag_pathname)
|
65
63
|
bag_dir = Pathname(bag_dir)
|
66
|
-
current_version = StorageObjectVersion.new(self,current_version_id)
|
64
|
+
current_version = StorageObjectVersion.new(self, current_version_id)
|
67
65
|
current_inventory = current_version.file_inventory('version')
|
68
|
-
new_version = StorageObjectVersion.new(self,current_version_id + 1)
|
69
|
-
if FileInventory.xml_pathname_exist?(bag_dir,'version')
|
70
|
-
new_inventory = FileInventory.read_xml_file(bag_dir,'version')
|
66
|
+
new_version = StorageObjectVersion.new(self, current_version_id + 1)
|
67
|
+
if FileInventory.xml_pathname_exist?(bag_dir, 'version')
|
68
|
+
new_inventory = FileInventory.read_xml_file(bag_dir, 'version')
|
71
69
|
elsif current_version.version_id == 0
|
72
|
-
new_inventory = versionize_bag(bag_dir,current_version,new_version)
|
70
|
+
new_inventory = versionize_bag(bag_dir, current_version, new_version)
|
73
71
|
end
|
74
72
|
validate_new_inventory(new_inventory)
|
75
73
|
new_version.ingest_bag_data(bag_dir)
|
76
|
-
new_version.update_catalog(current_version.signature_catalog,new_inventory)
|
77
|
-
new_version.generate_differences_report(current_inventory,new_inventory)
|
74
|
+
new_version.update_catalog(current_version.signature_catalog, new_inventory)
|
75
|
+
new_version.generate_differences_report(current_inventory, new_inventory)
|
78
76
|
new_version.generate_manifest_inventory
|
79
77
|
new_version
|
80
78
|
end
|
@@ -84,12 +82,12 @@ module Moab
|
|
84
82
|
# @param current_version[StorageObjectVersion] The current latest version of the object
|
85
83
|
# @param new_version [StorageObjectVersion] The version to be added
|
86
84
|
# @return [FileInventory] The file inventory of the specified type for this version
|
87
|
-
def versionize_bag(bag_dir,current_version,new_version)
|
85
|
+
def versionize_bag(bag_dir, current_version, new_version)
|
88
86
|
new_inventory = FileInventory.new(
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
87
|
+
:type => 'version',
|
88
|
+
:digital_object_id => @digital_object_id,
|
89
|
+
:version_id => new_version.version_id,
|
90
|
+
:inventory_datetime => Time.now
|
93
91
|
)
|
94
92
|
new_inventory.inventory_from_bagit_bag(bag_dir)
|
95
93
|
new_inventory.write_xml_file(bag_dir)
|
@@ -104,11 +102,11 @@ module Moab
|
|
104
102
|
# @return [void] Reconstruct an object version and package it in a bag for dissemination
|
105
103
|
# @example {include:file:spec/features/storage/reconstruct_spec.rb}
|
106
104
|
def reconstruct_version(version_id, bag_dir)
|
107
|
-
storage_version = StorageObjectVersion.new(self,version_id)
|
105
|
+
storage_version = StorageObjectVersion.new(self, version_id)
|
108
106
|
version_inventory = storage_version.file_inventory('version')
|
109
107
|
signature_catalog = storage_version.signature_catalog
|
110
108
|
bagger = Bagger.new(version_inventory, signature_catalog, bag_dir)
|
111
|
-
bagger.fill_bag(:reconstructor
|
109
|
+
bagger.fill_bag(:reconstructor, @object_pathname)
|
112
110
|
end
|
113
111
|
|
114
112
|
# @param [String] catalog_filepath The object-relative path of the file
|
@@ -142,7 +140,7 @@ module Moab
|
|
142
140
|
|
143
141
|
# @return [Array<StorageObjectVersion>] The list of all versions in this storage object
|
144
142
|
def version_list
|
145
|
-
version_id_list.collect{|id| self.storage_object_version(id)}
|
143
|
+
version_id_list.collect { |id| self.storage_object_version(id) }
|
146
144
|
end
|
147
145
|
alias :versions :version_list
|
148
146
|
|
@@ -175,15 +173,15 @@ module Moab
|
|
175
173
|
# @api external
|
176
174
|
# @param version_id [Integer] The existing version to return. If nil, return latest version
|
177
175
|
# @return [StorageObjectVersion] The representation of an existing version's storage area
|
178
|
-
def find_object_version(version_id=nil)
|
176
|
+
def find_object_version(version_id = nil)
|
179
177
|
current = current_version_id
|
180
178
|
case version_id
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
179
|
+
when nil
|
180
|
+
StorageObjectVersion.new(self, current)
|
181
|
+
when 1..current
|
182
|
+
StorageObjectVersion.new(self, version_id)
|
183
|
+
else
|
184
|
+
raise "Version ID #{version_id} does not exist"
|
187
185
|
end
|
188
186
|
end
|
189
187
|
|
@@ -194,7 +192,7 @@ module Moab
|
|
194
192
|
# * Current version + 1 is used for creation of a new version
|
195
193
|
def storage_object_version(version_id)
|
196
194
|
if version_id
|
197
|
-
StorageObjectVersion.new(self,version_id)
|
195
|
+
StorageObjectVersion.new(self, version_id)
|
198
196
|
else
|
199
197
|
raise "Version ID not specified"
|
200
198
|
end
|
@@ -207,7 +205,7 @@ module Moab
|
|
207
205
|
result.subentities << version.verify_version_storage
|
208
206
|
end
|
209
207
|
result.subentities << current_version.verify_signature_catalog
|
210
|
-
result.verified = result.subentities.all?{|entity| entity.verified}
|
208
|
+
result.verified = result.subentities.all? { |entity| entity.verified }
|
211
209
|
result
|
212
210
|
end
|
213
211
|
|
@@ -222,7 +220,7 @@ module Moab
|
|
222
220
|
# rename/save the original
|
223
221
|
storage_version.deactivate(timestamp)
|
224
222
|
# copy the recovered version into place
|
225
|
-
FileUtils.cp_r(recovery_version.version_pathname.to_s,storage_version.version_pathname.to_s)
|
223
|
+
FileUtils.cp_r(recovery_version.version_pathname.to_s, storage_version.version_pathname.to_s)
|
226
224
|
end
|
227
225
|
self
|
228
226
|
end
|
@@ -236,5 +234,4 @@ module Moab
|
|
236
234
|
size
|
237
235
|
end
|
238
236
|
end
|
239
|
-
|
240
237
|
end
|
@@ -5,7 +5,6 @@ module Moab
|
|
5
5
|
# Given a druid path, are the contents actually a well-formed Moab?
|
6
6
|
# Shameless green: repetitious code included.
|
7
7
|
class StorageObjectValidator
|
8
|
-
|
9
8
|
METADATA_DIR = "metadata".freeze
|
10
9
|
CONTENT_DIR = "content".freeze
|
11
10
|
EXPECTED_DATA_SUB_DIRS = [CONTENT_DIR, METADATA_DIR].freeze
|
@@ -25,7 +24,7 @@ module Moab
|
|
25
24
|
VERSION_DIR_BAD_FORMAT = 3
|
26
25
|
NO_SIGNATURE_CATALOG = 4
|
27
26
|
NO_MANIFEST_INVENTORY = 5
|
28
|
-
NO_FILES_IN_MANIFEST_DIR= 6
|
27
|
+
NO_FILES_IN_MANIFEST_DIR = 6
|
29
28
|
VERSIONS_NOT_IN_ORDER = 7
|
30
29
|
METADATA_SUB_DIRS_DETECTED = 8
|
31
30
|
FILES_IN_VERSION_DIR = 9
|
@@ -41,7 +40,7 @@ module Moab
|
|
41
40
|
@directory_entries_hash = {}
|
42
41
|
end
|
43
42
|
|
44
|
-
def validation_errors(allow_content_subdirs=true)
|
43
|
+
def validation_errors(allow_content_subdirs = true)
|
45
44
|
errors = []
|
46
45
|
errors.concat check_correctly_named_version_dirs
|
47
46
|
errors.concat check_sequential_version_dirs if errors.empty?
|
@@ -90,18 +89,14 @@ module Moab
|
|
90
89
|
|
91
90
|
# call only if the version directories are "correctly named" vdddd
|
92
91
|
def check_sequential_version_dirs
|
93
|
-
errors = []
|
94
92
|
version_directories.each_with_index do |dir_name, index|
|
95
|
-
|
96
|
-
|
97
|
-
errors << result_hash(VERSIONS_NOT_IN_ORDER, version_directories)
|
98
|
-
break
|
99
|
-
end
|
93
|
+
next if dir_name[1..-1].to_i == index + 1 # version numbering starts at 1, array indexing at 0
|
94
|
+
return [result_hash(VERSIONS_NOT_IN_ORDER, version_directories)]
|
100
95
|
end
|
101
|
-
|
96
|
+
[]
|
102
97
|
end
|
103
98
|
|
104
|
-
def check_correctly_formed_moab(allow_content_subdirs=true)
|
99
|
+
def check_correctly_formed_moab(allow_content_subdirs = true)
|
105
100
|
errors = []
|
106
101
|
version_directories.each do |version_dir|
|
107
102
|
version_path = File.join(storage_obj_path, version_dir)
|
@@ -114,20 +109,15 @@ module Moab
|
|
114
109
|
end
|
115
110
|
|
116
111
|
def check_version_sub_dirs(version_path, version)
|
117
|
-
errors = []
|
118
112
|
version_sub_dirs = directory_entries(version_path)
|
119
|
-
|
120
|
-
if
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
elsif version_sub_dir_count < EXPECTED_VERSION_SUB_DIRS.size
|
125
|
-
errors.concat missing_dir(version_sub_dirs, version, EXPECTED_VERSION_SUB_DIRS)
|
126
|
-
end
|
127
|
-
errors
|
113
|
+
count = version_sub_dirs.size
|
114
|
+
return expected_version_sub_dirs(version_path, version) if count == EXPECTED_VERSION_SUB_DIRS.size
|
115
|
+
return found_unexpected(version_sub_dirs, version, EXPECTED_VERSION_SUB_DIRS) if count > EXPECTED_VERSION_SUB_DIRS.size
|
116
|
+
return missing_dir(version_sub_dirs, version, EXPECTED_VERSION_SUB_DIRS) if count < EXPECTED_VERSION_SUB_DIRS.size
|
117
|
+
[]
|
128
118
|
end
|
129
119
|
|
130
|
-
def check_data_directory(version_path, version, allow_content_subdirs=true)
|
120
|
+
def check_data_directory(version_path, version, allow_content_subdirs = true)
|
131
121
|
errors = []
|
132
122
|
data_dir_path = File.join(version_path, DATA_DIR)
|
133
123
|
data_sub_dirs = directory_entries(data_dir_path)
|
@@ -138,18 +128,14 @@ module Moab
|
|
138
128
|
end
|
139
129
|
|
140
130
|
def check_data_sub_dirs(version, data_sub_dirs)
|
131
|
+
return found_unexpected(data_sub_dirs, version, EXPECTED_DATA_SUB_DIRS) if data_sub_dirs.size > EXPECTED_DATA_SUB_DIRS.size
|
141
132
|
errors = []
|
142
|
-
|
143
|
-
|
144
|
-
else
|
145
|
-
errors.concat missing_dir(data_sub_dirs, version, [METADATA_DIR]) unless data_sub_dirs.include?(METADATA_DIR)
|
146
|
-
errors.concat found_unexpected(data_sub_dirs, version, EXPECTED_DATA_SUB_DIRS) unless subset?(data_sub_dirs,
|
147
|
-
EXPECTED_DATA_SUB_DIRS)
|
148
|
-
end
|
133
|
+
errors.concat missing_dir(data_sub_dirs, version, [METADATA_DIR]) unless data_sub_dirs.include?(METADATA_DIR)
|
134
|
+
errors.concat found_unexpected(data_sub_dirs, version, EXPECTED_DATA_SUB_DIRS) unless data_sub_dirs.to_set.subset?(EXPECTED_DATA_SUB_DIRS.to_set)
|
149
135
|
errors
|
150
136
|
end
|
151
137
|
|
152
|
-
def check_optional_content_dir(version_path, allow_content_subdirs=true)
|
138
|
+
def check_optional_content_dir(version_path, allow_content_subdirs = true)
|
153
139
|
errors = []
|
154
140
|
content_dir_path = File.join(version_path, DATA_DIR, CONTENT_DIR)
|
155
141
|
errors << result_hash(NO_FILES_IN_CONTENT_DIR, basename(version_path)) if directory_entries(content_dir_path).empty?
|
@@ -168,10 +154,6 @@ module Moab
|
|
168
154
|
FORBIDDEN_CONTENT_SUB_DIRS.include?(dirname) || version_dir_format?(dirname)
|
169
155
|
end
|
170
156
|
|
171
|
-
def subset?(first_array, second_array)
|
172
|
-
first_array.to_set.subset?(second_array.to_set)
|
173
|
-
end
|
174
|
-
|
175
157
|
def check_metadata_dir_files_only(version_path)
|
176
158
|
errors = []
|
177
159
|
metadata_dir_path = File.join(version_path, DATA_DIR, METADATA_DIR)
|
@@ -183,39 +165,26 @@ module Moab
|
|
183
165
|
# This method removes the implicit '.' and '..' directories.
|
184
166
|
# Returns an array of strings.
|
185
167
|
def directory_entries(path)
|
186
|
-
@directory_entries_hash[path] ||=
|
187
|
-
begin
|
188
|
-
dirs = []
|
189
|
-
(Dir.entries(path).sort - IMPLICIT_DIRS).each do |child|
|
190
|
-
dirs << child
|
191
|
-
end
|
192
|
-
dirs
|
193
|
-
end
|
168
|
+
@directory_entries_hash[path] ||= Dir.entries(path).sort - IMPLICIT_DIRS
|
194
169
|
end
|
195
170
|
|
171
|
+
# @return [Array<Hash<Integer => String>>]
|
196
172
|
def found_unexpected(array, version, required_sub_dirs)
|
197
|
-
errors = []
|
198
173
|
unexpected = (array - required_sub_dirs)
|
199
|
-
|
200
|
-
errors << result_hash(EXTRA_CHILD_DETECTED, unexpected)
|
201
|
-
errors
|
174
|
+
[result_hash(EXTRA_CHILD_DETECTED, "#{unexpected} Version: #{version}")]
|
202
175
|
end
|
203
176
|
|
177
|
+
# @return [Array<Hash<Integer => String>>]
|
204
178
|
def missing_dir(array, version, required_sub_dirs)
|
205
|
-
errors = []
|
206
179
|
missing = (required_sub_dirs - array)
|
207
|
-
|
208
|
-
errors << result_hash(MISSING_DIR, missing)
|
209
|
-
errors
|
180
|
+
[result_hash(MISSING_DIR, "#{missing} Version: #{version}")]
|
210
181
|
end
|
211
182
|
|
212
183
|
def expected_version_sub_dirs(version_path, version)
|
213
184
|
errors = []
|
214
185
|
version_sub_dirs = directory_entries(version_path)
|
215
186
|
errors << result_hash(INCORRECT_DIR_CONTENTS, version) unless version_sub_dirs == EXPECTED_VERSION_SUB_DIRS
|
216
|
-
if contains_file?(version_path)
|
217
|
-
errors << result_hash(FILES_IN_VERSION_DIR, version)
|
218
|
-
end
|
187
|
+
errors << result_hash(FILES_IN_VERSION_DIR, version) if contains_file?(version_path)
|
219
188
|
errors
|
220
189
|
end
|
221
190
|
|
@@ -235,35 +204,24 @@ module Moab
|
|
235
204
|
path.split(File::SEPARATOR)[-1]
|
236
205
|
end
|
237
206
|
|
238
|
-
def result_hash(response_code, addl=nil)
|
207
|
+
def result_hash(response_code, addl = nil)
|
239
208
|
{ response_code => error_code_msg(response_code, addl) }
|
240
209
|
end
|
241
210
|
|
242
|
-
def error_code_msg(response_code, addl=nil)
|
211
|
+
def error_code_msg(response_code, addl = nil)
|
243
212
|
format(self.class.error_code_to_messages[response_code], addl: addl)
|
244
213
|
end
|
245
214
|
|
246
215
|
def check_required_manifest_files(dir, version)
|
216
|
+
return [result_hash(NO_FILES_IN_MANIFEST_DIR, version)] unless contains_file?(File.join(dir, MANIFESTS_DIR))
|
247
217
|
errors = []
|
248
|
-
unless
|
249
|
-
|
250
|
-
return errors
|
251
|
-
end
|
252
|
-
|
253
|
-
unless File.exist?(File.join(dir, MANIFEST_INVENTORY_PATH))
|
254
|
-
errors << result_hash(NO_MANIFEST_INVENTORY, version)
|
255
|
-
end
|
256
|
-
unless File.exist?(File.join(dir, SIGNATURE_CATALOG_PATH))
|
257
|
-
errors << result_hash(NO_SIGNATURE_CATALOG, version)
|
258
|
-
end
|
218
|
+
errors << result_hash(NO_MANIFEST_INVENTORY, version) unless File.exist?(File.join(dir, MANIFEST_INVENTORY_PATH))
|
219
|
+
errors << result_hash(NO_SIGNATURE_CATALOG, version) unless File.exist?(File.join(dir, SIGNATURE_CATALOG_PATH))
|
259
220
|
errors
|
260
221
|
end
|
261
222
|
|
262
|
-
def latest_manifest_inventory
|
263
|
-
File.join(storage_obj_path, version_directories.last, MANIFEST_INVENTORY_PATH)
|
264
|
-
end
|
265
|
-
|
266
223
|
def object_id_from_manifest_inventory
|
224
|
+
latest_manifest_inventory = File.join(storage_obj_path, version_directories.last, MANIFEST_INVENTORY_PATH)
|
267
225
|
Nokogiri::XML(File.open(latest_manifest_inventory)).at_xpath('//fileInventory/@objectId').value
|
268
226
|
end
|
269
227
|
end
|
@@ -1,9 +1,7 @@
|
|
1
1
|
require 'moab'
|
2
2
|
|
3
3
|
module Moab
|
4
|
-
|
5
4
|
# A class to represent a version subdirectory within an object's home directory in preservation storage
|
6
|
-
#
|
7
5
|
# ====Data Model
|
8
6
|
# * {StorageRepository} = represents a digital object repository storage node
|
9
7
|
# * {StorageServices} = supports application layer access to the repository's objects, data, and metadata
|
@@ -14,7 +12,6 @@ module Moab
|
|
14
12
|
# @note Copyright (c) 2012 by The Board of Trustees of the Leland Stanford Junior University.
|
15
13
|
# All rights reserved. See {file:LICENSE.rdoc} for details.
|
16
14
|
class StorageObjectVersion
|
17
|
-
|
18
15
|
# @return [Integer] The ordinal version number
|
19
16
|
attr_accessor :version_id
|
20
17
|
|
@@ -36,13 +33,13 @@ module Moab
|
|
36
33
|
if version_id.is_a?(Integer)
|
37
34
|
@version_id = version_id
|
38
35
|
elsif version_id.is_a?(String) and version_id =~ /^v(\d+)$/
|
39
|
-
@version_id = version_id.sub(/^v/,'').to_i
|
36
|
+
@version_id = version_id.sub(/^v/, '').to_i
|
40
37
|
else
|
41
38
|
raise "version_id (#{version_id}) is not in a recognized format"
|
42
39
|
end
|
43
40
|
@version_name = StorageObject.version_dirname(@version_id)
|
44
41
|
@version_pathname = storage_object.object_pathname.join(@version_name)
|
45
|
-
@storage_object=storage_object
|
42
|
+
@storage_object = storage_object
|
46
43
|
@inventory_cache = Hash.new
|
47
44
|
end
|
48
45
|
|
@@ -61,7 +58,7 @@ module Moab
|
|
61
58
|
# @return [FileSignature] signature of the specified file
|
62
59
|
def find_signature(file_category, file_id)
|
63
60
|
if file_category =~ /manifest/
|
64
|
-
file_inventory('manifests').file_signature('manifests',file_id)
|
61
|
+
file_inventory('manifests').file_signature('manifests', file_id)
|
65
62
|
else
|
66
63
|
file_inventory('version').file_signature(file_category, file_id)
|
67
64
|
end
|
@@ -103,7 +100,7 @@ module Moab
|
|
103
100
|
if file_category =~ /manifest/
|
104
101
|
@version_pathname.join('manifests')
|
105
102
|
else
|
106
|
-
@version_pathname.join('data',file_category)
|
103
|
+
@version_pathname.join('data', file_category)
|
107
104
|
end
|
108
105
|
end
|
109
106
|
|
@@ -116,12 +113,12 @@ module Moab
|
|
116
113
|
return @inventory_cache[type] if @inventory_cache.has_key?(type)
|
117
114
|
@inventory_cache[type] = FileInventory.read_xml_file(@version_pathname.join('manifests'), type)
|
118
115
|
else
|
119
|
-
groups = ['content','metadata'].collect { |id| FileGroup.new(:group_id=>id)}
|
116
|
+
groups = ['content', 'metadata'].collect { |id| FileGroup.new(:group_id => id) }
|
120
117
|
FileInventory.new(
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
118
|
+
:type => 'version',
|
119
|
+
:digital_object_id => @storage_object.digital_object_id,
|
120
|
+
:version_id => @version_id,
|
121
|
+
:groups => groups
|
125
122
|
)
|
126
123
|
end
|
127
124
|
end
|
@@ -142,10 +139,10 @@ module Moab
|
|
142
139
|
def ingest_bag_data(bag_dir)
|
143
140
|
raise "Version already exists: #{@version_pathname}" if @version_pathname.exist?
|
144
141
|
@version_pathname.join('manifests').mkpath
|
145
|
-
bag_dir=Pathname(bag_dir)
|
146
|
-
ingest_dir(bag_dir.join('data')
|
147
|
-
ingest_file(bag_dir.join(FileInventory.xml_filename('version'))
|
148
|
-
ingest_file(bag_dir.join(FileInventory.xml_filename('additions'))
|
142
|
+
bag_dir = Pathname(bag_dir)
|
143
|
+
ingest_dir(bag_dir.join('data'), @version_pathname.join('data'))
|
144
|
+
ingest_file(bag_dir.join(FileInventory.xml_filename('version')), @version_pathname.join('manifests'))
|
145
|
+
ingest_file(bag_dir.join(FileInventory.xml_filename('additions')), @version_pathname.join('manifests'))
|
149
146
|
end
|
150
147
|
|
151
148
|
# @api internal
|
@@ -153,7 +150,7 @@ module Moab
|
|
153
150
|
# @param target_dir [Pathname] The target location of the directory into which files are ingested
|
154
151
|
# @param use_links [Boolean] If true, use hard links; if false, make copies
|
155
152
|
# @return [void] recursively link or copy the source directory contents to the target directory
|
156
|
-
def ingest_dir(source_dir, target_dir, use_links=true)
|
153
|
+
def ingest_dir(source_dir, target_dir, use_links = true)
|
157
154
|
raise "cannot copy - target already exists: #{target_dir.expand_path}" if target_dir.exist?
|
158
155
|
target_dir.mkpath
|
159
156
|
source_dir.children.each do |child|
|
@@ -170,7 +167,7 @@ module Moab
|
|
170
167
|
# @param target_dir [Pathname] The location of the directory in which to place the file
|
171
168
|
# @param use_links [Boolean] If true, use hard links; if false, make copies
|
172
169
|
# @return [void] link or copy the specified file from source location to the version directory
|
173
|
-
def ingest_file(source_file, target_dir, use_links=true)
|
170
|
+
def ingest_file(source_file, target_dir, use_links = true)
|
174
171
|
if use_links
|
175
172
|
FileUtils.link(source_file.to_s, target_dir.to_s) #, :force => true)
|
176
173
|
else
|
@@ -183,7 +180,7 @@ module Moab
|
|
183
180
|
# @param new_inventory [FileInventory] The new version's inventory
|
184
181
|
# @return [void] Updates the catalog to include newly added files, then saves it to disk
|
185
182
|
# @see SignatureCatalog#update
|
186
|
-
def update_catalog(signature_catalog,new_inventory)
|
183
|
+
def update_catalog(signature_catalog, new_inventory)
|
187
184
|
signature_catalog.update(new_inventory, @version_pathname.join('data'))
|
188
185
|
signature_catalog.write_xml_file(@version_pathname.join('manifests'))
|
189
186
|
end
|
@@ -192,7 +189,7 @@ module Moab
|
|
192
189
|
# @param old_inventory [FileInventory] The old version's inventory
|
193
190
|
# @param new_inventory [FileInventory] The new version's inventory
|
194
191
|
# @return [void] generate a file inventory differences report and save to disk
|
195
|
-
def generate_differences_report(old_inventory,new_inventory)
|
192
|
+
def generate_differences_report(old_inventory, new_inventory)
|
196
193
|
differences = FileInventoryDifference.new.compare(old_inventory, new_inventory)
|
197
194
|
differences.write_xml_file(@version_pathname.join('manifests'))
|
198
195
|
end
|
@@ -201,11 +198,12 @@ module Moab
|
|
201
198
|
# @return [void] examine the version's directory and create/serialize a {FileInventory} containing the manifest files
|
202
199
|
def generate_manifest_inventory
|
203
200
|
manifest_inventory = FileInventory.new(
|
204
|
-
|
205
|
-
|
206
|
-
|
201
|
+
:type => 'manifests',
|
202
|
+
:digital_object_id => @storage_object.digital_object_id,
|
203
|
+
:version_id => @version_id
|
204
|
+
)
|
207
205
|
pathname = @version_pathname.join('manifests')
|
208
|
-
manifest_inventory.groups << FileGroup.new(:group_id=>'manifests').group_from_directory(pathname, false)
|
206
|
+
manifest_inventory.groups << FileGroup.new(:group_id => 'manifests').group_from_directory(pathname, false)
|
209
207
|
manifest_inventory.write_xml_file(pathname)
|
210
208
|
end
|
211
209
|
|
@@ -215,16 +213,16 @@ module Moab
|
|
215
213
|
result.subentities << self.verify_manifest_inventory
|
216
214
|
result.subentities << self.verify_version_inventory
|
217
215
|
result.subentities << self.verify_version_additions
|
218
|
-
result.verified = result.subentities.all?{|entity| entity.verified}
|
216
|
+
result.verified = result.subentities.all? { |entity| entity.verified }
|
219
217
|
result
|
220
218
|
end
|
221
219
|
|
222
|
-
# @return [
|
220
|
+
# @return [VerificationResult] return true if the manifest inventory matches the actual files
|
223
221
|
def verify_manifest_inventory
|
224
222
|
# read/parse manifestInventory.xml
|
225
223
|
result = VerificationResult.new("manifest_inventory")
|
226
224
|
manifest_inventory = self.file_inventory('manifests')
|
227
|
-
result.subentities << VerificationResult.verify_value('composite_key',self.composite_key,manifest_inventory.composite_key)
|
225
|
+
result.subentities << VerificationResult.verify_value('composite_key', self.composite_key, manifest_inventory.composite_key)
|
228
226
|
result.subentities << VerificationResult.verify_truth('manifests_group', !manifest_inventory.group_empty?('manifests'))
|
229
227
|
# measure the manifest signatures of the files in the directory (excluding manifestInventory.xml)
|
230
228
|
directory_inventory = FileInventory.new.inventory_from_directory(@version_pathname.join('manifests'), 'manifests')
|
@@ -233,19 +231,20 @@ module Moab
|
|
233
231
|
directory_group.remove_file_having_path("manifestInventory.xml")
|
234
232
|
# compare the measured signatures against the values in manifestInventory.xml
|
235
233
|
diff = FileInventoryDifference.new
|
236
|
-
diff.compare(manifest_inventory,directory_inventory)
|
234
|
+
diff.compare(manifest_inventory, directory_inventory)
|
237
235
|
compare_result = VerificationResult.new('file_differences')
|
238
236
|
compare_result.verified = (diff.difference_count == 0)
|
239
237
|
compare_result.details = diff.differences_detail
|
240
238
|
result.subentities << compare_result
|
241
|
-
result.verified = result.subentities.all?{|entity| entity.verified}
|
239
|
+
result.verified = result.subentities.all? { |entity| entity.verified }
|
242
240
|
result
|
243
241
|
end
|
244
242
|
|
243
|
+
# @return [VerificationResult]
|
245
244
|
def verify_signature_catalog
|
246
245
|
result = VerificationResult.new("signature_catalog")
|
247
|
-
signature_catalog =self.signature_catalog
|
248
|
-
result.subentities << VerificationResult.verify_value('signature_key',self.composite_key,signature_catalog.composite_key)
|
246
|
+
signature_catalog = self.signature_catalog
|
247
|
+
result.subentities << VerificationResult.verify_value('signature_key', self.composite_key, signature_catalog.composite_key)
|
249
248
|
found = 0
|
250
249
|
missing = Array.new
|
251
250
|
object_pathname = self.storage_object.object_pathname
|
@@ -260,12 +259,12 @@ module Moab
|
|
260
259
|
file_result = VerificationResult.new("storage_location")
|
261
260
|
file_result.verified = (found == signature_catalog.file_count)
|
262
261
|
file_result.details = {
|
263
|
-
|
264
|
-
|
262
|
+
'expected' => signature_catalog.file_count,
|
263
|
+
'found' => found
|
265
264
|
}
|
266
265
|
file_result.details['missing'] = missing unless missing.empty?
|
267
266
|
result.subentities << file_result
|
268
|
-
result.verified = result.subentities.all?{|entity| entity.verified}
|
267
|
+
result.verified = result.subentities.all? { |entity| entity.verified }
|
269
268
|
result
|
270
269
|
end
|
271
270
|
|
@@ -273,9 +272,9 @@ module Moab
|
|
273
272
|
def verify_version_inventory
|
274
273
|
result = VerificationResult.new("version_inventory")
|
275
274
|
version_inventory = self.file_inventory('version')
|
276
|
-
result.subentities << VerificationResult.verify_value('inventory_key',self.composite_key,version_inventory.composite_key)
|
277
|
-
signature_catalog =self.signature_catalog
|
278
|
-
result.subentities << VerificationResult.verify_value('signature_key',self.composite_key,signature_catalog.composite_key)
|
275
|
+
result.subentities << VerificationResult.verify_value('inventory_key', self.composite_key, version_inventory.composite_key)
|
276
|
+
signature_catalog = self.signature_catalog
|
277
|
+
result.subentities << VerificationResult.verify_value('signature_key', self.composite_key, signature_catalog.composite_key)
|
279
278
|
found = 0
|
280
279
|
missing = Array.new
|
281
280
|
version_inventory.groups.each do |group|
|
@@ -294,13 +293,12 @@ module Moab
|
|
294
293
|
file_result = VerificationResult.new("catalog_entry")
|
295
294
|
file_result.verified = (found == version_inventory.file_count)
|
296
295
|
file_result.details = {
|
297
|
-
|
298
|
-
|
296
|
+
'expected' => version_inventory.file_count,
|
297
|
+
'found' => found
|
299
298
|
}
|
300
299
|
file_result.details['missing'] = missing unless missing.empty?
|
301
|
-
|
302
300
|
result.subentities << file_result
|
303
|
-
result.verified = result.subentities.all?{|entity| entity.verified}
|
301
|
+
result.verified = result.subentities.all? { |entity| entity.verified }
|
304
302
|
result
|
305
303
|
end
|
306
304
|
|
@@ -308,28 +306,27 @@ module Moab
|
|
308
306
|
def verify_version_additions
|
309
307
|
result = VerificationResult.new("version_additions")
|
310
308
|
version_additions = self.file_inventory('additions')
|
311
|
-
result.subentities << VerificationResult.verify_value('composite_key',self.composite_key,version_additions.composite_key)
|
309
|
+
result.subentities << VerificationResult.verify_value('composite_key', self.composite_key, version_additions.composite_key)
|
312
310
|
data_directory = @version_pathname.join('data')
|
313
|
-
directory_inventory = FileInventory.new(:type=>'directory').inventory_from_directory(data_directory)
|
311
|
+
directory_inventory = FileInventory.new(:type => 'directory').inventory_from_directory(data_directory)
|
314
312
|
diff = FileInventoryDifference.new
|
315
313
|
diff.compare(version_additions, directory_inventory)
|
316
314
|
compare_result = VerificationResult.new('file_differences')
|
317
315
|
compare_result.verified = (diff.difference_count == 0)
|
318
316
|
compare_result.details = diff.differences_detail
|
319
317
|
result.subentities << compare_result
|
320
|
-
result.verified = result.subentities.all?{|entity| entity.verified}
|
318
|
+
result.verified = result.subentities.all? { |entity| entity.verified }
|
321
319
|
result
|
322
320
|
end
|
323
321
|
|
324
322
|
# @param timestamp [Time] The time at which the deactivation was initiated. Used to name the inactive directory
|
325
323
|
# @return [null] Deactivate this object version by moving it to another directory. (Used by restore operation)
|
326
324
|
def deactivate(timestamp)
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
end
|
325
|
+
return unless @version_pathname.exist?
|
326
|
+
timestamp_pathname = @version_pathname.parent.join(timestamp.utc.iso8601.gsub(/[-:]/, ''))
|
327
|
+
timestamp_pathname.mkpath
|
328
|
+
demote_pathame = timestamp_pathname.join(@version_pathname.basename)
|
329
|
+
@version_pathname.rename(demote_pathame)
|
333
330
|
end
|
334
331
|
end
|
335
332
|
end
|