moab-versioning 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/lib/moab.rb +59 -0
  3. data/lib/moab/bagger.rb +289 -0
  4. data/lib/moab/config.rb +21 -0
  5. data/lib/moab/exceptions.rb +18 -0
  6. data/lib/moab/file_group.rb +244 -0
  7. data/lib/moab/file_group_difference.rb +336 -0
  8. data/lib/moab/file_group_difference_subset.rb +45 -0
  9. data/lib/moab/file_instance.rb +82 -0
  10. data/lib/moab/file_instance_difference.rb +54 -0
  11. data/lib/moab/file_inventory.rb +279 -0
  12. data/lib/moab/file_inventory_difference.rb +132 -0
  13. data/lib/moab/file_manifestation.rb +85 -0
  14. data/lib/moab/file_signature.rb +200 -0
  15. data/lib/moab/signature_catalog.rb +195 -0
  16. data/lib/moab/signature_catalog_entry.rb +61 -0
  17. data/lib/moab/storage_object.rb +220 -0
  18. data/lib/moab/storage_object_version.rb +333 -0
  19. data/lib/moab/storage_repository.rb +57 -0
  20. data/lib/moab/storage_services.rb +104 -0
  21. data/lib/moab/verification_result.rb +83 -0
  22. data/lib/moab/version_metadata.rb +38 -0
  23. data/lib/moab/version_metadata_entry.rb +64 -0
  24. data/lib/moab/version_metadata_event.rb +47 -0
  25. data/lib/moab_stanford.rb +18 -0
  26. data/lib/monkey_patches.rb +65 -0
  27. data/lib/serializer.rb +36 -0
  28. data/lib/serializer/manifest.rb +76 -0
  29. data/lib/serializer/serializable.rb +178 -0
  30. data/lib/stanford/active_fedora_object.rb +34 -0
  31. data/lib/stanford/content_inventory.rb +236 -0
  32. data/lib/stanford/dor_metadata.rb +49 -0
  33. data/lib/stanford/storage_repository.rb +46 -0
  34. data/lib/stanford/storage_services.rb +66 -0
  35. data/lib/tasks/yard.rake +34 -0
  36. data/lib/tools/api_doc_generator.rb +396 -0
  37. data/lib/tools/spec_generator.rb +410 -0
  38. data/lib/tools/spec_generator_old.rb +49 -0
  39. metadata +252 -0
