moab-versioning 1.3.0 → 1.3.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 CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: fe7dbe157c6bc9418f8b336b65431a1266ca4375
4
- data.tar.gz: 5328ef34ef060962cf9598001c6d146cde7afdc3
5
- SHA512:
6
- metadata.gz: 79893b2a0f5b5d5791cee7ef9d26213d2f6876ea32f71a258d6d3030982fa8a6824f5285de20e343775ca1dc4625cbc16d32c793182116f830c7a4065dd5c5ce
7
- data.tar.gz: e73bae98354c6001887a6a1fa24589919e696596c74ffdaac0b3132b123c761fea0850e5000d3795443af49b696a3e4cbc92567d4ccb7fa5020f1cbfa7aeb191
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ ZjAyMmUwOGNiOGYzN2U4ZGQyMDNkNGE2ZDM5Y2IxMTc0OTdiZDY0Yg==
5
+ data.tar.gz: !binary |-
6
+ NDlmOGZjNmY1ZDZjMjk0ODIyNTYwYjk5Njk5ZTYwMjE2NGY1MGM0ZA==
7
+ !binary "U0hBNTEy":
8
+ metadata.gz: !binary |-
9
+ NTU0MjllMzVmNzk4NGIyNzczN2I5MzU5ODkwNGViZTlmMjAyM2MxZTQwMTI5
10
+ OTUxZTg4NTk2MTdiYzM1NjYwOGU3OGE4NmUyODExNjU1YmY3OThkMzU1MGJh
11
+ YTIzOWZmOGM0N2ZiZTNjODEwMDY4MjZiZjFhN2MzZTBiNTA4ZGI=
12
+ data.tar.gz: !binary |-
13
+ ZDE2NDAwOWU0YzJkOTRiNWY4ZGJmZDY4OGE3MjNhZmFjMmUzMTkyY2JkYTkx
14
+ NTk3ZmRiOGFmMGJkNmY3NmY2YWFkODBiMWQyOWYwM2JiNDQyMmQ5YTdmYWFl
15
+ MTU1MGZjZTBkNmNkM2I3ZjdhN2Y0MDlkMzA2ZGM3ZTdjNWY5ZTQ=
data/lib/moab/config.rb CHANGED
@@ -2,19 +2,11 @@ require 'moab'
2
2
 
3
3
  module Moab
4
4
 
5
- #class Configuration < Confstruct::Configuration
6
- #
7
- # def configure(*args, &block)
8
- # super(*args, &block)
9
- #
10
- # # Whatever you want to do after configuration
11
- # # Something.initialize(self.repository_home)
12
- # end
13
- #end
14
-
15
5
  # @return [Confstruct::Configuration] the configuration data
16
6
  Config = Confstruct::Configuration.new do
17
- repository_home nil
7
+ storage_roots nil
8
+ storage_trunk nil
9
+ deposit_trunk nil
18
10
  path_method :druid_tree
19
11
  end
20
12
 
@@ -21,9 +21,11 @@ module Moab
21
21
  # are further categorized as follows:
22
22
  # * <i>identical</i> = signature and file path is the same in both basis and other file group
23
23
  # * <i>renamed</i> = signature is unchanged, but the path has moved
24
- # * <i>modified</i> = path is present in both groups, but the signature has changed
25
- # * <i>deleted</i> = signature and path are only in the basis inventory
24
+ # * <i>copyadded</i> = duplicate copy of file was added
25
+ # * <i>copydeleted</i> = duplicate copy of file was deleted
26
+ # * <i>modified</i> = path is same in both groups, but the signature has changed
26
27
  # * <i>added</i> = signature and path are only in the other inventor
28
+ # * <i>deleted</i> = signature and path are only in the basis inventory
27
29
  #
28
30
  # ====Data Model
29
31
  # * {FileInventoryDifference} = compares two {FileInventory} instances based on file signatures and pathnames
@@ -40,9 +42,19 @@ module Moab
40
42
  # The name of the XML element used to serialize this objects data
41
43
  tag 'fileGroupDifference'
42
44
 
45
+ # @return [Hash<Symbol,FileGroupDifferenceSubset>] A set of containers (one for each change type),
46
+ # each of which contains a collection of file-level differences having that change type.
47
+ attr_accessor :subset_hash
48
+
49
+ # @param change [String] the change type to search for
50
+ # @return [FileGroupDifferenceSubset] Find a specified subset of changes
51
+ def subset(change)
52
+ @subset_hash[change.to_sym]
53
+ end
54
+
43
55
  # (see Serializable#initialize)
44
56
  def initialize(opts={})
45
- @subsets = Array.new
57
+ @subset_hash = OrderedHash.new {|hash, key| hash[key] = FileGroupDifferenceSubset.new(:change => key.to_s)}
46
58
  super(opts)
47
59
  end
48
60
 
@@ -55,43 +67,80 @@ module Moab
55
67
  attribute :difference_count, Integer, :tag => 'differenceCount', :on_save => Proc.new { |i| i.to_s }
56
68
 
57
69
  def difference_count
58
- @renamed + @modified + @deleted +@added
70
+ count = 0
71
+ @subset_hash.each do |type,subset|
72
+ count += subset.count if type != :identical
73
+ end
74
+ count
59
75
  end
60
76
 
61
77
  # @attribute
62
78
  # @return [Integer] How many files were unchanged
63
79
  attribute :identical, Integer, :on_save => Proc.new { |n| n.to_s }
