moab-versioning 1.3.0 → 1.3.1

Sign up to get free protection for your applications and to get access to all the features.
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