ddr-models 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (188) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +12 -0
  3. data/README.md +5 -0
  4. data/Rakefile +37 -0
  5. data/app/models/attachment.rb +7 -0
  6. data/app/models/collection.rb +54 -0
  7. data/app/models/component.rb +15 -0
  8. data/app/models/item.rb +19 -0
  9. data/app/models/solr_document.rb +36 -0
  10. data/app/models/target.rb +8 -0
  11. data/config/initializers/active_fedora_base.rb +77 -0
  12. data/config/initializers/active_fedora_datastream.rb +5 -0
  13. data/config/initializers/ddr.rb +8 -0
  14. data/config/initializers/devise.rb +245 -0
  15. data/config/initializers/devise.rb~ +245 -0
  16. data/config/initializers/subscriptions.rb +15 -0
  17. data/config/routes.rb +2 -0
  18. data/db/migrate/20141021233359_create_events.rb +28 -0
  19. data/db/migrate/20141021234156_create_minted_ids.rb +19 -0
  20. data/db/migrate/20141103192146_create_workflow_state.rb +13 -0
  21. data/db/migrate/20141104181418_create_users.rb +34 -0
  22. data/db/migrate/20141104181418_create_users.rb~ +6 -0
  23. data/lib/ddr-models.rb +1 -0
  24. data/lib/ddr/actions.rb +8 -0
  25. data/lib/ddr/actions/fixity_check.rb +35 -0
  26. data/lib/ddr/auth.rb +45 -0
  27. data/lib/ddr/auth.rb~ +47 -0
  28. data/lib/ddr/auth/ability.rb +204 -0
  29. data/lib/ddr/auth/ability.rb~ +204 -0
  30. data/lib/ddr/auth/group_service.rb +53 -0
  31. data/lib/ddr/auth/group_service.rb~ +53 -0
  32. data/lib/ddr/auth/grouper_service.rb +76 -0
  33. data/lib/ddr/auth/grouper_service.rb~ +77 -0
  34. data/lib/ddr/auth/remote_group_service.rb +35 -0
  35. data/lib/ddr/auth/remote_group_service.rb~ +35 -0
  36. data/lib/ddr/auth/superuser.rb +13 -0
  37. data/lib/ddr/auth/superuser.rb~ +9 -0
  38. data/lib/ddr/auth/user.rb +71 -0
  39. data/lib/ddr/auth/user.rb~ +65 -0
  40. data/lib/ddr/configurable.rb +34 -0
  41. data/lib/ddr/datastreams.rb +32 -0
  42. data/lib/ddr/datastreams/content_metadata_datastream.rb +147 -0
  43. data/lib/ddr/datastreams/datastream_behavior.rb +95 -0
  44. data/lib/ddr/datastreams/descriptive_metadata_datastream.rb +84 -0
  45. data/lib/ddr/datastreams/properties_datastream.rb +25 -0
  46. data/lib/ddr/datastreams/role_assignments_datastream.rb +19 -0
  47. data/lib/ddr/events.rb +17 -0
  48. data/lib/ddr/events/creation_event.rb +12 -0
  49. data/lib/ddr/events/event.rb +163 -0
  50. data/lib/ddr/events/fixity_check_event.rb +43 -0
  51. data/lib/ddr/events/ingestion_event.rb +12 -0
  52. data/lib/ddr/events/preservation_event_behavior.rb +37 -0
  53. data/lib/ddr/events/preservation_event_type.rb +24 -0
  54. data/lib/ddr/events/reindex_object_after_save.rb +18 -0
  55. data/lib/ddr/events/update_event.rb +9 -0
  56. data/lib/ddr/events/validation_event.rb +11 -0
  57. data/lib/ddr/events/virus_check_event.rb +30 -0
  58. data/lib/ddr/index_fields.rb +39 -0
  59. data/lib/ddr/metadata.rb +22 -0
  60. data/lib/ddr/metadata/duke_terms.rb +15 -0
  61. data/lib/ddr/metadata/premis_event.rb +59 -0
  62. data/lib/ddr/metadata/rdf_vocabulary_parser.rb +45 -0
  63. data/lib/ddr/metadata/roles_vocabulary.rb +10 -0
  64. data/lib/ddr/metadata/sources/duketerms.rdf.xml +856 -0
  65. data/lib/ddr/metadata/vocabulary.rb +37 -0
  66. data/lib/ddr/models.rb +60 -0
  67. data/lib/ddr/models/access_controllable.rb +23 -0
  68. data/lib/ddr/models/base.rb +37 -0
  69. data/lib/ddr/models/describable.rb +81 -0
  70. data/lib/ddr/models/engine.rb +58 -0
  71. data/lib/ddr/models/error.rb +12 -0
  72. data/lib/ddr/models/event_loggable.rb +36 -0
  73. data/lib/ddr/models/file_management.rb +183 -0
  74. data/lib/ddr/models/fixity_checkable.rb +20 -0
  75. data/lib/ddr/models/governable.rb +48 -0
  76. data/lib/ddr/models/has_attachments.rb +12 -0
  77. data/lib/ddr/models/has_children.rb +21 -0
  78. data/lib/ddr/models/has_content.rb +114 -0
  79. data/lib/ddr/models/has_content_metadata.rb +16 -0
  80. data/lib/ddr/models/has_properties.rb +15 -0
  81. data/lib/ddr/models/has_role_assignments.rb +17 -0
  82. data/lib/ddr/models/has_thumbnail.rb +27 -0
  83. data/lib/ddr/models/has_workflow.rb +29 -0
  84. data/lib/ddr/models/indexing.rb +53 -0
  85. data/lib/ddr/models/licensable.rb +28 -0
  86. data/lib/ddr/models/minted_id.rb +10 -0
  87. data/lib/ddr/models/permanent_identification.rb +48 -0
  88. data/lib/ddr/models/solr_document.rb +193 -0
  89. data/lib/ddr/models/version.rb +5 -0
  90. data/lib/ddr/notifications.rb +15 -0
  91. data/lib/ddr/services.rb +8 -0
  92. data/lib/ddr/services/id_service.rb +48 -0
  93. data/lib/ddr/utils.rb +153 -0
  94. data/lib/ddr/workflow.rb +8 -0
  95. data/lib/ddr/workflow/workflow_state.rb +39 -0
  96. data/spec/dummy/README.rdoc +28 -0
  97. data/spec/dummy/Rakefile +6 -0
  98. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  99. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  100. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  101. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  102. data/spec/dummy/app/models/user.rb +5 -0
  103. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  104. data/spec/dummy/bin/bundle +3 -0
  105. data/spec/dummy/bin/rails +4 -0
  106. data/spec/dummy/bin/rake +4 -0
  107. data/spec/dummy/config.ru +4 -0
  108. data/spec/dummy/config/application.rb +29 -0
  109. data/spec/dummy/config/boot.rb +5 -0
  110. data/spec/dummy/config/database.yml +25 -0
  111. data/spec/dummy/config/environment.rb +5 -0
  112. data/spec/dummy/config/environments/development.rb +37 -0
  113. data/spec/dummy/config/environments/production.rb +78 -0
  114. data/spec/dummy/config/environments/test.rb +39 -0
  115. data/spec/dummy/config/initializers/assets.rb +8 -0
  116. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  117. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  118. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  119. data/spec/dummy/config/initializers/inflections.rb +16 -0
  120. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  121. data/spec/dummy/config/initializers/session_store.rb +3 -0
  122. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  123. data/spec/dummy/config/locales/en.yml +23 -0
  124. data/spec/dummy/config/routes.rb +56 -0
  125. data/spec/dummy/config/secrets.yml +22 -0
  126. data/spec/dummy/db/development.sqlite3 +0 -0
  127. data/spec/dummy/db/schema.rb +80 -0
  128. data/spec/dummy/db/test.sqlite3 +0 -0
  129. data/spec/dummy/log/development.log +4974 -0
  130. data/spec/dummy/log/test.log +55627 -0
  131. data/spec/dummy/public/404.html +67 -0
  132. data/spec/dummy/public/422.html +67 -0
  133. data/spec/dummy/public/500.html +66 -0
  134. data/spec/dummy/public/favicon.ico +0 -0
  135. data/spec/factories/attachment_factories.rb +15 -0
  136. data/spec/factories/collection_factories.rb +16 -0
  137. data/spec/factories/component_factories.rb +15 -0
  138. data/spec/factories/event_factories.rb +7 -0
  139. data/spec/factories/item_factories.rb +16 -0
  140. data/spec/factories/target_factories.rb +11 -0
  141. data/spec/factories/test_model_factories.rb +133 -0
  142. data/spec/factories/user_factories.rb +7 -0
  143. data/spec/factories/user_factories.rb~ +7 -0
  144. data/spec/features/grouper_integration_spec.rb~ +21 -0
  145. data/spec/fixtures/contentMetadata.xml +37 -0
  146. data/spec/fixtures/image1.tiff +0 -0
  147. data/spec/fixtures/image2.tiff +0 -0
  148. data/spec/fixtures/image3.tiff +0 -0
  149. data/spec/fixtures/library-devil.tiff +0 -0
  150. data/spec/fixtures/sample.docx +0 -0
  151. data/spec/fixtures/sample.pdf +0 -0
  152. data/spec/fixtures/target.png +0 -0
  153. data/spec/models/ability_spec.rb +248 -0
  154. data/spec/models/ability_spec.rb~ +245 -0
  155. data/spec/models/active_fedora_base_spec.rb +107 -0
  156. data/spec/models/active_fedora_datastream_spec.rb +121 -0
  157. data/spec/models/attachment_spec.rb +13 -0
  158. data/spec/models/collection_spec.rb +33 -0
  159. data/spec/models/component_spec.rb +8 -0
  160. data/spec/models/descriptive_metadata_datastream_spec.rb +102 -0
  161. data/spec/models/events_spec.rb +64 -0
  162. data/spec/models/file_management_spec.rb +179 -0
  163. data/spec/models/has_role_assignments_spec.rb +29 -0
  164. data/spec/models/has_workflow_spec.rb +54 -0
  165. data/spec/models/item_spec.rb +8 -0
  166. data/spec/models/permanent_identification_spec.rb +65 -0
  167. data/spec/models/role_assignments_datastream_spec.rb +25 -0
  168. data/spec/models/superuser_spec.rb +13 -0
  169. data/spec/models/superuser_spec.rb~ +13 -0
  170. data/spec/models/target_spec.rb +8 -0
  171. data/spec/models/user_spec.rb +60 -0
  172. data/spec/models/user_spec.rb~ +56 -0
  173. data/spec/services/group_service_spec.rb +75 -0
  174. data/spec/services/group_service_spec.rb~ +71 -0
  175. data/spec/services/id_service_spec.rb +33 -0
  176. data/spec/spec_helper.rb +125 -0
  177. data/spec/support/shared_examples_for_access_controllables.rb +6 -0
  178. data/spec/support/shared_examples_for_associations.rb +8 -0
  179. data/spec/support/shared_examples_for_ddr_models.rb +7 -0
  180. data/spec/support/shared_examples_for_describables.rb +63 -0
  181. data/spec/support/shared_examples_for_event_loggables.rb +3 -0
  182. data/spec/support/shared_examples_for_events.rb +179 -0
  183. data/spec/support/shared_examples_for_governables.rb +17 -0
  184. data/spec/support/shared_examples_for_has_content.rb +136 -0
  185. data/spec/support/shared_examples_for_has_content_metadata.rb +74 -0
  186. data/spec/support/shared_examples_for_has_properties.rb +5 -0
  187. data/spec/support/shared_examples_for_indexing.rb +36 -0
  188. metadata +562 -0
