curation_concerns 0.12.0.pre1 → 0.12.0.pre2

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 (212) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +24 -16
  3. data/Gemfile +0 -4
  4. data/README.md +14 -0
  5. data/RELEASING.md +2 -2
  6. data/Rakefile +2 -0
  7. data/app/actors/concerns/curation_concerns/manages_embargoes_actor.rb +28 -0
  8. data/app/actors/curation_concerns/abstract_actor.rb +28 -0
  9. data/app/actors/curation_concerns/add_to_collection_actor.rb +38 -0
  10. data/app/actors/curation_concerns/apply_order_actor.rb +24 -0
  11. data/app/actors/curation_concerns/assign_identifier_actor.rb +7 -0
  12. data/app/actors/curation_concerns/assign_representative_actor.rb +18 -0
  13. data/app/actors/curation_concerns/attach_files_actor.rb +39 -0
  14. data/app/actors/curation_concerns/base_actor.rb +71 -0
  15. data/app/actors/curation_concerns/embargo_actor.rb +19 -0
  16. data/app/actors/curation_concerns/file_actor.rb +79 -0
  17. data/app/actors/curation_concerns/file_set_actor.rb +146 -0
  18. data/app/actors/curation_concerns/interpret_visibility_actor.rb +123 -0
  19. data/app/actors/curation_concerns/lease_actor.rb +19 -0
  20. data/app/actors/curation_concerns/root_actor.rb +17 -0
  21. data/app/actors/curation_concerns/work_actor_behavior.rb +8 -0
  22. data/app/assets/javascripts/curation_concerns/batch_select.js +42 -0
  23. data/app/assets/javascripts/curation_concerns/collections.js +13 -0
  24. data/app/assets/javascripts/curation_concerns/curation_concerns.js +2 -0
  25. data/app/assets/stylesheets/curation_concerns/_curation_concerns.scss +0 -3
  26. data/app/assets/stylesheets/curation_concerns/_modules.scss +1 -1
  27. data/app/assets/stylesheets/curation_concerns/_positioning.scss +3 -6
  28. data/app/assets/stylesheets/curation_concerns/_theme.scss +0 -39
  29. data/app/assets/stylesheets/curation_concerns/_typography.scss +0 -69
  30. data/app/assets/stylesheets/curation_concerns/modules/classify_work.scss +0 -2
  31. data/app/assets/stylesheets/curation_concerns/modules/collections.scss +4 -0
  32. data/app/assets/stylesheets/curation_concerns/modules/forms.scss +0 -4
  33. data/app/assets/stylesheets/curation_concerns/modules/site_actions.scss +34 -29
  34. data/app/assets/stylesheets/curation_concerns/modules/site_search.scss +0 -46
  35. data/app/assets/stylesheets/curation_concerns.scss +4 -0
  36. data/app/controllers/concerns/curation_concerns/collections_controller_behavior.rb +166 -21
  37. data/app/controllers/concerns/curation_concerns/embargoes_controller_behavior.rb +1 -1
  38. data/app/controllers/concerns/curation_concerns/leases_controller_behavior.rb +1 -1
  39. data/app/controllers/concerns/curation_concerns/selects_collections.rb +65 -0
  40. data/app/forms/curation_concerns/forms/collection_edit_form.rb +0 -29
  41. data/app/forms/curation_concerns/forms/work_form.rb +2 -1
  42. data/app/helpers/batch_select_helper.rb +23 -0
  43. data/app/helpers/collections_helper.rb +4 -0
  44. data/app/helpers/curation_concerns/collections_helper.rb +2 -2
  45. data/app/helpers/curation_concerns/collections_helper_behavior.rb +56 -0
  46. data/app/helpers/curation_concerns/render_constraints_helper.rb +14 -35
  47. data/app/helpers/curation_concerns/title_helper.rb +4 -0
  48. data/app/indexers/curation_concerns/collection_indexer.rb +16 -0
  49. data/app/indexers/curation_concerns/file_set_indexer.rb +46 -0
  50. data/app/indexers/curation_concerns/work_indexer.rb +15 -0
  51. data/app/jobs/audit_job.rb +49 -0
  52. data/app/jobs/characterize_job.rb +11 -0
  53. data/app/jobs/create_derivatives_job.rb +21 -0
  54. data/app/jobs/import_url_job.rb +48 -0
  55. data/app/jobs/ingest_file_job.rb +30 -0
  56. data/app/jobs/ingest_local_file_job.rb +20 -0
  57. data/app/jobs/resolrize_job.rb +7 -0
  58. data/app/models/checksum_audit_log.rb +20 -0
  59. data/app/models/collection.rb +6 -0
  60. data/app/models/concerns/curation_concerns/ability.rb +49 -0
  61. data/app/models/concerns/curation_concerns/basic_metadata.rb +64 -0
  62. data/app/models/concerns/curation_concerns/collection.rb +16 -0
  63. data/app/models/concerns/curation_concerns/collection_behavior.rb +62 -0
  64. data/app/models/concerns/curation_concerns/file_set/belongs_to_works.rb +47 -0
  65. data/app/models/concerns/curation_concerns/file_set/derivatives.rb +65 -0
  66. data/app/models/concerns/curation_concerns/file_set/full_text_indexing.rb +11 -0
  67. data/app/models/concerns/curation_concerns/file_set/indexing.rb +14 -0
  68. data/app/models/concerns/curation_concerns/file_set/querying.rb +17 -0
  69. data/app/models/concerns/curation_concerns/file_set_behavior.rb +36 -0
  70. data/app/models/concerns/curation_concerns/has_representative.rb +13 -0
  71. data/app/models/concerns/curation_concerns/human_readable_type.rb +17 -0
  72. data/app/models/concerns/curation_concerns/naming.rb +17 -0
  73. data/app/models/concerns/curation_concerns/permissions/readable.rb +18 -0
  74. data/app/models/concerns/curation_concerns/permissions/writable.rb +34 -0
  75. data/app/models/concerns/curation_concerns/permissions.rb +7 -0
  76. data/app/models/concerns/curation_concerns/required_metadata.rb +30 -0
  77. data/app/models/concerns/curation_concerns/serializers.rb +13 -0
  78. data/app/models/concerns/curation_concerns/solr_document_behavior.rb +147 -0
  79. data/app/models/concerns/curation_concerns/user.rb +18 -0
  80. data/app/models/concerns/curation_concerns/with_file_sets.rb +37 -0
  81. data/app/models/concerns/curation_concerns/work_behavior.rb +45 -0
  82. data/app/models/curation_concerns/classify_concern.rb +49 -0
  83. data/app/models/curation_concerns/quick_classification_query.rb +38 -0
  84. data/app/models/single_use_link.rb +34 -0
  85. data/app/models/version_committer.rb +2 -0
  86. data/app/search_builders/curation_concerns/collection_member_search_builder.rb +1 -1
  87. data/app/search_builders/curation_concerns/collection_search_builder.rb +33 -0
  88. data/app/search_builders/curation_concerns/member_search_builder.rb +17 -0
  89. data/app/services/curation_concerns/derivative_path.rb +49 -0
  90. data/app/services/curation_concerns/file_set_audit_service.rb +105 -0
  91. data/app/services/curation_concerns/indexes_thumbnails.rb +30 -0
  92. data/app/services/curation_concerns/local_file_service.rb +10 -0
  93. data/app/services/curation_concerns/lock_manager.rb +39 -0
  94. data/app/services/curation_concerns/lockable.rb +16 -0
  95. data/app/services/curation_concerns/noid.rb +23 -0
  96. data/app/services/curation_concerns/persist_derivatives.rb +33 -0
  97. data/app/services/curation_concerns/persist_directly_contained_output_file_service.rb +26 -0
  98. data/app/services/curation_concerns/repository_audit_service.rb +7 -0
  99. data/app/services/curation_concerns/thumbnail_path_service.rb +46 -0
  100. data/app/services/curation_concerns/time_service.rb +7 -0
  101. data/app/services/curation_concerns/versioning_service.rb +26 -0
  102. data/app/validators/has_one_title_validator.rb +8 -0
  103. data/app/views/batch_select/_add_button.html.erb +3 -0
  104. data/app/views/batch_select/_check_all.html.erb +4 -0
  105. data/app/views/batch_select/_tools.html.erb +10 -0
  106. data/app/views/catalog/_action_menu_partials/_collection.html.erb +3 -3
  107. data/app/views/catalog/_action_menu_partials/_default.html.erb +1 -1
  108. data/app/views/catalog/_document_list.html.erb +1 -1
  109. data/app/views/collections/_bookmark_control.html.erb +2 -0
  110. data/app/views/collections/_button_create_collection.html.erb +2 -0
  111. data/app/views/collections/_button_for_creating_empty_collection.html.erb +1 -1
  112. data/app/views/collections/_button_for_delete_collection.html.erb +4 -0
  113. data/app/views/collections/_button_for_remove_selected_from_collection.html.erb +8 -0
  114. data/app/views/collections/_button_for_update_collection.html.erb +4 -0
  115. data/app/views/collections/_button_remove_from_collection.html.erb +4 -0
  116. data/app/views/collections/_document_header.html.erb +9 -0
  117. data/app/views/collections/_edit_actions.html.erb +1 -1
  118. data/app/views/collections/_edit_descriptions.html.erb +1 -1
  119. data/app/views/collections/_form.html.erb +2 -2
  120. data/app/views/collections/_form_for_select_destination_collection.html.erb +21 -0
  121. data/app/views/collections/_form_to_add_member.html.erb +1 -1
  122. data/app/views/collections/_index_default.html.erb +2 -0
  123. data/app/views/collections/_index_header_default.html.erb +2 -0
  124. data/app/views/collections/_media_display.html.erb +1 -1
  125. data/app/views/collections/_paginate.html.erb +1 -1
  126. data/app/views/collections/_paginate_compact.html.erb +1 -0
  127. data/app/views/collections/_results_pagination.html.erb +9 -0
  128. data/app/views/collections/_search_collection_dashboard_form.html.erb +1 -1
  129. data/app/views/collections/_search_form.html.erb +1 -1
  130. data/app/views/collections/_search_results.html.erb +23 -0
  131. data/app/views/collections/_show_actions.html.erb +1 -1
  132. data/app/views/collections/_sort_and_per_page.html.erb +1 -1
  133. data/app/views/collections/_view_type_group.html.erb +1 -1
  134. data/app/views/collections/index.html.erb +9 -0
  135. data/app/views/collections/new.html.erb +3 -0
  136. data/app/views/curation_concerns/base/_form_permission.html.erb +10 -11
  137. data/app/views/curation_concerns/base/_form_permission_embargo.html.erb +1 -1
  138. data/app/views/curation_concerns/base/_form_permission_lease.html.erb +1 -1
  139. data/app/views/curation_concerns/base/_legally_binding_text.html.erb +7 -7
  140. data/app/views/curation_concerns/base/_related_files.html.erb +1 -1
  141. data/app/views/curation_concerns/base/_visibility.html.erb +2 -2
  142. data/app/views/curation_concerns/file_sets/_actions.html.erb +1 -1
  143. data/app/views/embargoes/_list_expired_active_embargoes.html.erb +1 -1
  144. data/app/views/error/single_use_error.html.erb +1 -1
  145. data/app/views/shared/_add_content.html.erb +17 -15
  146. data/app/views/shared/_brand_bar.html.erb +19 -10
  147. data/app/views/shared/_header.html.erb +2 -6
  148. data/app/views/shared/_my_actions.html.erb +28 -27
  149. data/app/views/shared/_site_actions.html.erb +5 -1
  150. data/app/views/shared/_site_search.html.erb +3 -2
  151. data/app/views/shared/_title_bar.html.erb +7 -16
  152. data/app/views/welcome/index.html.erb +2 -2
  153. data/config/locales/curation_concerns.en.yml +25 -1
  154. data/curation_concerns.gemspec +21 -5
  155. data/lib/curation_concerns/collections/accepts_batches.rb +53 -0
  156. data/lib/curation_concerns/collections/search_service.rb +57 -0
  157. data/lib/curation_concerns/collections.rb +10 -0
  158. data/lib/curation_concerns/configuration.rb +167 -0
  159. data/lib/curation_concerns/engine.rb +22 -1
  160. data/lib/curation_concerns/messages.rb +68 -0
  161. data/lib/curation_concerns/models.rb +42 -0
  162. data/lib/curation_concerns/name.rb +20 -0
  163. data/lib/curation_concerns/null_logger.rb +10 -0
  164. data/lib/curation_concerns/rails/routes.rb +1 -3
  165. data/lib/curation_concerns/version.rb +1 -1
  166. data/lib/curation_concerns.rb +2 -0
  167. data/lib/generators/curation_concerns/abstract_migration_generator.rb +31 -0
  168. data/lib/generators/curation_concerns/clamav_generator.rb +19 -0
  169. data/lib/generators/curation_concerns/collection_generator.rb +15 -0
  170. data/lib/generators/curation_concerns/install_generator.rb +1 -2
  171. data/lib/generators/curation_concerns/models_generator.rb +62 -0
  172. data/lib/generators/curation_concerns/templates/app/models/collection.rb +6 -0
  173. data/lib/generators/curation_concerns/templates/app/models/file_set.rb +4 -0
  174. data/lib/generators/curation_concerns/templates/config/clamav.rb +1 -0
  175. data/lib/generators/curation_concerns/templates/config/curation_concerns.rb +61 -0
  176. data/lib/generators/curation_concerns/templates/config/mime_types.rb +6 -0
  177. data/lib/generators/curation_concerns/templates/config/redis.yml +9 -0
  178. data/lib/generators/curation_concerns/templates/config/redis_config.rb +29 -0
  179. data/lib/generators/curation_concerns/templates/config/resque-pool.yml +1 -0
  180. data/lib/generators/curation_concerns/templates/config/resque_config.rb +6 -0
  181. data/lib/generators/curation_concerns/templates/curation_concerns.scss +3 -2
  182. data/lib/generators/curation_concerns/templates/migrations/create_checksum_audit_logs.rb +19 -0
  183. data/lib/generators/curation_concerns/templates/migrations/create_single_use_links.rb +12 -0
  184. data/lib/generators/curation_concerns/templates/migrations/create_version_committers.rb +15 -0
  185. data/lib/tasks/migrate.rake +11 -0
  186. data/lib/tasks/resque.rake +14 -0
  187. data/lib/tasks/solr_reindex.rake +8 -0
  188. data/spec/actors/curation_concerns/file_set_actor_spec.rb +31 -0
  189. data/spec/controllers/accepts_batches_controller_spec.rb +65 -0
  190. data/spec/controllers/collections_controller_spec.rb +272 -0
  191. data/spec/controllers/curation_concerns/collections_controller_spec.rb +1 -2
  192. data/spec/controllers/selects_collections_controller_spec.rb +109 -0
  193. data/spec/features/create_work_spec.rb +1 -1
  194. data/spec/features/work_generator_spec.rb +1 -1
  195. data/spec/forms/collection_edit_form_spec.rb +2 -9
  196. data/spec/forms/work_form_spec.rb +5 -0
  197. data/spec/helpers/collections_helper_spec.rb +129 -0
  198. data/spec/helpers/curation_concerns/collections_helper_spec.rb +2 -2
  199. data/spec/helpers/render_constraints_helper_spec.rb +23 -1
  200. data/spec/lib/curation_concerns/collections/search_service_spec.rb +33 -0
  201. data/spec/models/collection_spec.rb +165 -0
  202. data/spec/tasks/rake_spec.rb +1 -1
  203. data/spec/test_app_templates/lib/generators/test_app_generator.rb +1 -1
  204. data/spec/views/curation_concerns/base/_form_permission.html.erb_spec.rb +4 -1
  205. data/spec/views/curation_concerns/file_sets/show.html.erb_spec.rb +1 -0
  206. data/spec/views/shared/_add_content.html.erb_spec.rb +3 -3
  207. metadata +341 -24
  208. data/VERSION +0 -1
  209. data/app/assets/stylesheets/curation_concerns/_global-variables.scss +0 -5
  210. data/app/assets/stylesheets/curation_concerns/modules/multi_value_fields.scss +0 -52
  211. data/app/views/collections/_form_required_information.html.erb +0 -11
  212. data/tasks/release.rake +0 -93