80
+ def identical
81
+ @subset_hash[:identical].count
82
+ end
83
+
84
+ # @attribute
85
+ # @return [Integer] How many duplicate copies of files were added
86
+ attribute :copyadded, Integer, :on_save => Proc.new { |n| n.to_s }
87
+ def copyadded
88
+ @subset_hash[:copyadded].count
89
+ end
90
+
91
+ # @attribute
92
+ # @return [Integer] How many duplicate copies of files were deleted
93
+ attribute :copydeleted, Integer, :on_save => Proc.new { |n| n.to_s }
94
+ def copydeleted
95
+ @subset_hash[:copydeleted].count
96
+ end
64
97
 
65
98
  # @attribute
66
99
  # @return [Integer] How many files were renamed
67
100
  attribute :renamed, Integer, :on_save => Proc.new { |n| n.to_s }
101
+ def renamed
102
+ @subset_hash[:renamed].count
103
+ end
68
104
 
69
105
  # @attribute
70
106
  # @return [Integer] How many files were modified
71
107
  attribute :modified, Integer, :on_save => Proc.new { |n| n.to_s }
72
-
73
- # @attribute
74
- # @return [Integer] How many files were deleted
75
- attribute :deleted, Integer, :on_save => Proc.new { |n| n.to_s }
108
+ def modified
109
+ @subset_hash[:modified].count
110
+ end
76
111
 
77
112
  # @attribute
78
113
  # @return [Integer] How many files were added
79
114
  attribute :added, Integer, :on_save => Proc.new { |n| n.to_s }
115
+ def added
116
+ @subset_hash[:added].count
117
+ end
118
+
119
+ # @attribute
120
+ # @return [Integer] How many files were deleted
121
+ attribute :deleted, Integer, :on_save => Proc.new { |n| n.to_s }
122
+ def deleted
123
+ @subset_hash[:deleted].count
124
+ end
80
125
 
81
126
  # @attribute
82
127
  # @return [Array<FileGroupDifferenceSubset>] A set of Arrays (one for each change type),
83
128
  # each of which contains an collection of file-level differences having that change type.
84
129
  has_many :subsets, FileGroupDifferenceSubset, :tag => 'subset'
85
130
 
86
- # @param change [String] the change type to search for
87
- # @return [FileGroupDifferenceSubset] Find a specified subset of changes
88
- def subset(change)
89
- @subsets.find{ |subset| subset.change == change}
131
+ def subsets
132
+ @subset_hash.values
133
+ end
134
+
135
+ def subsets=(array)
136
+ if array
137
+ array.each{|subset| @subset_hash[subset.change.to_sym] = subset}
138
+ end
90
139
  end
91
140
 
92
141
  # @return [Array<String>] The data fields to include in summary reports
93
142
  def summary_fields
94
- %w{group_id difference_count identical renamed modified deleted added}
143
+ %w{group_id difference_count identical copyadded copydeleted renamed modified deleted added}
95
144
  end
96
145
 
97
146
 
@@ -99,49 +148,17 @@ module Moab
99
148
  # @return [FileGroupDifference] Clone just this element for inclusion in a versionMetadata structure
100
149
  def summary()
101
150
  FileGroupDifference.new(
102
- :group_id => @group_id,
103
- :identical => @identical,
104
- :renamed => @renamed,
105
- :modified => @modified,
106
- :deleted => @deleted,
107
- :added => @added
151
+ :group_id => group_id,
152
+ :identical => identical,
153
+ :copyadded => copyadded,
154
+ :copydeleted => copydeleted,
155
+ :renamed => renamed,
156
+ :modified => modified,
157
+ :added => added,
158
+ :deleted => deleted
108
159
  )
109
160
  end
110
161
 
111
-
112
- # @return [Hash<Symbol,Array>] Sets of filenames grouped by change type for use in performing file or metadata operations
113
- def file_deltas()
114
- # The hash to be returned
115
- deltas = Hash.new
116
- # Container for a files whose checksums matched across versions, but may have copies removed, added, or renamed
117
- copied = Hash.new {|hash, key| hash[key] = {:basis=>Array.new , :other=>Array.new} }
118
- # Capture the filename data
119
- @subsets.each do |subset|
120
- case subset.change
121
- when "added"
122
- deltas[:added] = subset.files.collect {|file| file.other_path}
123
- when "deleted"
124
- deltas[:deleted] = subset.files.collect {|file| file.basis_path}
125
- when "modified"
126
- deltas[:modified] = subset.files.collect {|file| file.basis_path}
127
- when "identical"
128
- subset.files.each do |instance|
129
- signature = instance.signatures[0]
130
- copied[signature][:basis] << instance.basis_path
131
- copied[signature][:other] << instance.basis_path
132
- end
133
- when "renamed"
134
- subset.files.each do |instance|
135
- signature = instance.signatures[0]
136
- copied[signature][:basis] << instance.basis_path unless (instance.basis_path.nil? or instance.basis_path.empty?)
137
- copied[signature][:other] << instance.other_path unless (instance.other_path.nil? or instance.other_path.empty?)
138
- end
139
- end
140
- end
141
- deltas[:copied] = copied.values
142
- deltas
143
- end
144
-
145
162
  # @api internal
146
163
  # @param basis_hash [Hash] The first hash being compared
147
164
  # @param other_hash [Hash] The second hash being compared
@@ -177,17 +194,18 @@ module Moab
177
194
 
178
195
  # @api internal
179
196
  # @param (see #compare_file_groups)
