moab-versioning 4.2.0 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
|