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,105 @@
1
+ module CurationConcerns
2
+ class FileSetAuditService
3
+ attr_reader :file_set
4
+ def initialize(file_set)
5
+ @file_set = file_set
6
+ end
7
+
8
+ NO_RUNS = 999
9
+
10
+ # provides a human readable version of the audit status
11
+ # This may trigger audits to be run if required
12
+ # @param [Hydra::PCDM::File] file the file to get the audit status for, defaults to the original_file.
13
+ def human_readable_audit_status(file = file_set.original_file)
14
+ audit_stat(file)
15
+ end
16
+
17
+ # Check the file by only what is in the audit log.
18
+ # Do not try to access the versions if we do not have access to them.
19
+ # Use this when a file_set is loaded from solr instead of fedora
20
+ def logged_audit_status
21
+ audit_results = ChecksumAuditLog.logs_for(file_set.id, "original_file")
22
+ .collect { |result| result["pass"] }
23
+
24
+ if audit_results.length > 0
25
+ stat_to_string(audit_results.reduce(true) { |sum, value| sum && value })
26
+ else
27
+ 'Audits have not yet been run on this file.'
28
+ end
29
+ end
30
+
31
+ # Audits each version of each file if it hasn't been audited recently
32
+ # Returns the set of most recent audit status for each version of the content file
33
+ # @param [Hash] log container for messages, mapping file ids to status
34
+ def audit(log = {})
35
+ file_set.files.each { |f| log[f.id] = audit_file(f) }
36
+ log
37
+ end
38
+
39
+ private
40
+
41
+ def stat_to_string(stat)
42
+ case stat
43
+ when 0
44
+ 'failing'
45
+ when 1
46
+ 'passing'
47
+ else
48
+ fail ArgumentError, "Unknown status `#{stat}'"
49
+ end
50
+ end
51
+
52
+ # Retrieve or generate the audit check for a file (all versions are checked for versioned files)
53
+ # @param [ActiveFedora::File] file to audit
54
+ # @param [Array] log container for messages
55
+ def audit_file(file, log = [])
56
+ versions = file.has_versions? ? file.versions.all : file
57
+ versions.each { |v| log << audit_file_version(file.id, v.uri) }
58
+ log
59
+ end
60
+
61
+ # Retrieve or generate the audit check for a file and provide a human-readable status message.
62
+ # @param [ActiveFedora::File] file to audit
63
+ def audit_stat(file)
64
+ audit_results = audit_file(file).collect { |result| result['pass'] }
65
+ # check how many non runs we had
66
+ non_runs = audit_results.reduce(0) { |sum, value| value == NO_RUNS ? sum + 1 : sum }
67
+ if non_runs == 0
68
+ result = audit_results.reduce(true) { |sum, value| sum && value }
69
+ stat_to_string(result)
70
+ elsif non_runs < audit_results.length
71
+ result = audit_results.reduce(true) { |sum, value| value == NO_RUNS ? sum : sum && value }
72
+ "Some audits have not been run, but the ones run were #{stat_to_string(result)}."
73
+ else
74
+ 'Audits have not yet been run on this file.'
75
+ end
76
+ end
77
+
78
+ # Retrieve or generate the audit check for a specific version of a file
79
+ # @param [String] file_id used to find the file within its parent object (usually "original_file")
80
+ # @param [String] version_uri the version to be audited (or the file uri for non-versioned files)
81
+ def audit_file_version(file_id, version_uri)
82
+ latest_audit = ChecksumAuditLog.logs_for(file_set.id, file_id).first
83
+ return latest_audit unless needs_audit?(latest_audit)
84
+ AuditJob.perform_later(file_set, file_id, version_uri.to_s)
85
+ latest_audit || ChecksumAuditLog.new(pass: NO_RUNS, file_set_id: file_set.id, file_id: file_id, version: version_uri)
86
+ end
87
+
88
+ # Check if time since the last audit is greater than the maximum days allowed between audits
89
+ # @param [ChecksumAuditLog] latest_audit the most recent audit event
90
+ def needs_audit?(latest_audit)
91
+ return true unless latest_audit
92
+ unless latest_audit.updated_at
93
+ logger.warn "***AUDIT*** problem with audit log! Latest Audit is not nil, but updated_at is not set #{latest_audit}"
94
+ return true
95
+ end
96
+ days_since_last_audit(latest_audit) >= CurationConcerns.config.max_days_between_audits
97
+ end
98
+
99
+ # Return the number of days since the latest audit event
100
+ # @param [ChecksumAuditLog] latest_audit the most recent audit event
101
+ def days_since_last_audit(latest_audit)
102
+ (DateTime.now - latest_audit.updated_at.to_date).to_i
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,30 @@
1
+ module CurationConcerns
2
+ module IndexesThumbnails
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :thumbnail_path_service
7
+ self.thumbnail_path_service = ThumbnailPathService
8
+ class_attribute :thumbnail_field
9
+ self.thumbnail_field = 'thumbnail_path_ss'.freeze
10
+ end
11
+
12
+ # Adds thumbnail indexing to the solr document
13
+ def generate_solr_document
14
+ super.tap do |solr_doc|
15
+ index_thumbnails(solr_doc)
16
+ end
17
+ end
18
+
19
+ # Write the thumbnail paths into the solr_document
20
+ # @params [Hash] solr_document the solr document to add the field to
21
+ def index_thumbnails(solr_document)
22
+ solr_document[thumbnail_field] = thumbnail_path
23
+ end
24
+
25
+ # Returns the value for the thumbnail path to put into the solr document
26
+ def thumbnail_path
27
+ self.class.thumbnail_path_service.call(object)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ module CurationConcerns
2
+ class LocalFileService
3
+ # @param [String] file_name path to the file
4
+ # @param [Hash] _options
5
+ # @yield [File] opens the file and yields it to the block
6
+ def self.call(file_name, _options)
7
+ yield File.open(file_name)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,39 @@
1
+ require 'redlock'
2
+ module CurationConcerns
3
+ class LockManager
4
+ class UnableToAcquireLockError < StandardError; end
5
+
6
+ attr_reader :client
7
+
8
+ # @param [Fixnum] time_to_live How long to hold the lock in milliseconds
9
+ # @param [Fixnum] retry_count How many times to retry to acquire the lock before raising UnableToAcquireLockError
10
+ # @param [Fixnum] retry_delay Maximum wait time in milliseconds before retrying. Wait time is a random value between 0 and retry_delay.
11
+ def initialize(time_to_live, retry_count, retry_delay)
12
+ @ttl = time_to_live
13
+ @client = Redlock::Client.new([uri], retry_count: retry_count, retry_delay: retry_delay)
14
+ end
15
+
16
+ # Blocks until lock is acquired or timeout.
17
+ def lock(key)
18
+ returned_from_block = nil
19
+ client.lock(key, @ttl) do |locked|
20
+ raise UnableToAcquireLockError unless locked
21
+ returned_from_block = yield
22
+ end
23
+ returned_from_block
24
+ end
25
+
26
+ private
27
+
28
+ def uri
29
+ @uri ||= begin
30
+ opts = options
31
+ URI("#{opts[:scheme]}://#{opts[:host]}:#{opts[:port]}").to_s
32
+ end
33
+ end
34
+
35
+ def options
36
+ ::Resque.redis.redis.client.options
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,16 @@
1
+ module CurationConcerns
2
+ module Lockable
3
+ extend ActiveSupport::Concern
4
+
5
+ def acquire_lock_for(lock_key, &block)
6
+ lock_manager.lock(lock_key, &block)
7
+ end
8
+
9
+ def lock_manager
10
+ @lock_manager ||= CurationConcerns::LockManager.new(
11
+ CurationConcerns.config.lock_time_to_live,
12
+ CurationConcerns.config.lock_retry_count,
13
+ CurationConcerns.config.lock_retry_delay)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ require 'active_fedora/noid'
2
+
3
+ module CurationConcerns
4
+ module Noid
5
+ extend ActiveSupport::Concern
6
+
7
+ ## This overrides the default behavior, which is to ask Fedora for an id
8
+ # @see ActiveFedora::Persistence.assign_id
9
+ def assign_id
10
+ service.mint if CurationConcerns.config.enable_noids
11
+ end
12
+
13
+ def to_param
14
+ id
15
+ end
16
+
17
+ private
18
+
19
+ def service
20
+ @service ||= ActiveFedora::Noid::Service.new
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,33 @@
1
+ module CurationConcerns
2
+ class PersistDerivatives < Hydra::Derivatives::PersistOutputFileService
3
+ # Persists a derivative to the local file system.
4
+ # This Service conforms to the signature of `Hydra::Derivatives::PersistOutputFileService`.
5
+ # This service is an alternative to the default Hydra::Derivatives::PersistOutputFileService.
6
+ # This service will always update existing and does not do versioning of persisted files.
7
+ #
8
+ # @param [#read] stream the derivative filestream
9
+ # @param [Hash] directives
10
+ # @option directives [String] :url a url to the file destination
11
+ def self.call(stream, directives)
12
+ output_file(directives) do |output|
13
+ IO.copy_stream(stream, output)
14
+ end
15
+ end
16
+
17
+ # Open the output file to write and yield the block to the
18
+ # file. It makes the directories in the path if necessary.
19
+ def self.output_file(directives, &blk)
20
+ # name = derivative_path_factory.derivative_path_for_reference(object, destination_name)
21
+ raise ArgumentError, "No :url was provided in the transcoding directives" unless directives.key?(:url)
22
+ uri = URI(directives.fetch(:url))
23
+ raise ArgumentError, "Must provide a file uri" unless uri.scheme == 'file'
24
+ output_file_dir = File.dirname(uri.path)
25
+ FileUtils.mkdir_p(output_file_dir) unless File.directory?(output_file_dir)
26
+ File.open(uri.path, 'wb', &blk)
27
+ end
28
+
29
+ def self.derivative_path_factory
30
+ DerivativePath
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ module CurationConcerns
2
+ # This Service is an implementation of the Hydra::Derivatives::PersistOutputFileService
3
+ # It supports directly contained files
4
+ class PersistDirectlyContainedOutputFileService < Hydra::Derivatives::PersistBasicContainedOutputFileService
5
+ # This method conforms to the signature of the .call method on Hydra::Derivatives::PersistOutputFileService
6
+ # * Persists the file within the DirectContainer specified by :container
7
+ #
8
+ # @param [#read] stream the data to be persisted
9
+ # @param [Hash] directives directions which can be used to determine where to persist to.
10
+ # @option directives [String] url URI for the parent object.
11
+ # @option directives [String] container Name of the container association.
12
+ def self.call(stream, directives)
13
+ file = Hydra::Derivatives::IoDecorator.new(stream, new_mime_type(directives.fetch(:format)))
14
+ o_name = determine_original_name(file)
15
+ m_type = determine_mime_type(file)
16
+ uri = URI(directives.fetch(:url))
17
+ raise ArgumentError, "#{uri} is not an http uri" unless uri.scheme == 'http'
18
+ file_set = ActiveFedora::Base.find(ActiveFedora::Base.uri_to_id(uri.to_s))
19
+ remote_file = file_set.send("build_#{directives.fetch(:container)}".to_sym)
20
+ remote_file.content = file
21
+ remote_file.mime_type = m_type
22
+ remote_file.original_name = o_name
23
+ file_set.save
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,7 @@
1
+ module CurationConcerns
2
+ class RepositoryAuditService
3
+ def self.audit_everything
4
+ ::FileSet.find_each(&:audit)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,46 @@
1
+ module CurationConcerns
2
+ class ThumbnailPathService
3
+ class << self
4
+ # @param [Work, FileSet] the object to get the thumbnail for
5
+ # @return [String] a path to the thumbnail
6
+ def call(object)
7
+ return default_image unless object.thumbnail_id
8
+
9
+ thumb = fetch_thumbnail(object)
10
+ return unless thumb
11
+ if thumb.audio?
12
+ audio_image
13
+ elsif thumbnail?(thumb)
14
+ Rails.application.routes.url_helpers.download_path(object.thumbnail_id, file: 'thumbnail')
15
+ else
16
+ default_image
17
+ end
18
+ end
19
+
20
+ def fetch_thumbnail(object)
21
+ return object if object.thumbnail_id == object.id
22
+ ::FileSet.load_instance_from_solr(object.thumbnail_id)
23
+ rescue ActiveFedora::ObjectNotFoundError
24
+ Rails.logger.error("Couldn't find thumbnail #{object.thumbnail_id} for #{object.id}")
25
+ nil
26
+ end
27
+
28
+ def default_image
29
+ ActionController::Base.helpers.image_path 'default.png'
30
+ end
31
+
32
+ def audio_image
33
+ ActionController::Base.helpers.image_path 'audio.png'
34
+ end
35
+
36
+ # @return true if there a file on disk for this object, otherwise false
37
+ def thumbnail?(thumb)
38
+ File.exist?(thumbnail_filepath(thumb))
39
+ end
40
+
41
+ def thumbnail_filepath(thumb)
42
+ CurationConcerns::DerivativePath.derivative_path_for_reference(thumb, 'thumbnail')
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,7 @@
1
+ module CurationConcerns
2
+ class TimeService
3
+ def self.time_in_utc
4
+ DateTime.now.utc
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,26 @@
1
+ module CurationConcerns
2
+ class VersioningService
3
+ # Make a version and record the version committer
4
+ # @param [ActiveFedora::File] content
5
+ # @param [User, String] user
6
+ def self.create(content, user = nil)
7
+ content.create_version
8
+ record_committer(content, user) if user
9
+ end
10
+
11
+ # @param [ActiveFedora::File] file
12
+ def self.latest_version_of(file)
13
+ file.versions.last
14
+ end
15
+
16
+ # Record the version committer of the last version
17
+ # @param [ActiveFedora::File] content
18
+ # @param [User, String] user_key
19
+ def self.record_committer(content, user_key)
20
+ user_key = user_key.user_key if user_key.respond_to?(:user_key)
21
+ version = latest_version_of(content)
22
+ return if version.nil?
23
+ VersionCommitter.create(version_id: version.uri, committer_login: user_key)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,8 @@
1
+ # validates that the title has at least one title
2
+ class HasOneTitleValidator < ActiveModel::Validator
3
+ def validate(record)
4
+ if record.title.reject(&:empty?).empty?
5
+ record.errors[:title] << "You must provide a title"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ <div data-behavior="batch-add-button">
2
+ <%= check_box_tag "batch_document_ids[]", document.id, false, class:"batch_document_selector", id: "batch_document_#{document.id}" %>
3
+ </div>
@@ -0,0 +1,4 @@
1
+ <%= form_tag(all_batch_edits_path, :method => :put, :class=> "batch-select-all hidden", "data-behavior" => 'batch-select-all') do -%>
2
+ <%= submit_tag(label, :class=>'batch-all-button btn') %>
3
+ <% end %>
4
+
@@ -0,0 +1,10 @@
1
+ <div class="btn-group batch-tools hidden" data-behavior="batch-tools">
2
+ <button class="btn"><i class="icon-cog"></i> Tools</button>
3
+ <button class="btn dropdown-toggle" data-toggle="dropdown">
4
+ <span class="caret"></span>
5
+ </button>
6
+ <ul class="dropdown-menu">
7
+ <li data-behavior="batch-edit-activate" data-state="<%= batch_edit_state %>"><a href="#"><i class=""></i> Batch edit</a></li>
8
+ </ul>
9
+ </div>
10
+
@@ -3,15 +3,15 @@
3
3
  <ul class="dropdown-menu">
