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 +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
|