180
- # @return [void] For signatures that are present in both groups,
197
+ # @return [FileGroupDifference] For signatures that are present in both groups,
181
198
  # report which file instances are identical or renamed
182
199
  def compare_matching_signatures(basis_group, other_group)
183
200
  matching_signatures = matching_keys(basis_group.signature_hash, other_group.signature_hash)
184
201
  tabulate_unchanged_files(matching_signatures, basis_group.signature_hash, other_group.signature_hash)
185
202
  tabulate_renamed_files(matching_signatures, basis_group.signature_hash, other_group.signature_hash)
203
+ self
186
204
  end
187
205
 
188
206
  # @api internal
189
207
  # @param (see #compare_file_groups)
190
- # @return [void] For signatures that are present in only one or the other group,
208
+ # @return [FileGroupDifference] For signatures that are present in only one or the other group,
191
209
  # report which file instances are modified, deleted, or added
192
210
  def compare_non_matching_signatures(basis_group, other_group)
193
211
  basis_only_signatures = basis_only_keys(basis_group.signature_hash, other_group.signature_hash)
@@ -195,8 +213,9 @@ module Moab
195
213
  basis_path_hash = basis_group.path_hash_subset(basis_only_signatures)
196
214
  other_path_hash = other_group.path_hash_subset(other_only_signatures)
197
215
  tabulate_modified_files(basis_path_hash, other_path_hash)
198
- tabulate_deleted_files(basis_path_hash, other_path_hash)
199
216
  tabulate_added_files(basis_path_hash, other_path_hash)
217
+ tabulate_deleted_files(basis_path_hash, other_path_hash)
218
+ self
200
219
  end
201
220
 
202
221
  # @api internal
@@ -205,10 +224,9 @@ module Moab
205
224
  # Signature to file path mapping from the file group that is the basis of the comparison
206
225
  # @param other_signature_hash [OrderedHash<FileSignature, FileManifestation>]
207
226
  # Signature to file path mapping from the file group that is the being compared to the basis group
208
- # @return [FileGroupDifferenceSubset]
227
+ # @return [FileGroupDifference]
209
228
  # Container for reporting the set of file-level differences of type 'identical'
210
229
  def tabulate_unchanged_files(matching_signatures, basis_signature_hash, other_signature_hash)
211
- unchanged_files = Array.new
212
230
  matching_signatures.each do |signature|
213
231
  basis_paths = basis_signature_hash[signature].paths
214
232
  other_paths = other_signature_hash[signature].paths
@@ -218,14 +236,10 @@ module Moab
218
236
  fid.basis_path = path
219
237
  fid.other_path = "same"
220
238
  fid.signatures << signature
221
- unchanged_files << fid
239
+ @subset_hash[:identical].files << fid
222
240
  end
223
241
  end
224
- unchanged_subset = FileGroupDifferenceSubset.new(:change => 'identical')
225
- unchanged_subset.files = unchanged_files
226
- @subsets << unchanged_subset
227
- @identical = unchanged_subset.count
228
- unchanged_subset
242
+ self
229
243
  end
230
244
 
231
245
 
@@ -235,10 +249,9 @@ module Moab
235
249
  # Signature to file path mapping from the file group that is the basis of the comparison
236
250
  # @param other_signature_hash [OrderedHash<FileSignature, FileManifestation>]
237
251
  # Signature to file path mapping from the file group that is the being compared to the basis group
238
- # @return [FileGroupDifferenceSubset]
239
- # Container for reporting the set of file-level differences of type 'renamed'
252
+ # @return [FileGroupDifference]
253
+ # Container for reporting the set of file-level differences of type 'renamed','copyadded', or 'copydeleted'
240
254
  def tabulate_renamed_files(matching_signatures, basis_signature_hash, other_signature_hash)
241
- renamed_files = Array.new
242
255
  matching_signatures.each do |signature|
243
256
  basis_paths = basis_signature_hash[signature].paths
244
257
  other_paths = other_signature_hash[signature].paths
@@ -246,18 +259,23 @@ module Moab
246
259
  other_only_paths = other_paths - basis_paths
247
260
  maxsize = [basis_only_paths.size, other_only_paths.size].max
248
261
  (0..maxsize-1).each do |n|
249
- fid = FileInstanceDifference.new(:change => 'renamed')
262
+ fid = FileInstanceDifference.new()
250
263
  fid.basis_path = basis_only_paths[n]
251
264
  fid.other_path = other_only_paths[n]
252
265
  fid.signatures << signature
253
- renamed_files << fid
266
+ case true
267
+ when fid.basis_path.nil?
268
+ fid.change = 'copyadded'
269
+ fid.basis_path = basis_paths[0]
270
+ when fid.other_path.nil?
271
+ fid.change = 'copydeleted'
272
+ else
273
+ fid.change = 'renamed'
274
+ end
275
+ @subset_hash[fid.change.to_sym].files << fid
254
276
  end
255
277
  end
256
- renamed_subset = FileGroupDifferenceSubset.new(:change => 'renamed')
257
- renamed_subset.files = renamed_files
258
- @subsets << renamed_subset
259
- @renamed = renamed_subset.count
260
- renamed_subset
278
+ self
261
279
  end
262
280
 
263
281
 
@@ -266,23 +284,18 @@ module Moab
266
284
  # The file paths and associated signatures for manifestations appearing only in the basis group
267
285
  # @param other_path_hash [OrderedHash<String,FileSignature>]