4
4
  <% if can? :edit, document %>
5
5
  <li>
6
- <%= link_to [collections, :edit, document], class: 'itemicon itemedit' do %><i class="glyphicon glyphicon-pencil"></i> Edit <%= document.human_readable_type %>
6
+ <%= link_to [:edit, document], class: 'itemicon itemedit' do %><i class="glyphicon glyphicon-pencil"></i> Edit <%= document.human_readable_type %>
7
7
  <% end %>
8
8
  </li>
9
9
  <li>
10
10
  <% if @collection # We're on the view page for @collection. -%>
11
11
  <%= button_for_remove_from_collection(document) %>
12
12
  <% else %>
13
- <%= link_to [collections, document], class: 'itemicon itemtrash', title: 'Delete Collection', method: :delete, data: {
14
- confirm: "Deleting a collection from #{t('curation_concerns.product_name')} is permanent. Click OK to delete this collection from #{t('curation_concerns.product_name')}, or Cancel to cancel this operation" } do %>
13
+ <%= link_to document, class: 'itemicon itemtrash', title: 'Delete Collection', method: :delete, data: {
14
+ confirm: "Deleting a collection from #{application_name} is permanent. Click OK to delete this collection from #{application_name}, or Cancel to cancel this operation" } do %>
15
15
  <i class="glyphicon glyphicon-trash"></i> Delete <%= document.human_readable_type %>