@@ -0,0 +1,15 @@
1
+ module CurationConcerns
2
+ class WorkIndexer < ActiveFedora::IndexingService
3
+ include IndexesThumbnails
4
+ def generate_solr_document
5
+ super.tap do |solr_doc|
6
+ # We know that all the members of GenericWorks are FileSets so we can use
7
+ # member_ids which requires fewer Fedora API calls than file_set_ids.
8
+ # file_set_ids requires loading all the members from Fedora but member_ids
9
+ # looks just at solr
10
+ solr_doc[Solrizer.solr_name('member_ids', :symbol)] = object.member_ids
11
+ Solrizer.set_field(solr_doc, 'generic_type', 'Work', :facetable)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,49 @@
1
+ class AuditJob < ActiveJob::Base
2
+ queue_as :audit
3
+
4
+ # URI of the resource to audit.
5
+ # This URI could include the actual resource (e.g. content) and the version to audit:
6
+ # http://localhost:8983/fedora/rest/test/a/b/c/abcxyz/content/fcr:versions/version1
7
+ # but it could also just be:
8
+ # http://localhost:8983/fedora/rest/test/a/b/c/abcxyz/content
9
+ # @param [FileSet] the parent object
10
+ # @param [String] file_id used to find the file within its parent object (usually "original_file")
11
+ # @param [String] uri of the specific file/version to be audited
12
+ def perform(file_set, file_id, uri)
13
+ log = run_audit(file_set, file_id, uri)
14
+ fixity_ok = log.pass == 1
15
+ unless fixity_ok
16
+ if CurationConcerns.config.callback.set?(:after_audit_failure)
17
+ login = file_set.depositor
18
+ user = User.find_by_user_key(login)
19
+ CurationConcerns.config.callback.run(:after_audit_failure, file_set, user, log.created_at)
20
+ end
21
+ end
22
+ fixity_ok
23
+ end
24
+
25
+ protected
26
+
27
+ def run_audit(file_set, file_id, uri)
28
+ begin
29
+ fixity_ok = ActiveFedora::FixityService.new(uri).check
30
+ rescue Ldp::NotFound
31
+ error_msg = 'resource not found'
32
+ end
33
+
34
+ if fixity_ok
35
+ passing = 1
36
+ ChecksumAuditLog.prune_history(file_set.id, file_id)
37
+ else
38
+ logger.warn "***AUDIT*** Audit failed for #{uri} #{error_msg}"
39
+ passing = 0
40
+ end
41
+ ChecksumAuditLog.create!(pass: passing, file_set_id: file_set.id, version: uri, file_id: file_id)
42
+ end
43
+
44
+ private
45
+
46
+ def logger
47
+ ActiveFedora::Base.logger || CurationConcerns::NullLogger.new
48
+ end
49
+ end
@@ -0,0 +1,11 @@
1
+ class CharacterizeJob < ActiveJob::Base
2
+ queue_as :characterize
3
+
4
+ # @param [FileSet] file_set
5
+ # @param [String] filename a local path for the file to characterize. By using this, we don't have to pull a copy out of fedora.
6
+ def perform(file_set, filename)
7
+ Hydra::Works::CharacterizationService.run(file_set, filename)
8
+ file_set.save!
9
+ CreateDerivativesJob.perform_later(file_set, filename)
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ class CreateDerivativesJob < ActiveJob::Base
2
+ queue_as :derivatives
3
+
4
+ # @param [FileSet] file_set
5
+ # @param [String] file_name
6
+ def perform(file_set, file_name)
7
+ return if file_set.video? && !CurationConcerns.config.enable_ffmpeg
8
+
9
+ file_set.create_derivatives(file_name)
10
+ # The thumbnail is indexed in the solr document, so reindex
11
+ file_set.update_index
12
+ file_set.parent.update_index if parent_needs_reindex?(file_set)
13
+ end
14
+
15
+ # If this file_set is the thumbnail for the parent work,
16
+ # then the parent also needs to be reindexed.
17
+ def parent_needs_reindex?(file_set)
18
+ return false unless file_set.parent
19
+ file_set.parent.thumbnail_id == file_set.id
20
+ end
21
+ end
@@ -0,0 +1,48 @@
1
+ require 'net/https'
2
+ require 'uri'
3
+ require 'tempfile'
4
+
5
+ class ImportUrlJob < ActiveJob::Base
6
+ queue_as :import_url
7
+
8
+ def perform(file_set)
9
+ user = User.find_by_user_key(file_set.depositor)
10
+
11
+ Tempfile.open(file_set.id.tr('/', '_')) do |f|
12
+ copy_remote_file(file_set, f)
13
+
14
+ # reload the generic file once the data is copied since this is a long running task
15
+ file_set.reload
16
+
17
+ # attach downloaded file to generic file stubbed out
18
+ if CurationConcerns::FileSetActor.new(file_set, user).create_content(f)
19
+ # send message to user on download success
20
+ CurationConcerns.config.callback.run(:after_import_url_success, file_set, user)
21
+ else
22
+ CurationConcerns.config.callback.run(:after_import_url_failure, file_set, user)
23
+ end
24
+ end
25
+ end
26
+
27
+ protected
28
+
29
+ def copy_remote_file(file_set, f)
30
+ f.binmode
31
+ # download file from url
32
+ uri = URI(file_set.import_url)
33
+ http = Net::HTTP.new(uri.host, uri.port)
34
+ http.use_ssl = uri.scheme == 'https' # enable SSL/TLS
35
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
36
+ mime_type = nil
37
+
38
+ http.start do
39
+ http.request_get(uri.request_uri) do |resp|
40
+ mime_type = resp.content_type
41
+ resp.read_body do |segment|
42
+ f.write(segment)
43
+ end
44
+ end
45
+ end
46
+ f.rewind
47
+ end
48
+ end
@@ -0,0 +1,30 @@
1
+ class IngestFileJob < ActiveJob::Base
2
+ queue_as :ingest
3
+
4
+ # @param [FileSet] file_set
5
+ # @param [String] filename
6
+ # @param [String,NilClass] mime_type
7
+ # @param [String] user_key
8
+ # @param [String] relation ('original_file')
9
+ def perform(file_set, filename, mime_type, user_key, relation = 'original_file')
10
+ file = File.open(filename, "rb")
11
+ # If mime-type is known, wrap in an IO decorator
12
+ # Otherwise allow Hydra::Works service to determine mime_type
13
+ if mime_type
14
+ file = Hydra::Derivatives::IoDecorator.new(file)
15
+ file.mime_type = mime_type
16
+ file.original_name = File.basename(filename)
17
+ end
18
+
19
+ # Tell AddFileToFileSet service to skip versioning because versions will be minted by VersionCommitter (called by save_characterize_and_record_committer) when necessary
20
+ Hydra::Works::AddFileToFileSet.call(file_set, file, relation.to_sym, versioning: false)
21
+
22
+ # Persist changes to the file_set
23
+ file_set.save!
24
+
25
+ # Do post file ingest actions
26
+ user = User.find_by_user_key(user_key)
27
+ CurationConcerns::VersioningService.create(file_set.send(relation.to_sym), user)
28
+ CurationConcerns.config.callback.run(:after_create_content, file_set, user)
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ class IngestLocalFileJob < ActiveJob::Base
2
+ queue_as :ingest_local
3
+
4
+ def perform(file_set_id, directory, filename, user_key)
5
+ user = User.find_by_user_key(user_key)
6
+ fail "Unable to find user for #{user_key}" unless user
7
+ file_set = FileSet.find(file_set_id)
8
+ file_set.label ||= filename
9
+ path = File.join(directory, filename)
10
+
11
+ actor = CurationConcerns::FileSetActor.new(file_set, user)
12
+
13
+ if actor.create_content(File.open(path))
14
+ FileUtils.rm(path)
15
+ CurationConcerns.config.callback.run(:after_import_local_file_success, file_set, user, filename)
16
+ else
17
+ CurationConcerns.config.callback.run(:after_import_local_file_failure, file_set, user, filename)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ class ResolrizeJob < ActiveJob::Base
2
+ queue_as :resolrize
3
+
4
+ def perform
5
+ ActiveFedora::Base.reindex_everything
6
+ end
7
+ end
@@ -0,0 +1,20 @@
1
+ class ChecksumAuditLog < ActiveRecord::Base
2
+ def self.get_audit_log(id, path, version_uri)
3
+ ChecksumAuditLog.find_or_create_by(file_set_id: id, file_id: path, version: version_uri)
4
+ end
5
+
6
+ # Check to see if there are previous passing logs that we can delete
7
+ # we want to keep the first passing event after a failure, the most current passing event,
8
+ # and all failures so that this table doesn't grow too large
9
+ # Simple way (a little naieve): if the last 2 were passing, delete the first one
10
+ def self.prune_history(id, path)
11
+ list = logs_for(id, path).limit(2)
12
+ if list.size > 1 && (list[0].pass == 1) && (list[1].pass == 1)
13
+ list[0].destroy
14
+ end
15
+ end
16
+
17
+ def self.logs_for(id, path)
18
+ ChecksumAuditLog.where(file_set_id: id, file_id: path).order('created_at desc, id desc')
19
+ end
20
+ end
@@ -0,0 +1,6 @@
1
+ # Generated by curation_concerns:models:install
2
+ class Collection < ActiveFedora::Base
3
+ include ::CurationConcerns::CollectionBehavior
4
+ # You can replace these metadata if they're not suitable
5
+ include CurationConcerns::BasicMetadata
6
+ end
@@ -0,0 +1,49 @@
1
+ module CurationConcerns
2
+ module Ability
3
+ extend ActiveSupport::Concern
4
+ included do
5
+ self.ability_logic += [:curation_concerns_permissions, :add_to_collection]
6
+ end
7
+
8
+ def curation_concerns_permissions
9
+ unless current_user.new_record?
10
+ can :create, CurationConcerns::ClassifyConcern
11
+ end
12
+
13
+ # user can version if they can edit
14
+ alias_action :versions, to: :update
15
+
16
+ if admin?
17
+ admin_permissions
18
+ else
19
+ cannot :index, Hydra::AccessControls::Embargo
20
+ cannot :index, Hydra::AccessControls::Lease
21
+ end
22
+ end
23
+
24
+ def admin_permissions
25
+ can [:create, :discover, :show, :read, :edit, :update, :destroy], :all
26
+ end
27
+
28
+ def admin?
29
+ user_groups.include? 'admin'
30
+ end
31
+
32
+ def add_to_collection
33
+ return if current_user.new_record?
34
+ can :collect, :all
35
+ end
36
+
37
+ def registered_user?
38
+ user_groups.include? 'registered'
39
+ end
40
+
41
+ # Add this to your ability_logic if you want all logged in users to be able
42
+ # to submit content
43
+ def everyone_can_create_curation_concerns
44
+ return unless registered_user?
45
+ can :create, [::FileSet, ::Collection]
46
+ can :create, [CurationConcerns.config.curation_concerns]
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,64 @@
1
+ module CurationConcerns
2
+ module BasicMetadata
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ property :label, predicate: ActiveFedora::RDF::Fcrepo::Model.downloadFilename, multiple: false
7
+
8
+ property :relative_path, predicate: ::RDF::URI.new('http://scholarsphere.psu.edu/ns#relativePath'), multiple: false
9
+
10
+ property :import_url, predicate: ::RDF::URI.new('http://scholarsphere.psu.edu/ns#importUrl'), multiple: false do |index|
11
+ index.as :symbol
12
+ end
13
+
14
+ property :part_of, predicate: ::RDF::Vocab::DC.isPartOf
15
+ property :resource_type, predicate: ::RDF::Vocab::DC.type do |index|
16
+ index.as :stored_searchable, :facetable
17
+ end
18
+ property :creator, predicate: ::RDF::Vocab::DC11.creator do |index|
19
+ index.as :stored_searchable, :facetable
20
+ end
21
+ property :contributor, predicate: ::RDF::Vocab::DC11.contributor do |index|
22
+ index.as :stored_searchable, :facetable
23
+ end
24
+ property :description, predicate: ::RDF::Vocab::DC11.description do |index|
25
+ index.type :text
26
+ index.as :stored_searchable
27
+ end
28
+ property :tag, predicate: ::RDF::Vocab::DC11.relation do |index|
29
+ index.as :stored_searchable, :facetable
30
+ end
31
+ property :rights, predicate: ::RDF::Vocab::DC.rights do |index|
32
+ index.as :stored_searchable
33
+ end
34
+ property :publisher, predicate: ::RDF::Vocab::DC11.publisher do |index|
35
+ index.as :stored_searchable, :facetable
36
+ end
37
+ property :date_created, predicate: ::RDF::Vocab::DC.created do |index|
38
+ index.as :stored_searchable
39
+ end
40
+
41
+ property :subject, predicate: ::RDF::Vocab::DC11.subject do |index|
42
+ index.as :stored_searchable, :facetable
43
+ end
44
+ property :language, predicate: ::RDF::Vocab::DC11.language do |index|
45
+ index.as :stored_searchable, :facetable
46
+ end
47
+ property :identifier, predicate: ::RDF::Vocab::DC.identifier do |index|
48
+ index.as :stored_searchable
49
+ end
50
+ property :based_near, predicate: ::RDF::Vocab::FOAF.based_near do |index|
51
+ index.as :stored_searchable, :facetable
52
+ end
53
+ property :related_url, predicate: ::RDF::RDFS.seeAlso do |index|
54
+ index.as :stored_searchable
55
+ end
56
+ property :bibliographic_citation, predicate: ::RDF::Vocab::DC.bibliographicCitation do |index|
57
+ index.as :stored_searchable
58
+ end
59
+ property :source, predicate: ::RDF::Vocab::DC.source do |index|
60
+ index.as :stored_searchable
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,16 @@
1
+ module CurationConcerns
2
+ module Collection
3
+ extend ActiveSupport::Concern
4
+ extend Deprecation
5
+ include Hydra::Works::CollectionBehavior
6
+ include Hydra::WithDepositor # for access to apply_depositor_metadata
7
+ include Hydra::AccessControls::Permissions
8
+ include CurationConcerns::RequiredMetadata
9
+ include Hydra::Works::CollectionBehavior
10
+
11
+ def add_members(new_member_ids)
12
+ return if new_member_ids.nil? || new_member_ids.empty?
13
+ members << ActiveFedora::Base.find(new_member_ids)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,62 @@
1
+ module CurationConcerns
2
+ module CollectionBehavior
3
+ extend ActiveSupport::Concern
4
+
5
+ include Hydra::AccessControls::WithAccessRight
6
+ include CurationConcerns::Collection
7
+ include CurationConcerns::Noid
8
+ include CurationConcerns::HumanReadableType
9
+ include CurationConcerns::HasRepresentative
10
+ include CurationConcerns::Permissions
11
+
12
+ included do
13
+ validates_with HasOneTitleValidator
14
+ end
15
+
16
+ def to_s
17
+ title.present? ? title : 'No Title'
18
+ end
19
+
20
+ module ClassMethods
21
+ def indexer
22
+ CurationConcerns::CollectionIndexer
23
+ end
24
+ end
25
+
26
+ # Compute the sum of each file in the collection using Solr to
27
+ # avoid having to access Fedora
28
+ #
29
+ # @return [Fixnum] size of collection in bytes
30
+ # @raise [RuntimeError] unsaved record does not exist in solr
31
+ def bytes
32
+ return 0 if member_ids.count == 0
33
+
34
+ raise "Collection must be saved to query for bytes" if new_record?
35
+
36
+ # One query per member_id because Solr is not a relational database
37
+ sizes = member_ids.collect do |work_id|
38
+ query = ActiveFedora::SolrQueryBuilder.construct_query_for_rel(has_model: ::FileSet.to_class_uri)
39
+ argz = { fl: "id, #{file_size_field}",
40
+ fq: "{!join from=#{member_ids_field} to=id}id:#{work_id}"
41
+ }
42
+ files = ActiveFedora::SolrService.query(query, argz)
43
+ files.reduce(0) { |sum, f| sum + f[file_size_field].to_i }
44
+ end
45
+
46
+ sizes.reduce(0, :+)
47
+ end
48
+
49
+ private
50
+
51
+ # Field name to look up when locating the size of each file in Solr.
52
+ # Override for your own installation if using something different
53
+ def file_size_field
54
+ Solrizer.solr_name(:file_size, CurationConcerns::FileSetIndexer::STORED_INTEGER)
55
+ end
56
+
57
+ # Solr field name collections and works use to index member ids
58
+ def member_ids_field
59
+ Solrizer.solr_name('member_ids', :symbol)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,47 @@
1
+ module CurationConcerns
2
+ module FileSet
3
+ module BelongsToWorks
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ before_destroy :remove_representative_relationship
8
+ end
9
+
10
+ def generic_works
11
+ in_objects # in_objects is provided by Hydra::PCDM::ObjectBehavior
12
+ end
13
+
14
+ # OPTIMIZE: We can load this from Solr much faster than loading the objects
15
+ def generic_work_ids
16
+ generic_works.map(&:id)
17
+ end
18
+
19
+ # Returns the first parent object
20
+ # This is a hack to handle things like FileSets inheriting access controls from their parent. (see CurationConcerns::ParentContainer in app/controllers/concerns/curation_concers/parent_container.rb)
21
+ def parent
22
+ in_objects.first
23
+ end
24
+
25
+ # Returns the id of first parent object
26
+ # This is a hack to handle things like FileSets inheriting access controls from their parent. (see CurationConcerns::ParentContainer in app/controllers/concerns/curation_concers/parent_container.rb)
27
+ delegate :id, to: :parent, prefix: true
28
+
29
+ # Files with sibling relationships
30
+ # Returns all FileSets aggregated by any of the GenericWorks that aggregate the current object
31
+ def related_files
32
+ generic_works = self.generic_works
33
+ return [] if generic_works.empty?
34
+ generic_works.flat_map { |work| work.file_sets.select { |file_set| file_set.id != id } }
35
+ end
36
+
37
+ # If any parent works are pointing at this object as their representative, remove that pointer.
38
+ def remove_representative_relationship
39
+ generic_works = self.generic_works
40
+ return if generic_works.empty?
41
+ generic_works.each do |work|
42
+ work.update(representative_id: nil) if work.representative_id == id
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,65 @@
1
+ module CurationConcerns
2
+ module FileSet
3
+ module Derivatives
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ Hydra::Derivatives.source_file_service = CurationConcerns::LocalFileService
8
+ Hydra::Derivatives.output_file_service = CurationConcerns::PersistDerivatives
9
+ Hydra::Derivatives::FullTextExtract.output_file_service = CurationConcerns::PersistDirectlyContainedOutputFileService
10
+ after_destroy :cleanup_derivatives
11
+ end
12
+
13
+ # This completely overrides the version in Hydra::Works so that we
14
+ # read and write to a local file. It's important that characterization runs
15
+ # before derivatives so that we have a credible mime_type field to work with.
16
+ def create_derivatives(filename)
17
+ case mime_type
18
+ when *self.class.pdf_mime_types
19
+ Hydra::Derivatives::PdfDerivatives.create(filename,
20
+ outputs: [{ label: :thumbnail, format: 'jpg', size: '338x493', url: derivative_url('thumbnail') }])
21
+ Hydra::Derivatives::FullTextExtract.create(filename,
22
+ outputs: [{ url: uri, container: "extracted_text" }])
23
+ when *self.class.office_document_mime_types
24
+ Hydra::Derivatives::DocumentDerivatives.create(filename,
25
+ outputs: [{ label: :thumbnail, format: 'jpg',
26
+ size: '200x150>',
27
+ url: derivative_url('thumbnail') }])
28
+ Hydra::Derivatives::FullTextExtract.create(filename,
29
+ outputs: [{ url: uri, container: "extracted_text" }])
30
+ when *self.class.audio_mime_types
31
+ Hydra::Derivatives::AudioDerivatives.create(filename,
32
+ outputs: [{ label: 'mp3', format: 'mp3', url: derivative_url('mp3') },
33
+ { label: 'ogg', format: 'ogg', url: derivative_url('ogg') }])
34
+ when *self.class.video_mime_types
35
+ Hydra::Derivatives::VideoDerivatives.create(filename,
36
+ outputs: [{ label: :thumbnail, format: 'jpg', url: derivative_url('thumbnail') },
37
+ { label: 'webm', format: 'webm', url: derivative_url('webm') },
38
+ { label: 'mp4', format: 'mp4', url: derivative_url('mp4') }])
39
+ when *self.class.image_mime_types
40
+ Hydra::Derivatives::ImageDerivatives.create(filename,
41
+ outputs: [{ label: :thumbnail, format: 'jpg', size: '200x150>', url: derivative_url('thumbnail') }])
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ # The destination_name parameter has to match up with the file parameter
48
+ # passed to the DownloadsController
49
+ def derivative_url(destination_name)
50
+ path = derivative_path_factory.derivative_path_for_reference(self, destination_name)
51
+ URI("file://#{path}").to_s
52
+ end
53
+
54
+ def cleanup_derivatives
55
+ derivative_path_factory.derivatives_for_reference(self).each do |path|
56
+ FileUtils.rm_f(path)
57
+ end
58
+ end
59
+
60
+ def derivative_path_factory
61
+ DerivativePath
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,11 @@
1
+ module CurationConcerns
2
+ module FileSet
3
+ module FullTextIndexing
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ contains 'full_text'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module CurationConcerns
2
+ module FileSet
3
+ module Indexing
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ # override the default indexing service
8
+ def indexer
9
+ CurationConcerns::FileSetIndexer
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ module CurationConcerns
2
+ module FileSet
3
+ module Querying
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def where_digest_is(digest_string)
8
+ where Solrizer.solr_name('digest', :symbol) => urnify(digest_string)
9
+ end
10
+
11
+ def urnify(digest_string)
12
+ "urn:sha1:#{digest_string}"
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ module CurationConcerns
2
+ module FileSetBehavior
3
+ extend ActiveSupport::Concern
4
+ include Hydra::Works::FileSetBehavior
5
+ include Hydra::Works::VirusCheck
6
+ include Hydra::Works::Characterization
7
+ include Hydra::WithDepositor
8
+ include CurationConcerns::Serializers
9
+ include CurationConcerns::Noid
10
+ include CurationConcerns::FileSet::Derivatives
11
+ include CurationConcerns::Permissions
12
+ include CurationConcerns::BasicMetadata
13
+ include CurationConcerns::FileSet::FullTextIndexing
14
+ include CurationConcerns::FileSet::Indexing
15
+ include CurationConcerns::FileSet::BelongsToWorks
16
+ include CurationConcerns::FileSet::Querying
17
+ include CurationConcerns::HumanReadableType
18
+ include CurationConcerns::RequiredMetadata
19
+ include CurationConcerns::Naming
20
+ include Hydra::AccessControls::Embargoable
21
+ include GlobalID::Identification
22
+
23
+ included do
24
+ attr_accessor :file
25
+ self.human_readable_type = 'File'
26
+ end
27
+
28
+ def representative_id
29
+ to_param
30
+ end
31
+
32
+ def thumbnail_id
33
+ to_param
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,13 @@
1
+ module CurationConcerns::HasRepresentative
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ belongs_to :representative,
6
+ predicate: ::RDF::Vocab::EBUCore.hasRelatedMediaFragment,
7
+ class_name: 'ActiveFedora::Base'
8
+
9
+ belongs_to :thumbnail,
10
+ predicate: ::RDF::Vocab::EBUCore.hasRelatedImage,
11
+ class_name: 'ActiveFedora::Base'
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ module CurationConcerns
2
+ module HumanReadableType
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :human_readable_type
7
+ self.human_readable_type = name.demodulize.titleize
8
+ end
9
+
10
+ def to_solr(solr_doc = {})
11
+ super(solr_doc).tap do |doc|
12
+ doc[Solrizer.solr_name('human_readable_type', :facetable)] = human_readable_type
13
+ doc[Solrizer.solr_name('human_readable_type', :stored_searchable)] = human_readable_type
14
+ end
15
+ end
16
+ end
17
+ end