dor-services 4.8.3 → 4.9.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -6,6 +6,7 @@ module Dor
6
6
  set_terminology do |t|
7
7
  t.root :path => 'contentMetadata', :index_as => [:not_searchable]
8
8
  t.contentType :path => '/contentMetadata/@type', :index_as => [:not_searchable]
9
+ t.stacks :path=> '/contentMetadata/@stacks', :index_as => [:not_searchable]
9
10
  t.resource(:index_as => [:not_searchable]) do
10
11
  t.id_ :path => { :attribute => 'id' }
11
12
  t.sequence :path => { :attribute => 'sequence' }#, :data_type => :integer
@@ -7,21 +7,58 @@ module Dor
7
7
 
8
8
  # Push file changes for shelve-able files into the stacks
9
9
  def shelve
10
+ # retrieve the differences between the current contentMetadata and the previously ingested version
11
+ shelve_diff = get_shelve_diff
12
+ stacks_object_pathname = get_stacks_location
13
+ # determine the location of the object's files in the stacks area
14
+ stacks_druid = DruidTools::StacksDruid.new id, stacks_object_pathname
15
+ stacks_object_pathname = Pathname(stacks_druid.path)
16
+ # determine the location of the object's content files in the workspace area
17
+ workspace_druid = DruidTools::Druid.new(id,Config.stacks.local_workspace_root)
18
+ workspace_content_pathname = workspace_content_dir(shelve_diff, workspace_druid)
19
+ # delete, rename, or copy files to the stacks area
20
+ DigitalStacksService.remove_from_stacks(stacks_object_pathname, shelve_diff)
21
+ DigitalStacksService.rename_in_stacks(stacks_object_pathname, shelve_diff)
22
+ DigitalStacksService.shelve_to_stacks(workspace_content_pathname, stacks_object_pathname, shelve_diff)
23
+ end
24
+
25
+ # retrieve the differences between the current contentMetadata and the previously ingested version
26
+ # (filtering to select only the files that should be shelved to stacks)
27
+ def get_shelve_diff
10
28
  inventory_diff_xml = self.get_content_diff(:shelve)
11
29
  inventory_diff = Moab::FileInventoryDifference.parse(inventory_diff_xml)
12
- content_group_diff = inventory_diff.group_difference("content")
13
- deltas = content_group_diff.file_deltas
14
-
15
- if content_group_diff.rename_require_temp_files(deltas[:renamed])
16
- triplets = content_group_diff.rename_tempfile_triplets(deltas[:renamed])
17
- DigitalStacksService.rename_in_stacks self.pid, triplets.collect{|old,new,temp| [old,temp]}
18
- DigitalStacksService.rename_in_stacks self.pid, triplets.collect{|old,new,temp| [temp,new]}
19
- else
20
- DigitalStacksService.rename_in_stacks self.pid, deltas[:renamed]
21
- end
22
- DigitalStacksService.shelve_to_stacks self.pid, deltas[:modified] + deltas[:added] + deltas[:copyadded].collect{|old,new| new}
23
- DigitalStacksService.remove_from_stacks self.pid, deltas[:deleted] + deltas[:copydeleted]
30
+ shelve_diff = inventory_diff.group_difference("content")
31
+ shelve_diff
24
32
  end
25
33
 
34
+ # Find the location of the object's content files in the workspace area
35
+ # @param [Moab::FileGroupDifference] content_diff The differences between the current contentMetadata and the previously ingested version
36
+ # @param [DruidTools::Druid] workspace_druid the location of the object's files in the workspace area
37
+ # @return [Pathname] The location of the object's content files in the workspace area
38
+ def workspace_content_dir (content_diff, workspace_druid)
39
+ deltas = content_diff.file_deltas
40
+ filelist = deltas[:modified] + deltas[:added] + deltas[:copyadded].collect{|old,new| new}
41
+ return nil if filelist.empty?
42
+ content_pathname = Pathname(workspace_druid.find_filelist_parent('content', filelist))
43
+ content_pathname
44
+ end
45
+
46
+
47
+ # get the stack location based on the contentMetadata stacks attribute
48
+ # or using the default value from the config file if it doesn't exist
49
+ def get_stacks_location
50
+
51
+ contentMetadataDS = self.datastreams['contentMetadata']
52
+ unless contentMetadataDS.nil? or contentMetadataDS.stacks.length == 0
53
+ stacks_location = contentMetadataDS.stacks[0]
54
+ if stacks_location.start_with?"/" #Absolute stacks path
55
+ return stacks_location
56
+ else
57
+ raise "stacks attribute for item: "+self.id+ " contentMetadata should start with /. The current value is "+stacks_location
58
+ end
59
+ end
60
+ return Config.stacks.local_stacks_root #Default stacks
61
+
62
+ end
26
63
  end
