ddr-models 1.2.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.
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