curation_concerns 0.12.0.pre1 → 0.12.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
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