dor-services 4.8.3 → 4.9.0

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.
@@ -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