16
16
  <% end %>
17
17
  <% end %>
@@ -14,7 +14,7 @@
14
14
  <%= link_to document, class: 'itemicon itemtrash',
15
15
  title: "Delete #{document.human_readable_type}", method: :delete,
16
16
  data: {
17
- confirm: "Deleting a #{document.human_readable_type} from #{t('curation_concerns.product_name')} is permanent. Click OK to delete this #{document.human_readable_type} from #{t('curation_concerns.product_name')}, or Cancel to cancel this operation" } do %>
17
+ confirm: "Deleting a #{document.human_readable_type} from #{application_name} is permanent. Click OK to delete this #{document.human_readable_type} from #{application_name}, or Cancel to cancel this operation" } do %>
18
18
  <i class="glyphicon glyphicon-trash"></i> Delete <%= document.human_readable_type %>
19
19
  <% end %>
20
20
  <% end %>
@@ -1,4 +1,4 @@
1
- <h3 id="document-list-heading" class="sr-only">List of items deposited in <%= t('curation_concerns.product_name') %> that match your search criteria</h3>
1
+ <h3 id="document-list-heading" class="sr-only">List of items deposited in <%= application_name %> that match your search criteria</h3>
2
2
  <ol id="documents" class="container-fluid search-results-list" start="<%= document_counter_with_offset(0) %>" aria-labeled-by="document-list-heading">
