hyrax 5.2.0 → 5.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.dassie/.env +5 -1
- data/.dassie/Gemfile +4 -2
- data/.dassie/app/assets/images/bulkrax/removed.png +0 -0
- data/.dassie/app/assets/javascripts/application.js +2 -0
- data/.dassie/app/assets/stylesheets/application.css +1 -0
- data/.dassie/app/controllers/catalog_controller.rb +1 -0
- data/.dassie/app/forms/admin_set_resource_form.rb +6 -0
- data/.dassie/app/forms/collection_resource_form.rb +5 -2
- data/.dassie/app/forms/generic_work_resource_form.rb +5 -2
- data/.dassie/app/forms/monograph_form.rb +5 -3
- data/.dassie/app/indexers/admin_set_resource_indexer.rb +5 -0
- data/.dassie/app/indexers/collection_resource_indexer.rb +3 -3
- data/.dassie/app/indexers/generic_work_resource_indexer.rb +6 -3
- data/.dassie/app/indexers/monograph_indexer.rb +6 -2
- data/.dassie/app/models/ability.rb +8 -0
- data/.dassie/app/models/collection_resource.rb +4 -2
- data/.dassie/app/models/concerns/bulkrax/has_local_processing.rb +8 -0
- data/.dassie/app/models/generic_work_resource.rb +5 -2
- data/.dassie/app/models/monograph.rb +5 -2
- data/.dassie/app/views/blacklight_dynamic_sitemap/sitemap/show.xml.builder +22 -0
- data/.dassie/bin/importer +142 -0
- data/.dassie/bin/web +4 -0
- data/.dassie/config/browse_everything_providers.yml +1 -1
- data/.dassie/config/bulkrax_api.yml +86 -0
- data/.dassie/config/initializers/bulkrax.rb +92 -0
- data/.dassie/config/initializers/clamav.rb +2 -0
- data/.dassie/config/initializers/hyrax.rb +7 -1
- data/.dassie/config/initializers/riiif.rb +4 -80
- data/.dassie/config/initializers/wings.rb +1 -1
- data/.dassie/config/metadata_profiles/m3_profile.yaml +929 -0
- data/.dassie/config/puma.rb +4 -2
- data/.dassie/config/routes.rb +7 -0
- data/.dassie/config/sidekiq.yml +17 -0
- data/.dassie/config/uv/uv.html +63 -81
- data/.dassie/db/migrate/20240606205215_create_hyrax_flexible_schemas.rb +9 -0
- data/.dassie/db/migrate/20240606205216_add_contexts_to_hyrax_flexible_schemas.rb +5 -0
- data/.dassie/db/migrate/20250826142438_create_bulkrax_importers.bulkrax.rb +19 -0
- data/.dassie/db/migrate/20250826142439_create_bulkrax_importer_runs.bulkrax.rb +17 -0
- data/.dassie/db/migrate/20250826142440_create_bulkrax_entries.bulkrax.rb +17 -0
- data/.dassie/db/migrate/20250826142441_add_status_to_entry.bulkrax.rb +10 -0
- data/.dassie/db/migrate/20250826142442_add_collections_to_importer_runs.bulkrax.rb +7 -0
- data/.dassie/db/migrate/20250826142443_change_collection_ids_on_entries.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142444_create_bulkrax_exporters.bulkrax.rb +20 -0
- data/.dassie/db/migrate/20250826142445_create_bulkrax_exporter_runs.bulkrax.rb +15 -0
- data/.dassie/db/migrate/20250826142446_change_importer_and_exporter_to_polymorphic.bulkrax.rb +19 -0
- data/.dassie/db/migrate/20250826142447_add_total_collection_records_to_importer_runs.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142448_add_children_to_importer_runs.bulkrax.rb +7 -0
- data/.dassie/db/migrate/20250826142449_change_total_records_to_total_work_entries.bulkrax.rb +7 -0
- data/.dassie/db/migrate/20250826142450_change_entry_last_error.bulkrax.rb +20 -0
- data/.dassie/db/migrate/20250826142451_add_validate_only_to_bulkrax_importers.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142452_add_status_to_importers.bulkrax.rb +10 -0
- data/.dassie/db/migrate/20250826142453_remove_foreign_key_from_bulkrax_entries.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142454_add_status_to_exporters.bulkrax.rb +8 -0
- data/.dassie/db/migrate/20250826142455_add_invalid_record_to_importer_run.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142456_create_bulkrax_statuses.bulkrax.rb +19 -0
- data/.dassie/db/migrate/20250826142457_move_to_statuses.bulkrax.rb +31 -0
- data/.dassie/db/migrate/20250826142458_add_date_filter_and_status_to_bulkrax_exporters.bulkrax.rb +8 -0
- data/.dassie/db/migrate/20250826142459_add_workflow_status_to_bulkrax_exporter.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142460_remove_unused_last_error.bulkrax.rb +8 -0
- data/.dassie/db/migrate/20250826142461_increase_text_sizes.bulkrax.rb +13 -0
- data/.dassie/db/migrate/20250826142462_change_bulkrax_statuses_error_message_column_type_to_text.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142463_rename_children_counters_to_relationships.bulkrax.rb +7 -0
- data/.dassie/db/migrate/20250826142464_add_file_set_counters_to_importer_runs.bulkrax.rb +8 -0
- data/.dassie/db/migrate/20250826142465_add_import_attempts_to_entries.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142466_add_work_counters_to_importer_runs.bulkrax.rb +7 -0
- data/.dassie/db/migrate/20250826142467_create_bulkrax_pending_relationships.bulkrax.rb +14 -0
- data/.dassie/db/migrate/20250826142468_add_order_to_bulkrax_pending_relationships.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142469_add_include_thumbnails_to_bulkrax_exporters.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142470_add_generated_metadata_to_bulkrax_exporters.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142471_rename_bulkrax_importer_run_to_importer_run.bulkrax.rb +18 -0
- data/.dassie/db/migrate/20250826142472_add_indices_to_bulkrax.bulkrax.rb +26 -0
- data/.dassie/db/migrate/20250826142473_denormalize_status_message.bulkrax.rb +8 -0
- data/.dassie/db/migrate/20250826142474_update_identifier_index.bulkrax.rb +7 -0
- data/.dassie/db/migrate/20250826142475_add_index_to_metadata_bulkrax_identifier.bulkrax.rb +19 -0
- data/.dassie/db/migrate/20250826142476_add_file_name_to_uploaded_files.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142477_add_error_tracking_to_pending_relationships.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142478_add_last_imported_at_to_bulkrax_importers.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142479_add_next_import_at_to_bulkrax_importers.bulkrax.rb +6 -0
- data/.dassie/db/migrate/20250826142480_entry_error_denormalization.bulkrax.rb +8 -0
- data/.dassie/db/migrate/20250826142481_faster_first_entry.bulkrax.rb +8 -0
- data/.dassie/db/migrate/20260430000001_create_hyrax_redirect_paths.hyrax.rb +13 -0
- data/.dassie/db/migrate/20260521003627_add_additional_redirect_info.hyrax.rb +32 -0
- data/.dassie/db/schema.rb +154 -1
- data/.dassie/package.json +1 -1
- data/.dassie/rspec_split_configuration.json +835 -0
- data/.dassie/solr/conf/solrconfig.xml +21 -5
- data/.github/{ISSUE_TEMPLATE.md → ISSUE_TEMPLATE/issue_template.md} +5 -0
- data/.github/workflows/lint-build-test.yml +5 -1
- data/.github/workflows/release-charts.yml +48 -0
- data/.gitignore +6 -0
- data/.koppie/.env +5 -1
- data/.koppie/Gemfile +3 -1
- data/.koppie/app/assets/images/bulkrax/removed.png +0 -0
- data/.koppie/app/assets/javascripts/application.js +2 -0
- data/.koppie/app/assets/stylesheets/application.css +1 -0
- data/.koppie/app/controllers/catalog_controller.rb +1 -1
- data/.koppie/app/forms/collection_resource_form.rb +5 -2
- data/.koppie/app/forms/file_set_form.rb +5 -0
- data/.koppie/app/forms/generic_work_form.rb +5 -3
- data/.koppie/app/forms/monograph_form.rb +5 -3
- data/.koppie/app/indexers/collection_resource_indexer.rb +5 -2
- data/.koppie/app/indexers/file_set_indexer.rb +7 -0
- data/.koppie/app/indexers/generic_work_indexer.rb +6 -3
- data/.koppie/app/indexers/monograph_indexer.rb +6 -2
- data/.koppie/app/models/ability.rb +8 -0
- data/.koppie/app/models/collection_resource.rb +4 -2
- data/.koppie/app/models/concerns/bulkrax/has_local_processing.rb +8 -0
- data/.koppie/app/models/generic_work.rb +5 -2
- data/.koppie/app/models/monograph.rb +5 -2
- data/.koppie/app/views/blacklight_dynamic_sitemap/sitemap/show.xml.builder +22 -0
- data/.koppie/bin/importer +142 -0
- data/.koppie/bin/web +4 -0
- data/.koppie/config/browse_everything_providers.yml +2 -0
- data/.koppie/config/bulkrax_api.yml +86 -0
- data/.koppie/config/initializers/bulkrax.rb +92 -0
- data/.koppie/config/initializers/clamav.rb +2 -0
- data/.koppie/config/initializers/hyrax.rb +11 -3
- data/.koppie/config/initializers/riiif.rb +4 -82
- data/.koppie/config/metadata_profiles/m3_profile.yaml +930 -0
- data/.koppie/config/routes.rb +7 -0
- data/.koppie/config/sidekiq.yml +2 -0
- data/.koppie/config/uv/uv.html +63 -81
- data/.koppie/db/migrate/20240606205215_create_hyrax_flexible_schemas.rb +9 -0
- data/.koppie/db/migrate/20240606205216_add_contexts_to_hyrax_flexible_schemas.rb +5 -0
- data/.koppie/db/migrate/20250826142438_create_bulkrax_importers.bulkrax.rb +19 -0
- data/.koppie/db/migrate/20250826142439_create_bulkrax_importer_runs.bulkrax.rb +17 -0
- data/.koppie/db/migrate/20250826142440_create_bulkrax_entries.bulkrax.rb +17 -0
- data/.koppie/db/migrate/20250826142441_add_status_to_entry.bulkrax.rb +10 -0
- data/.koppie/db/migrate/20250826142442_add_collections_to_importer_runs.bulkrax.rb +7 -0
- data/.koppie/db/migrate/20250826142443_change_collection_ids_on_entries.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142444_create_bulkrax_exporters.bulkrax.rb +20 -0
- data/.koppie/db/migrate/20250826142445_create_bulkrax_exporter_runs.bulkrax.rb +15 -0
- data/.koppie/db/migrate/20250826142446_change_importer_and_exporter_to_polymorphic.bulkrax.rb +19 -0
- data/.koppie/db/migrate/20250826142447_add_total_collection_records_to_importer_runs.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142448_add_children_to_importer_runs.bulkrax.rb +7 -0
- data/.koppie/db/migrate/20250826142449_change_total_records_to_total_work_entries.bulkrax.rb +7 -0
- data/.koppie/db/migrate/20250826142450_change_entry_last_error.bulkrax.rb +20 -0
- data/.koppie/db/migrate/20250826142451_add_validate_only_to_bulkrax_importers.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142452_add_status_to_importers.bulkrax.rb +10 -0
- data/.koppie/db/migrate/20250826142453_remove_foreign_key_from_bulkrax_entries.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142454_add_status_to_exporters.bulkrax.rb +8 -0
- data/.koppie/db/migrate/20250826142455_add_invalid_record_to_importer_run.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142456_create_bulkrax_statuses.bulkrax.rb +19 -0
- data/.koppie/db/migrate/20250826142457_move_to_statuses.bulkrax.rb +31 -0
- data/.koppie/db/migrate/20250826142458_add_date_filter_and_status_to_bulkrax_exporters.bulkrax.rb +8 -0
- data/.koppie/db/migrate/20250826142459_add_workflow_status_to_bulkrax_exporter.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142460_remove_unused_last_error.bulkrax.rb +8 -0
- data/.koppie/db/migrate/20250826142461_increase_text_sizes.bulkrax.rb +13 -0
- data/.koppie/db/migrate/20250826142462_change_bulkrax_statuses_error_message_column_type_to_text.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142463_rename_children_counters_to_relationships.bulkrax.rb +7 -0
- data/.koppie/db/migrate/20250826142464_add_file_set_counters_to_importer_runs.bulkrax.rb +8 -0
- data/.koppie/db/migrate/20250826142465_add_import_attempts_to_entries.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142466_add_work_counters_to_importer_runs.bulkrax.rb +7 -0
- data/.koppie/db/migrate/20250826142467_create_bulkrax_pending_relationships.bulkrax.rb +14 -0
- data/.koppie/db/migrate/20250826142468_add_order_to_bulkrax_pending_relationships.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142469_add_include_thumbnails_to_bulkrax_exporters.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142470_add_generated_metadata_to_bulkrax_exporters.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142471_rename_bulkrax_importer_run_to_importer_run.bulkrax.rb +18 -0
- data/.koppie/db/migrate/20250826142472_add_indices_to_bulkrax.bulkrax.rb +26 -0
- data/.koppie/db/migrate/20250826142473_denormalize_status_message.bulkrax.rb +8 -0
- data/.koppie/db/migrate/20250826142474_update_identifier_index.bulkrax.rb +7 -0
- data/.koppie/db/migrate/20250826142475_add_index_to_metadata_bulkrax_identifier.bulkrax.rb +19 -0
- data/.koppie/db/migrate/20250826142476_add_file_name_to_uploaded_files.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142477_add_error_tracking_to_pending_relationships.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142478_add_last_imported_at_to_bulkrax_importers.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142479_add_next_import_at_to_bulkrax_importers.bulkrax.rb +6 -0
- data/.koppie/db/migrate/20250826142480_entry_error_denormalization.bulkrax.rb +8 -0
- data/.koppie/db/migrate/20250826142481_faster_first_entry.bulkrax.rb +8 -0
- data/.koppie/db/migrate/20260430000001_create_hyrax_redirect_paths.hyrax.rb +13 -0
- data/.koppie/db/migrate/20260521003627_add_additional_redirect_info.hyrax.rb +32 -0
- data/.koppie/db/schema.rb +154 -1
- data/.koppie/package.json +1 -1
- data/.koppie/rspec_split_configuration.json +835 -0
- data/.koppie/solr/conf/solrconfig.xml +0 -5
- data/.koppie/yarn.lock +5587 -752
- data/.rubocop.yml +1 -0
- data/CONTAINERS.md +2 -0
- data/Dockerfile +9 -22
- data/Gemfile +0 -1
- data/Gemfile.allinson +2 -0
- data/README.md +1 -2
- data/app/actors/hyrax/actors/file_actor.rb +2 -0
- data/app/actors/hyrax/actors/file_set_actor.rb +1 -1
- data/app/assets/javascripts/hyrax/app.js.erb +143 -10
- data/app/assets/javascripts/hyrax/collection_select.js +68 -0
- data/app/assets/javascripts/hyrax/copy_permalink_button.js +28 -0
- data/app/assets/javascripts/hyrax/redirects.js +45 -0
- data/app/assets/javascripts/hyrax/relationships/control.es6 +21 -1
- data/app/assets/javascripts/hyrax/select_work_type.es6 +7 -2
- data/app/assets/javascripts/hyrax.js +3 -1
- data/app/assets/stylesheets/hyrax/_file-show.scss +1 -0
- data/app/assets/stylesheets/hyrax/_viewer.scss +8 -0
- data/app/assets/stylesheets/hyrax/dashboard.scss +22 -1
- data/app/assets/stylesheets/hyrax/sidebar.scss +98 -3
- data/app/authorities/qa/authorities/collections.rb +9 -9
- data/app/controllers/concerns/hyrax/analytics_error_handling.rb +97 -0
- data/app/controllers/concerns/hyrax/collections_controller_behavior.rb +3 -1
- data/app/controllers/concerns/hyrax/ensure_migrated_behavior.rb +50 -0
- data/app/controllers/concerns/hyrax/flexible_catalog_behavior.rb +206 -0
- data/app/controllers/concerns/hyrax/flexible_schema_behavior.rb +17 -0
- data/app/controllers/concerns/hyrax/redirect_to_display_url.rb +54 -0
- data/app/controllers/concerns/hyrax/works_controller_behavior.rb +52 -10
- data/app/controllers/hyrax/admin/admin_sets_controller.rb +21 -13
- data/app/controllers/hyrax/admin/analytics/collection_reports_controller.rb +2 -0
- data/app/controllers/hyrax/admin/analytics/work_reports_controller.rb +1 -0
- data/app/controllers/hyrax/admin/metadata_profiles_controller.rb +66 -0
- data/app/controllers/hyrax/batch_uploads_controller.rb +2 -0
- data/app/controllers/hyrax/dashboard/collections_controller.rb +27 -6
- data/app/controllers/hyrax/dashboard/nest_collections_controller.rb +2 -1
- data/app/controllers/hyrax/dashboard_controller.rb +2 -1
- data/app/controllers/hyrax/file_sets_controller.rb +19 -1
- data/app/controllers/hyrax/homepage_controller.rb +7 -0
- data/app/controllers/hyrax/my/works_controller.rb +2 -21
- data/app/controllers/hyrax/permissions_controller.rb +4 -2
- data/app/controllers/hyrax/redirects_controller.rb +56 -0
- data/app/controllers/hyrax/single_use_links_viewer_controller.rb +2 -1
- data/app/controllers/hyrax/transcripts_controller.rb +31 -0
- data/app/controllers/hyrax/uploads_controller.rb +18 -9
- data/app/forms/concerns/hyrax/based_near_field_behavior.rb +77 -0
- data/app/forms/concerns/hyrax/flexible_form_behavior.rb +37 -0
- data/app/forms/concerns/hyrax/redirects_field_behavior.rb +95 -0
- data/app/forms/concerns/hyrax/transcripts_behavior.rb +40 -0
- data/app/forms/hyrax/forms/admin_set_form.rb +1 -1
- data/app/forms/hyrax/forms/administrative_set_form.rb +3 -0
- data/app/forms/hyrax/forms/batch_edit_form.rb +1 -3
- data/app/forms/hyrax/forms/batch_upload_form.rb +14 -0
- data/app/forms/hyrax/forms/file_set_edit_form.rb +1 -1
- data/app/forms/hyrax/forms/file_set_form.rb +6 -7
- data/app/forms/hyrax/forms/pcdm_collection_form.rb +17 -5
- data/app/forms/hyrax/forms/pcdm_object_form.rb +11 -3
- data/app/forms/hyrax/forms/resource_batch_edit_form.rb +8 -22
- data/app/forms/hyrax/forms/resource_form.rb +110 -10
- data/app/forms/hyrax/forms.rb +1 -0
- data/app/helpers/hyrax/attributes_helper.rb +109 -0
- data/app/helpers/hyrax/blacklight_override.rb +26 -0
- data/app/helpers/hyrax/collections_helper.rb +2 -0
- data/app/helpers/hyrax/file_set_form_helper.rb +24 -0
- data/app/helpers/hyrax/hyrax_helper_behavior.rb +51 -2
- data/app/helpers/hyrax/iiif_helper.rb +11 -1
- data/app/helpers/hyrax/permalink_helper.rb +37 -0
- data/app/helpers/hyrax/redirects_tab_helper.rb +29 -0
- data/app/helpers/hyrax/work_form_helper.rb +9 -5
- data/app/helpers/hyrax/works_helper.rb +14 -0
- data/app/indexers/concerns/hyrax/workflow_indexer.rb +30 -0
- data/app/indexers/hyrax/file_set_indexer.rb +1 -0
- data/app/indexers/hyrax/indexers/administrative_set_indexer.rb +2 -1
- data/app/indexers/hyrax/indexers/file_set_indexer.rb +6 -2
- data/app/indexers/hyrax/indexers/pcdm_collection_indexer.rb +11 -1
- data/app/indexers/hyrax/indexers/pcdm_object_indexer.rb +25 -4
- data/app/indexers/hyrax/indexers/redirects_indexer.rb +29 -0
- data/app/indexers/hyrax/indexers/resource_indexer.rb +4 -0
- data/app/indexers/hyrax/indexers.rb +2 -0
- data/app/inputs/controlled_vocabulary_input.rb +7 -1
- data/app/jobs/content_deposit_error_event_job.rb +14 -0
- data/app/jobs/create_work_job.rb +2 -0
- data/app/jobs/migrate_files_to_valkyrie_job.rb +7 -1
- data/app/models/batch_upload_item.rb +4 -0
- data/app/models/concerns/hyrax/ability/flexible_metadata_ability.rb +10 -0
- data/app/models/concerns/hyrax/ability.rb +3 -1
- data/app/models/concerns/hyrax/collection_behavior.rb +4 -0
- data/app/models/concerns/hyrax/file_set/transcripts.rb +16 -0
- data/app/models/concerns/hyrax/file_set_behavior.rb +9 -0
- data/app/models/concerns/hyrax/flexibility.rb +107 -0
- data/app/models/concerns/hyrax/redirects_normalization.rb +38 -0
- data/app/models/concerns/hyrax/solr_document/characterization.rb +1 -1
- data/app/models/concerns/hyrax/solr_document/metadata.rb +9 -0
- data/app/models/concerns/hyrax/solr_document/ordered_members.rb +4 -0
- data/app/models/concerns/hyrax/solr_document_behavior.rb +28 -6
- data/app/models/concerns/hyrax/suppressible.rb +1 -1
- data/app/models/concerns/hyrax/valkyrie_lazy_migration.rb +1 -1
- data/app/models/concerns/hyrax/with_embargoes_and_leases.rb +30 -0
- data/app/models/concerns/hyrax/work_behavior.rb +8 -0
- data/app/models/hyrax/administrative_set.rb +1 -1
- data/app/models/hyrax/change_set.rb +3 -1
- data/app/models/hyrax/embargo.rb +3 -1
- data/app/models/hyrax/file_set.rb +4 -2
- data/app/models/hyrax/flexible_schema.rb +219 -0
- data/app/models/hyrax/lease.rb +3 -1
- data/app/models/hyrax/pcdm_collection.rb +4 -1
- data/app/models/hyrax/redirect.rb +64 -0
- data/app/models/hyrax/redirect_path.rb +14 -0
- data/app/models/hyrax/resource.rb +22 -25
- data/app/models/hyrax/riiif/file.rb +43 -0
- data/app/models/hyrax/riiif/file_resolver.rb +65 -0
- data/app/models/hyrax/uploaded_file.rb +9 -0
- data/app/models/hyrax/virus_scanner.rb +16 -3
- data/app/models/hyrax/work.rb +4 -1
- data/app/models/job_io_wrapper.rb +12 -3
- data/app/models/user_mailbox.rb +3 -1
- data/app/presenters/hyrax/admin_set_presenter.rb +11 -0
- data/app/presenters/hyrax/annotates_content.rb +35 -0
- data/app/presenters/hyrax/collection_presenter.rb +5 -1
- data/app/presenters/hyrax/displays_content.rb +133 -0
- data/app/presenters/hyrax/displays_image.rb +11 -4
- data/app/presenters/hyrax/displays_transcripts.rb +59 -0
- data/app/presenters/hyrax/file_set_presenter.rb +8 -18
- data/app/presenters/hyrax/google_scholar_presenter.rb +39 -0
- data/app/presenters/hyrax/iiif_manifest_presenter.rb +22 -4
- data/app/presenters/hyrax/missing_method_behavior.rb +15 -0
- data/app/presenters/hyrax/pcdm_member_presenter_factory.rb +2 -4
- data/app/presenters/hyrax/work_show_presenter.rb +56 -14
- data/app/renderers/hyrax/renderers/attribute_renderer.rb +6 -2
- data/app/renderers/hyrax/renderers/license_attribute_renderer.rb +10 -10
- data/app/renderers/hyrax/renderers/redirects_label_attribute_renderer.rb +25 -0
- data/app/renderers/hyrax/renderers/rights_statement_attribute_renderer.rb +10 -11
- data/app/search_builders/hyrax/dashboard/nested_collections_search_builder.rb +1 -5
- data/app/search_builders/hyrax/filter_by_type.rb +1 -1
- data/app/services/hyrax/action/create_valkyrie_work.rb +3 -2
- data/app/services/hyrax/authority_rendering_helper.rb +28 -0
- data/app/services/hyrax/authority_service.rb +107 -0
- data/app/services/hyrax/callbacks.rb +2 -2
- data/app/services/hyrax/collections/nested_collection_query_service.rb +9 -3
- data/app/services/hyrax/collections_service.rb +17 -0
- data/app/services/hyrax/custom_queries/find_by_property_value.rb +89 -0
- data/app/services/hyrax/edit_permissions_service.rb +4 -4
- data/app/services/hyrax/file_set_type_service.rb +12 -4
- data/app/services/hyrax/flexible_schema_validator_service.rb +153 -0
- data/app/services/hyrax/flexible_schema_validators/class_validator.rb +123 -0
- data/app/services/hyrax/flexible_schema_validators/core_metadata_validator.rb +211 -0
- data/app/services/hyrax/flexible_schema_validators/existing_records_validator.rb +83 -0
- data/app/services/hyrax/flexible_schema_validators/redirects_validator.rb +116 -0
- data/app/services/hyrax/flexible_schema_validators/schema_validator.rb +34 -0
- data/app/services/hyrax/flexible_schema_validators/sort_properties_validator.rb +54 -0
- data/app/services/hyrax/license_service.rb +1 -1
- data/app/services/hyrax/listeners/metadata_index_listener.rb +11 -0
- data/app/services/hyrax/m3_profile_editor.rb +32 -0
- data/app/services/hyrax/m3_schema_loader.rb +119 -0
- data/app/services/hyrax/manifest_builder_service.rb +18 -24
- data/app/services/hyrax/permalink_path.rb +25 -0
- data/app/services/hyrax/qa_select_service.rb +9 -5
- data/app/services/hyrax/redirect_path_normalizer.rb +58 -0
- data/app/services/hyrax/redirects_lookup.rb +59 -0
- data/app/services/hyrax/resource_permissions_visibility_propagator.rb +54 -0
- data/app/services/hyrax/resource_types_service.rb +3 -20
- data/app/services/hyrax/rights_statement_service.rb +1 -1
- data/app/services/hyrax/schema_loader.rb +205 -0
- data/app/services/hyrax/search_service.rb +1 -1
- data/app/services/hyrax/simple_schema_loader.rb +28 -127
- data/app/services/hyrax/solr_service.rb +39 -2
- data/app/services/hyrax/tolerant_select_service.rb +5 -3
- data/app/services/hyrax/valkyrie_upload.rb +2 -2
- data/app/services/hyrax/visibility_propagator.rb +4 -1
- data/app/services/hyrax/work_uploads_handler.rb +17 -9
- data/app/services/hyrax/workflow/workflow_factory.rb +5 -4
- data/app/services/migrate_resource_service.rb +7 -1
- data/app/uploaders/hyrax/uploaded_file_uploader.rb +2 -2
- data/app/utils/hyrax/data_destroyers/collection_branding_destroyer.rb +1 -1
- data/app/utils/hyrax/required_data_seeder.rb +1 -0
- data/app/utils/hyrax/required_data_seeders/collection_type_seeder.rb +1 -1
- data/app/utils/hyrax/required_data_seeders/flexible_profile_seeder.rb +28 -0
- data/app/validators/hyrax/controlled_vocabulary_validator.rb +64 -0
- data/app/validators/hyrax/redirect_validator.rb +128 -0
- data/app/views/catalog/_thumbnail_list_default.html.erb +2 -2
- data/app/views/collections/edit_fields/_schema_version.html.erb +1 -0
- data/app/views/hyrax/admin/admin_sets/_form_metadata.html.erb +24 -2
- data/app/views/hyrax/admin/admin_sets/_show_document_list_row.html.erb +1 -1
- data/app/views/hyrax/admin/admin_sets/show.html.erb +1 -1
- data/app/views/hyrax/admin/analytics/_analytics_error.html.erb +42 -0
- data/app/views/hyrax/admin/analytics/collection_reports/index.html.erb +11 -9
- data/app/views/hyrax/admin/analytics/work_reports/index.html.erb +60 -57
- data/app/views/hyrax/admin/features/index.html.erb +1 -1
- data/app/views/hyrax/admin/metadata_profiles/_import_modal.html.erb +20 -0
- data/app/views/hyrax/admin/metadata_profiles/index.html.erb +54 -0
- data/app/views/hyrax/base/_attribute_rows.html.erb +4 -21
- data/app/views/hyrax/base/_citations.html.erb +1 -0
- data/app/views/hyrax/base/_currently_shared.html.erb +2 -1
- data/app/views/hyrax/base/_file_manager_thumbnail.html.erb +1 -1
- data/app/views/hyrax/base/_form_member_of_collections.html.erb +6 -6
- data/app/views/hyrax/base/_form_redirects.html.erb +125 -0
- data/app/views/hyrax/base/_form_share.html.erb +7 -2
- data/app/views/hyrax/base/_member.html.erb +1 -1
- data/app/views/hyrax/base/_social_media.html.erb +1 -1
- data/app/views/hyrax/base/iiif_viewers/_universal_viewer.html.erb +1 -1
- data/app/views/hyrax/base/show.html.erb +3 -0
- data/app/views/hyrax/collections/_default_group.html.erb +3 -0
- data/app/views/hyrax/collections/_list_collections.html.erb +3 -0
- data/app/views/hyrax/collections/_list_works.html.erb +1 -1
- data/app/views/hyrax/collections/_show_document_list_row.html.erb +1 -1
- data/app/views/hyrax/collections/show.html.erb +4 -0
- data/app/views/hyrax/dashboard/_sidebar.html.erb +4 -2
- data/app/views/hyrax/dashboard/collections/_button_for_update_collection.html.erb +2 -1
- data/app/views/hyrax/dashboard/collections/_default_group.html.erb +3 -0
- data/app/views/hyrax/dashboard/collections/_form.html.erb +17 -0
- data/app/views/hyrax/dashboard/collections/_form_for_select_collection.html.erb +7 -10
- data/app/views/hyrax/dashboard/collections/_list_collections.html.erb +3 -0
- data/app/views/hyrax/dashboard/collections/_list_works.html.erb +1 -1
- data/app/views/hyrax/dashboard/collections/_show_document_list_row.html.erb +1 -1
- data/app/views/hyrax/dashboard/profiles/_edit_primary.html.erb +1 -8
- data/app/views/hyrax/dashboard/profiles/_trophy_edit.html.erb +1 -1
- data/app/views/hyrax/dashboard/profiles/show.html.erb +5 -5
- data/app/views/hyrax/dashboard/sidebar/_metadata.html.erb +11 -0
- data/app/views/hyrax/dashboard/sidebar/_repository_content.html.erb +1 -0
- data/app/views/hyrax/dashboard/works/_default_group.html.erb +3 -0
- data/app/views/hyrax/dashboard/works/_list_works.html.erb +4 -2
- data/app/views/hyrax/file_sets/_form.html.erb +16 -3
- data/app/views/hyrax/file_sets/_metadata.html.erb +35 -0
- data/app/views/hyrax/file_sets/_valkyrie_form.html.erb +37 -0
- data/app/views/hyrax/file_sets/edit.html.erb +14 -8
- data/app/views/hyrax/file_sets/media_display/_audio.html.erb +4 -0
- data/app/views/hyrax/file_sets/media_display/_image.html.erb +2 -2
- data/app/views/hyrax/file_sets/media_display/_video.html.erb +4 -0
- data/app/views/hyrax/homepage/_explore_collections.html.erb +1 -1
- data/app/views/hyrax/homepage/_featured_fields.html.erb +1 -1
- data/app/views/hyrax/homepage/_recent_document.html.erb +1 -1
- data/app/views/hyrax/homepage/robots.text.erb +17 -0
- data/app/views/hyrax/my/collections/_default_group.html.erb +3 -0
- data/app/views/hyrax/my/collections/_list_collections.html.erb +3 -0
- data/app/views/hyrax/my/collections/_modal_add_subcollection.html.erb +2 -2
- data/app/views/hyrax/my/collections/_modal_add_to_collection.html.erb +2 -2
- data/app/views/hyrax/my/works/_default_group.html.erb +3 -0
- data/app/views/hyrax/my/works/_list_works.html.erb +5 -2
- data/app/views/hyrax/shared/_copy_permalink.html.erb +10 -0
- data/app/views/hyrax/users/_user_info.html.erb +1 -6
- data/app/views/layouts/_head_tag_content.html.erb +2 -0
- data/app/views/layouts/hyrax/dashboard.html.erb +2 -1
- data/app/views/records/edit_fields/_contexts.html.erb +1 -0
- data/app/views/records/edit_fields/_license.html.erb +17 -5
- data/app/views/records/edit_fields/_resource_type.html.erb +16 -2
- data/app/views/records/edit_fields/_rights_statement.html.erb +17 -5
- data/app/views/records/edit_fields/_schema_version.html.erb +1 -0
- data/app/views/records/edit_fields/_transcript_ids.html.erb +18 -0
- data/app/views/records/show_fields/_rights.html.erb +1 -1
- data/app/views/shared/_citations.html.erb +46 -31
- data/app/views/shared/_schema_version.html.erb +38 -0
- data/bin/dev-entrypoint.sh +14 -4
- data/chart/hyrax/Chart.yaml +4 -4
- data/chart/hyrax/templates/ingress.yaml +3 -0
- data/chart/hyrax/values.yaml +10 -8
- data/config/features.rb +27 -0
- data/config/initializers/blacklight_dynamic_sitemap.rb +43 -0
- data/config/initializers/deprecation_patch.rb +27 -0
- data/config/initializers/hydra_works.rb +3 -0
- data/config/initializers/i18n_extensions.rb +197 -0
- data/config/initializers/storage_adapter_initializer.rb +15 -9
- data/config/locales/hyrax.de.yml +172 -54
- data/config/locales/hyrax.en.yml +183 -66
- data/config/locales/hyrax.es.yml +174 -49
- data/config/locales/hyrax.fr.yml +169 -45
- data/config/locales/hyrax.it.yml +172 -48
- data/config/locales/hyrax.pt-BR.yml +172 -54
- data/config/locales/hyrax.zh.yml +145 -21
- data/config/metadata/basic_metadata.yaml +66 -0
- data/config/metadata/batch_edit_metadata.yaml +119 -0
- data/config/metadata/file_set_metadata.yaml +19 -0
- data/config/metadata/redirects.yaml +15 -0
- data/config/metadata_profiles/m3_json_schema.json +487 -0
- data/config/metadata_profiles/m3_profile.yaml +954 -0
- data/config/routes.rb +9 -0
- data/docker-compose-allinson.yml +176 -0
- data/docker-compose-dassie.yml +14 -6
- data/docker-compose-koppie.yml +18 -10
- data/docker-compose-sirenia.yml +20 -10
- data/documentation/copy_permalink.md +29 -0
- data/documentation/developing-your-hyrax-based-app.md +2 -2
- data/documentation/file-ingest-characterization.md +763 -0
- data/documentation/flexible_metadata.md +113 -0
- data/documentation/forms/field_behaviors.md +297 -0
- data/documentation/redirects.md +428 -0
- data/hyrax.gemspec +7 -2
- data/lib/freyja/metadata_adapter.rb +1 -1
- data/lib/freyja/persister.rb +25 -1
- data/lib/generators/hyrax/collection_resource/collection_resource_generator.rb +2 -2
- data/lib/generators/hyrax/collection_resource/templates/collection.rb.erb +5 -1
- data/lib/generators/hyrax/collection_resource/templates/collection_form.rb.erb +5 -1
- data/lib/generators/hyrax/collection_resource/templates/collection_indexer.rb.erb +6 -1
- data/lib/generators/hyrax/iiif_viewer/USAGE +22 -0
- data/lib/generators/hyrax/iiif_viewer/iiif_viewer_generator.rb +49 -0
- data/lib/generators/hyrax/iiif_viewer/templates/_clover.html.erb +7 -0
- data/lib/generators/hyrax/iiif_viewer/templates/clover/clover.css +3328 -0
- data/lib/generators/hyrax/iiif_viewer/templates/clover/clover.html +16 -0
- data/lib/generators/hyrax/iiif_viewer/templates/clover/clover.js +237 -0
- data/lib/generators/hyrax/install_generator.rb +25 -1
- data/lib/generators/hyrax/riiif_generator.rb +1 -1
- data/lib/generators/hyrax/templates/app/views/blacklight_dynamic_sitemap/sitemap/show.xml.builder +22 -0
- data/lib/generators/hyrax/templates/config/clamav.rb +2 -0
- data/lib/generators/hyrax/templates/config/initializers/riiif.rb +4 -80
- data/lib/generators/hyrax/templates/db/migrate/20240606205215_create_hyrax_flexible_schemas.rb.erb +9 -0
- data/lib/generators/hyrax/templates/db/migrate/20240606205216_add_contexts_to_hyrax_flexible_schemas.rb.erb +5 -0
- data/lib/generators/hyrax/templates/db/migrate/20260430000001_create_hyrax_redirect_paths.rb.erb +22 -0
- data/lib/generators/hyrax/templates/db/migrate/20260521003627_add_additional_redirect_info.rb.erb +39 -0
- data/lib/generators/hyrax/templates/package.json +1 -1
- data/lib/generators/hyrax/templates/uv.html +60 -82
- data/lib/generators/hyrax/work/templates/indexer.rb.erb +1 -1
- data/lib/generators/hyrax/work/templates/model.rb.erb +5 -1
- data/lib/generators/hyrax/work_resource/templates/form.rb.erb +5 -2
- data/lib/generators/hyrax/work_resource/templates/indexer.rb.erb +6 -2
- data/lib/generators/hyrax/work_resource/templates/work.rb.erb +5 -2
- data/lib/generators/hyrax/work_resource/work_resource_generator.rb +1 -0
- data/lib/hyrax/active_fedora_dummy_model.rb +6 -0
- data/lib/hyrax/configuration.rb +197 -3
- data/lib/hyrax/deprecation.rb +31 -0
- data/lib/hyrax/engine.rb +2 -0
- data/lib/hyrax/form_fields.rb +17 -5
- data/lib/hyrax/indexer.rb +31 -2
- data/lib/hyrax/resource_sync/resource_list_writer.rb +3 -0
- data/lib/hyrax/schema.rb +15 -4
- data/lib/hyrax/specs/shared_specs/factories/administrative_sets.rb +12 -0
- data/lib/hyrax/specs/shared_specs/factories/hyrax_embargo.rb +1 -1
- data/lib/hyrax/specs/shared_specs/factories/hyrax_file_set.rb +13 -0
- data/lib/hyrax/specs/shared_specs/factories/hyrax_lease.rb +5 -1
- data/lib/hyrax/specs/shared_specs/factories/hyrax_work.rb +14 -0
- data/lib/hyrax/specs/shared_specs/simple_work.rb +41 -0
- data/lib/hyrax/specs/shared_specs.rb +1 -0
- data/lib/hyrax/transactions/collection_create.rb +2 -1
- data/lib/hyrax/transactions/collection_destroy.rb +1 -0
- data/lib/hyrax/transactions/collection_update.rb +2 -1
- data/lib/hyrax/transactions/container.rb +28 -0
- data/lib/hyrax/transactions/steps/apply_permission_template_on_update.rb +65 -0
- data/lib/hyrax/transactions/steps/apply_workflow_on_admin_set_change.rb +97 -0
- data/lib/hyrax/transactions/steps/ensure_admin_set.rb +1 -1
- data/lib/hyrax/transactions/steps/remove_redirect_paths.rb +35 -0
- data/lib/hyrax/transactions/steps/save.rb +10 -0
- data/lib/hyrax/transactions/steps/set_default_admin_set.rb +1 -1
- data/lib/hyrax/transactions/steps/sync_redirect_paths.rb +141 -0
- data/lib/hyrax/transactions/work_create.rb +2 -1
- data/lib/hyrax/transactions/work_destroy.rb +1 -0
- data/lib/hyrax/transactions/work_update.rb +4 -1
- data/lib/hyrax/version.rb +1 -1
- data/lib/hyrax.rb +61 -0
- data/lib/tasks/m3_profile_cleanup.rake +35 -0
- data/template.rb +4 -1
- metadata +313 -19
- data/.dassie/public/robots.txt +0 -1
- data/.koppie/public/robots.txt +0 -1
- data/app/forms/concerns/hyrax/basic_metadata_form_fields_behavior.rb +0 -39
- data/app/views/catalog/_thumbnail_list_collection.html.erb +0 -4
|
@@ -0,0 +1,763 @@
|
|
|
1
|
+
# File Ingest and Characterization in Hyrax
|
|
2
|
+
|
|
3
|
+
This document describes how files are ingested, characterized, and processed in Hyrax, with explicit coverage of both the **ActiveFedora** (legacy actor-stack) path and the **Valkyrie** (resource/transaction) path. It is intended for developers who need to trace, debug, or modify these flows.
|
|
4
|
+
|
|
5
|
+
> **Scope:** Hyrax supports two persistence back-ends. The _ActiveFedora_ path stores files directly in a Fedora repository via Hydra/LDP. The _Valkyrie_ path stores files via a pluggable `Valkyrie::StorageAdapter` and persists metadata through the Valkyrie persister/query-service stack. Wings is the compatibility shim that lets ActiveFedora objects participate in Valkyrie workflows.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
1. [Shared Concepts](#1-shared-concepts)
|
|
12
|
+
2. [ActiveFedora Ingest Path](#2-activefedora-ingest-path)
|
|
13
|
+
- [Actor Stack Entry Point](#21-actor-stack-entry-point)
|
|
14
|
+
- [File Attachment Job](#22-file-attachment-job)
|
|
15
|
+
- [FileSet and FileActor](#23-fileset-and-fileactor)
|
|
16
|
+
- [IngestJob and JobIoWrapper](#24-ingestjob-and-jobiowrapper)
|
|
17
|
+
3. [Valkyrie Ingest Path](#3-valkyrie-ingest-path)
|
|
18
|
+
- [Transaction Entry Point](#31-transaction-entry-point)
|
|
19
|
+
- [WorkUploadsHandler](#32-workuploadshandler)
|
|
20
|
+
- [ValkyrieIngestJob and ValkyrieUpload](#33-valkyrieingestjob-and-valkyrieupload)
|
|
21
|
+
4. [File Characterization](#4-file-characterization)
|
|
22
|
+
- [ActiveFedora Characterization](#41-activefedora-characterization)
|
|
23
|
+
- [Valkyrie Characterization](#42-valkyrie-characterization)
|
|
24
|
+
5. [Derivative Generation](#5-derivative-generation)
|
|
25
|
+
- [ActiveFedora Derivatives](#51-activefedora-derivatives)
|
|
26
|
+
- [Valkyrie Derivatives](#52-valkyrie-derivatives)
|
|
27
|
+
- [Derivative Service Factory](#53-derivative-service-factory)
|
|
28
|
+
6. [Event System (The Connective Tissue)](#6-event-system-the-connective-tissue)
|
|
29
|
+
7. [Metadata Persistence and Indexing](#7-metadata-persistence-and-indexing)
|
|
30
|
+
8. [Wings: The AF ↔ Valkyrie Bridge](#8-wings-the-af--valkyrie-bridge)
|
|
31
|
+
9. [Key Configuration Points](#9-key-configuration-points)
|
|
32
|
+
10. [Developer Cautions](#10-developer-cautions)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 1. Shared Concepts
|
|
37
|
+
|
|
38
|
+
### Hyrax::UploadedFile
|
|
39
|
+
|
|
40
|
+
`app/models/hyrax/uploaded_file.rb`
|
|
41
|
+
|
|
42
|
+
All file uploads, regardless of back-end, start as an `Hyrax::UploadedFile` ActiveRecord record. CarrierWave mounts a `UploadedFileUploader` on the `:file` column.
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
# Relates uploaded file to a file set after creation
|
|
46
|
+
def add_file_set!(file_set)
|
|
47
|
+
# Sets file_set_uri based on whether file_set is AF or Valkyrie
|
|
48
|
+
end
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Key columns: `file` (CarrierWave uploader), `user_id`, `file_set_uri`.
|
|
52
|
+
|
|
53
|
+
The `file_set_uri` is used for idempotency: both the AF and Valkyrie paths skip files whose `file_set_uri` is already set, preventing duplicate ingest on job retries.
|
|
54
|
+
|
|
55
|
+
### JobIoWrapper
|
|
56
|
+
|
|
57
|
+
`app/models/job_io_wrapper.rb`
|
|
58
|
+
|
|
59
|
+
A serializable ActiveRecord shim that wraps a file so it can be passed to ActiveJob without losing file-system context. Used exclusively in the AF path.
|
|
60
|
+
|
|
61
|
+
Key fields: `path`, `file_set_id`, `relation` (`:original_file`, etc.), `original_name`, `mime_type`, `uploaded_file_id`.
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
# Factory used by FileSetActor
|
|
65
|
+
JobIoWrapper.create_with_varied_file_handling!(
|
|
66
|
+
user:, file:, relation:, file_set:, use_valkyrie:
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Called by IngestJob
|
|
70
|
+
wrapper.ingest_file # delegates to FileActor#ingest_file
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Hyrax::FileMetadata (Valkyrie only)
|
|
74
|
+
|
|
75
|
+
`app/models/hyrax/file_metadata.rb`
|
|
76
|
+
|
|
77
|
+
A `Valkyrie::Resource` that records metadata about a single file attached to a FileSet. There is no AF equivalent; AF stores file properties directly on the `Hydra::PCDM::File` LDP resource.
|
|
78
|
+
|
|
79
|
+
Key attributes: `file_identifier` (Valkyrie storage-adapter ID), `file_set_id`, `pcdm_use` (array of `RDF::URI` use constants from `Hyrax::FileMetadata::Use`), `mime_type`, `original_filename`, `recorded_size`, characterization properties (`height`, `width`, `file_size`, `format_label`, etc.).
|
|
80
|
+
|
|
81
|
+
Use constants (defined under `Hyrax::FileMetadata::Use`):
|
|
82
|
+
|
|
83
|
+
| Constant | Purpose |
|
|
84
|
+
|----------|---------|
|
|
85
|
+
| `ORIGINAL_FILE` | The uploaded/original file |
|
|
86
|
+
| `THUMBNAIL_IMAGE` | Thumbnail derivative |
|
|
87
|
+
| `EXTRACTED_TEXT` | Full-text extraction |
|
|
88
|
+
| `INTERMEDIATE_FILE` | Intermediate processing copy |
|
|
89
|
+
| `PRESERVATION_FILE` | Long-term preservation copy |
|
|
90
|
+
| `SERVICE_FILE` | Access/service copy |
|
|
91
|
+
| `TRANSCRIPT` | Transcript |
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 2. ActiveFedora Ingest Path
|
|
96
|
+
|
|
97
|
+
### 2.1 Actor Stack Entry Point
|
|
98
|
+
|
|
99
|
+
`app/services/hyrax/default_middleware_stack.rb`
|
|
100
|
+
|
|
101
|
+
The ActorStack is a middleware chain that processes work create/update operations. Each actor calls `next_actor.create(env)` (or `update`) to pass control down the chain.
|
|
102
|
+
|
|
103
|
+
Relevant actors in default order:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
Hyrax::Actors::OptimisticLockValidator
|
|
107
|
+
Hyrax::Actors::CreateWithRemoteFilesActor
|
|
108
|
+
Hyrax::Actors::CreateWithFilesActor ← triggers file attachment
|
|
109
|
+
Hyrax::Actors::CollectionsMembershipActor
|
|
110
|
+
Hyrax::Actors::AddToWorkActor
|
|
111
|
+
Hyrax::Actors::AttachMembersActor
|
|
112
|
+
Hyrax::Actors::ApplyOrderActor
|
|
113
|
+
Hyrax::Actors::DefaultAdminSetActor
|
|
114
|
+
Hyrax::Actors::InterpretVisibilityActor
|
|
115
|
+
Hyrax::Actors::TransferRequestActor
|
|
116
|
+
Hyrax::Actors::ApplyPermissionTemplateActor
|
|
117
|
+
Hyrax::Actors::CleanupFileSetsActor
|
|
118
|
+
Hyrax::Actors::CleanupTrophiesActor
|
|
119
|
+
Hyrax::Actors::FeaturedWorkActor
|
|
120
|
+
Hyrax::Actors::ModelActor ← final AF persistence
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`app/actors/hyrax/actors/create_with_files_actor.rb`
|
|
124
|
+
|
|
125
|
+
`CreateWithFilesActor` extracts `uploaded_file_ids` from `env.attributes`, validates all are `Hyrax::UploadedFile` records, calls the next actor to persist the work, and then enqueues `AttachFilesToWorkJob`:
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
def create(env)
|
|
129
|
+
uploaded_file_ids = env.attributes.delete(:uploaded_files)
|
|
130
|
+
# ... validation ...
|
|
131
|
+
next_actor.create(env) &&
|
|
132
|
+
AttachFilesToWorkJob.perform_later(env.curation_concern, uploaded_files)
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 2.2 File Attachment Job
|
|
137
|
+
|
|
138
|
+
`app/jobs/attach_files_to_work_job.rb`
|
|
139
|
+
|
|
140
|
+
`AttachFilesToWorkJob` is the routing point between AF and Valkyrie:
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
def perform(work, uploaded_files, **work_attributes)
|
|
144
|
+
case work
|
|
145
|
+
when ActiveFedora::Base
|
|
146
|
+
perform_af(work, uploaded_files, work_attributes) # AF path
|
|
147
|
+
else
|
|
148
|
+
Hyrax::WorkUploadsHandler.new(work: work) # Valkyrie path
|
|
149
|
+
.add(files: uploaded_files)
|
|
150
|
+
.attach
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
The AF branch (`perform_af`) loops over `uploaded_files`, skipping any with an existing `file_set_uri`, and for each remaining file:
|
|
156
|
+
|
|
157
|
+
1. Instantiates a new `FileSet` (AF model) and a `Hyrax::Actors::FileSetActor`.
|
|
158
|
+
2. Calls `actor.create_metadata(visibility_attrs)` — sets depositor, dates, visibility.
|
|
159
|
+
3. Calls `actor.create_content(uploaded_file)` — queues `IngestJob`.
|
|
160
|
+
4. Calls `actor.attach_to_work(work, metadata)` — acquires a lock, appends the FileSet to `work.ordered_members`, and fires the `:after_create_fileset` callback.
|
|
161
|
+
|
|
162
|
+
### 2.3 FileSet and FileActor
|
|
163
|
+
|
|
164
|
+
`app/actors/hyrax/actors/file_set_actor.rb`
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
# Creates metadata on the file_set and saves it
|
|
168
|
+
def create_metadata(file_set_params = {})
|
|
169
|
+
file_set.depositor = user.user_key
|
|
170
|
+
# ... dates, creator, visibility via actor stack ...
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Queues ingest for the file
|
|
174
|
+
def create_content(file, relation = :original_file, from_url: false)
|
|
175
|
+
# sets label/title from filename
|
|
176
|
+
# saves file_set
|
|
177
|
+
# unless from_url: IngestJob.perform_later(wrapper)
|
|
178
|
+
# if from_url: FileActor.ingest_file(file); then visibility/permission jobs
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Appends file_set to work with lock
|
|
182
|
+
def attach_to_work(work, file_set_params = {})
|
|
183
|
+
acquire_lock_for(work.id) do
|
|
184
|
+
work.ordered_members << file_set
|
|
185
|
+
work.representative_id ||= file_set.id
|
|
186
|
+
work.thumbnail_id ||= file_set.id
|
|
187
|
+
work.save!
|
|
188
|
+
end
|
|
189
|
+
Hyrax.publisher.publish('file.set.attached', file_set: file_set, user: user)
|
|
190
|
+
end
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
`app/actors/hyrax/actors/file_actor.rb`
|
|
194
|
+
|
|
195
|
+
`FileActor` performs the actual LDP write:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
def ingest_file(io)
|
|
199
|
+
io.use_valkyrie = false # forces AF objects even in mixed environments
|
|
200
|
+
|
|
201
|
+
Hydra::Works::AddFileToFileSet.call(file_set, io, relation, versioning: false)
|
|
202
|
+
return false unless file_set.save
|
|
203
|
+
|
|
204
|
+
repository_file = related_file # the Hydra::PCDM::File LDP node
|
|
205
|
+
create_version(repository_file, user) # via Hyrax::VersioningService
|
|
206
|
+
CharacterizeJob.perform_later(file_set, repository_file.id, pathhint(io))
|
|
207
|
+
end
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`Hydra::Works::AddFileToFileSet` is a `hydra-works` gem operation that streams the file content to Fedora via LDP.
|
|
211
|
+
|
|
212
|
+
### 2.4 IngestJob and JobIoWrapper
|
|
213
|
+
|
|
214
|
+
`app/jobs/ingest_job.rb`
|
|
215
|
+
|
|
216
|
+
```ruby
|
|
217
|
+
class IngestJob < Hyrax::ApplicationJob
|
|
218
|
+
queue_as Hyrax.config.ingest_queue_name
|
|
219
|
+
|
|
220
|
+
def perform(wrapper, notification: false)
|
|
221
|
+
wrapper.ingest_file # calls FileActor#ingest_file → CharacterizeJob
|
|
222
|
+
ContentNewVersionEventJob.perform_later(wrapper.file_set_id, ...) if notification
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
`IngestJob` accepts a `JobIoWrapper`, which is serialized to the job queue. `wrapper.ingest_file` delegates back to `Hyrax::Actors::FileActor#ingest_file`.
|
|
228
|
+
|
|
229
|
+
**Full AF ingest sequence:**
|
|
230
|
+
|
|
231
|
+
```
|
|
232
|
+
CreateWithFilesActor#create
|
|
233
|
+
└─ AttachFilesToWorkJob (async)
|
|
234
|
+
└─ FileSetActor#create_metadata
|
|
235
|
+
└─ FileSetActor#create_content
|
|
236
|
+
└─ IngestJob (async)
|
|
237
|
+
└─ JobIoWrapper#ingest_file
|
|
238
|
+
└─ FileActor#ingest_file
|
|
239
|
+
└─ Hydra::Works::AddFileToFileSet (writes to Fedora)
|
|
240
|
+
└─ VersioningService#create
|
|
241
|
+
└─ CharacterizeJob (async) → see §4.1
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## 3. Valkyrie Ingest Path
|
|
247
|
+
|
|
248
|
+
### 3.1 Transaction Entry Point
|
|
249
|
+
|
|
250
|
+
`lib/hyrax/transactions/work_create.rb`
|
|
251
|
+
|
|
252
|
+
The Valkyrie path uses `Dry::Transaction`-style step chains instead of an actor stack.
|
|
253
|
+
|
|
254
|
+
Default steps for `Hyrax::Transactions::WorkCreate`:
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
change_set.set_default_admin_set
|
|
258
|
+
change_set.ensure_admin_set
|
|
259
|
+
change_set.set_user_as_depositor
|
|
260
|
+
change_set.apply
|
|
261
|
+
work_resource.apply_permission_template
|
|
262
|
+
work_resource.save_acl
|
|
263
|
+
work_resource.add_file_sets ← file handling
|
|
264
|
+
work_resource.change_depositor
|
|
265
|
+
work_resource.add_to_parent
|
|
266
|
+
work_resource.sync_redirect_paths
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
`lib/hyrax/transactions/steps/add_file_sets.rb`
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
def call(obj, uploaded_files: [], file_set_params: [])
|
|
273
|
+
# delegates entirely to WorkUploadsHandler
|
|
274
|
+
Hyrax::WorkUploadsHandler.new(work: obj)
|
|
275
|
+
.add(files: uploaded_files, file_set_params: file_set_params)
|
|
276
|
+
.attach
|
|
277
|
+
# then propagates embargo/lease to file_sets
|
|
278
|
+
Success(obj)
|
|
279
|
+
end
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### 3.2 WorkUploadsHandler
|
|
283
|
+
|
|
284
|
+
`app/services/hyrax/work_uploads_handler.rb`
|
|
285
|
+
|
|
286
|
+
This is the Valkyrie orchestrator. Its public API is:
|
|
287
|
+
|
|
288
|
+
```ruby
|
|
289
|
+
Hyrax::WorkUploadsHandler.new(work: my_work)
|
|
290
|
+
.add(files: [file1, file2], file_set_params: [...])
|
|
291
|
+
.attach
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`add` validates all files are `Hyrax::UploadedFile` and discards those with an existing `file_set_uri` (idempotency guard).
|
|
295
|
+
|
|
296
|
+
`attach` acquires a Redis/Redlock lock on the work ID, then for each file:
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
# private make_file_set_and_ingest
|
|
300
|
+
def make_file_set_and_ingest(file, file_set_params = {})
|
|
301
|
+
file_set = @persister.save(
|
|
302
|
+
resource: Hyrax.config.valkyrie_file_set_class.new(file_set_args(file, file_set_params))
|
|
303
|
+
)
|
|
304
|
+
Hyrax.publisher.publish('object.deposited', object: file_set, user: file.user)
|
|
305
|
+
file.add_file_set!(file_set) # sets UploadedFile#file_set_uri
|
|
306
|
+
Hyrax::AccessControlList.copy_permissions(source: work_acl, target: file_set)
|
|
307
|
+
append_to_work(file_set) # work.member_ids << file_set.id
|
|
308
|
+
{ file_set: file_set, user: file.user, job: ValkyrieIngestJob.new(file) }
|
|
309
|
+
end
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
After all file_sets are created, `attach` saves the work (with updated `member_ids`), publishes `'object.metadata.updated'`, then enqueues each `ValkyrieIngestJob` and publishes `'file.set.attached'`.
|
|
313
|
+
|
|
314
|
+
### 3.3 ValkyrieIngestJob and ValkyrieUpload
|
|
315
|
+
|
|
316
|
+
`app/jobs/valkyrie_ingest_job.rb`
|
|
317
|
+
|
|
318
|
+
```ruby
|
|
319
|
+
class ValkyrieIngestJob < Hyrax::ApplicationJob
|
|
320
|
+
queue_as Hyrax.config.ingest_queue_name
|
|
321
|
+
|
|
322
|
+
def perform(file, pcdm_use: Hyrax::FileMetadata::Use::ORIGINAL_FILE)
|
|
323
|
+
file_set = Hyrax.query_service.find_by(id: Valkyrie::ID.new(file.file_set_uri))
|
|
324
|
+
upload_file(file: file, file_set: file_set, pcdm_use: pcdm_use, user: file.user)
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`upload_file` calls `Hyrax::ValkyrieUpload.file(...)`.
|
|
330
|
+
|
|
331
|
+
`app/services/hyrax/valkyrie_upload.rb`
|
|
332
|
+
|
|
333
|
+
`ValkyrieUpload` is the Valkyrie equivalent of `FileActor#ingest_file`:
|
|
334
|
+
|
|
335
|
+
```ruby
|
|
336
|
+
def upload(io:, filename:, file_set:, pcdm_use:, use_valkyrie: Hyrax.config.use_valkyrie?)
|
|
337
|
+
# 1. Handle versioning if storage adapter supports it
|
|
338
|
+
if pcdm_use == ORIGINAL_FILE && file_set.original_file_id && adapter.supports?(:versions)
|
|
339
|
+
streamfile = adapter.upload_version(id: file_set.original_file_id, file: io)
|
|
340
|
+
else
|
|
341
|
+
streamfile = adapter.upload(file: io, original_filename: filename, resource: file_set)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
# 2. Create and persist FileMetadata
|
|
345
|
+
file_metadata = Hyrax::FileMetadata.new(
|
|
346
|
+
file_identifier: streamfile.id,
|
|
347
|
+
file_set_id: file_set.id,
|
|
348
|
+
pcdm_use: [pcdm_use],
|
|
349
|
+
mime_type: streamfile.content_type,
|
|
350
|
+
original_filename: filename,
|
|
351
|
+
recorded_size: [streamfile.size]
|
|
352
|
+
)
|
|
353
|
+
Hyrax.persister.save(resource: file_metadata)
|
|
354
|
+
|
|
355
|
+
# 3. Update file_set.file_ids
|
|
356
|
+
file_set.file_ids += [file_metadata.id]
|
|
357
|
+
# (title/label also set from filename)
|
|
358
|
+
Hyrax.persister.save(resource: file_set)
|
|
359
|
+
|
|
360
|
+
# 4. Publish events (triggers characterization via FileListener)
|
|
361
|
+
Hyrax.publisher.publish('file.uploaded', metadata: file_metadata)
|
|
362
|
+
Hyrax.publisher.publish('file.metadata.updated', metadata: file_metadata, user: user)
|
|
363
|
+
Hyrax.publisher.publish('object.membership.updated', object: file_set, user: user)
|
|
364
|
+
end
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
The storage adapter (`Hyrax.storage_adapter`) is configurable — common choices are the disk adapter (development), an S3 adapter, or a Fedora 6 adapter via the Valkyrie Fedora gem.
|
|
368
|
+
|
|
369
|
+
**Full Valkyrie ingest sequence:**
|
|
370
|
+
|
|
371
|
+
```
|
|
372
|
+
WorkCreate transaction
|
|
373
|
+
└─ Steps::AddFileSets
|
|
374
|
+
└─ WorkUploadsHandler#attach (with Redis lock)
|
|
375
|
+
└─ persister.save(FileSet) (creates Valkyrie FileSet)
|
|
376
|
+
└─ AccessControlList.copy_permissions
|
|
377
|
+
└─ ValkyrieIngestJob (enqueued)
|
|
378
|
+
└─ ValkyrieUpload#upload
|
|
379
|
+
└─ storage_adapter.upload (writes bytes to storage backend)
|
|
380
|
+
└─ persister.save(FileMetadata)
|
|
381
|
+
└─ persister.save(FileSet)
|
|
382
|
+
└─ publisher.publish('file.uploaded') → FileListener → ValkyrieCharacterizationJob
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## 4. File Characterization
|
|
388
|
+
|
|
389
|
+
Characterization extracts technical metadata (MIME type, dimensions, checksum, format label, etc.) from the stored file. Both paths ultimately rely on FITS (via `Hydra::FileCharacterization` and `Hydra::Works::CharacterizationService`), but they wrap it differently.
|
|
390
|
+
|
|
391
|
+
### 4.1 ActiveFedora Characterization
|
|
392
|
+
|
|
393
|
+
`app/jobs/characterize_job.rb`
|
|
394
|
+
|
|
395
|
+
Triggered directly from `FileActor#ingest_file`:
|
|
396
|
+
|
|
397
|
+
```ruby
|
|
398
|
+
CharacterizeJob.perform_later(file_set, repository_file.id, pathhint)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
```ruby
|
|
402
|
+
def perform(file_set, file_id, filepath = nil)
|
|
403
|
+
# Ensure file is on disk (downloads from Fedora if needed)
|
|
404
|
+
filepath ||= Hyrax::WorkingDirectory.find_or_retrieve(file_id, file_set.id)
|
|
405
|
+
return unless file_set.characterization_proxy?
|
|
406
|
+
characterize(file_set, file_id, filepath)
|
|
407
|
+
Hyrax.publisher.publish('file.characterized', file_set: file_set,
|
|
408
|
+
file_id: file_id, path_hint: filepath)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
def characterize(file_set, _file_id, filepath)
|
|
412
|
+
# Clear old values first (important — see §10)
|
|
413
|
+
clear_metadata(file_set)
|
|
414
|
+
|
|
415
|
+
# Run FITS via Hydra::Works
|
|
416
|
+
CharacterizeJob.characterization_service.run(
|
|
417
|
+
file_set.characterization_proxy, filepath, **Hyrax.config.characterization_options
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
file_set.characterization_proxy.save!
|
|
421
|
+
file_set.update_index
|
|
422
|
+
file_set.save # persists date_modified etc.
|
|
423
|
+
end
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
Default `characterization_service` is `Hydra::Works::CharacterizationService`. The FITS result is parsed and properties are set directly on the `Hydra::PCDM::File` LDP object (the `characterization_proxy`).
|
|
427
|
+
|
|
428
|
+
Properties written to the proxy include: `format_label`, `file_size`, `height`, `width`, `filename`, `well_formed`, `valid`, `date_created`, `fits_version`, `exif_version`, `checksum`, `character_set`, `markup_basis`, `markup_language`, `byte_order`, `compression`, `color_space`, `profile_name`, `profile_version`, `orientation`, `color_map`, `image_producer`, `capture_device`, `scanning_software`, `gps_timestamp`, `latitude`, `longitude`, `file_title`, `creator`, `page_count`, `language`, `word_count`, `character_count`, `line_count`, `graphics_count`, `data_format`, `offset`, `alpha_channels` (if IIIF enabled), `duration`, `sample_rate`, and others.
|
|
429
|
+
|
|
430
|
+
### 4.2 Valkyrie Characterization
|
|
431
|
+
|
|
432
|
+
`app/jobs/valkyrie_characterization_job.rb`
|
|
433
|
+
|
|
434
|
+
Triggered by the event `'file.uploaded'` via `FileListener`:
|
|
435
|
+
|
|
436
|
+
```ruby
|
|
437
|
+
class ValkyrieCharacterizationJob < Hyrax::ApplicationJob
|
|
438
|
+
queue_as Hyrax.config.ingest_queue_name
|
|
439
|
+
|
|
440
|
+
def perform(file_metadata_id)
|
|
441
|
+
metadata = Hyrax.custom_queries.find_file_metadata_by(id: file_metadata_id)
|
|
442
|
+
Hyrax.config.characterization_service.run(
|
|
443
|
+
metadata: metadata,
|
|
444
|
+
file: metadata.file, # retrieved from storage_adapter
|
|
445
|
+
user: metadata.file_set.depositor
|
|
446
|
+
)
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
`app/services/hyrax/characterization/valkyrie_characterization_service.rb`
|
|
452
|
+
|
|
453
|
+
```ruby
|
|
454
|
+
def self.run(metadata:, file:, user:, **options)
|
|
455
|
+
new(metadata, file, user, **options).tap do |service|
|
|
456
|
+
service.characterize
|
|
457
|
+
Hyrax.persister.save(resource: metadata)
|
|
458
|
+
Hyrax.publisher.publish('file.metadata.updated', metadata: metadata, user: user)
|
|
459
|
+
Hyrax.publisher.publish('file.characterized',
|
|
460
|
+
file_set: metadata.file_set, file_id: metadata.id.to_s, path_hint: file.disk_path)
|
|
461
|
+
end
|
|
462
|
+
end
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Internally uses the same FITS toolchain (`Hydra::FileCharacterization`) but maps results to `FileMetadata` attributes rather than to PCDM file properties. Key differences:
|
|
466
|
+
|
|
467
|
+
- `mime_type` is set to the **last** value returned (FITS may return multiple).
|
|
468
|
+
- `height` and `width` are set to the **maximum** value (handles multi-page documents).
|
|
469
|
+
- Results are persisted via `Hyrax.persister.save(resource: metadata)`.
|
|
470
|
+
|
|
471
|
+
Configurable via `Hyrax.config.characterization_service` (replace the default to use custom characterizers such as MediaInfo for AV files or Tika for documents).
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
## 5. Derivative Generation
|
|
476
|
+
|
|
477
|
+
Derivatives (thumbnails, web-friendly formats, extracted text) are created after characterization.
|
|
478
|
+
|
|
479
|
+
### 5.1 ActiveFedora Derivatives
|
|
480
|
+
|
|
481
|
+
`app/jobs/create_derivatives_job.rb`
|
|
482
|
+
|
|
483
|
+
Triggered by `'file.characterized'` event via `FileListener`:
|
|
484
|
+
|
|
485
|
+
```ruby
|
|
486
|
+
def perform(file_set, file_id, filepath = nil)
|
|
487
|
+
return if Hyrax.config.disable_ffmpeg_check? # video guard
|
|
488
|
+
filepath ||= Hyrax::WorkingDirectory.find_or_retrieve(file_id, file_set.id)
|
|
489
|
+
file_set.create_derivatives(filepath) # delegates to FileSetDerivativesService
|
|
490
|
+
file_set.reload
|
|
491
|
+
file_set.update_index
|
|
492
|
+
work = file_set.parent
|
|
493
|
+
work&.update_index if file_set.id == work&.thumbnail_id
|
|
494
|
+
end
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
`file_set.create_derivatives(filepath)` calls `Hyrax::FileSetDerivativesService#create_derivatives`, which dispatches by MIME type to format-specific processors (see §5.3).
|
|
498
|
+
|
|
499
|
+
### 5.2 Valkyrie Derivatives
|
|
500
|
+
|
|
501
|
+
`app/jobs/valkyrie_create_derivatives_job.rb`
|
|
502
|
+
|
|
503
|
+
Triggered by `'file.characterized'` event via `FileListener`:
|
|
504
|
+
|
|
505
|
+
```ruby
|
|
506
|
+
def perform(file_set_id, file_id, _filepath = nil)
|
|
507
|
+
return if Hyrax.config.disable_ffmpeg_check?
|
|
508
|
+
file_metadata = Hyrax.custom_queries.find_file_metadata_by(id: file_id)
|
|
509
|
+
file = Hyrax.storage_adapter.find_by(id: file_metadata.file_identifier)
|
|
510
|
+
Hyrax::DerivativeService.for(file_metadata).create_derivatives(file.disk_path)
|
|
511
|
+
|
|
512
|
+
# Reindex parent work if this file set is the thumbnail
|
|
513
|
+
reindex_parent(file_set_id)
|
|
514
|
+
end
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
The key difference: AF passes the `FileSet` object; Valkyrie passes a `FileMetadata` to the derivative service factory.
|
|
518
|
+
|
|
519
|
+
### 5.3 Derivative Service Factory
|
|
520
|
+
|
|
521
|
+
`app/services/hyrax/derivative_service.rb`
|
|
522
|
+
|
|
523
|
+
```ruby
|
|
524
|
+
def self.for(file_set, services: Hyrax.config.derivative_services)
|
|
525
|
+
services.each do |service_class|
|
|
526
|
+
service = service_class.new(file_set)
|
|
527
|
+
return service if service.valid?
|
|
528
|
+
end
|
|
529
|
+
new(file_set) # fallback to base service
|
|
530
|
+
end
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
Each service implements `#valid?` (checks MIME type), `#create_derivatives(filename)`, and `#cleanup_derivatives`.
|
|
534
|
+
|
|
535
|
+
`app/services/hyrax/file_set_derivatives_service.rb` — the default service:
|
|
536
|
+
|
|
537
|
+
| MIME type group | Derivatives created |
|
|
538
|
+
|-----------------|---------------------|
|
|
539
|
+
| `application/pdf` | Thumbnail (ImageMagick), full-text extraction (Solr) |
|
|
540
|
+
| Office documents (`application/msword`, etc.) | Thumbnail (LibreOffice → ImageMagick), full-text |
|
|
541
|
+
| Audio (`audio/*`) | MP3 (FFmpeg), OGG (FFmpeg) |
|
|
542
|
+
| Video (`video/*`) | JPG thumbnail (FFmpeg), WebM (FFmpeg), MP4 (FFmpeg) |
|
|
543
|
+
| Image (`image/*`) | JPG thumbnail (ImageMagick, layer 0 for pyramidal TIFF) |
|
|
544
|
+
|
|
545
|
+
Derivatives are stored at paths derived from `Hyrax::DerivativePath.derivative_path_for_reference`.
|
|
546
|
+
|
|
547
|
+
---
|
|
548
|
+
|
|
549
|
+
## 6. Event System (The Connective Tissue)
|
|
550
|
+
|
|
551
|
+
Hyrax uses `Dry::Events` (via `Hyrax::Publisher`) to decouple ingest stages. Listeners are registered in `config/initializers/listeners.rb`.
|
|
552
|
+
|
|
553
|
+
### Events fired during ingest
|
|
554
|
+
|
|
555
|
+
| Event | Published by | Consumed by |
|
|
556
|
+
|-------|-------------|-------------|
|
|
557
|
+
| `'object.deposited'` | `WorkUploadsHandler` | Analytics, notifications |
|
|
558
|
+
| `'file.set.attached'` | `WorkUploadsHandler`, `FileSetActor#attach_to_work` | Analytics, notifications |
|
|
559
|
+
| `'file.uploaded'` | `ValkyrieUpload#upload` | `FileListener` → `ValkyrieCharacterizationJob` |
|
|
560
|
+
| `'file.metadata.updated'` | `ValkyrieUpload#upload`, `ValkyrieCharacterizationService` | `MetadataIndexListener` → Solr reindex |
|
|
561
|
+
| `'object.membership.updated'` | `ValkyrieUpload#upload` | `MetadataIndexListener` → Solr reindex |
|
|
562
|
+
| `'object.metadata.updated'` | `WorkUploadsHandler#attach` | `MetadataIndexListener` → Solr reindex |
|
|
563
|
+
| `'file.characterized'` | `CharacterizeJob`, `ValkyrieCharacterizationService` | `FileListener` → `Create[Valkyrie]DerivativesJob` |
|
|
564
|
+
|
|
565
|
+
### FileListener dispatch (AF vs Valkyrie)
|
|
566
|
+
|
|
567
|
+
`app/services/hyrax/listeners/file_listener.rb`
|
|
568
|
+
|
|
569
|
+
```ruby
|
|
570
|
+
def on_file_characterized(event)
|
|
571
|
+
case event[:file_set]
|
|
572
|
+
when ActiveFedora::Base
|
|
573
|
+
CreateDerivativesJob.perform_later(event[:file_set], event[:file_id], event[:path_hint])
|
|
574
|
+
else
|
|
575
|
+
ValkyrieCreateDerivativesJob.perform_later(event[:file_set].id.to_s, event[:file_id])
|
|
576
|
+
end
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
def on_file_uploaded(event)
|
|
580
|
+
# Only for original files; respects skip_derivatives flag
|
|
581
|
+
return if event.payload[:skip_derivatives] || !event[:metadata]&.original_file?
|
|
582
|
+
ValkyrieCharacterizationJob.perform_later(event[:metadata].id.to_s)
|
|
583
|
+
end
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
`on_file_uploaded` is Valkyrie-only; the AF path triggers `CharacterizeJob` directly from `FileActor#ingest_file` without an event.
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
## 7. Metadata Persistence and Indexing
|
|
591
|
+
|
|
592
|
+
### ActiveFedora
|
|
593
|
+
|
|
594
|
+
- File properties live on the `Hydra::PCDM::File` LDP resource (a node within the FileSet in Fedora).
|
|
595
|
+
- `CharacterizeJob` writes properties directly to `file_set.characterization_proxy` and calls `file_set.update_index` to push to Solr.
|
|
596
|
+
- `CreateDerivativesJob` calls `file_set.reload` + `file_set.update_index` after creating derivatives.
|
|
597
|
+
|
|
598
|
+
### Valkyrie
|
|
599
|
+
|
|
600
|
+
- File properties live on `Hyrax::FileMetadata` resources persisted via `Hyrax.persister`.
|
|
601
|
+
- Solr indexing is event-driven: the `'file.metadata.updated'` event is consumed by `Hyrax::Listeners::MetadataIndexListener`.
|
|
602
|
+
|
|
603
|
+
`app/services/hyrax/listeners/metadata_index_listener.rb`
|
|
604
|
+
|
|
605
|
+
```ruby
|
|
606
|
+
def on_file_metadata_updated(event)
|
|
607
|
+
Hyrax.index_adapter.save(resource: event[:metadata])
|
|
608
|
+
# If this is the original file, reindex the parent file_set too
|
|
609
|
+
if event[:metadata].original_file?
|
|
610
|
+
file_set = Hyrax.query_service.find_by(id: event[:metadata].file_set_id)
|
|
611
|
+
Hyrax.index_adapter.save(resource: file_set)
|
|
612
|
+
end
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
def on_object_metadata_updated(event)
|
|
616
|
+
Hyrax.index_adapter.save(resource: event[:object])
|
|
617
|
+
# Reindex parent work if object is a thumbnail file_set
|
|
618
|
+
...
|
|
619
|
+
end
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
Custom queries for file lookup:
|
|
623
|
+
|
|
624
|
+
- `Hyrax.custom_queries.find_file_metadata_by(id:)` — single `FileMetadata`
|
|
625
|
+
- `Hyrax.custom_queries.find_many_file_metadata_by_use(resource:, use:)` — files by use predicate
|
|
626
|
+
- `Hyrax.custom_queries.find_original_file(file_set:)` — original file for a FileSet
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
## 8. Wings: The AF ↔ Valkyrie Bridge
|
|
631
|
+
|
|
632
|
+
Wings (`lib/wings/`) is a compatibility adapter that allows AF objects to be used in Valkyrie workflows by wrapping them as `Valkyrie::Resource` instances on the fly. It is used when `Hyrax.config.use_valkyrie?` is true but the application still has AF-backed models.
|
|
633
|
+
|
|
634
|
+
Key components:
|
|
635
|
+
|
|
636
|
+
| File | Purpose |
|
|
637
|
+
|------|---------|
|
|
638
|
+
| `lib/wings/active_fedora_converter/` | Converts AF objects to Valkyrie resources |
|
|
639
|
+
| `lib/wings/valkyrie/` | Reverse: Valkyrie resources back to AF |
|
|
640
|
+
| `lib/wings/model_transformer.rb` | Model class mapping |
|
|
641
|
+
| `lib/wings/transformer_value_mapper.rb` | Property value mapping |
|
|
642
|
+
| `app/actors/hyrax/actors/file_actor.rb` | Explicitly forces `io.use_valkyrie = false` in the AF path to avoid using Valkyrie objects inadvertently |
|
|
643
|
+
|
|
644
|
+
`AttachFilesToWorkJob` performs the `case work` switch: AF objects are an `ActiveFedora::Base` instance; Valkyrie resources are not, even if Wings-wrapped.
|
|
645
|
+
|
|
646
|
+
The `'file.characterized'` event dispatch in `FileListener` uses the same `case` pattern: `when ActiveFedora::Base` routes to `CreateDerivativesJob`; otherwise routes to `ValkyrieCreateDerivativesJob`.
|
|
647
|
+
|
|
648
|
+
---
|
|
649
|
+
|
|
650
|
+
## 9. Key Configuration Points
|
|
651
|
+
|
|
652
|
+
`Hyrax.config` controls which path is taken at several junctions. Relevant knobs:
|
|
653
|
+
|
|
654
|
+
| Config key | Type | Purpose |
|
|
655
|
+
|------------|------|---------|
|
|
656
|
+
| `use_valkyrie?` | Boolean | Master switch for Valkyrie path |
|
|
657
|
+
| `valkyrie_file_set_class` | Class | The `Hyrax::FileSet` subclass used by `WorkUploadsHandler` |
|
|
658
|
+
| `ingest_queue_name` | String/Symbol | ActiveJob queue for `IngestJob`, `ValkyrieIngestJob`, characterize, and derivatives jobs |
|
|
659
|
+
| `characterization_service` | Class | Replaced to customise characterization (e.g., swap out FITS) |
|
|
660
|
+
| `characterization_options` | Hash | Extra options forwarded to characterization service |
|
|
661
|
+
| `derivative_services` | Array<Class> | Priority-ordered list of derivative service classes; first `#valid?` wins |
|
|
662
|
+
| `extract_full_text?` | Boolean | Whether to run full-text extraction during derivatives |
|
|
663
|
+
| `enable_ffmpeg` | Boolean | Whether to run video/audio derivatives |
|
|
664
|
+
| `iiif_image_server?` | Boolean | Whether to write `alpha_channels` attribute during AF characterization |
|
|
665
|
+
| `file_set_file_service` | Class | Controls how files are located in the working directory |
|
|
666
|
+
|
|
667
|
+
Valkyrie-specific infrastructure:
|
|
668
|
+
|
|
669
|
+
| Accessor | Purpose |
|
|
670
|
+
|----------|---------|
|
|
671
|
+
| `Hyrax.storage_adapter` | `Valkyrie::StorageAdapter` for file content |
|
|
672
|
+
| `Hyrax.persister` | `Valkyrie::MetadataAdapter::Persister` for metadata |
|
|
673
|
+
| `Hyrax.query_service` | `Valkyrie::MetadataAdapter::QueryService` for reads |
|
|
674
|
+
| `Hyrax.index_adapter` | Solr-backed adapter for search indexing |
|
|
675
|
+
| `Hyrax.custom_queries` | Registry of domain-specific query handlers |
|
|
676
|
+
|
|
677
|
+
---
|
|
678
|
+
|
|
679
|
+
## 10. Developer Cautions
|
|
680
|
+
|
|
681
|
+
### Clearing characterization metadata before re-running FITS
|
|
682
|
+
|
|
683
|
+
`CharacterizeJob#clear_metadata` (AF path) explicitly resets all characterization properties to empty arrays before running FITS. If this is skipped, old values from a previous characterization run can remain and produce incorrect Solr documents. The Valkyrie path (`ValkyrieCharacterizationService#apply_metadata`) overwrites properties, but only those returned by FITS — stale values for _missing_ properties are not automatically cleared.
|
|
684
|
+
|
|
685
|
+
### `io.use_valkyrie = false` in FileActor
|
|
686
|
+
|
|
687
|
+
`FileActor#ingest_file` hard-codes `io.use_valkyrie = false` before calling `Hydra::Works::AddFileToFileSet`. This is intentional: the AF actor path must write to Fedora LDP regardless of the `Hyrax.config.use_valkyrie?` flag. Removing this line or making it conditional will break the AF ingest path.
|
|
688
|
+
|
|
689
|
+
### File idempotency guard
|
|
690
|
+
|
|
691
|
+
Both paths skip files whose `Hyrax::UploadedFile#file_set_uri` is already set. This is an incomplete idempotency guard: if a job fails _after_ `add_file_set!` is called but _before_ the file is fully ingested, the record will be skipped on retry and the file will not be re-ingested. Track [hyrax#TODO note in WorkUploadsHandler] when investigating stuck uploads.
|
|
692
|
+
|
|
693
|
+
### Lock scope
|
|
694
|
+
|
|
695
|
+
`WorkUploadsHandler` (Valkyrie) acquires a single Redlock on the work for the entire batch of file_sets. `FileSetActor#attach_to_work` (AF) acquires a separate lock per file_set attachment. If Redis is unavailable in the AF path, the lock silently degrades (check `Lockable`).
|
|
696
|
+
|
|
697
|
+
### Derivative ordering vs. characterization
|
|
698
|
+
|
|
699
|
+
Derivatives are triggered by the `'file.characterized'` event, not by `'file.uploaded'`. If characterization is skipped (e.g., via `skip_derivatives: true` on `'file.uploaded'`), derivatives will also be skipped. Some tests and bulk-ingest scripts pass `skip_derivatives: true` for performance; verify this flag is not accidentally retained in production workflows.
|
|
700
|
+
|
|
701
|
+
### Storage adapter `disk_path` requirement
|
|
702
|
+
|
|
703
|
+
`ValkyrieCreateDerivativesJob` calls `file.disk_path` on the storage-adapter result. Not all storage adapters guarantee a disk path (e.g., a pure-S3 adapter may return a temporary local path or raise). Verify your storage adapter's behaviour before using a non-disk-backed adapter in production; you may need to override the derivatives job to download the file locally first.
|
|
704
|
+
|
|
705
|
+
### Wings and Valkyrie FileMetadata
|
|
706
|
+
|
|
707
|
+
Wings wraps AF FileSet objects as Valkyrie resources, but the AF path never creates `Hyrax::FileMetadata` records. Queries like `find_file_metadata_by` and `find_original_file` will return nothing for AF-backed file sets unless Wings re-projects the PCDM file graph. Do not mix these query paths when operating on AF works.
|
|
708
|
+
|
|
709
|
+
### Listener registration order
|
|
710
|
+
|
|
711
|
+
`config/initializers/listeners.rb` registers listeners using `Hyrax.publisher.subscribe`. If you add a custom listener that responds to `'file.uploaded'`, ensure it handles both the Valkyrie case (where `event[:metadata]` is a `Hyrax::FileMetadata`) and the AF case (where this event is never published — AF characterization is triggered synchronously in `FileActor`).
|
|
712
|
+
|
|
713
|
+
### Transaction steps vs. actor stack customization
|
|
714
|
+
|
|
715
|
+
Adding file-related behaviour to the AF path means modifying or inserting actors in `Hyrax.config.actor_middleware`. Adding the equivalent to the Valkyrie path means adding a step to the `WorkCreate` (or `WorkUpdate`) transaction, or customizing `WorkUploadsHandler`. These are separate extension points; changes to one do not affect the other.
|
|
716
|
+
|
|
717
|
+
---
|
|
718
|
+
|
|
719
|
+
## Appendix: Simplified Sequence Diagrams
|
|
720
|
+
|
|
721
|
+
### ActiveFedora
|
|
722
|
+
|
|
723
|
+
```
|
|
724
|
+
Browser/API → Work Form
|
|
725
|
+
→ CurationConcern::Actor (actor stack)
|
|
726
|
+
→ CreateWithFilesActor
|
|
727
|
+
→ AttachFilesToWorkJob [async]
|
|
728
|
+
→ FileSetActor#create_content
|
|
729
|
+
→ IngestJob [async]
|
|
730
|
+
→ FileActor#ingest_file
|
|
731
|
+
→ Hydra::Works::AddFileToFileSet [Fedora LDP write]
|
|
732
|
+
→ VersioningService#create
|
|
733
|
+
→ CharacterizeJob [async]
|
|
734
|
+
→ Hydra::Works::CharacterizationService [FITS]
|
|
735
|
+
→ file_set.update_index [Solr]
|
|
736
|
+
→ publisher 'file.characterized'
|
|
737
|
+
→ CreateDerivativesJob [async]
|
|
738
|
+
→ FileSetDerivativesService
|
|
739
|
+
→ file_set.update_index [Solr]
|
|
740
|
+
```
|
|
741
|
+
|
|
742
|
+
### Valkyrie
|
|
743
|
+
|
|
744
|
+
```
|
|
745
|
+
Browser/API → Work Form / Transaction
|
|
746
|
+
→ Hyrax::Transactions::WorkCreate
|
|
747
|
+
→ Steps::AddFileSets
|
|
748
|
+
→ WorkUploadsHandler#attach [Redis lock]
|
|
749
|
+
→ persister.save(FileSet)
|
|
750
|
+
→ ValkyrieIngestJob [async]
|
|
751
|
+
→ ValkyrieUpload#upload
|
|
752
|
+
→ storage_adapter.upload [storage backend write]
|
|
753
|
+
→ persister.save(FileMetadata)
|
|
754
|
+
→ publisher 'file.uploaded'
|
|
755
|
+
→ ValkyrieCharacterizationJob [async]
|
|
756
|
+
→ ValkyrieCharacterizationService [FITS]
|
|
757
|
+
→ persister.save(FileMetadata)
|
|
758
|
+
→ publisher 'file.metadata.updated' → MetadataIndexListener → Solr
|
|
759
|
+
→ publisher 'file.characterized'
|
|
760
|
+
→ ValkyrieCreateDerivativesJob [async]
|
|
761
|
+
→ DerivativeService#create_derivatives
|
|
762
|
+
→ index_adapter.save(parent work) [Solr]
|
|
763
|
+
```
|