27
64
  end
@@ -2,42 +2,128 @@ require 'net/ssh'
2
2
  require 'net/sftp'
3
3
 
4
4
  module Dor
5
+
5
6
  class DigitalStacksService
6
7
 
7
- def self.transfer_to_document_store(id, content, filename)
8
- druid = DruidTools::PurlDruid.new id, Config.stacks.local_document_cache_root
9
- druid.content_dir # create the druid tree if it doesn't exist yet
10
- File.open(File.join(druid.content_dir, filename), 'w') {|f| f.write content }
8
+ # Delete files from stacks that have change type 'deleted', 'copydeleted', or 'modified'
9
+ # @param [Pathname] stacks_object_pathname the stacks location of the digital object
10
+ # @param [Moab::FileGroupDifference] content_diff the content file version differences report
11
+ def self.remove_from_stacks(stacks_object_pathname, content_diff)
12
+ [:deleted, :copydeleted, :modified].each do |change_type|
13
+ subset = content_diff.subset(change_type) # {Moab::FileGroupDifferenceSubset}
14
+ subset.files.each do |moab_file| # {Moab::FileInstanceDifference}
15
+ moab_signature = moab_file.signatures.first # {Moab::FileSignature}
16
+ file_pathname = stacks_object_pathname.join(moab_file.basis_path)
17
+ self.delete_file(file_pathname, moab_signature)
18
+ end
19
+ end
20
+ end
21
+
22
+ # Delete a file, but only if it exists and matches the expected signature
23
+ # @param [Pathname] file_pathname The location of the file to be deleted
24
+ # @param [Moab::FileSignature] moab_signature The fixity values of the file
25
+ # @return [Boolean] true if file deleted, false otherwise
26
+ def self.delete_file(file_pathname, moab_signature)
27
+ if file_pathname.exist? and (file_pathname.size == moab_signature.size)
28
+ file_signature = Moab::FileSignature.new.signature_from_file(file_pathname)
29
+ if (file_signature == moab_signature)
30
+ file_pathname.delete
31
+ return true
32
+ end
33
+ end
34
+ return false
35
+ end
36
+
37
+ # Rename files from stacks that have change type 'renamed' using an intermediate temp filename.
38
+ # The 2-step renaming allows chained or cyclic renames to occur without file collisions.
39
+ # @param [Pathname] stacks_object_pathname the stacks location of the digital object
40
+ # @param [Moab::FileGroupDifference] content_diff the content file version differences report
41
+ def self.rename_in_stacks(stacks_object_pathname, content_diff)
42
+ subset = content_diff.subset(:renamed) # {Moab::FileGroupDifferenceSubset
43
+
44
+ # 1st Pass - rename files from original name to checksum-based name
45
+ subset.files.each do |moab_file| # {Moab::FileInstanceDifference}
46
+ moab_signature = moab_file.signatures.first # {Moab::FileSignature}
47
+ original_pathname = stacks_object_pathname.join(moab_file.basis_path)
48
+ temp_pathname = stacks_object_pathname.join(moab_signature.checksums.values.last)
49
+ self.rename_file(original_pathname, temp_pathname, moab_signature)
50
+ end
51
+
52
+ # 2nd Pass - rename files from checksum-based name to new name
53
+ subset.files.each do |moab_file| # {Moab::FileInstanceDifference}
54
+ moab_signature = moab_file.signatures.first # {Moab::FileSignature}
55
+ temp_pathname = stacks_object_pathname.join(moab_signature.checksums.values.last)
56
+ new_pathname = stacks_object_pathname.join(moab_file.other_path)
57
+ self.rename_file(temp_pathname, new_pathname, moab_signature)
58
+ end
59
+
11
60
  end
12
61
 
13
- def self.remove_from_stacks(id, files)
14
- files.each do |file|
15
- dr = DruidTools::StacksDruid.new id, Config.stacks.local_stacks_root
16
- content = dr.find_content file
17
- FileUtils.rm content if content
62
+ # Rename a file, but only if it exists and has the expected signature
63
+ # @param [Pathname] old_pathname The original location/name of the file being renamed
64
+ # @param [Pathname] new_pathname The new location/name of the file
65
+ # @param [Moab::FileSignature] moab_signature The fixity values of the file
66
+ # @return [Boolean] true if file renamed, false otherwise
67
+ def self.rename_file(old_pathname, new_pathname, moab_signature)
68
+ if old_pathname.exist? and (old_pathname.size == moab_signature.size)
69
+ file_signature = Moab::FileSignature.new.signature_from_file(old_pathname)
70
+ if (file_signature == moab_signature)
71
+ new_pathname.parent.mkpath
72
+ old_pathname.rename(new_pathname)
73
+ return true
74
+ end
18
75
  end