268
286
  # The file paths and associated signatures for manifestations appearing only in the other group
269
- # @return [FileGroupDifferenceSubset]
287
+ # @return [FileGroupDifference]
270
288
  # Container for reporting the set of file-level differences of type 'modified'
271
289
  def tabulate_modified_files(basis_path_hash, other_path_hash)
272
- modified_files = Array.new
273
290
  matching_keys(basis_path_hash, other_path_hash).each do |path|
274
291
  fid = FileInstanceDifference.new(:change => 'modified')
275
292
  fid.basis_path = path
276
293
  fid.other_path = "same"
277
294
  fid.signatures << basis_path_hash[path]
278
295
  fid.signatures << other_path_hash[path]
279
- modified_files << fid
296
+ @subset_hash[:modified].files << fid
280
297
  end
281
- modified_subset = FileGroupDifferenceSubset.new(:change => 'modified')
282
- modified_subset.files = modified_files
283
- @subsets << modified_subset
284
- @modified = modified_subset.count
285
- modified_subset
298
+ self
286
299
  end
287
300
 
288
301
  # @api internal
@@ -290,45 +303,78 @@ module Moab
290
303
  # The file paths and associated signatures for manifestations appearing only in the basis group
291
304
  # @param other_path_hash [OrderedHash<String,FileSignature>]
292
305
  # The file paths and associated signatures for manifestations appearing only in the other group
293
- # @return [FileGroupDifferenceSubset]
306
+ # @return [FileGroupDifference]
307
+ # Container for reporting the set of file-level differences of type 'added'
308
+ def tabulate_added_files(basis_path_hash, other_path_hash)
309
+ other_only_keys(basis_path_hash, other_path_hash).each do |path|
310
+ fid = FileInstanceDifference.new(:change => 'added')
311
+ fid.basis_path = ""
312
+ fid.other_path = path
313
+ fid.signatures << other_path_hash[path]
314
+ @subset_hash[:added].files << fid
315
+ end
316
+ self
317
+ end
318
+
319
+ # @api internal
320
+ # @param basis_path_hash [OrderedHash<String,FileSignature>]
321
+ # The file paths and associated signatures for manifestations appearing only in the basis group
322
+ # @param other_path_hash [OrderedHash<String,FileSignature>]
323
+ # The file paths and associated signatures for manifestations appearing only in the other group
324
+ # @return [FileGroupDifference]
294
325
  # Container for reporting the set of file-level differences of type 'deleted'
295
326
  def tabulate_deleted_files(basis_path_hash, other_path_hash)
296
- deleted_files = Array.new
297
327
  basis_only_keys(basis_path_hash, other_path_hash).each do |path|
298
328
  fid = FileInstanceDifference.new(:change => 'deleted')
299
329
  fid.basis_path = path
300
330
  fid.other_path = ""
301
331
  fid.signatures << basis_path_hash[path]
302
- deleted_files << fid
332
+ @subset_hash[:deleted].files << fid
303
333
  end
304
- deleted_subset = FileGroupDifferenceSubset.new(:change => 'deleted')
305
- deleted_subset.files = deleted_files
306
- @subsets << deleted_subset
307
- @deleted = deleted_subset.count
308
- deleted_subset
334
+ self
309
335
  end
310
336
 
311
- # @api internal
312
- # @param basis_path_hash [OrderedHash<String,FileSignature>]
313
- # The file paths and associated signatures for manifestations appearing only in the basis group
314
- # @param other_path_hash [OrderedHash<String,FileSignature>]
315
- # The file paths and associated signatures for manifestations appearing only in the other group
316
- # @return [FileGroupDifferenceSubset]
317
- # Container for reporting the set of file-level differences of type 'added'
318
- def tabulate_added_files(basis_path_hash, other_path_hash)
319
- added_files = Array.new
320
- other_only_keys(basis_path_hash, other_path_hash).each do |path|
321
- fid = FileInstanceDifference.new(:change => 'added')
322
- fid.basis_path = ""
323
- fid.other_path = path
324
- fid.signatures << other_path_hash[path]
325
- added_files << fid
337
+ # @return [Hash<Symbol,Array>] Sets of filenames grouped by change type for use in performing file or metadata operations
338
+ def file_deltas()
339
+ # The hash to be returned
340
+ deltas = Hash.new {|hash, key| hash[key] = []}
341
+ # case where other_path is empty or 'same'. (create array of strings)
342
+ [:identical, :modified, :deleted, :copydeleted].each do |change|
343
+ deltas[change].concat @subset_hash[change].files.collect{|file| file.basis_path}
326
344
  end