@@ -0,0 +1,37 @@
1
+ module Ddr
2
+ module Metadata
3
+ class Vocabulary
4
+
5
+ def self.label(rdf_vocabulary)
6
+ case rdf_vocabulary.to_uri
7
+ when RDF::DC.to_uri
8
+ "DC Terms"
9
+ when DukeTerms.to_uri
10
+ "Duke Terms"
11
+ end
12
+ end
13
+
14
+ def self.namespace_prefix(rdf_vocabulary)
15
+ case rdf_vocabulary.to_uri
16
+ when RDF::DC.to_uri
17
+ "dcterms"
18
+ when DukeTerms.to_uri
19
+ "duke"
20
+ end
21
+ end
22
+
23
+ def self.property_terms(rdf_vocabulary)
24
+ rdf_vocabulary.properties.select { |p| p.type.include?("http://www.w3.org/1999/02/22-rdf-syntax-ns#Property") }
25
+ end
26
+
27
+ def self.term_names(rdf_vocabulary)
28
+ self.property_terms(rdf_vocabulary).map { |term| self.term_name(rdf_vocabulary, term) }
29
+ end
30
+
31
+ def self.term_name(rdf_vocabulary, term)
32
+ term.to_s.gsub(rdf_vocabulary.to_uri.to_s, "").to_sym
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,60 @@
1
+ require 'ddr/models/engine'
2
+ require 'ddr/models/version'
3
+
4
+ # Awful hack to make Hydra::AccessControls::Permissions accessible
5
+ $: << Gem.loaded_specs['hydra-access-controls'].full_gem_path + "/app/models/concerns"
6
+
7
+ require 'active_record'
8
+
9
+ require 'hydra-core'
10
+ require 'hydra/derivatives'
11
+ require 'hydra/validations'
12
+
13
+ require 'ddr/actions'
14
+ require 'ddr/auth'
15
+ require 'ddr/configurable'
16
+ require 'ddr/datastreams'
17
+ require 'ddr/events'
18
+ require 'ddr/index_fields'
19
+ require 'ddr/metadata'
20
+ require 'ddr/notifications'
21
+ require 'ddr/services'
22
+ require 'ddr/utils'
23
+ require 'ddr/workflow'
24
+
25
+ module Ddr
26
+ module Models
27
+ extend ActiveSupport::Autoload
28
+
29
+ autoload :Configurable
30
+
31
+ autoload :Base
32
+ autoload :AccessControllable
33
+ autoload :Describable
34
+ autoload :EventLoggable
35
+ autoload :Error
36
+ autoload :ChecksumInvalid, 'ddr/models/error'
37
+ autoload :VirusFoundError, 'ddr/models/error'
38
+ autoload :FixityCheckable
39
+ autoload :Governable
40
+ autoload :HasAttachments
41
+ autoload :HasChildren
42
+ autoload :HasContent
43
+ autoload :HasContentMetadata
44
+ autoload :HasProperties
45
+ autoload :HasRoleAssignments
46
+ autoload :HasThumbnail
47
+ autoload :HasWorkflow
48
+ autoload :Indexing
49
+ autoload :FileManagement
50
+ autoload :Licensable
51
+ autoload :MintedId
52
+ autoload :PermanentIdentification
53
+ autoload :SolrDocument
54
+
55
+ include Ddr::Configurable
56
+
57
+ end
58
+ end
59
+
60
+ Dir[Ddr::Models::Engine.root.to_s + "/app/models/*.rb"].each { |m| require m }
@@ -0,0 +1,23 @@
1
+ module Ddr
2
+ module Models
3
+ module AccessControllable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ # adds methods for managing Hydra rightsMetadata content
8
+ include Hydra::AccessControls::Permissions unless include? Hydra::AccessControls::Permissions
9
+ end
10
+
11
+ def set_initial_permissions(user_creator = nil)
12
+ if user_creator
13
+ self.permissions_attributes = [{type: "user", access: "edit", name: user_creator.to_s}]
14
+ end
15
+ end
16
+
17
+ def copy_permissions_from(other)
18
+ # XXX active-fedora < 7.0
19
+ self.permissions_attributes = other.permissions.collect { |p| p.to_hash }
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ module Ddr
2
+ module Models
3
+ class Base < ActiveFedora::Base
4
+
5
+ include Describable
6
+ include Governable
7
+ include AccessControllable
8
+ include Licensable
9
+ include HasProperties
10
+ include HasThumbnail
11
+ include ActiveFedora::Auditable
12
+ include EventLoggable
13
+ include FixityCheckable
14
+ include FileManagement
15
+ include Indexing
16
+ include PermanentIdentification
17
+ include HasRoleAssignments
18
+ include Hydra::Validations
19
+ include HasWorkflow
20
+
21
+ def copy_admin_policy_or_permissions_from(other)
22
+ copy_permissions_from(other) unless copy_admin_policy_from(other)
23
+ end
24
+
25
+ def association_query(association)
26
+ # XXX Ideally we would include a clause to limit by AF model, but this should suffice
27
+ ActiveFedora::SolrService.construct_query_for_rel(reflections[association].options[:property] => internal_uri)
28
+ end
29
+
30
+ # e.g., "Collection duke:1"
31
+ def model_pid
32
+ [self.class.to_s, pid].join(" ")
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,81 @@
1
+ module Ddr
2
+ module Models
3
+ module Describable
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ has_metadata name: Ddr::Datastreams::DESC_METADATA,
8
+ type: Ddr::Datastreams::DescriptiveMetadataDatastream,
9
+ versionable: true,
10
+ label: "Descriptive Metadata for this object",
11
+ control_group: 'M'
12
+ has_attributes *Ddr::Metadata::Vocabulary.term_names(RDF::DC11),
13
+ datastream: Ddr::Datastreams::DESC_METADATA,
14
+ multiple: true
15
+ end
16
+
17
+ def has_desc_metadata?
18
+ descMetadata.has_content?
19
+ end
20
+
21
+ def desc_metadata_terms *args
22
+ return Ddr::Datastreams::DescriptiveMetadataDatastream.term_names if args.empty?
23
+ arg = args.pop
24
+ terms = case arg.to_sym
25
+ when :empty
26
+ desc_metadata_terms.select { |t| desc_metadata_values(t).empty? }
27
+ when :present
28
+ desc_metadata_terms.select { |t| desc_metadata_values(t).present? }
29
+ when :defined_attributes
30
+ desc_metadata_terms & desc_metadata_attributes
31
+ when :required
32
+ desc_metadata_terms(:defined_attributes).select {|t| required? t}
33
+ when :dcterms
34
+ Ddr::Metadata::Vocabulary.term_names(RDF::DC11) +
35
+ (Ddr::Metadata::Vocabulary.term_names(RDF::DC) - Ddr::Metadata::Vocabulary.term_names(RDF::DC11))
36
+ when :dcterms_elements11
37
+ Ddr::Metadata::Vocabulary.term_names(RDF::DC11)
38
+ when :duke
39
+ Ddr::Metadata::Vocabulary.term_names(Ddr::Metadata::DukeTerms)
40
+ else
41
+ raise ArgumentError, "Invalid argument: #{arg.inspect}"
42
+ end
43
+ if args.empty?
44
+ terms
45
+ else
46
+ terms | desc_metadata_terms(*args)
47
+ end
48
+ end
49
+
50
+ def desc_metadata_attributes
51
+ defattrs = self.class.defined_attributes
52
+ defattrs.keys.select {|k| defattrs[k].dsid == "descMetadata"}.map(&:to_sym)
53
+ end
54
+
55
+ def desc_metadata_values term
56
+ descMetadata.values term
57
+ end
58
+
59
+ def desc_metadata_vocabs
60
+ descMetadata.class.vocabularies
61
+ end
62
+
63
+ def set_desc_metadata_values term, values
64
+ descMetadata.set_values term, values
65
+ end
66
+
67
+ # Update all descMetadata terms with values in hash
68
+ # Note that term not having key in hash will be set to nil!
69
+ def set_desc_metadata term_values_hash
70
+ desc_metadata_terms.each { |t| set_desc_metadata_values(t, term_values_hash[t]) }
71
+ end
72
+
73
+ module ClassMethods
74
+ def find_by_identifier(identifier)
75
+ find(Ddr::IndexFields::IDENTIFIER => identifier)
76
+ end
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,58 @@
1
+ require 'rails'
2
+
3
+ module Ddr
4
+ module Models
5
+ class Engine < ::Rails::Engine
6
+
7
+ engine_name 'ddr_models'
8
+
9
+ config.generators do |g|
10
+ g.test_framework :rspec
11
+ g.fixture_replacement :factory_girl
12
+ g.assets false
13
+ g.helper false
14
+ end
15
+
16
+ # Add custom predicates to ActiveFedora
17
+ initializer 'ddr_models.predicates' do
18
+ ActiveFedora::Predicates.set_predicates(Ddr::Metadata::PREDICATES)
19
+ end
20
+
21
+ # Configure devise-remote-user
22
+ initializer 'ddr_auth.remote_user' do
23
+ require 'devise_remote_user'
24
+ DeviseRemoteUser.configure do |config|
25
+ config.auto_create = true
26
+ config.attribute_map = {
27
+ email: 'mail',
28
+ first_name: 'givenName',
29
+ middle_name: 'duMiddleName1',
30
+ nickname: 'eduPersonNickname',
31
+ last_name: 'sn',
32
+ display_name: 'displayName'
33
+ }
34
+ config.auto_update = true
35
+ config.logout_url = "/Shibboleth.sso/Logout?return=https://shib.oit.duke.edu/cgi-bin/logout.pl"
36
+ end
37
+ end
38
+
39
+ # Integration of remote (Grouper) groups via Shibboleth
40
+ initializer 'ddr_auth.grouper' do
41
+ # Load configuration for Grouper service, if present
42
+ if File.exists? "#{Rails.root}/config/grouper.yml"
43
+ Ddr::Auth::GrouperService.config = YAML.load_file("#{Rails.root}/config/grouper.yml")[Rails.env]
44
+ end
45
+
46
+ Warden::Manager.after_set_user do |user, auth, opts|
47
+ user.group_service = Ddr::Auth::RemoteGroupService.new(auth.env)
48
+ end
49
+ end
50
+
51
+ # Set superuser group
52
+ initializer 'ddr_auth.superuser' do
53
+ Ddr::Auth.superuser_group = ENV["SUPERUSER_GROUP"]
54
+ end
55
+
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,12 @@
1
+ module Ddr
2
+ module Models
3
+ # Base class for custom exceptions
4
+ class Error < StandardError; end
5
+
6
+ # Invalid checksum
7
+ class ChecksumInvalid < Ddr::Models::Error; end
8
+
9
+ # Virus found
10
+ class VirusFoundError < Ddr::Models::Error; end
11
+ end
12
+ end
@@ -0,0 +1,36 @@
1
+ module Ddr
2
+ module Models
3
+ module EventLoggable
4
+ extend ActiveSupport::Concern
5
+
6
+ def events
7
+ event_class.for_object(self)
8
+ end
9
+
10
+ def update_events
11
+ event_class(:update).for_object(self)
12
+ end
13
+
14
+ # TESTME
15
+ def notify_event(type, args={})
16
+ Ddr::Notifications.notify_event(type, args.merge(pid: pid))
17
+ end
18
+
19
+ def has_events?
20
+ events.count > 0
21
+ end
22
+
23
+ private
24
+
25
+ def event_class_name(token=nil)
26
+ type = token ? "#{token.to_s.camelize}Event" : "Event"
27
+ "Ddr::Events::#{type}"
28
+ end
29
+
30
+ def event_class(token=nil)
31
+ event_class_name(token).constantize
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,183 @@
1
+ require "ddr-antivirus"
2
+
3
+ module Ddr
4
+ module Models
5
+ module FileManagement
6
+ extend ActiveSupport::Concern
7
+
8
+ EXTERNAL_FILE_PERMISSIONS = 0644
9
+
10
+ included do
11
+ attr_accessor :file_to_add
12
+
13
+ define_model_callbacks :add_file
14
+ before_add_file :virus_scan
15
+
16
+ after_save :notify_virus_scan_results
17
+
18
+ # Deleting the datastream external files on destroying the object can't
19
+ # be handled with a datastream around_destroy callback.
20
+ # See https://groups.google.com/d/msg/hydra-tech/xJaZr2wVhbg/4iafvso98w8J
21
+ around_destroy :cleanup_external_files_on_destroy
22
+ end
23
+
24
+ # add_file(file, dsid, opts={})
25
+ #
26
+ # Comparable to Hydra::ModelMethods#add_file(file, dsid, file_name)
27
+ #
28
+ # Options:
29
+ #
30
+ # :mime_type - Explicit mime type to set (otherwise discerned from file path or name)
31
+ #
32
+ # :original_name - A String value will be understood as the original name of the file.
33
+ # `false` or `nil` indicate that the file basename is not the original
34
+ # name. Default processing will take the file basename as the original
35
+ # name.
36
+ #
37
+ # :external - Add to file to external datastream. Not required for datastream specs
38
+ # where :control_group=>"E".
39
+ #
40
+ # :use_original - For external datastream file, do not copy file to new file path,
41
+ # but use in place (set dsLocation to file URI for current path.
42
+ def add_file file, dsid, opts={}
43
+ opts[:mime_type] ||= Ddr::Utils.mime_type_for(file)
44
+
45
+ # @file_to_add is set for callbacks to access the data
46
+ original_name = opts.fetch(:original_name, Ddr::Utils.file_name_for(file))
47
+ self.file_to_add = FileToAdd.new(file, dsid, original_name)
48
+
49
+ run_callbacks(:add_file) do
50
+ if opts.delete(:external) || datastreams.include?(dsid) && datastreams[dsid].external?
51
+ add_external_file(file, dsid, opts)
52
+ else
53
+ file = File.new(file, "rb") if Ddr::Utils.file_path?(file)
54
+ # ActiveFedora method accepts file-like objects, not paths
55
+ add_file_datastream(file, dsid: dsid, mimeType: opts[:mime_type])
56
+ end
57
+ end
58
+
59
+ # clear the instance data
60
+ self.file_to_add = nil
61
+ end
62
+
63
+ # Normally this method should not be called directly. Call `add_file` with dsid for
64
+ # external datastream id, or with `:external=>true` option if no spec for dsid.
65
+ def add_external_file file, dsid, opts={}
66
+ file_path = Ddr::Utils.file_path(file) # raises ArgumentError
67
+
68
+ # Retrieve or create the datastream
69
+ ds = datastreams.include?(dsid) ? datastreams[dsid] : add_external_datastream(dsid)
70
+
71
+ unless ds.external?
72
+ raise ArgumentError, "Cannot add external file to datastream with controlGroup \"#{ds.controlGroup}\""
73
+ end
74
+
75
+ if ds.dsLocation_changed?
76
+ raise Ddr::Models::Error, "Cannot add external file to datastream when dsLocation change is pending."
77
+ end
78
+
79
+ # Set the MIME type
80
+ # The :mime_type option will be present when called from `add_file`.
81
+ # The fallback is there in case `add_external_file` is called directly.
82
+ ds.mimeType = opts[:mime_type] || Ddr::Utils.mime_type_for(file, file_path)
83
+
84
+ # Copy the file to storage unless we're using the original
85
+ if opts[:use_original]
86
+ raise Ddr::Models::Error, "Cannot add file to repository that is owned by another user." unless File.owned?(file_path)
87
+ store_path = file_path
88
+ else
89
+ # generate new storage path for file
90
+ store_path = create_external_file_path!
91
+ # copy the original file to the storage location
92
+ FileUtils.cp file_path, store_path
93
+ end
94
+
95
+ # set appropriate permissions on the file
96
+ set_external_file_permissions!(store_path)
97
+
98
+ # set dsLocation to file URI for storage path
99
+ ds.dsLocation = Ddr::Utils.path_to_uri(store_path)
100
+ end
101
+
102
+ # Create directory (if necessary) for newly generated file path and return path
103
+ def create_external_file_path!
104
+ file_path = generate_external_file_path
105
+ FileUtils.mkdir_p(File.dirname(file_path))
106
+ file_path
107
+ end
108
+
109
+ #
110
+ # Generates a new external file storage location
111
+ #
112
+ # => {external_file_store}/1/e/69/1e691815-0631-4f9b-8e23-2dfb2eec9c70
113
+ #
114
+ def generate_external_file_path
115
+ file_name = generate_external_file_name
116
+ File.join(external_file_store, generate_external_directory_subpath(file_name), file_name)
117
+ end
118
+
119
+ def external_datastreams
120
+ datastreams.values.select { |ds| ds.external? }
121
+ end
122
+
123
+ def external_datastream_file_paths
124
+ external_datastreams.map(&:file_paths).flatten
125
+ end
126
+
127
+ def add_external_datastream dsid, opts={}
128
+ klass = self.class.datastream_class_for_name(dsid)
129
+ datastream = create_datastream(klass, dsid, controlGroup: "E")
130
+ add_datastream(datastream)
131
+ self.class.build_datastream_accessor(dsid)
132
+ datastream
133
+ end
134
+
135
+ protected
136
+
137
+ FileToAdd = Struct.new(:file, :dsid, :original_name)
138
+
139
+ def virus_scan_results
140
+ @virus_scan_results ||= []
141
+ end
142
+
143
+ def virus_scan
144
+ path = Ddr::Utils.file_path(file_to_add[:file])
145
+ virus_scan_results << Ddr::Antivirus::Scanner.scan(path)
146
+ rescue ArgumentError => e # file is a blob
147
+ logger.error(e)
148
+ end
149
+
150
+ def notify_virus_scan_results
151
+ while result = virus_scan_results.shift
152
+ ActiveSupport::Notifications.instrument(Ddr::Notifications::VIRUS_CHECK, result: result, pid: pid)
153
+ end
154
+ end
155
+
156
+ def external_file_store
157
+ Ddr::Models.external_file_store
158
+ end
159
+
160
+ def set_external_file_permissions! file_path
161
+ File.chmod(EXTERNAL_FILE_PERMISSIONS, file_path)
162
+ end
163
+
164
+ def generate_external_file_name
165
+ SecureRandom.uuid
166
+ end
167
+
168
+ def generate_external_directory_subpath(file_name)
169
+ m = Ddr::Models.external_file_subpath_regexp.match(file_name)
170
+ raise "File name does not match external file subpath pattern: #{file_name}" unless m
171
+ subpath_segments = m.to_a[1..-1]
172
+ File.join *subpath_segments
173
+ end
174
+
175
+ def cleanup_external_files_on_destroy
176
+ paths = external_datastream_file_paths
177
+ yield
178
+ File.unlink *paths
179
+ end
180
+
181
+ end
182
+ end
183
+ end