3
3
  <%= render documents, as: :document %>
4
4
  </ol>
@@ -0,0 +1,2 @@
1
+ <%= render 'catalog/bookmark_control', document: document %>
2
+
@@ -0,0 +1,2 @@
1
+ <%= button_to label, new_collection_path, :method=>:get, :class=>"btn btn-primary collection-add submits-batches", 'data-behavior'=>'hydra-collections', :id=>'hydra-collection-add' %>
2
+
@@ -1,3 +1,3 @@
1
1
  <div class="collection-empty">
2
- <%= button_to label, collections.new_collection_path, method: :get, class: "btn btn-primary collection-add collection-empty", 'data-behavior'=>'hydra-collections', id: 'hydra-collection-add' %>
2
+ <%= button_to label, new_collection_path, method: :get, class: "btn btn-primary collection-add collection-empty", 'data-behavior'=>'hydra-collections', id: 'hydra-collection-add' %>
3
3
  </div>
@@ -0,0 +1,4 @@
1
+ <%# button for deleting a collections %>
2
+ <%# collection -- collection to be deleted %>
3
+ <%= button_to label, collection_path(collection.id), data:{confirm: confirm, behavior: 'hydra-collections'}, method: :delete, class: "btn btn-primary collection-delete", id: 'hydra-collection-add' %>
4
+
@@ -0,0 +1,8 @@
1
+ <%# button for removing a batch from a collection %>
2
+ <%# collection -- collection to be updated %>
3
+ <%# label -- button label %>
4
+ <%= form_for collection, url:collection_path(collection.id), :method=>:put do |f| %>
5
+ <%= f.hidden_field :members, :value => "remove" %>
6
+ <%= f.submit label, :class => "btn btn-primary collection-remove-selected submits-batches" %>
7
+ <% end %>
8
+
@@ -0,0 +1,4 @@
1
+ <%# button for updating a collections %>
2
+ <%# collection_id -- collection to be updated (use 'collection_replace_id' if you wish the form to be updated by a form value) %>
3
+ <%# label -- button label %>
4
+ <%= button_to label, collection_path(collection_id), :method=>:put, :class=>"btn btn-primary updates-collection submits-batches collection-update", 'data-behavior'=>'hydra-collections', :id=>'hydra-collection-update' %>
@@ -0,0 +1,4 @@
1
+ <%= form_for collection, url: collection_path(collection), method: :put, as: 'collection' do |f| %>
2
+ <%= single_item_action_remove_form_fields(f, document) %>
3
+ <%= f.submit label, class: "btn btn-primary collection-remove" %>
4
+ <% end %>
@@ -0,0 +1,9 @@
1
+ <% # header bar for doc items in index view -%>
2
+ <div class="documentHeader clearfix">
3
+ <% # main title container for doc partial view -%>
4
+ <h5 class="index_title"><%= t('blacklight.search.documents.counter', :counter => (document_counter + 1 + @response.params[:start].to_i)) %><%= link_to_document document, :label=>document_show_link_field(document), :counter => (document_counter + 1 + @response.params[:start].to_i) %></h5>
5
+
6
+ <div class="documentFunctions span2">
7
+ <%= button_for_remove_from_collection(@collection, document) %>
8
+ </div>
9
+ </div>
@@ -1,5 +1,5 @@
1
1
  <h2 class="non lower">Actions</h2>