@@ -0,0 +1,61 @@
1
+ require 'moab'
2
+
3
+ module Moab
4
+
5
+ # A file-level entry in a digital object's {SignatureCatalog}.
6
+ # It has a child {FileSignature} element that identifies the file's contents (the bytestream)
7
+ # along with data that specfies the SDR storage location that was used to preserve a single file instance.
8
+ #
9
+ # ====Data Model
10
+ # * {SignatureCatalog} = lookup table containing a cumulative collection of all files ever ingested
11
+ # * <b>{SignatureCatalogEntry} [1..*] = an row in the lookup table containing storage information about a single file</b>
12
+ # * {FileSignature} [1] = file fixity information
13
+ #
14
+ # @note Copyright (c) 2012 by The Board of Trustees of the Leland Stanford Junior University.
15
+ # All rights reserved. See {file:LICENSE.rdoc} for details.
16
+ class SignatureCatalogEntry < Serializable
17
+
18
+ include HappyMapper
19
+
20
+ # The name of the XML element used to serialize this objects data
21
+ tag 'entry'
22
+
23
+ # (see Serializable#initialize)
24
+ def initialize(opts={})
25
+ super(opts)
26
+ end
27
+
28
+ # @attribute
29
+ # @return [Integer] The ordinal version number
30
+ attribute :version_id, Integer, :tag => 'originalVersion', :key => true, :on_save => Proc.new {|n| n.to_s}
31
+
32
+ # @attribute
33
+ # @return [String] The name of the file group
34
+ attribute :group_id, String, :tag => 'groupId', :key => true
35
+
36
+ # @attribute
37
+ # @return [String] The id is the filename path, relative to the file group's base directory
38
+ attribute :path, String, :key => true, :tag => 'storagePath'
39
+
40
+ # @attribute
41
+ # @return [FileSignature] The fixity data of the file instance
42
+ element :signature, FileSignature, :tag => 'fileSignature'
43
+
44
+ def signature
45
+ # HappyMapper's parser tries to put an array of signatures in the signature field
46
+ @signature.is_a?(Array) ? @signature[0] : @signature
47
+ end
48
+
49
+ def signature=(signature)
50
+ @signature = signature.is_a?(Array) ? signature[0] : signature
51
+ end
52
+
53
+ # @api internal
54
+ # @return [String] Returns the storage path to a file, relative to the object storage home directory
55
+ def storage_path
56
+ File.join(StorageObject.version_dirname(version_id),'data', group_id, path)
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,220 @@
1
+ require 'moab'
2
+
3
+ module Moab
4
+
5
+ # A class to represent a digital object's repository storage location
6
+ # and methods for
7
+ # * packaging a bag for ingest of a new object version to the repository
8
+ # * ingesting a bag
9
+ # * disseminating a bag containing a reconstructed object version
10
+ #
11
+ # ====Data Model
12
+ # * {StorageRepository} = represents a digital object repository storage node
13
+ # * {StorageServices} = supports application layer access to the repository's objects, data, and metadata
14
+ # * <b>{StorageObject} = represents a digital object's repository storage location and ingest/dissemination methods</b>
15
+ # * {StorageObjectVersion} [1..*] = represents a version subdirectory within an object's home directory
16
+ # * {Bagger} [1] = utility for creating bagit packages for ingest or dissemination
17
+ #
18
+ # @note Copyright (c) 2012 by The Board of Trustees of the Leland Stanford Junior University.
19
+ # All rights reserved. See {file:LICENSE.rdoc} for details.
20
+ class StorageObject
21
+
22
+ # @return [String] The digital object ID (druid)
23
+ attr_accessor :digital_object_id
24
+
25
+ # @return [Pathname] The location of the object's storage home directory
26
+ attr_accessor :object_pathname
27
+
28
+ # @param object_id [String] The digital object identifier
29
+ # @param object_dir [Pathname,String] The location of the object's storage home directory
30
+ def initialize(object_id, object_dir, mkpath=false)
31
+ @digital_object_id = object_id
32
+ @object_pathname = Pathname.new(object_dir)
33
+ initialize_storage if mkpath
34
+ end
35
+
36
+ # @return [Boolean] true if the object's storage directory exists
37
+ def exist?
38
+ @object_pathname.exist?
39
+ end
40
+
41
+ # @api external
42
+ # @return [void] Create the directory for the digital object home unless it already exists
43
+ def initialize_storage
44
+ @object_pathname.mkpath
45
+ end
46
+
47
+ # @api external
48
+ # @param bag_dir [Pathname,String] The location of the bag to be ingested
49
+ # @return [void] Ingest a new object version contained in a bag into this objects storage area
50
+ # @example {include:file:spec/features/storage/ingest_spec.rb}
51
+ def ingest_bag(bag_dir)
52
+ bag_dir = Pathname.new(bag_dir)
53
+ current_version = StorageObjectVersion.new(self,current_version_id)
54
+ current_inventory = current_version.file_inventory('version')
55
+ new_version = StorageObjectVersion.new(self,current_version_id + 1)
56
+ if FileInventory.xml_pathname_exist?(bag_dir,'version')
57
+ new_inventory = FileInventory.read_xml_file(bag_dir,'version')
58
+ elsif current_version.version_id == 0
59
+ new_inventory = versionize_bag(bag_dir,current_version,new_version)
60
+ end
61
+ validate_new_inventory(new_inventory)
62
+ new_version.ingest_bag_data(bag_dir)
63
+ new_version.update_catalog(current_version.signature_catalog,new_inventory)
64
+ new_version.generate_differences_report(current_inventory,new_inventory)
65
+ new_version.generate_manifest_inventory
66
+ new_version
67
+ end
68
+
69
+ # @api internal
70
+ # @param bag_dir [Pathname] The location of the bag to be ingested
71
+ # @param current_version[StorageObjectVersion] The current latest version of the object
72
+ # @param new_version [StorageObjectVersion] The version to be added
73
+ # @return [FileInventory] The file inventory of the specified type for this version
74
+ def versionize_bag(bag_dir,current_version,new_version)
75
+ new_inventory = FileInventory.new(
76
+ :type=>'version',
77
+ :digital_object_id=>@digital_object_id,
78
+ :version_id=>new_version.version_id,
79
+ :inventory_datetime => Time.now
80
+ )
81
+ new_inventory.inventory_from_bagit_bag(bag_dir)
82
+ new_inventory.write_xml_file(bag_dir)
83
+ version_additions = current_version.signature_catalog.version_additions(new_inventory)
84
+ version_additions.write_xml_file(bag_dir)
85
+ new_inventory
86
+ end
87
+
88
+ # @api external
89
+ # @param version_id [Integer] The version identifier of the object version to be disseminated
90
+ # @param bag_dir [Pathname,String] The location of the bag to be created
91
+ # @return [void] Reconstruct an object version and package it in a bag for dissemination
92
+ # @example {include:file:spec/features/storage/reconstruct_spec.rb}
93
+ def reconstruct_version(version_id, bag_dir)
94
+ storage_version = StorageObjectVersion.new(self,version_id)
95
+ version_inventory = storage_version.file_inventory('version')
96
+ signature_catalog = storage_version.signature_catalog
97
+ bagger = Bagger.new(version_inventory, signature_catalog, bag_dir)
98
+ bagger.fill_bag(:reconstructor,@object_pathname)
99
+ end
100
+
101
+ # @param [String] catalog_filepath The object-relative path of the file
102
+ # @return [Pathname] The absolute storage path of the file, including the object's home directory
103
+ def storage_filepath(catalog_filepath)
104
+ storage_filepath = @object_pathname.join(catalog_filepath)
105
+ raise FileNotFoundException, "#{catalog_filepath} missing from storage location #{storage_filepath}" unless storage_filepath.exist?
106
+ storage_filepath
107
+ end
108
+
109
+ # @api external
110
+ # @param version_id [Integer] The version identifier of an object version
111
+ # @return [String] The directory name of the version, relative to the digital object home directory (e.g v0002)
112
+ def self.version_dirname(version_id)
113
+ ("v%04d" % version_id)
114
+ end
115
+
116
+ # @return [Array<Integer>] The list of all version ids for this object
117
+ def version_id_list
118
+ list = Array.new
119
+ @object_pathname.children.each do |dirname|
120
+ vnum = dirname.basename.to_s
121
+ if vnum.match /^v(\d+)$/
122
+ list << vnum[1..-1].to_i
123
+ end
124
+ end
125
+ list.sort
126
+ end
127
+
128
+ # @return [Array<StorageObjectVersion>] The list of all versions in this storage object
129
+ def version_list
130
+ version_id_list.collect{|id| self.storage_object_version(id)}
131
+ end
132
+ alias :versions :version_list
133
+
134
+ # @return [Boolean] true if there are no versions yet in this object
135
+ def empty?
136
+ version_id_list.empty?
137
+ end
138
+
139
+ # @api external
140
+ # @return [Integer] The identifier of the latest version of this object, or 0 if no versions exist
141
+ def current_version_id
142
+ return @current_version_id unless @current_version_id.nil?
143
+ list = self.version_id_list
144
+ version_id = list.empty? ? 0 : list.last
145
+ @current_version_id = version_id
146
+ end
147
+
148
+ # @return [StorageObjectVersion] The most recent version in the storage object
149
+ def current_version
150
+ self.storage_object_version(current_version_id)
151
+ end
152
+
153
+ # @api internal
154
+ # @param version_inventory [FileInventory] The inventory of the object version to be ingested
155
+ # @return [Boolean] Tests whether the new version number is one higher than the current version number
156
+ def validate_new_inventory(version_inventory)
157
+ if version_inventory.version_id != (current_version_id + 1)
158
+ raise "version mismatch - current: #{current_version_id} new: #{version_inventory.version_id}"
159
+ end
160
+ true
161
+ end
162
+
163
+ # @api external
164
+ # @param version_id [Integer] The existing version to return. If nil, return latest version
165
+ # @return [StorageObjectVersion] The representation of an existing version's storage area
166
+ def find_object_version(version_id=nil)
167
+ current = current_version_id
168
+ case version_id
169
+ when nil
170
+ StorageObjectVersion.new(self,current)
171
+ when 1..current
172
+ StorageObjectVersion.new(self,version_id)
173
+ else
174
+ raise "Version ID #{version_id} does not exist"
175
+ end
176
+ end
177
+
178
+ # @api external
179
+ # @param version_id [Integer] The version to return. OK if version does not exist
180
+ # @return [StorageObjectVersion] The representation of a specified version.
181
+ # * Version 0 is a special case used to generate empty manifests
182
+ # * Current version + 1 is used for creation of a new version
183
+ def storage_object_version(version_id)
184
+ if version_id
185
+ StorageObjectVersion.new(self,version_id)
186
+ else
187
+ raise "Version ID not specified"
188
+ end
189
+ end
190
+
191
+ # @return [VerificationResult] Return result of storage verfication
192
+ def verify_object_storage
193
+ result = VerificationResult.new(digital_object_id)
194
+ self.version_list.each do |version|
195
+ result.subentities << version.verify_version_storage
196
+ end
197
+ result.subentities << current_version.verify_signature_catalog
198
+ result.verified = result.subentities.all?{|entity| entity.verified}
199
+ result
200
+ end
201
+
202
+ # @param recovery_path [Pathname, String] The location of the recovered object versions
203
+ # @return [Boolean] Restore all recovered versions to online storage and verify results
204
+ def restore_object(recovery_path)
205
+ timestamp = Time.now
206
+ recovery_object = StorageObject.new(@digital_object_id, recovery_path, mkpath=false)
207
+ recovery_object.versions.each do |recovery_version|
208
+ version_id = recovery_version.version_id
209
+ storage_version = self.storage_object_version(version_id)
210
+ # rename/save the original
211
+ storage_version.deactivate(timestamp)
212
+ # copy the recovered version into place
213
+ FileUtils.cp_r(recovery_version.version_pathname.to_s,storage_version.version_pathname.to_s)
214
+ end
215
+ self
216
+ end
217
+
218
+ end
219
+
220
+ end
@@ -0,0 +1,333 @@
1
+ require 'moab'
2
+
3
+ module Moab
4
+
5
+ # A class to represent a version subdirectory within an object's home directory in preservation storage
6
+ #
7
+ # ====Data Model
8
+ # * {StorageRepository} = represents a digital object repository storage node
9
+ # * {StorageServices} = supports application layer access to the repository's objects, data, and metadata
10
+ # * {StorageObject} = represents a digital object's repository storage location and ingest/dissemination methods
11
+ # * <b>{StorageObjectVersion} [1..*] = represents a version subdirectory within an object's home directory</b>
12
+ # * {Bagger} [1] = utility for creating bagit packages for ingest or dissemination
13
+ #
14
+ # @note Copyright (c) 2012 by The Board of Trustees of the Leland Stanford Junior University.
15
+ # All rights reserved. See {file:LICENSE.rdoc} for details.
16
+ class StorageObjectVersion
17
+
18
+ # @return [Integer] The ordinal version number
19
+ attr_accessor :version_id
20
+
21
+ # @return [String] The "v0001" directory name derived from the version id
22
+ attr_accessor :version_name
23
+
24
+ # @return [Pathname] The location of the version inside the home directory
25
+ attr_accessor :version_pathname
26
+
27
+ # @return [Pathname] The location of the object's home directory
28
+ attr_accessor :storage_object
29
+
30
+ # @return [Hash<FileInventory>] Cached copies of versionInventory, versionAdditions, or manifestInventory
31
+ attr_accessor :inventory_cache
32
+
33
+ # @param storage_object [StorageObject] The object representing the digital object's storage location
34
+ # @param version_id [Integer,String] The ordinal version number or a string like 'v0003'
35
+ def initialize(storage_object, version_id)
36
+ if version_id.is_a?(Integer)
37
+ @version_id = version_id
38
+ elsif version_id.is_a?(String) and version_id.match /^v(\d+)$/
39
+ @version_id = version_id.sub(/^v/,'').to_i
40
+ else
41
+ raise "version_id (#{version_id}) is not in a recognized format"
42
+ end
43
+ @version_name = StorageObject.version_dirname(@version_id)
44
+ @version_pathname = storage_object.object_pathname.join(@version_name)
45
+ @storage_object=storage_object
46
+ @inventory_cache = Hash.new
47
+ end
48
+
49
+ # @return [String] The unique identifier concatenating digital object id with version id
50
+ def composite_key
51
+ @storage_object.digital_object_id + '-' + StorageObject.version_dirname(@version_id)
52
+ end
53
+
54
+ # @return [Boolean] true if the object version directory exists
55
+ def exist?
56
+ @version_pathname.exist?
57
+ end
58
+
59
+ # @param [String] file_category The category of file ('content', 'metadata', or 'manifest'))
60
+ # @param [String] file_id The name of the file (path relative to base directory)
61
+ # @return [FileSignature] signature of the specified file
62
+ def find_signature(file_category, file_id)
63
+ if file_category =~ /manifest/
64
+ file_inventory('manifests').file_signature('manifests',file_id)
65
+ else
66
+ file_inventory('version').file_signature(file_category, file_id)
67
+ end
68
+ end
69
+
70
+ # @param [String] file_category The category of file ('content', 'metadata', or 'manifest')
71
+ # @param [String] file_id The name of the file (path relative to base directory)
72
+ # @return [Pathname] Pathname object containing the full path for the specified file
73
+ def find_filepath(file_category, file_id)
74
+ this_version_filepath = file_pathname(file_category, file_id)
75
+ return this_version_filepath if this_version_filepath.exist?
76
+ raise FileNotFoundException, "manifest file #{file_id} not found for #{@storage_object.digital_object_id} - #{@version_id}" if file_category == 'manifest'
77
+ file_signature = file_inventory('version').file_signature(file_category, file_id)
78
+ catalog_filepath = signature_catalog.catalog_filepath(file_signature)
79
+ @storage_object.storage_filepath(catalog_filepath)
80
+ end
81
+
82
+ # @param [String] file_category The category of file ('content', 'metadata', or 'manifest')
83
+ # @param [FileSignature] file_signature The signature of the file
84
+ # @return [Pathname] Pathname object containing the full path for the specified file
85
+ def find_filepath_using_signature(file_category, file_signature)
86
+ catalog_filepath = signature_catalog.catalog_filepath(file_signature)
87
+ @storage_object.storage_filepath(catalog_filepath)
88
+ end
89
+
90
+ # @param [String] file_category The category of file ('content', 'metadata', or 's')
91
+ # @param [String] file_id The name of the file (path relative to base directory)
92
+ # @return [Pathname] Pathname object containing this version's storage path for the specified file
93
+ def file_pathname(file_category, file_id)
94
+ file_category_pathname(file_category).join(file_id)
95
+ end
96
+
97
+ # @param [String] file_category The category of file ('content', 'metadata', or 's')
98
+ # @return [Pathname] Pathname object containing this version's storage home for the specified file category
99
+ def file_category_pathname(file_category)
100
+ if file_category =~ /manifest/
101
+ @version_pathname.join('manifests')
102
+ else
103
+ @version_pathname.join('data',file_category)
104
+ end
105
+ end
106
+
107
+ # @api external
108
+ # @param type [String] The type of inventory to return (version|additions|manifests)
109
+ # @return [FileInventory] The file inventory of the specified type for this version
110
+ # @see FileInventory#read_xml_file
111
+ def file_inventory(type)
112
+ if version_id > 0
113
+ return @inventory_cache[type] if @inventory_cache.has_key?(type)
114
+ @inventory_cache[type] = FileInventory.read_xml_file(@version_pathname.join('manifests'), type)
115
+ else
116
+ groups = ['content','metadata'].collect { |id| FileGroup.new(:group_id=>id)}
117
+ FileInventory.new(
118
+ :type=>'version',
119
+ :digital_object_id => @storage_object.digital_object_id,
120
+ :version_id => @version_id,
121
+ :groups => groups
122
+ )
123
+ end
124
+ end
125
+
126
+ # @api external
127
+ # @return [SignatureCatalog] The signature catalog of the digital object as of this version
128
+ def signature_catalog
129
+ if version_id > 0
130
+ SignatureCatalog.read_xml_file(@version_pathname.join('manifests'))
131
+ else
132
+ SignatureCatalog.new(:digital_object_id => @storage_object.digital_object_id)
133
+ end
134
+ end
135
+
136
+ # @api internal
137
+ # @param bag_dir [Pathname,String] The location of the bag to be ingested
138
+ # @return [void] Create the version subdirectory and move files into it
139
+ def ingest_bag_data(bag_dir)
140
+ raise "Version already exists: #{@version_pathname.to_s}" if @version_pathname.exist?
141
+ @version_pathname.join('manifests').mkpath
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'))
146
+ end
147
+
148
+ # @api internal
149
+ # @param source_dir [Pathname] The source location of the directory whose contents are to be ingested
150
+ # @param target_dir [Pathname] The target location of the directory into which files are ingested
151
+ # @param use_links [Boolean] If true, use hard links; if false, make copies
152
+ # @return [void] recursively link or copy the source directory contents to the target directory
153
+ def ingest_dir(source_dir, target_dir, use_links=true)
154
+ raise "cannot copy - target already exists: #{target_dir.expand_path}" if target_dir.exist?
155
+ target_dir.mkpath
156
+ source_dir.children.each do |child|
157
+ if child.directory?
158
+ ingest_dir(child, target_dir.join(child.basename), use_links)
159
+ else
160
+ ingest_file(child, target_dir, use_links)
161
+ end
162
+ end
163
+ end
164
+
165
+ # @api internal
166
+ # @param source_file [Pathname] The source location of the file to be ingested
167
+ # @param target_dir [Pathname] The location of the directory in which to place the file
168
+ # @param use_links [Boolean] If true, use hard links; if false, make copies
169
+ # @return [void] link or copy the specified file from source location to the version directory
170
+ def ingest_file(source_file, target_dir, use_links=true)
171
+ if use_links
172
+ FileUtils.link(source_file.to_s, target_dir.to_s) #, :force => true)
173
+ else
174
+ FileUtils.copy(source_file.to_s, target_dir.to_s)
175
+ end
176
+ end
177
+
178
+ # @api internal
179
+ # @param signature_catalog [SignatureCatalog] The current version's catalog
180
+ # @param new_inventory [FileInventory] The new version's inventory
181
+ # @return [void] Updates the catalog to include newly added files, then saves it to disk
182
+ # @see SignatureCatalog#update
183
+ def update_catalog(signature_catalog,new_inventory)
184
+ signature_catalog.update(new_inventory, @version_pathname.join('data'))
185
+ signature_catalog.write_xml_file(@version_pathname.join('manifests'))
186
+ end
187
+
188
+ # @api internal
189
+ # @param old_inventory [FileInventory] The old version's inventory
190
+ # @param new_inventory [FileInventory] The new version's inventory
191
+ # @return [void] generate a file inventory differences report and save to disk
192
+ def generate_differences_report(old_inventory,new_inventory)
193
+ differences = FileInventoryDifference.new.compare(old_inventory, new_inventory)
194
+ differences.write_xml_file(@version_pathname.join('manifests'))
195
+ end
196
+
197
+ # @api internal
198
+ # @return [void] examine the version's directory and create/serialize a {FileInventory} containing the manifest files
199
+ def generate_manifest_inventory
200
+ manifest_inventory = FileInventory.new(
201
+ :type=>'manifests',
202
+ :digital_object_id=>@storage_object.digital_object_id,
203
+ :version_id=>@version_id)
204
+ manifest_inventory.groups << FileGroup.new(:group_id=>'manifests').group_from_directory(@version_pathname.join('manifests'), recursive=false)
205
+ manifest_inventory.write_xml_file(@version_pathname.join('manifests'))
206
+ end
207
+
208
+ # @return [VerificationResult] return result of testing correctness of version manifests
209
+ def verify_version_storage()
210
+ result = VerificationResult.new(self.composite_key)
211
+ result.subentities << self.verify_manifest_inventory
212
+ result.subentities << self.verify_version_inventory
213
+ result.subentities << self.verify_version_additions
214
+ result.verified = result.subentities.all?{|entity| entity.verified}
215
+ result
216
+ end
217
+
218
+ # @return [Boolean] return true if the manifest inventory matches the actual files
219
+ def verify_manifest_inventory
220
+ # read/parse manifestInventory.xml
221
+ result = VerificationResult.new("manifest_inventory")
222
+ manifest_inventory = self.file_inventory('manifests')
223
+ result.subentities << VerificationResult.verify_value('composite_key',self.composite_key,manifest_inventory.composite_key)
224
+ result.subentities << VerificationResult.verify_truth('manifests_group', ! manifest_inventory.group_empty?('manifests'))
225
+ # measure the manifest signatures of the files in the directory (excluding manifestInventory.xml)
226
+ directory_inventory = FileInventory.new.inventory_from_directory(@version_pathname.join('manifests'),'manifests')
227
+ directory_inventory.digital_object_id = storage_object.digital_object_id
228
+ directory_group = directory_inventory.group('manifests')
229
+ directory_group.remove_file_having_path("manifestInventory.xml")
230
+ # compare the measured signatures against the values in manifestInventory.xml
231
+ diff = FileInventoryDifference.new
232
+ diff.compare(manifest_inventory,directory_inventory)
233
+ compare_result = VerificationResult.new('file_differences')
234
+ compare_result.verified = (diff.difference_count == 0)
235
+ compare_result.details = diff.differences_detail
236
+ result.subentities << compare_result
237
+ result.verified = result.subentities.all?{|entity| entity.verified}
238
+ result
239
+ end
240
+
241
+ def verify_signature_catalog
242
+ result = VerificationResult.new("signature_catalog")
243
+ signature_catalog =self.signature_catalog
244
+ result.subentities << VerificationResult.verify_value('signature_key',self.composite_key,signature_catalog.composite_key)
245
+ found = 0
246
+ missing = Array.new
247
+ object_pathname = self.storage_object.object_pathname
248
+ signature_catalog.entries.each do |catalog_entry|
249
+ storage_location = object_pathname.join(catalog_entry.storage_path)
250
+ if storage_location.exist?
251
+ found += 1
252
+ else
253
+ missing << storage_location.to_s
254
+ end
255
+ end
256
+ file_result = VerificationResult.new("storage_location")
257
+ file_result.verified = (found == signature_catalog.file_count)
258
+ file_result.details = {
259
+ 'expected' => signature_catalog.file_count,
260
+ 'found' => found
261
+ }
262
+ file_result.details['missing'] = missing unless missing.empty?
263
+ result.subentities << file_result
264
+ result.verified = result.subentities.all?{|entity| entity.verified}
265
+ result
266
+ end
267
+
268
+ # @return [Boolean] true if files & signatures listed in version inventory can all be found
269
+ def verify_version_inventory
270
+ result = VerificationResult.new("version_inventory")
271
+ version_inventory = self.file_inventory('version')
272
+ result.subentities << VerificationResult.verify_value('inventory_key',self.composite_key,version_inventory.composite_key)
273
+ signature_catalog =self.signature_catalog
274
+ result.subentities << VerificationResult.verify_value('signature_key',self.composite_key,signature_catalog.composite_key)
275
+ found = 0
276
+ missing = Array.new
277
+ version_inventory.groups.each do |group|
278
+ group.files.each do |file|
279
+ file.instances.each do |instance|
280
+ relative_path = File.join(group.group_id, instance.path)
281
+ catalog_entry = signature_catalog.signature_hash[file.signature]
282
+ if ! catalog_entry.nil?
283
+ found += 1
284
+ else
285
+ missing << relative_path.to_s
286
+ end
287
+ end
288
+ end
289
+ end
290
+ file_result = VerificationResult.new("catalog_entry")
291
+ file_result.verified = (found == version_inventory.file_count)
292
+ file_result.details = {
293
+ 'expected' => version_inventory.file_count,
294
+ 'found' => found
295
+ }
296
+ file_result.details['missing'] = missing unless missing.empty?
297
+
298
+ result.subentities << file_result
299
+ result.verified = result.subentities.all?{|entity| entity.verified}
300
+ result
301
+ end
302
+
303
+ # @return [Boolean] returns true if files in data folder match files listed in version addtions inventory
304
+ def verify_version_additions
305
+ result = VerificationResult.new("version_additions")
306
+ version_additions = self.file_inventory('additions')
307
+ result.subentities << VerificationResult.verify_value('composite_key',self.composite_key,version_additions.composite_key)
308
+ data_directory = @version_pathname.join('data')
309
+ directory_inventory = FileInventory.new(:type=>'directory').inventory_from_directory(data_directory)
310
+ diff = FileInventoryDifference.new
311
+ diff.compare(version_additions, directory_inventory)
312
+ compare_result = VerificationResult.new('file_differences')
313
+ compare_result.verified = (diff.difference_count == 0)
314
+ compare_result.details = diff.differences_detail
315
+ result.subentities << compare_result
316
+ result.verified = result.subentities.all?{|entity| entity.verified}
317
+ result
318
+ end
319
+
320
+ # @param timestamp [Time] The time at which the deactivation was initiated. Used to name the inactive directory
321
+ # @return [null] Deactivate this object version by moving it to another directory. (Used by restore operation)
322
+ def deactivate(timestamp)
323
+ if @version_pathname.exist?
324
+ timestamp_pathname = @version_pathname.parent.join(timestamp.utc.iso8601.gsub(/[-:]/,''))
325
+ timestamp_pathname.mkpath
326
+ demote_pathame = timestamp_pathname.join(@version_pathname.basename)
327
+ @version_pathname.rename(demote_pathame)
328
+ end
329
+ end
330
+
331
+ end
332
+
333
+ end