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.
- checksums.yaml +4 -4
- data/.rubocop.yml +24 -16
- data/Gemfile +0 -4
- data/README.md +14 -0
- data/RELEASING.md +2 -2
- data/Rakefile +2 -0
- data/app/actors/concerns/curation_concerns/manages_embargoes_actor.rb +28 -0
- data/app/actors/curation_concerns/abstract_actor.rb +28 -0
- data/app/actors/curation_concerns/add_to_collection_actor.rb +38 -0
- data/app/actors/curation_concerns/apply_order_actor.rb +24 -0
- data/app/actors/curation_concerns/assign_identifier_actor.rb +7 -0
- data/app/actors/curation_concerns/assign_representative_actor.rb +18 -0
- data/app/actors/curation_concerns/attach_files_actor.rb +39 -0
- data/app/actors/curation_concerns/base_actor.rb +71 -0
- data/app/actors/curation_concerns/embargo_actor.rb +19 -0
- data/app/actors/curation_concerns/file_actor.rb +79 -0
- data/app/actors/curation_concerns/file_set_actor.rb +146 -0
- data/app/actors/curation_concerns/interpret_visibility_actor.rb +123 -0
- data/app/actors/curation_concerns/lease_actor.rb +19 -0
- data/app/actors/curation_concerns/root_actor.rb +17 -0
- data/app/actors/curation_concerns/work_actor_behavior.rb +8 -0
- data/app/assets/javascripts/curation_concerns/batch_select.js +42 -0
- data/app/assets/javascripts/curation_concerns/collections.js +13 -0
- data/app/assets/javascripts/curation_concerns/curation_concerns.js +2 -0
- data/app/assets/stylesheets/curation_concerns/_curation_concerns.scss +0 -3
- data/app/assets/stylesheets/curation_concerns/_modules.scss +1 -1
- data/app/assets/stylesheets/curation_concerns/_positioning.scss +3 -6
- data/app/assets/stylesheets/curation_concerns/_theme.scss +0 -39
- data/app/assets/stylesheets/curation_concerns/_typography.scss +0 -69
- data/app/assets/stylesheets/curation_concerns/modules/classify_work.scss +0 -2
- data/app/assets/stylesheets/curation_concerns/modules/collections.scss +4 -0
- data/app/assets/stylesheets/curation_concerns/modules/forms.scss +0 -4
- data/app/assets/stylesheets/curation_concerns/modules/site_actions.scss +34 -29
- data/app/assets/stylesheets/curation_concerns/modules/site_search.scss +0 -46
- data/app/assets/stylesheets/curation_concerns.scss +4 -0
- data/app/controllers/concerns/curation_concerns/collections_controller_behavior.rb +166 -21
- data/app/controllers/concerns/curation_concerns/embargoes_controller_behavior.rb +1 -1
- data/app/controllers/concerns/curation_concerns/leases_controller_behavior.rb +1 -1
- data/app/controllers/concerns/curation_concerns/selects_collections.rb +65 -0
- data/app/forms/curation_concerns/forms/collection_edit_form.rb +0 -29
- data/app/forms/curation_concerns/forms/work_form.rb +2 -1
- data/app/helpers/batch_select_helper.rb +23 -0
- data/app/helpers/collections_helper.rb +4 -0
- data/app/helpers/curation_concerns/collections_helper.rb +2 -2
- data/app/helpers/curation_concerns/collections_helper_behavior.rb +56 -0
- data/app/helpers/curation_concerns/render_constraints_helper.rb +14 -35
- data/app/helpers/curation_concerns/title_helper.rb +4 -0
- data/app/indexers/curation_concerns/collection_indexer.rb +16 -0
- data/app/indexers/curation_concerns/file_set_indexer.rb +46 -0
- data/app/indexers/curation_concerns/work_indexer.rb +15 -0
- data/app/jobs/audit_job.rb +49 -0
- data/app/jobs/characterize_job.rb +11 -0
- data/app/jobs/create_derivatives_job.rb +21 -0
- data/app/jobs/import_url_job.rb +48 -0
- data/app/jobs/ingest_file_job.rb +30 -0
- data/app/jobs/ingest_local_file_job.rb +20 -0
- data/app/jobs/resolrize_job.rb +7 -0
- data/app/models/checksum_audit_log.rb +20 -0
- data/app/models/collection.rb +6 -0
- data/app/models/concerns/curation_concerns/ability.rb +49 -0
- data/app/models/concerns/curation_concerns/basic_metadata.rb +64 -0
- data/app/models/concerns/curation_concerns/collection.rb +16 -0
- data/app/models/concerns/curation_concerns/collection_behavior.rb +62 -0
- data/app/models/concerns/curation_concerns/file_set/belongs_to_works.rb +47 -0
- data/app/models/concerns/curation_concerns/file_set/derivatives.rb +65 -0
- data/app/models/concerns/curation_concerns/file_set/full_text_indexing.rb +11 -0
- data/app/models/concerns/curation_concerns/file_set/indexing.rb +14 -0
- data/app/models/concerns/curation_concerns/file_set/querying.rb +17 -0
- data/app/models/concerns/curation_concerns/file_set_behavior.rb +36 -0
- data/app/models/concerns/curation_concerns/has_representative.rb +13 -0
- data/app/models/concerns/curation_concerns/human_readable_type.rb +17 -0
- data/app/models/concerns/curation_concerns/naming.rb +17 -0
- data/app/models/concerns/curation_concerns/permissions/readable.rb +18 -0
- data/app/models/concerns/curation_concerns/permissions/writable.rb +34 -0
- data/app/models/concerns/curation_concerns/permissions.rb +7 -0
- data/app/models/concerns/curation_concerns/required_metadata.rb +30 -0
- data/app/models/concerns/curation_concerns/serializers.rb +13 -0
- data/app/models/concerns/curation_concerns/solr_document_behavior.rb +147 -0
- data/app/models/concerns/curation_concerns/user.rb +18 -0
- data/app/models/concerns/curation_concerns/with_file_sets.rb +37 -0
- data/app/models/concerns/curation_concerns/work_behavior.rb +45 -0
- data/app/models/curation_concerns/classify_concern.rb +49 -0
- data/app/models/curation_concerns/quick_classification_query.rb +38 -0
- data/app/models/single_use_link.rb +34 -0
- data/app/models/version_committer.rb +2 -0
- data/app/search_builders/curation_concerns/collection_member_search_builder.rb +1 -1
- data/app/search_builders/curation_concerns/collection_search_builder.rb +33 -0
- data/app/search_builders/curation_concerns/member_search_builder.rb +17 -0
- data/app/services/curation_concerns/derivative_path.rb +49 -0
- data/app/services/curation_concerns/file_set_audit_service.rb +105 -0
- data/app/services/curation_concerns/indexes_thumbnails.rb +30 -0
- data/app/services/curation_concerns/local_file_service.rb +10 -0
- data/app/services/curation_concerns/lock_manager.rb +39 -0
- data/app/services/curation_concerns/lockable.rb +16 -0
- data/app/services/curation_concerns/noid.rb +23 -0
- data/app/services/curation_concerns/persist_derivatives.rb +33 -0
- data/app/services/curation_concerns/persist_directly_contained_output_file_service.rb +26 -0
- data/app/services/curation_concerns/repository_audit_service.rb +7 -0
- data/app/services/curation_concerns/thumbnail_path_service.rb +46 -0
- data/app/services/curation_concerns/time_service.rb +7 -0
- data/app/services/curation_concerns/versioning_service.rb +26 -0
- data/app/validators/has_one_title_validator.rb +8 -0
- data/app/views/batch_select/_add_button.html.erb +3 -0
- data/app/views/batch_select/_check_all.html.erb +4 -0
- data/app/views/batch_select/_tools.html.erb +10 -0
- data/app/views/catalog/_action_menu_partials/_collection.html.erb +3 -3
- data/app/views/catalog/_action_menu_partials/_default.html.erb +1 -1
- data/app/views/catalog/_document_list.html.erb +1 -1
- data/app/views/collections/_bookmark_control.html.erb +2 -0
- data/app/views/collections/_button_create_collection.html.erb +2 -0
- data/app/views/collections/_button_for_creating_empty_collection.html.erb +1 -1
- data/app/views/collections/_button_for_delete_collection.html.erb +4 -0
- data/app/views/collections/_button_for_remove_selected_from_collection.html.erb +8 -0
- data/app/views/collections/_button_for_update_collection.html.erb +4 -0
- data/app/views/collections/_button_remove_from_collection.html.erb +4 -0
- data/app/views/collections/_document_header.html.erb +9 -0
- data/app/views/collections/_edit_actions.html.erb +1 -1
- data/app/views/collections/_edit_descriptions.html.erb +1 -1
- data/app/views/collections/_form.html.erb +2 -2
- data/app/views/collections/_form_for_select_destination_collection.html.erb +21 -0
- data/app/views/collections/_form_to_add_member.html.erb +1 -1
- data/app/views/collections/_index_default.html.erb +2 -0
- data/app/views/collections/_index_header_default.html.erb +2 -0
- data/app/views/collections/_media_display.html.erb +1 -1
- data/app/views/collections/_paginate.html.erb +1 -1
- data/app/views/collections/_paginate_compact.html.erb +1 -0
- data/app/views/collections/_results_pagination.html.erb +9 -0
- data/app/views/collections/_search_collection_dashboard_form.html.erb +1 -1
- data/app/views/collections/_search_form.html.erb +1 -1
- data/app/views/collections/_search_results.html.erb +23 -0
- data/app/views/collections/_show_actions.html.erb +1 -1
- data/app/views/collections/_sort_and_per_page.html.erb +1 -1
- data/app/views/collections/_view_type_group.html.erb +1 -1
- data/app/views/collections/index.html.erb +9 -0
- data/app/views/collections/new.html.erb +3 -0
- data/app/views/curation_concerns/base/_form_permission.html.erb +10 -11
- data/app/views/curation_concerns/base/_form_permission_embargo.html.erb +1 -1
- data/app/views/curation_concerns/base/_form_permission_lease.html.erb +1 -1
- data/app/views/curation_concerns/base/_legally_binding_text.html.erb +7 -7
- data/app/views/curation_concerns/base/_related_files.html.erb +1 -1
- data/app/views/curation_concerns/base/_visibility.html.erb +2 -2
- data/app/views/curation_concerns/file_sets/_actions.html.erb +1 -1
- data/app/views/embargoes/_list_expired_active_embargoes.html.erb +1 -1
- data/app/views/error/single_use_error.html.erb +1 -1
- data/app/views/shared/_add_content.html.erb +17 -15
- data/app/views/shared/_brand_bar.html.erb +19 -10
- data/app/views/shared/_header.html.erb +2 -6
- data/app/views/shared/_my_actions.html.erb +28 -27
- data/app/views/shared/_site_actions.html.erb +5 -1
- data/app/views/shared/_site_search.html.erb +3 -2
- data/app/views/shared/_title_bar.html.erb +7 -16
- data/app/views/welcome/index.html.erb +2 -2
- data/config/locales/curation_concerns.en.yml +25 -1
- data/curation_concerns.gemspec +21 -5
- data/lib/curation_concerns/collections/accepts_batches.rb +53 -0
- data/lib/curation_concerns/collections/search_service.rb +57 -0
- data/lib/curation_concerns/collections.rb +10 -0
- data/lib/curation_concerns/configuration.rb +167 -0
- data/lib/curation_concerns/engine.rb +22 -1
- data/lib/curation_concerns/messages.rb +68 -0
- data/lib/curation_concerns/models.rb +42 -0
- data/lib/curation_concerns/name.rb +20 -0
- data/lib/curation_concerns/null_logger.rb +10 -0
- data/lib/curation_concerns/rails/routes.rb +1 -3
- data/lib/curation_concerns/version.rb +1 -1
- data/lib/curation_concerns.rb +2 -0
- data/lib/generators/curation_concerns/abstract_migration_generator.rb +31 -0
- data/lib/generators/curation_concerns/clamav_generator.rb +19 -0
- data/lib/generators/curation_concerns/collection_generator.rb +15 -0
- data/lib/generators/curation_concerns/install_generator.rb +1 -2
- data/lib/generators/curation_concerns/models_generator.rb +62 -0
- data/lib/generators/curation_concerns/templates/app/models/collection.rb +6 -0
- data/lib/generators/curation_concerns/templates/app/models/file_set.rb +4 -0
- data/lib/generators/curation_concerns/templates/config/clamav.rb +1 -0
- data/lib/generators/curation_concerns/templates/config/curation_concerns.rb +61 -0
- data/lib/generators/curation_concerns/templates/config/mime_types.rb +6 -0
- data/lib/generators/curation_concerns/templates/config/redis.yml +9 -0
- data/lib/generators/curation_concerns/templates/config/redis_config.rb +29 -0
- data/lib/generators/curation_concerns/templates/config/resque-pool.yml +1 -0
- data/lib/generators/curation_concerns/templates/config/resque_config.rb +6 -0
- data/lib/generators/curation_concerns/templates/curation_concerns.scss +3 -2
- data/lib/generators/curation_concerns/templates/migrations/create_checksum_audit_logs.rb +19 -0
- data/lib/generators/curation_concerns/templates/migrations/create_single_use_links.rb +12 -0
- data/lib/generators/curation_concerns/templates/migrations/create_version_committers.rb +15 -0
- data/lib/tasks/migrate.rake +11 -0
- data/lib/tasks/resque.rake +14 -0
- data/lib/tasks/solr_reindex.rake +8 -0
- data/spec/actors/curation_concerns/file_set_actor_spec.rb +31 -0
- data/spec/controllers/accepts_batches_controller_spec.rb +65 -0
- data/spec/controllers/collections_controller_spec.rb +272 -0
- data/spec/controllers/curation_concerns/collections_controller_spec.rb +1 -2
- data/spec/controllers/selects_collections_controller_spec.rb +109 -0
- data/spec/features/create_work_spec.rb +1 -1
- data/spec/features/work_generator_spec.rb +1 -1
- data/spec/forms/collection_edit_form_spec.rb +2 -9
- data/spec/forms/work_form_spec.rb +5 -0
- data/spec/helpers/collections_helper_spec.rb +129 -0
- data/spec/helpers/curation_concerns/collections_helper_spec.rb +2 -2
- data/spec/helpers/render_constraints_helper_spec.rb +23 -1
- data/spec/lib/curation_concerns/collections/search_service_spec.rb +33 -0
- data/spec/models/collection_spec.rb +165 -0
- data/spec/tasks/rake_spec.rb +1 -1
- data/spec/test_app_templates/lib/generators/test_app_generator.rb +1 -1
- data/spec/views/curation_concerns/base/_form_permission.html.erb_spec.rb +4 -1
- data/spec/views/curation_concerns/file_sets/show.html.erb_spec.rb +1 -0
- data/spec/views/shared/_add_content.html.erb_spec.rb +3 -3
- metadata +341 -24
- data/VERSION +0 -1
- data/app/assets/stylesheets/curation_concerns/_global-variables.scss +0 -5
- data/app/assets/stylesheets/curation_concerns/modules/multi_value_fields.scss +0 -52
- data/app/views/collections/_form_required_information.html.erb +0 -11
- 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,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,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,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
|