327
- added_subset = FileGroupDifferenceSubset.new(:change => 'added')
328
- added_subset.files = added_files
329
- @subsets << added_subset
330
- @added = added_subset.count
331
- added_subset
345
+ # case where basis_path and other_path are both present. (create array of arrays)
346
+ [:copyadded, :renamed].each do |change|
347
+ deltas[change].concat @subset_hash[change].files.collect{|file| [file.basis_path,file.other_path]}
348
+ end
349
+ # case where basis_path is empty. (create array of strings)
350
+ [:added].each do |change|
351
+ deltas[change].concat @subset_hash[change].files.collect{|file| file.other_path}
352
+ end
353
+ deltas
354
+ end
355
+
356
+ # @param [Array<Array<String>>] filepairs The set of oldname, newname pairs for all files being renamed
357
+ # @return [Boolean] Test whether any of the new names are the same as one of the old names,
358
+ # such as would be true for insertion of a new file into a page sequence, or a circular rename.
359
+ # In such a case, return true, indicating that use of intermediate temporary files would be required
360
+ # when updating a copy of an object's files at a given location.
361
+ def rename_require_temp_files(filepairs)
362
+ # Split the filepairs into two arrays
363
+ oldnames = []
364
+ newnames = []
365
+ filepairs.each do |old,new|
366
+ oldnames << old
367
+ newnames << new
368
+ end
369
+ # Are any of the filenames the same in set of oldnames and set of newnames?
370
+ intersection = oldnames & newnames
371
+ intersection.count > 0
372
+ end
373
+
374
+ # @param [Array<Array<String>>] filepairs The set of oldname, newname pairs for all files being renamed
375
+ # @return [Array<Array<String>>] a set of file triples containing oldname, tempname, newname
376
+ def rename_tempfile_triplets(filepairs)
377
+ filepairs.collect{|old,new| [old, new, "#{new}-#{Time.now.strftime('%Y%m%d%H%H%S')}-tmp"]}
332
378
  end
333
379
 
334
380
  end
@@ -25,6 +25,9 @@ module Moab
25
25
  # @return [Pathname] The location of the object's storage home directory
26
26
  attr_accessor :object_pathname
27
27
 
28
+ # @return [Pathname] The location of the storage filesystem that contains (or will contain) the object
29
+ attr_accessor :storage_root
30
+
28
31
  # @param object_id [String] The digital object identifier
29
32
  # @param object_dir [Pathname,String] The location of the object's storage home directory
30
33
  def initialize(object_id, object_dir, mkpath=false)
@@ -44,12 +47,22 @@ module Moab
44
47
  @object_pathname.mkpath
45
48
  end
46
49
 
50
+ # @return [Pathname] The absolute location of the area in which bags are deposited
51
+ def deposit_home
52
+ storage_root.join(StorageServices.deposit_trunk)
53
+ end
54
+
55
+ # @return [Pathname] The absolute location of this object's deposit bag
56
+ def deposit_bag_pathname
57
+ deposit_home.join(StorageServices.deposit_branch(digital_object_id))
58
+ end
59
+
47
60
  # @api external
48
61
  # @param bag_dir [Pathname,String] The location of the bag to be ingested
49
62
  # @return [void] Ingest a new object version contained in a bag into this objects storage area
50
63
  # @example {include:file:spec/features/storage/ingest_spec.rb}
51
- def ingest_bag(bag_dir)
52
- bag_dir = Pathname.new(bag_dir)
64
+ def ingest_bag(bag_dir=deposit_bag_pathname)
65
+ bag_dir = Pathname(bag_dir)
53
66
  current_version = StorageObjectVersion.new(self,current_version_id)
54
67
  current_inventory = current_version.file_inventory('version')
55
68
  new_version = StorageObjectVersion.new(self,current_version_id + 1)
@@ -116,6 +129,7 @@ module Moab
116
129
  # @return [Array<Integer>] The list of all version ids for this object
117
130
  def version_id_list
118
131
  list = Array.new
132
+ return list unless @object_pathname.exist?
119
133
  @object_pathname.children.each do |dirname|
120
134
  vnum = dirname.basename.to_s
121
135
  if vnum.match /^v(\d+)$/
@@ -5,7 +5,7 @@ module Moab
5
5
  # A class to represent the SDR repository store
6
6
  #
7
7
  # ====Data Model
8
- # * <b>{StorageRepository} = represents a digital object repository storage node</b>
8
+ # * <b>{StorageRepository} = represents the digital object repository storage areas</b>
9
9
  # * {StorageServices} = supports application layer access to the repository's objects, data, and metadata
10
10
  # * {StorageObject} = represents a digital object's repository storage location and ingest/dissemination methods
11
11
  # * {StorageObjectVersion} [1..*] = represents a version subdirectory within an object's home directory
@@ -15,35 +15,109 @@ module Moab
15
15
  # All rights reserved. See {file:LICENSE.rdoc} for details.
16
16
  class StorageRepository
17
17
 
18
- # @return [Pathname] The location of the root directory of the repository storage node
19
- def repository_home
20
- Pathname.new(Moab::Config.repository_home)
18
+ #Note that Moab::Config is not initialized from environment config file until after this object is initialized by StorageServices
19
+ # (see sequence of requires in spec_helper.rb and in applications that use)
20
+
21
+ # @return [Array<Pathname>] The list of filesystem root paths in which objects are stored
22
+ def storage_roots
23
+ unless defined?(@storage_roots)
24
+ raise "Moab::Config.storage_roots not found in config file" if Moab::Config.storage_roots.nil?
25
+ @storage_roots = [Moab::Config.storage_roots].flatten.collect{|filesystem| Pathname(filesystem)}
26
+ raise "Moab::Config.storage_roots empty" if @storage_roots.empty?
27
+ @storage_roots.each{|root| raise "Storage root #{root} not found on system" unless root.exist?}
28
+ end
29
+ @storage_roots
21
30
  end
22
31
 