2
2
  <p>
3
- <%= link_to "Go to Browse View", collections.collection_path %> &nbsp;&nbsp;
3
+ <%= link_to "Go to Browse View", collection_path %> &nbsp;&nbsp;
4
4
  <%= link_to "Add files from your dashboard", search_path_for_my_works %>
5
5
  </p>
@@ -1,6 +1,6 @@
1
1
  <%# This mimics the file_sets/descriptions partial & re-uses some of its sub-partials %>
2
2
  <div id="descriptions_display" class="tab-pane active">
3
- <%= form_for collection, url: collections.collection_path, html: {multipart: true, class: 'form-horizontal'} do |f| %>
3
+ <%= form_for collection, url: collection_path, html: {multipart: true, class: 'form-horizontal'} do |f| %>
4
4
  <%= hidden_field_tag('redirect_tab', 'descriptions') %>
5
5
  <h2 class="non lower">Descriptions <small class="pull-right"><span class="error">*</span> indicates required fields</small> </h2>
6
6
  <div class="well">
@@ -1,4 +1,4 @@
1
- <%= simple_form_for [collections, @form] do |f| %>
1
+ <%= simple_form_for @form do |f| %>
2
2
 
3
3
  <% if f.error_notification %>
4
4
  <div class="alert alert-danger fade in">