76
+ return false
19
77
  end
20
78
 
21
- # @param [String] id object pid
22
- # @param [Array<Array<String>>] file_map an array of two string arrays. Each inner array represents old-file/new-file mappings. First string is the old file name, second string is the new file name. e.g:
23
- # [ ['src1.file', 'dest1.file'], ['src2.file', 'dest2.file'] ]
24
- def self.rename_in_stacks(id, file_map)
25
- return if file_map.nil? or file_map.empty?
26
- dr = DruidTools::StacksDruid.new id, Config.stacks.local_stacks_root
27
- content_dir = dr.find_filelist_parent('content', file_map.first.first)
28
- file_map.each do |src, dest|
29
- File.rename(File.join(content_dir, src), File.join(content_dir, dest))
79
+ # Add files to stacks that have change type 'added', 'copyadded' or 'modified'.
80
+ # @param [Pathname] workspace_content_pathname The dor workspace location of the digital object's content fies
81
+ # @param [Pathname] stacks_object_pathname the stacks location of the digital object's shelved files
82
+ # @param [Moab::FileGroupDifference] content_diff the content file version differences report
83
+ def self.shelve_to_stacks(workspace_content_pathname, stacks_object_pathname, content_diff)
84
+ return false if workspace_content_pathname.nil?
85
+ [:added, :copyadded, :modified,].each do |change_type|
86
+ subset = content_diff.subset(change_type) # {Moab::FileGroupDifferenceSubset
87
+ subset.files.each do |moab_file| # {Moab::FileInstanceDifference}
88
+ moab_signature = moab_file.signatures.last # {Moab::FileSignature}
89
+ filename = (change_type == :modified) ? moab_file.basis_path : moab_file.other_path
90
+ workspace_pathname = workspace_content_pathname.join(filename)
91
+ stacks_pathname = stacks_object_pathname.join(filename)
92
+ self.copy_file(workspace_pathname, stacks_pathname, moab_signature)
93
+ end
30
94
  end
95
+ true
31
96
  end
32
97
 
33
- def self.shelve_to_stacks(id, files)
34
- workspace_druid = DruidTools::Druid.new(id,Config.stacks.local_workspace_root)
35
- stacks_druid = DruidTools::StacksDruid.new(id,Config.stacks.local_stacks_root)
36
- files.each do |file|
37
- stacks_druid.content_dir
38
- workspace_file = workspace_druid.find_content(file)
39
- FileUtils.cp workspace_file, stacks_druid.content_dir
98
+ # Copy a file to stacks, but only if it does not yet exist with the expected signature
99
+ # @param [Pathname] workspace_pathname The location of the file in the DOR workspace
100
+ # @param [Pathname] stacks_pathname The location of the file in the stacks
101
+ # @param [Moab::FileSignature] moab_signature The fixity values of the file
102
+ # @return [Boolean] true if file copied, false otherwise
103
+ def self.copy_file(workspace_pathname, stacks_pathname, moab_signature)
104
+ if stacks_pathname.exist?
105
+ file_signature = Moab::FileSignature.new.signature_from_file(stacks_pathname)
106
+ stacks_pathname.delete if (file_signature != moab_signature)
107
+ end
108
+ unless stacks_pathname.exist?
109
+ stacks_pathname.parent.mkpath
110
+ FileUtils.cp workspace_pathname.to_s, stacks_pathname.to_s
111
+ return true
40
112
  end
113
+ return false
114
+ end
115
+
116
+ ### depricated ???
117
+
118
+ # Create a file inside the content directory under the stacks.local_document_cache_root
119
+ # @param [String] id The druid identifier for the object
120
+ # @param [String] content The contents of the file to be created
121
+ # @param [String] filename The name of the file to be created
122
+ # @return [void]
123
+ def self.transfer_to_document_store(id, content, filename)
124
+ druid = DruidTools::PurlDruid.new id, Config.stacks.local_document_cache_root
125
+ druid.content_dir # create the druid tree if it doesn't exist yet
126
+ File.open(File.join(druid.content_dir, filename), 'w') { |f| f.write content }
41
127
  end
42
128
 
43
129
  # Assumes the digital stacks storage root is mounted to the local file system
data/lib/dor/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dor
2
- VERSION = '4.8.3'
2
+ VERSION = '4.9.0'
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dor-services
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.8.3
4
+ version: 4.9.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ authors:
13
13
  autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
- date: 2014-06-24 00:00:00.000000000 Z
16
+ date: 2014-07-01 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: active-fedora