23
- # @param object_id [String] The identifier of the digital object whose version is desired
24
- # @return [StorageObject] The representation of the desired object storage directory
25
- def storage_object(object_id, create=false)
26
- object_pathname = storage_object_pathname(object_id)
27
- if object_pathname.exist?
28
- StorageObject.new(object_id, object_pathname)
29
- elsif create
30
- object_pathname.mkpath
31
- StorageObject.new(object_id, object_pathname)
32
- else
33
- raise Moab::ObjectNotFoundException, "No object found for #{object_id} at #{object_pathname}"
32
+ # @return [String] The trunk segment of the object storage path
33
+ def storage_trunk
34
+ unless defined?(@storage_trunk)
35
+ raise "oab::Config.storage_trunk not found in config file" if Moab::Config.storage_trunk.nil?
36
+ @storage_trunk = Moab::Config.storage_trunk
34
37
  end
38
+ @storage_trunk
35
39
  end
36
40
 
37
- # @param object_id [String] The identifier of the digital object whose version is desired
38
- # @return [Pathname] The location of the desired object's home directory (default=pairtree)
39
- def storage_object_pathname(object_id)
41
+ # @param object_id [String] The identifier of the digital object
42
+ # @return [String] The branch segment of the object storage path
43
+ def storage_branch(object_id)
40
44
  #todo This method should be customized, or overridden in a subclass
45
+ # split a object ID into 2-character segments, followed by a copy of the object ID
41
46
  # for a more sophisticated pairtree implementation see https://github.com/microservices/pairtree
42
47
  path_segments = object_id.scan(/..?/) << object_id
43
- object_path = path_segments.join(File::SEPARATOR).gsub(/:/,'_')
44
- repository_home.join(object_path)
48
+ path_segments.join(File::SEPARATOR).gsub(/:/,'_')
49
+ end
50
+
51
+ # @return [String] The trunk segment of the object deposit path
52
+ def deposit_trunk
53
+ unless defined?(@deposit_trunk)
54
+ # do not raise error. this parameter will be ignored if missing
55
+ # raise "Moab::Config.deposit_trunk not found in config file" if Moab::Config.deposit_trunk.nil?
56
+ @deposit_trunk = Moab::Config.deposit_trunk
57
+ end
58
+ @deposit_trunk
59
+ end
60
+
61
+ # @param object_id [String] The identifier of the digital object
62
+ # @return [Pathname] The branch segment of the object deposit path
63
+ def deposit_branch(object_id)
64
+ #todo This method should be customized, or overridden in a subclass
65
+ object_id
66
+ end
67
+
68
+ # @param object_id [String] The identifier of the digital object whose version is desired
69
+ # @param include_deposit [Boolean] specifies whether to look in deposit areas for objects in process of initial ingest
70
+ # @return [Pathname] The location of the desired object's home directory (default=pairtree)
71
+ def find_storage_root(object_id, include_deposit=false)
72
+ # Search for the object's home directory in the storage areas
73
+ branch = storage_branch(object_id)
74
+ storage_roots.each do |root|
75
+ root_trunk = root.join(storage_trunk)
76
+ raise "Storage area not found at #{root_trunk}" unless root_trunk.exist?
77
+ root_trunk_branch = root_trunk.join(branch)
78
+ return root if root_trunk_branch.exist?
79
+ end
80
+ # Search for the object's directory in the deposit areas
81
+ if include_deposit and deposit_trunk
82
+ branch = deposit_branch(object_id)
83
+ storage_roots.each do |root|
84
+ root_trunk = root.join(deposit_trunk)
85
+ raise "Deposit area not found at #{root_trunk}" unless root_trunk.exist?
86
+ root_trunk_branch = root_trunk.join(branch)
87
+ return root if root_trunk_branch.exist?
88
+ end
89
+ end
90
+ # object not found, will store new objects in the newest (most empty) filesystem
91
+ storage_roots.last
92
+ end
93
+
94
+ # @param object_id [String] The identifier of the digital object whose version is desired
95
+ # @param include_deposit [Boolean] specifies whether to look in deposit areas for objects in process of initial ingest
96
+ # @return [StorageObject] The representation of a digitial object's storage directory, which might not exist yet.
97
+ def find_storage_object(object_id, include_deposit=false)
98
+ root = find_storage_root(object_id, include_deposit)
99
+ storage_pathname = root.join(storage_trunk, storage_branch(object_id))
100
+ storage_object = StorageObject.new(object_id, storage_pathname)
101
+ storage_object.storage_root = root
102
+ storage_object
103
+ end
104
+
105
+ # @param object_id [String] The identifier of the digital object whose version is desired
106
+ # @param create [Boolean] If true, the object home directory should be created if it does not exist
107
+ # @return [StorageObject] The representation of a digitial object's storage directory, which must exist.
108
+ def storage_object(object_id, create=false)
109
+ storage_object = find_storage_object(object_id)
110
+ unless storage_object.object_pathname.exist?
111
+ if create
112
+ storage_object.object_pathname.mkpath
113
+ else
114
+ raise Moab::ObjectNotFoundException, "No storage object found for #{object_id}"
115
+ end
116
+ end
117
+ storage_object
45
118
  end
46
119
 
120
+ # @depricated Please use StorageObject#ingest_bag
47
121
  # @param druid [String] The object identifier
48
122
  # @return [void] transfer the object to the preservation repository
49
123
  def store_new_object_version(druid, bag_pathname )
@@ -18,6 +18,40 @@ module Moab
18
18
  # @return [StorageRepository] an instance of the interface to SDR storage
19
19
  @@repository = Moab::StorageRepository.new
20
20
 