@@ -15,7 +15,7 @@
15
15
  <div class="col-md-12 form-actions">
16
16
  <%= f.submit class: 'btn btn-primary require-contributor-agreement' %>
17
17
  <% if @form.persisted? %>
18
- <%= link_to 'Cancel', collections.collection_path(@form), class: 'btn btn-link' %>
18
+ <%= link_to 'Cancel', collection_path(@form), class: 'btn btn-link' %>
19
19
  <% else %>
20
20
  <%= link_to 'Cancel', main_app.root_path, class: 'btn btn-link' %>
21
21
  <% end %>
@@ -0,0 +1,21 @@
1
+ <% if user_collections.nil?%>
2
+ <b> Make sure you have included CurationConcerns::SelectsCollections in your controller for this page and
3
+ added a before filter with one of the find_collections variations depending on your access level: find_collections, find_collections_with_read_access, find_collections_with_edit_access
4
+ </b>
5
+ <% elsif user_collections.blank? %>
6
+ <b> You do not have access to any existing collections please create a collection. </b>
7
+ <% else %>
8
+ Please Select your collection to add you files to:
9
+ <%= form_tag(collection_path(@collection), :method => "put") do %>
10
+ <input type="hidden" name="test" value="val" />
11
+ <%= hash_as_hidden_fields({:collection =>{members:"move"}}) %>
12
+ <div class="collection-list">
13
+ <ul>
14
+ <% user_collections.each do |collection| %>
15
+ <li> <%= radio_button_tag(:destination_collection_id, collection.id, false, :class => "collection-selector") %><%= label_tag(:collection, collection.title) %> </li>
16
+ <% end %>
17
+ </ul>
18
+ </div>
19
+ <%= button_to "Move to Selected Collection", collections_path, :method=>:put, :class=>"btn btn-primary collection-update submits-batches", 'data-behavior'=>'hydra-collections', :id=>'hydra-collection-move' %>
20
+ <% end %>
21
+ <% end %>
@@ -1,7 +1,7 @@
1
1
  <% fieldset_class ||= '' %>
