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 +14 -6
- data/lib/moab/config.rb +3 -11
- data/lib/moab/file_group_difference.rb +156 -110
- data/lib/moab/storage_object.rb +16 -2
- data/lib/moab/storage_repository.rb +94 -20
- data/lib/moab/storage_services.rb +34 -0
- data/lib/monkey_patches.rb +2 -0
- data/lib/stanford/storage_repository.rb +12 -6
- metadata +29 -29
checksums.yaml
CHANGED
@@ -1,7 +1,15 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
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>
|
25
|
-
# * <i>
|
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
|
-
@
|
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
|
-
|
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
|
-
|
74
|
-
|
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
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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 =>
|
103
|
-
:identical =>
|
104
|
-
:
|
105
|
-
:
|
106
|
-
:
|
107
|
-
:
|
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 [
|
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 [
|
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 [
|
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
|
-
|
239
|
+
@subset_hash[:identical].files << fid
|
222
240
|
end
|
223
241
|
end
|
224
|
-
|
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 [
|
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(
|
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
|
-
|
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
|
-
|
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 [
|
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
|
-
|
296
|
+
@subset_hash[:modified].files << fid
|
280
297
|
end
|
281
|
-
|
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 [
|
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
|
-
|
332
|
+
@subset_hash[:deleted].files << fid
|
303
333
|
end
|
304
|
-
|
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
|
-
# @
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
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
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
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
|
data/lib/moab/storage_object.rb
CHANGED
@@ -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
|
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
|
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
|
-
|
19
|
-
|
20
|
-
|
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
|
-
# @
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
38
|
-
# @return [
|
39
|
-
def
|
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
|
-
|
44
|
-
|
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)
|
data/lib/monkey_patches.rb
CHANGED
@@ -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
|
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
|
15
|
-
# @return [
|
16
|
-
def
|
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
|
-
|
19
|
+
druid_tree(object_id)
|
20
20
|
when 'druid'
|
21
|
-
|
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.
|
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-
|
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.
|
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
|