21
+ def self.repository
22
+ @@repository
23
+ end
24
+
25
+ # @return [Array<Pathname>] A list of the filesystems currently used for storage
26
+ def self.storage_roots
27
+ @@repository.storage_roots
28
+ end
29
+
30
+ # @return [String] The trunk segment of the object deposit path
31
+ def self.deposit_trunk
32
+ @@repository.deposit_trunk
33
+ end
34
+
35
+ # @param object_id [String] The identifier of the digital object
36
+ # @return [Pathname] The branch segment of the object deposit path
37
+ def self.deposit_branch(object_id)
38
+ @@repository.deposit_branch(object_id)
39
+ end
40
+
41
+ # @param object_id [String] The identifier of the digital object
42
+ # @param [Object] include_deposit
43
+ # @return [StorageObject] The representation of a digitial object's storage directory, which might not exist yet.
44
+ def self.find_storage_object(object_id, include_deposit=false)
45
+ @@repository.find_storage_object(object_id, include_deposit)
46
+ end
47
+
48
+ # @param object_id [String] The identifier of the digital object whose version is desired
49
+ # @param create [Boolean] If true, the object home directory should be created if it does not exist
50
+ # @return [StorageObject] The representation of a digitial object's storage directory, which must exist.
51
+ def self.storage_object(object_id, create=false)
52
+ @@repository.storage_object(object_id, create)
53
+ end
54
+
21
55
  # @param object_id [String] The digital object identifier of the object
22
56
  # @return [String] the location of the storage object
23
57
  def self.object_path(object_id)
@@ -23,6 +23,8 @@ class Time
23
23
  case datetime
24
24
  when nil
25
25
  nil
26
+ when ""
27
+ nil
26
28
  when String
27
29
  Time.parse(datetime)
28
30
  when Time
@@ -5,23 +5,29 @@ module Stanford
5
5
  # A class to represent the SDR repository store
6
6
  #
7
7
  # ====Data Model
8
- # * <b>{StorageRepository} = represents a digital object repository storage node</b>
8
+ # * <b>{StorageRepository} = represents the digital object repository storage areas</b>
9
9
  #
10
10
  # @note Copyright (c) 2012 by The Board of Trustees of the Leland Stanford Junior University.
11
11
  # All rights reserved. See {file:LICENSE.rdoc} for details.
12
12
  class StorageRepository < Moab::StorageRepository
13
13
 
14
- # @param object_id [String] The identifier of the digital object whose version is desired
15
- # @return [Pathname] The location of the desired object's home directory
16
- def storage_object_pathname(object_id)
14
+ # @param object_id [String] The identifier of the digital object
15
+ # @return [String] The branch segment of the object storage path
16
+ def storage_branch(object_id)
17
17
  case Moab::Config.path_method.to_s
18
18
  when 'druid_tree'
19
- repository_home.join(druid_tree(object_id))
19
+ druid_tree(object_id)
20
20
  when 'druid'
21
- repository_home.join(object_id.split(/:/)[-1])
21
+ object_id.split(/:/)[-1]
22
22
  end
23
23
  end
24
24
 
25
+ # @param object_id [String] The identifier of the digital object
26
+ # @return [Pathname] The branch segment of the object deposit path
27
+ def deposit_branch(object_id)
28
+ object_id.split(/:/)[-1]
29
+ end
30
+
25
31
  # @param object_id [String] The identifier of the digital object whose path is requested
26
32
  # @return [String] the druid tree directory path based on the given object identifier.
27
33
  def druid_tree(object_id)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moab-versioning
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Richard Anderson
@@ -10,174 +10,174 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-10-15 00:00:00.000000000 Z
13
+ date: 2013-11-08 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: confstruct
17
17
  requirement: !ruby/object:Gem::Requirement
18
18
  requirements:
19
- - - '>='
19
+ - - ! '>='
20
20
  - !ruby/object:Gem::Version
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
- - - '>='
26
+ - - ! '>='
27
27
  - !ruby/object:Gem::Version
28
28
  version: '0'
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: nokogiri
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
- - - '>='
33
+ - - ! '>='
34
34
  - !ruby/object:Gem::Version
35
35
  version: '0'
36
36
  type: :runtime
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
- - - '>='
40
+ - - ! '>='
41
41
  - !ruby/object:Gem::Version
42
42
  version: '0'
43
43
  - !ruby/object:Gem::Dependency
44
44
  name: hashery
45
45
  requirement: !ruby/object:Gem::Requirement
46
46
  requirements:
47
- - - '>='
47
+ - - ! '>='
48
48
  - !ruby/object:Gem::Version
49
49
  version: 2.0.0
50
50
  type: :runtime
51
51
  prerelease: false
52
52
  version_requirements: !ruby/object:Gem::Requirement
53
53
  requirements:
54
- - - '>='
54
+ - - ! '>='
55
55
  - !ruby/object:Gem::Version
56
56
  version: 2.0.0
57
57
  - !ruby/object:Gem::Dependency
58
58
  name: json
59
59
  requirement: !ruby/object:Gem::Requirement
60
60
  requirements:
61
- - - '>='
61
+ - - ! '>='
62
62
  - !ruby/object:Gem::Version
63
63
  version: '0'
64
64
  type: :runtime
65
65
  prerelease: false
66
66
  version_requirements: !ruby/object:Gem::Requirement
67
67
  requirements:
68
- - - '>='
68
+ - - ! '>='
69
69
  - !ruby/object:Gem::Version
70
70
  version: '0'
71
71
  - !ruby/object:Gem::Dependency
72
72
  name: happymapper
73
73
  requirement: !ruby/object:Gem::Requirement