2
2
  <% select_label_id ||= '' %>
3
3
 
4
- <%= form_tag collections.collections_path, method: :put do %>
4
+ <%= form_tag main_app.collections_path, method: :put do %>
5
5
  <fieldset class="required <%= fieldset_class %>">
6
6
  <%= hidden_field_tag 'collection[members]', 'add' %>
7
7
  <%= hidden_field_tag 'batch_document_ids[]', collectible.id %>
@@ -0,0 +1,2 @@
1
+ <%= render 'catalog/index_default', document: document, document_counter: document_counter %>
2
+
@@ -0,0 +1,2 @@
1
+ <%= render 'catalog/index_header_default', document: document, document_counter: document_counter %>
2
+
@@ -1 +1 @@
1
- <a href="<%= collections.collection_path %>" target="_new"><img src="Other.png" alt="No preview available" width="338" /></a>
1
+ <a href="<%= collection_path %>" target="_new"><img src="Other.png" alt="No preview available" width="338" /></a>
@@ -1,6 +1,6 @@
1
1
  <% if @response.total_pages > 1 %>
2
2
  <div class="pager">
3
- <%= paginate @response, outer_window: 2, theme: 'blacklight', route_set: collections %>
3
+ <%= paginate @response, outer_window: 2, theme: 'blacklight' %>
4
4
  <div class="clearfix"></div>
5
5
  </div><!-- /pager -->
6
6
  <% end %>
@@ -0,0 +1 @@
1
+ <%= paginate paginate_compact, page_entries_info: page_entries_info(paginate_compact), theme: :blacklight_compact %>
@@ -0,0 +1,9 @@
1
+ <% if show_pagination? and @response.total_pages > 1 %>
2
+ <div class="row record-padding">
3
+ <div class="col-md-9">
4
+ <div class="pagination">
5
+ <%= paginate @response, :outer_window => 2, :theme => 'blacklight' %>
6
+ </div>
7
+ </div>
8
+ </div>
9
+ <% end %>
@@ -1,5 +1,5 @@
1
1
  <div class="col-md-3 pull-right">
2
- <%= form_for([collections, @collection], url: collections.edit_collection_path , method: :get, class: "well form-search") do |f| %>
2
+ <%= form_for @collection, url: edit_collection_path, method: :get, class: "well form-search" do |f| %>
3
3
 
4
4
  <label class="sr-only">Search Collection <%= @collection.title %></label>
5
5
  <div class="input-group">
@@ -1,5 +1,5 @@
1
1
  <div class="col-md-3 pull-right">
2
- <%= form_for [collections, @presenter], method: :get, class: "well form-search" do |f| %>
2
+ <%= form_for @presenter, method: :get, class: "well form-search" do |f| %>
3
3
 
4
4
  <label class="sr-only">Search Collection <%= @presenter.title %></label>
5
5
  <div class="input-group">