74
74
  requirements:
75
- - - '>='
75
+ - - ! '>='
76
76
  - !ruby/object:Gem::Version
77
77
  version: '0'
78
78
  type: :runtime
79
79
  prerelease: false
80
80
  version_requirements: !ruby/object:Gem::Requirement
81
81
  requirements:
82
- - - '>='
82
+ - - ! '>='
83
83
  - !ruby/object:Gem::Version
84
84
  version: '0'
85
85
  - !ruby/object:Gem::Dependency
86
86
  name: systemu
87
87
  requirement: !ruby/object:Gem::Requirement
88
88
  requirements:
89
- - - '>='
89
+ - - ! '>='
90
90
  - !ruby/object:Gem::Version
91
91
  version: '0'
92
92
  type: :runtime
93
93
  prerelease: false
94
94
  version_requirements: !ruby/object:Gem::Requirement
95
95
  requirements:
96
- - - '>='
96
+ - - ! '>='
97
97
  - !ruby/object:Gem::Version
98
98
  version: '0'
99
99
  - !ruby/object:Gem::Dependency
100
100
  name: awesome_print
101
101
  requirement: !ruby/object:Gem::Requirement
102
102
  requirements:
103
- - - '>='
103
+ - - ! '>='
104
104
  - !ruby/object:Gem::Version
105
105
  version: '0'
106
106
  type: :development
107
107
  prerelease: false
108
108
  version_requirements: !ruby/object:Gem::Requirement
109
109
  requirements:
110
- - - '>='
110
+ - - ! '>='
111
111
  - !ruby/object:Gem::Version
112
112
  version: '0'
113
113
  - !ruby/object:Gem::Dependency
114
114
  name: equivalent-xml
115
115
  requirement: !ruby/object:Gem::Requirement
116
116
  requirements:
117
- - - '>='
117
+ - - ! '>='
118
118
  - !ruby/object:Gem::Version
119
119
  version: 0.2.2
120
120
  type: :development
121
121
  prerelease: false
122
122
  version_requirements: !ruby/object:Gem::Requirement
123
123
  requirements:
124
- - - '>='
124
+ - - ! '>='
125
125
  - !ruby/object:Gem::Version
126
126
  version: 0.2.2
127
127
  - !ruby/object:Gem::Dependency
128
128
  name: rake
129
129
  requirement: !ruby/object:Gem::Requirement
130
130
  requirements:
131
- - - '>='
131
+ - - ! '>='
132
132
  - !ruby/object:Gem::Version
133
133
  version: 0.8.7
134
134
  type: :development
135
135
  prerelease: false
136
136
  version_requirements: !ruby/object:Gem::Requirement
137
137
  requirements:
138
- - - '>='
138
+ - - ! '>='
139
139
  - !ruby/object:Gem::Version
140
140
  version: 0.8.7
141
141
  - !ruby/object:Gem::Dependency
142
142
  name: rdoc
143
143
  requirement: !ruby/object:Gem::Requirement
144
144
  requirements:
145
- - - '>='
145
+ - - ! '>='
146
146
  - !ruby/object:Gem::Version
147
147
  version: '0'
148
148
  type: :development
149
149
  prerelease: false
150
150
  version_requirements: !ruby/object:Gem::Requirement
151
151
  requirements:
152
- - - '>='
152
+ - - ! '>='
153
153
  - !ruby/object:Gem::Version
154
154
  version: '0'
155
155
  - !ruby/object:Gem::Dependency
156
156
  name: rspec
157
157
  requirement: !ruby/object:Gem::Requirement
158
158
  requirements:
159
- - - '>='
159
+ - - ! '>='
160
160
  - !ruby/object:Gem::Version
161
161
  version: '0'
162
162
  type: :development
163
163
  prerelease: false
164
164
  version_requirements: !ruby/object:Gem::Requirement
165
165
  requirements:
166
- - - '>='
166
+ - - ! '>='
167
167
  - !ruby/object:Gem::Version
168
168
  version: '0'
169
169
  - !ruby/object:Gem::Dependency
170
170
  name: yard
171
171
  requirement: !ruby/object:Gem::Requirement
172
172
  requirements:
173
- - - '>='
173
+ - - ! '>='
174
174
  - !ruby/object:Gem::Version
175
175
  version: '0'
176
176
  type: :development
177
177
  prerelease: false
178
178
  version_requirements: !ruby/object:Gem::Requirement
179
179
  requirements:
180
- - - '>='
180
+ - - ! '>='
181
181
  - !ruby/object:Gem::Version
182
182
  version: '0'
183
183
  description: Contains classes to process digital object version content and metadata
@@ -233,17 +233,17 @@ require_paths:
233
233
  - lib
234
234
  required_ruby_version: !ruby/object:Gem::Requirement
235
235
  requirements:
236
- - - '>='
236
+ - - ! '>='
237
237
  - !ruby/object:Gem::Version
238
238
  version: '0'
239
239
  required_rubygems_version: !ruby/object:Gem::Requirement
240
240
  requirements:
241
- - - '>='
241
+ - - ! '>='
242
242
  - !ruby/object:Gem::Version
243
243
  version: 1.3.6
244
244
  requirements: []
245
245
  rubyforge_project:
246
- rubygems_version: 2.0.3
246
+ rubygems_version: 2.0.7
247
247
  signing_key:
248
248
  specification_version: 4
249
249
  summary: Ruby implmentation of digital object versioning toolkit used by the SULAIR