iiif_print 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE.md +18 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +16 -0
- data/.github/workflows/build-lint-test-action.yaml +4 -5
- data/.gitignore +5 -4
- data/.rubocop.yml +1 -0
- data/.solargraph.yml +19 -0
- data/Gemfile.lock +1025 -0
- data/README.md +102 -9
- data/Rakefile +6 -0
- data/app/actors/iiif_print/actors/cleanup_file_sets_actor_decorator.rb +24 -0
- data/app/actors/iiif_print/actors/file_set_actor_decorator.rb +30 -28
- data/app/controllers/iiif_print/split_pdfs_controller.rb +38 -0
- data/app/helpers/iiif_print/iiif_helper_decorator.rb +32 -0
- data/app/helpers/iiif_print/iiif_print_helper_behavior.rb +23 -0
- data/app/helpers/iiif_print_helper.rb +0 -20
- data/app/indexers/concerns/iiif_print/child_work_indexer.rb +27 -0
- data/app/indexers/concerns/iiif_print/file_set_indexer.rb +45 -17
- data/{lib → app/jobs}/iiif_print/jobs/application_job.rb +2 -1
- data/app/jobs/iiif_print/jobs/child_works_from_pdf_job.rb +153 -0
- data/app/jobs/iiif_print/jobs/create_relationships_job.rb +117 -0
- data/app/jobs/iiif_print/jobs/request_split_pdf_job.rb +31 -0
- data/app/listeners/iiif_print/listener.rb +31 -0
- data/app/models/concerns/iiif_print/set_child_flag.rb +10 -1
- data/app/models/concerns/iiif_print/solr/document.rb +19 -3
- data/app/models/iiif_print/iiif_search_decorator.rb +35 -0
- data/app/models/iiif_print/iiif_search_response_decorator.rb +25 -2
- data/app/models/iiif_print/pending_relationship.rb +3 -0
- data/app/presenters/iiif_print/file_set_presenter_decorator.rb +11 -0
- data/app/presenters/iiif_print/iiif_manifest_presenter_behavior.rb +120 -0
- data/app/presenters/iiif_print/iiif_manifest_presenter_factory_behavior.rb +1 -1
- data/app/presenters/iiif_print/work_show_presenter_decorator.rb +23 -11
- data/app/search_builders/concerns/iiif_print/allinson_flex_fields.rb +15 -0
- data/app/search_builders/concerns/iiif_print/highlight_search_params.rb +2 -1
- data/app/services/iiif_print/derivative_rodeo_service.rb +382 -0
- data/app/services/iiif_print/manifest_builder_service_behavior.rb +90 -31
- data/app/services/iiif_print/pluggable_derivative_service.rb +8 -10
- data/app/services/iiif_print/simple_schema_loader_decorator.rb +11 -0
- data/app/transactions/hyrax/transactions/iiif_print_container_decorator.rb +34 -0
- data/app/transactions/hyrax/transactions/steps/conditionally_destroy_children_from_split.rb +32 -0
- data/app/transactions/hyrax/transactions/steps/delete_all_file_sets_decorator.rb +35 -0
- data/app/views/catalog/_index_header_list_default.html.erb +13 -0
- data/app/views/hyrax/base/_representative_media.html.erb +4 -3
- data/app/views/hyrax/base/iiif_viewers/_universal_viewer.html.erb +1 -1
- data/app/views/hyrax/file_sets/_show_actions.html.erb +24 -0
- data/config/initializers/simple_schema_loader.rb +1 -0
- data/config/locales/iiif_print.en.yml +4 -0
- data/config/metadata/child_works_from_pdf_splitting.yaml +21 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20181214181358_create_iiif_print_derivative_attachments.rb +8 -6
- data/db/migrate/20190107165909_create_iiif_print_ingest_file_relations.rb +7 -5
- data/db/migrate/20230109000000_create_iiif_print_pending_relationships.rb +8 -6
- data/db/migrate/20231110163052_add_model_details_to_iiif_print_pending_relationships.rb +7 -0
- data/docker-compose.yml +2 -2
- data/iiif_print.gemspec +11 -10
- data/lib/generators/iiif_print/install_generator.rb +21 -1
- data/lib/generators/iiif_print/templates/config/initializers/iiif_print.rb +11 -4
- data/lib/generators/iiif_print/templates/helpers/iiif_print_helper.rb +5 -0
- data/lib/iiif_print/base_derivative_service.rb +14 -2
- data/lib/iiif_print/blacklight_iiif_search/annotation_decorator.rb +58 -6
- data/lib/iiif_print/catalog_search_builder.rb +7 -3
- data/lib/iiif_print/configuration.rb +205 -8
- data/lib/iiif_print/data/fileset_helper.rb +3 -3
- data/lib/iiif_print/data/work_derivatives.rb +4 -4
- data/lib/iiif_print/engine.rb +53 -15
- data/lib/iiif_print/errors.rb +18 -0
- data/lib/iiif_print/homepage_search_builder.rb +17 -0
- data/lib/iiif_print/image_tool.rb +12 -8
- data/lib/iiif_print/jp2_derivative_service.rb +4 -1
- data/lib/iiif_print/lineage_service.rb +47 -13
- data/lib/iiif_print/metadata.rb +67 -48
- data/lib/iiif_print/pdf_derivative_service.rb +3 -1
- data/lib/iiif_print/persistence_layer/active_fedora_adapter.rb +189 -0
- data/lib/iiif_print/persistence_layer/valkyrie_adapter.rb +183 -0
- data/lib/iiif_print/persistence_layer.rb +118 -0
- data/lib/iiif_print/split_pdfs/base_splitter.rb +153 -0
- data/lib/iiif_print/split_pdfs/child_work_creation_from_pdf_service.rb +83 -37
- data/lib/iiif_print/split_pdfs/derivative_rodeo_splitter.rb +166 -0
- data/lib/iiif_print/split_pdfs/destroy_pdf_child_works_service.rb +22 -0
- data/lib/iiif_print/split_pdfs/pages_to_jpgs_splitter.rb +19 -0
- data/lib/iiif_print/split_pdfs/pages_to_pngs_splitter.rb +26 -0
- data/lib/iiif_print/split_pdfs/pages_to_tiffs_splitter.rb +41 -0
- data/lib/iiif_print/split_pdfs/pdf_image_extraction_service.rb +64 -59
- data/lib/iiif_print/text_extraction/hocr_reader.rb +7 -3
- data/lib/iiif_print/text_extraction/page_ocr.rb +5 -4
- data/lib/iiif_print/text_extraction_derivative_service.rb +4 -2
- data/lib/iiif_print/text_formats_from_alto_service.rb +3 -1
- data/lib/iiif_print/tiff_derivative_service.rb +3 -1
- data/lib/iiif_print/version.rb +1 -1
- data/lib/iiif_print.rb +210 -20
- data/lib/samvera/derivatives/configuration.rb +83 -0
- data/lib/samvera/derivatives/hyrax.rb +129 -0
- data/lib/samvera/derivatives.rb +238 -0
- data/tasks/copy_authorities_to_test_app.rake +11 -0
- data/tasks/iiif_print_dev.rake +4 -4
- metadata +111 -196
- data/app/helpers/hyrax/iiif_helper.rb +0 -22
- data/app/indexers/concerns/iiif_print/child_indexer.rb +0 -34
- data/app/views/hyrax/file_sets/_actions.html.erb +0 -45
- data/bin/rails +0 -13
- data/lib/iiif_print/jobs/child_works_from_pdf_job.rb +0 -107
- data/lib/iiif_print/jobs/create_relationships_job.rb +0 -78
- data/lib/iiif_print/split_pdfs/pages_into_images_service.rb +0 -130
- data/spec/.keep.txt +0 -1
- data/spec/factories/ability.rb +0 -6
- data/spec/factories/newspaper_issue.rb +0 -7
- data/spec/factories/newspaper_page.rb +0 -7
- data/spec/factories/newspaper_page_solr_document.rb +0 -12
- data/spec/factories/newspaper_title.rb +0 -8
- data/spec/factories/uploaded_pdf_file.rb +0 -9
- data/spec/factories/uploaded_txt_file.rb +0 -9
- data/spec/factories/user.rb +0 -13
- data/spec/fixtures/files/4.1.07.jp2 +0 -0
- data/spec/fixtures/files/4.1.07.tiff +0 -0
- data/spec/fixtures/files/README.md +0 -7
- data/spec/fixtures/files/alto-2-0.xsd +0 -714
- data/spec/fixtures/files/broken-truncated.pdf +0 -0
- data/spec/fixtures/files/credits.md +0 -16
- data/spec/fixtures/files/lowres-gray-via-ndnp-sample.tiff +0 -0
- data/spec/fixtures/files/minimal-1-page.pdf +0 -0
- data/spec/fixtures/files/minimal-2-page.pdf +0 -0
- data/spec/fixtures/files/minimal-alto.xml +0 -31
- data/spec/fixtures/files/ndnp-alto-sample.xml +0 -24
- data/spec/fixtures/files/ndnp-sample1-json.json +0 -1
- data/spec/fixtures/files/ndnp-sample1-txt.txt +0 -1
- data/spec/fixtures/files/ndnp-sample1.pdf +0 -0
- data/spec/fixtures/files/ocr_alto.xml +0 -202
- data/spec/fixtures/files/ocr_alto_scaled_4pts_per_px.xml +0 -202
- data/spec/fixtures/files/ocr_color.tiff +0 -0
- data/spec/fixtures/files/ocr_gray.jp2 +0 -0
- data/spec/fixtures/files/ocr_gray.tiff +0 -0
- data/spec/fixtures/files/ocr_mono.tiff +0 -0
- data/spec/fixtures/files/ocr_mono_text_hocr.html +0 -78
- data/spec/fixtures/files/page1.tiff +0 -0
- data/spec/fixtures/files/sample-4page-issue.pdf +0 -0
- data/spec/fixtures/files/sample-color-newsletter.pdf +0 -0
- data/spec/fixtures/files/thumbnail.jpg +0 -0
- data/spec/helpers/hyrax/iiif_helper_spec.rb +0 -65
- data/spec/helpers/iiif_print_helper_spec.rb +0 -43
- data/spec/iiif_print/base_derivative_service_spec.rb +0 -11
- data/spec/iiif_print/blacklight_iiif_search/annotation_decorator_spec.rb +0 -51
- data/spec/iiif_print/catalog_search_builder_spec.rb +0 -60
- data/spec/iiif_print/configuration_spec.rb +0 -67
- data/spec/iiif_print/data/work_derivatives_spec.rb +0 -245
- data/spec/iiif_print/data/work_file_spec.rb +0 -99
- data/spec/iiif_print/data/work_files_spec.rb +0 -237
- data/spec/iiif_print/image_tool_spec.rb +0 -109
- data/spec/iiif_print/jobs/child_works_from_pdf_job_spec.rb +0 -30
- data/spec/iiif_print/jobs/create_relationships_job_spec.rb +0 -17
- data/spec/iiif_print/jp2_image_metadata_spec.rb +0 -37
- data/spec/iiif_print/lineage_service_spec.rb +0 -13
- data/spec/iiif_print/metadata_spec.rb +0 -115
- data/spec/iiif_print/split_pdfs/pages_into_images_service_spec.rb +0 -6
- data/spec/iiif_print/text_extraction/alto_reader_spec.rb +0 -49
- data/spec/iiif_print/text_extraction/hocr_reader_spec.rb +0 -45
- data/spec/iiif_print/text_extraction/page_ocr_spec.rb +0 -84
- data/spec/iiif_print/text_extraction/render_alto_spec.rb +0 -54
- data/spec/iiif_print/text_extraction/word_coords_builder_spec.rb +0 -44
- data/spec/iiif_print_spec.rb +0 -51
- data/spec/misc_shared.rb +0 -111
- data/spec/models/iiif_print/derivative_attachment_spec.rb +0 -37
- data/spec/models/iiif_print/ingest_file_relation_spec.rb +0 -56
- data/spec/models/solr_document_spec.rb +0 -14
- data/spec/presenters/iiif_print/iiif_manifest_presenter_behavior_spec.rb +0 -19
- data/spec/presenters/iiif_print/iiif_manifest_presenter_factory_behavior_spec.rb +0 -49
- data/spec/services/iiif_print/jp2_derivative_service_spec.rb +0 -59
- data/spec/services/iiif_print/pdf_derivative_service_spec.rb +0 -66
- data/spec/services/iiif_print/pluggable_derivative_service_spec.rb +0 -178
- data/spec/services/iiif_print/text_extraction_derivative_service_spec.rb +0 -82
- data/spec/services/iiif_print/text_formats_from_alto_service_spec.rb +0 -127
- data/spec/services/iiif_print/tiff_derivative_service_spec.rb +0 -65
- data/spec/spec_helper.rb +0 -181
- data/spec/support/controller_level_helpers.rb +0 -28
- data/spec/support/iiif_print_models.rb +0 -127
- data/spec/test_app_templates/blacklight.yml +0 -9
- data/spec/test_app_templates/fedora.yml +0 -15
- data/spec/test_app_templates/lib/generators/test_app_generator.rb +0 -40
- data/spec/test_app_templates/redis.yml +0 -9
- data/spec/test_app_templates/solr/conf/schema.xml +0 -362
- data/spec/test_app_templates/solr/conf/solrconfig.xml +0 -322
- data/spec/test_app_templates/solr.yml +0 -7
@@ -0,0 +1,117 @@
|
|
1
|
+
# rubocop:disable Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
2
|
+
module IiifPrint
|
3
|
+
module Jobs
|
4
|
+
# Link newly created child works to the parent
|
5
|
+
class CreateRelationshipsJob < IiifPrint::Jobs::ApplicationJob
|
6
|
+
include Hyrax::Lockable
|
7
|
+
|
8
|
+
RETRY_MAX = 10
|
9
|
+
|
10
|
+
# @param parent_id: [<String>] parent work id
|
11
|
+
# @param parent_model: [<String>] parent model
|
12
|
+
# @param child_model: [<String>] child model
|
13
|
+
# @param retries: [<Integer>] count used during rescheduling to prevent infinite retries
|
14
|
+
def perform(parent_id:, parent_model:, child_model:, retries: 0, **)
|
15
|
+
@parent_id = parent_id
|
16
|
+
@parent_model = parent_model
|
17
|
+
@child_model = child_model
|
18
|
+
@retries = retries + 1
|
19
|
+
|
20
|
+
@number_of_successes = 0
|
21
|
+
@number_of_failures = 0
|
22
|
+
@parent_record_members_added = false
|
23
|
+
@errors = []
|
24
|
+
|
25
|
+
# Because we need our children in the correct order, we can't create any
|
26
|
+
# relationships until all child works have been created.
|
27
|
+
if completed_child_data
|
28
|
+
# add the members
|
29
|
+
add_children_to_parent
|
30
|
+
if @number_of_failures.zero? && @number_of_successes == @pending_children.count
|
31
|
+
# remove pending relationships upon valid completion
|
32
|
+
@pending_children.each(&:destroy)
|
33
|
+
elsif @number_of_failures.zero? && @number_of_successes > @pending_children.count
|
34
|
+
# remove pending relationships but raise error that too many relationships formed
|
35
|
+
@pending_children.each(&:destroy)
|
36
|
+
raise "CreateRelationshipsJob for parent id: #{@parent_id} " \
|
37
|
+
"added #{@number_of_successes} children, " \
|
38
|
+
"expected #{@pending_children.count} children."
|
39
|
+
else
|
40
|
+
# report failures & keep pending relationships
|
41
|
+
raise "CreateRelationshipsJob failed for parent id: #{@parent_id} " \
|
42
|
+
"had #{@number_of_successes} successes & #{@number_of_failures} failures, " \
|
43
|
+
"with errors: #{@errors}. Wanted #{@pending_children.count} children."
|
44
|
+
end
|
45
|
+
else
|
46
|
+
# if we aren't ready yet, reschedule the job and end this one normally
|
47
|
+
reschedule_job
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# load @child_works and @pending children, and
|
54
|
+
# return boolean indicating whether all chilren are present
|
55
|
+
def completed_child_data
|
56
|
+
@child_works = []
|
57
|
+
found_all_children = true
|
58
|
+
|
59
|
+
# find and sequence all pending children
|
60
|
+
@pending_children = IiifPrint::PendingRelationship.where(parent_id: @parent_id).order('child_order asc')
|
61
|
+
|
62
|
+
# find child works (skip out if any haven't yet been created)
|
63
|
+
@pending_children.each do |child|
|
64
|
+
# find by title... if any aren't found, the child works are not yet ready
|
65
|
+
found_children = IiifPrint.find_by_title_for(title: child.child_title, model: @child_model)
|
66
|
+
found_all_children = false if found_children.empty?
|
67
|
+
break unless found_all_children == true
|
68
|
+
@child_works += found_children
|
69
|
+
end
|
70
|
+
# return boolean
|
71
|
+
found_all_children
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_children_to_parent
|
75
|
+
parent_work = IiifPrint.find_by(id: @parent_id)
|
76
|
+
create_relationships(parent: parent_work, ordered_children: @child_works)
|
77
|
+
end
|
78
|
+
|
79
|
+
def reschedule_job
|
80
|
+
return if @retries > RETRY_MAX
|
81
|
+
CreateRelationshipsJob.set(wait: 10.minutes).perform_later(
|
82
|
+
parent_id: @parent_id,
|
83
|
+
parent_model: @parent_model,
|
84
|
+
child_model: @child_model,
|
85
|
+
retries: @retries
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
def create_relationships(parent:, ordered_children:)
|
90
|
+
acquire_lock_for(parent.id) do
|
91
|
+
# Not sure uncached is needed here, but kept
|
92
|
+
# for consistency with Bulkrax's relationships logic
|
93
|
+
ActiveRecord::Base.uncached do
|
94
|
+
ordered_children.each do |child|
|
95
|
+
add_to_work(child_record: child, parent_record: parent)
|
96
|
+
@number_of_successes += 1
|
97
|
+
rescue => e
|
98
|
+
@number_of_failures += 1
|
99
|
+
@errors << e
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
IiifPrint.save(object: parent) if @parent_record_members_added && @number_of_failures.zero?
|
104
|
+
end
|
105
|
+
|
106
|
+
# Bulkrax no longer reindexes file_sets, but IiifPrint needs both to add is_page_of_ssim for universal viewer.
|
107
|
+
# This is where child works need to be indexed (AFTER the parent save), as opposed to how Bulkrax does it.
|
108
|
+
IiifPrint.index_works(objects: ordered_children)
|
109
|
+
end
|
110
|
+
|
111
|
+
def add_to_work(child_record:, parent_record:)
|
112
|
+
@parent_record_members_added = IiifPrint.create_relationship_between(child_record: child_record, parent_record: parent_record)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
# rubocop:enable Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
117
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module IiifPrint
|
2
|
+
module Jobs
|
3
|
+
##
|
4
|
+
# Encapsulates logic for cleanup when the PDF is destroyed after pdf splitting into child works
|
5
|
+
class RequestSplitPdfJob < IiifPrint::Jobs::ApplicationJob
|
6
|
+
##
|
7
|
+
# @param file_set [FileSet]
|
8
|
+
# @param user [User]
|
9
|
+
# rubocop:disable Metrics/MethodLength
|
10
|
+
def perform(file_set:, user:)
|
11
|
+
return true unless file_set.pdf?
|
12
|
+
|
13
|
+
work = IiifPrint.parent_for(file_set)
|
14
|
+
|
15
|
+
# Woe is ye who changes the configuration of the model, thus removing the splitting.
|
16
|
+
raise WorkNotConfiguredToSplitFileSetError.new(work: work, file_set: file_set) unless work&.iiif_print_config&.pdf_splitter_job&.presence
|
17
|
+
|
18
|
+
# clean up any existing spawned child works of this file_set
|
19
|
+
IiifPrint::SplitPdfs::DestroyPdfChildWorksService.conditionally_destroy_spawned_children_of(
|
20
|
+
file_set: file_set,
|
21
|
+
work: work
|
22
|
+
)
|
23
|
+
|
24
|
+
location = Hyrax::WorkingDirectory.find_or_retrieve(file_set.files.first.id, file_set.id)
|
25
|
+
|
26
|
+
IiifPrint.conditionally_submit_split_for(work: work, file_set: file_set, locations: [location], user: user)
|
27
|
+
end
|
28
|
+
# rubocop:enable Metrics/MethodLength
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
##
|
2
|
+
# @see https://github.com/samvera/hyrax/wiki/Hyrax's-Event-Bus-(Hyrax::Publisher)
|
3
|
+
# @see https://www.rubydoc.info/gems/hyrax/Hyrax/Publisher
|
4
|
+
# @see https://dry-rb.org/gems/dry-events
|
5
|
+
module IiifPrint
|
6
|
+
class Listener
|
7
|
+
##
|
8
|
+
# Responsible for conditionally enqueuing the creation of child works from a PDF.
|
9
|
+
#
|
10
|
+
# @param event [#[]] a hash like construct with keys :user and :file_set
|
11
|
+
# @param service [#conditionally_enqueue]
|
12
|
+
#
|
13
|
+
# @see Hyrax::WorkUploadsHandler
|
14
|
+
def on_file_characterized(event, service: IiifPrint::SplitPdfs::ChildWorkCreationFromPdfService)
|
15
|
+
file_set = event[:file_set]
|
16
|
+
return false unless file_set
|
17
|
+
return false unless file_set.file_set?
|
18
|
+
return false unless file_set.original_file.pdf?
|
19
|
+
|
20
|
+
work = IiifPrint.parent_for(file_set)
|
21
|
+
# A short-circuit to avoid fetching the underlying file.
|
22
|
+
return false unless work
|
23
|
+
|
24
|
+
user = work.depositor
|
25
|
+
# TODO: Verify that this is the correct thing to be sending off for conditional enquing. That
|
26
|
+
# will require a more involved integration test.
|
27
|
+
file = file_set.original_file
|
28
|
+
service.conditionally_enqueue(file_set: file_set, work: work, file: file, user: user)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -4,6 +4,10 @@ module RDF
|
|
4
4
|
class CustomIsChildTerm < Vocabulary('http://id.loc.gov/vocabulary/identifiers/')
|
5
5
|
property 'is_child'
|
6
6
|
end
|
7
|
+
|
8
|
+
class FromPdfIdTerm < Vocabulary('http://id.loc.gov/vocabulary/identifiers/')
|
9
|
+
property 'split_from_pdf_id'
|
10
|
+
end
|
7
11
|
end
|
8
12
|
|
9
13
|
module IiifPrint
|
@@ -18,10 +22,15 @@ module IiifPrint
|
|
18
22
|
multiple: false do |index|
|
19
23
|
index.as :stored_searchable
|
20
24
|
end
|
25
|
+
property :split_from_pdf_id,
|
26
|
+
predicate: ::RDF::FromPdfIdTerm.split_from_pdf_id,
|
27
|
+
multiple: false do |index|
|
28
|
+
index.as :stored_searchable
|
29
|
+
end
|
21
30
|
end
|
22
31
|
|
23
32
|
def set_children
|
24
|
-
|
33
|
+
IiifPrint.object_ordered_works(self).each do |child_work|
|
25
34
|
child_work.update(is_child: true) unless child_work.is_child
|
26
35
|
end
|
27
36
|
end
|
@@ -19,6 +19,8 @@ module IiifPrint::Solr::Document
|
|
19
19
|
def self.decorate(base)
|
20
20
|
base.prepend(self)
|
21
21
|
base.send(:attribute, :is_child, Hyrax::SolrDocument::Metadata::Solr::String, 'is_child_bsi')
|
22
|
+
base.send(:attribute, :split_from_pdf_id, Hyrax::SolrDocument::Metadata::Solr::String, 'split_from_pdf_id_ssi')
|
23
|
+
base.send(:attribute, :digest, Hyrax::SolrDocument::Metadata::Solr::String, 'digest_ssim')
|
22
24
|
|
23
25
|
# @note These properties came from the newspaper_works gem. They are configurable.
|
24
26
|
base.class_attribute :iiif_print_solr_field_names, default: %w[alternative_title genre
|
@@ -31,17 +33,31 @@ module IiifPrint::Solr::Document
|
|
31
33
|
base
|
32
34
|
end
|
33
35
|
|
36
|
+
def digest_sha1
|
37
|
+
digest[/urn:sha1:([\w]+)/, 1]
|
38
|
+
end
|
39
|
+
|
34
40
|
def method_missing(method_name, *args, &block)
|
35
41
|
super unless iiif_print_solr_field_names.include? method_name.to_s
|
36
|
-
self[
|
42
|
+
self[IiifPrint.solr_name(method_name.to_s)]
|
37
43
|
end
|
38
44
|
|
39
45
|
def respond_to_missing?(method_name, include_private = false)
|
40
46
|
iiif_print_solr_field_names.include?(method_name.to_s) || super
|
41
47
|
end
|
42
48
|
|
43
|
-
#
|
49
|
+
# @see https://github.com/samvera/hyrax/commit/7108409c619cd2ba4ae8c836b9f3b429a7e9837b
|
44
50
|
def file_set_ids
|
45
|
-
|
51
|
+
# Yes, this looks a little odd. But the truth is the prior key (e.g. `file_set_ids_ssim`) was
|
52
|
+
# an alias of `member_ids_ssim`.
|
53
|
+
self['member_ids_ssim']
|
54
|
+
end
|
55
|
+
|
56
|
+
def any_highlighting?
|
57
|
+
response&.[]('highlighting')&.[](id)&.present?
|
58
|
+
end
|
59
|
+
|
60
|
+
def solr_document
|
61
|
+
self
|
46
62
|
end
|
47
63
|
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# OVERRIDE: Blacklight IIIF Search v1.0.0
|
4
|
+
# IiifSearchDecorator module extends the functionality of the BlacklightIiifSearch::IiifSearch class
|
5
|
+
# by overriding the solr_params method to modify the search query to include the parent's metadata.
|
6
|
+
module IiifPrint
|
7
|
+
module IiifSearchDecorator
|
8
|
+
##
|
9
|
+
# Overrides the solr_params method from BlacklightIiifSearch::IiifSearch to modify the search query.
|
10
|
+
# The method adds an additional filter to the query to include either the object_relation_field OR the
|
11
|
+
# parent document's id and removes the :f parameter from the query.
|
12
|
+
# :object_relation_field refers to the CatalogController's configuration which is typically set to
|
13
|
+
# 'is_page_of_ssim' in the host application which only searches child works by default.
|
14
|
+
#
|
15
|
+
# config.iiif_search = {
|
16
|
+
# full_text_field: 'all_text_tsimv',
|
17
|
+
# object_relation_field: 'is_page_of_ssim',
|
18
|
+
# supported_params: %w[q page],
|
19
|
+
# autocomplete_handler: 'iiif_suggest',
|
20
|
+
# suggester_name: 'iiifSuggester'
|
21
|
+
# }
|
22
|
+
#
|
23
|
+
# @return [Hash] A hash containing the modified Solr search parameters
|
24
|
+
#
|
25
|
+
def solr_params
|
26
|
+
return { q: 'nil:nil' } unless q
|
27
|
+
|
28
|
+
{
|
29
|
+
q: "#{q} AND (#{iiif_config[:object_relation_field]}:\"#{parent_document.id}\" OR id:\"#{parent_document.id}\")",
|
30
|
+
rows: rows,
|
31
|
+
page: page
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -4,14 +4,37 @@ module IiifPrint
|
|
4
4
|
# @see https://github.com/scientist-softserv/louisville-hyku/commit/67467e5cf9fdb755f54419f17d3c24c87032d0af
|
5
5
|
def annotation_list
|
6
6
|
json_results = super
|
7
|
-
|
7
|
+
|
8
|
+
# Check and process invalid hit
|
9
|
+
if json_results&.[]('resources')
|
10
|
+
remove_invalid_hit(json_results)
|
11
|
+
add_metadata_match(json_results)
|
12
|
+
end
|
13
|
+
|
14
|
+
json_results
|
15
|
+
end
|
16
|
+
|
17
|
+
def remove_invalid_hit(json_results)
|
18
|
+
resources = json_results['resources']
|
19
|
+
invalid_hit = resources.detect { |resource| resource["on"].include?(IiifPrint::BlacklightIiifSearch::AnnotationDecorator::INVALID_MATCH_TEXT) }
|
20
|
+
return unless invalid_hit
|
21
|
+
|
22
|
+
# Delete invalid hit from resources, remove first hit (which is from the invalid hit), decrement total within
|
23
|
+
resources.delete(invalid_hit)
|
24
|
+
json_results['hits'].shift
|
25
|
+
json_results['within']['total'] -= 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def add_metadata_match(json_results)
|
29
|
+
json_results['resources'].each do |result_hit|
|
8
30
|
next if result_hit['resource'].present?
|
31
|
+
|
32
|
+
# Add resource details if not present
|
9
33
|
result_hit['resource'] = {
|
10
34
|
"@type": "cnt:ContentAsText",
|
11
35
|
"chars": "Metadata match, see sidebar for details"
|
12
36
|
}
|
13
37
|
end
|
14
|
-
json_results
|
15
38
|
end
|
16
39
|
end
|
17
40
|
end
|
@@ -3,5 +3,8 @@ module IiifPrint
|
|
3
3
|
validates :parent_id, presence: true
|
4
4
|
validates :child_title, presence: true
|
5
5
|
validates :child_order, presence: true
|
6
|
+
validates :parent_model, presence: true
|
7
|
+
validates :child_model, presence: true
|
8
|
+
validates :file_id, presence: true
|
6
9
|
end
|
7
10
|
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IiifPrint
|
4
|
+
module FileSetPresenterDecorator
|
5
|
+
# uses Hyku's TenantConfig to determine whether to allow PDF splitting button
|
6
|
+
def show_split_button?
|
7
|
+
return parent.try(:split_pdfs?) if parent.respond_to?(:split_pdfs?)
|
8
|
+
true
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -3,8 +3,128 @@ module IiifPrint
|
|
3
3
|
module IiifManifestPresenterBehavior
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
+
# Extending the presenter to the base url which includes the protocol.
|
7
|
+
# We need the base url to render the facet links and normalize the interface.
|
8
|
+
attr_accessor :base_url
|
9
|
+
|
10
|
+
def manifest_metadata
|
11
|
+
# ensure we are using a SolrDocument
|
12
|
+
@manifest_metadata ||= IiifPrint.manifest_metadata_from(work: model.solr_document, presenter: self)
|
13
|
+
end
|
14
|
+
|
6
15
|
def search_service
|
7
16
|
Rails.application.routes.url_helpers.solr_document_iiif_search_url(id, host: hostname)
|
8
17
|
end
|
18
|
+
|
19
|
+
# OVERRIDE: Hyrax 3x, avoid nil returning to IIIF Manifest gem
|
20
|
+
# @see https://github.com/samvera/iiif_manifest/blob/c408f90eba11bef908796c7236ba6bcf8d687acc/lib/iiif_manifest/v3/manifest_builder/record_property_builder.rb#L28
|
21
|
+
##
|
22
|
+
# @return [Array<Hash{String => String}>]
|
23
|
+
def sequence_rendering
|
24
|
+
Array(try(:rendering_ids)).map do |file_set_id|
|
25
|
+
rendering = file_set_presenters.find { |p| p.id == file_set_id }
|
26
|
+
return [] unless rendering
|
27
|
+
|
28
|
+
{ '@id' => Hyrax::Engine.routes.url_helpers.download_url(rendering.id, host: hostname),
|
29
|
+
'format' => rendering.mime_type.presence || I18n.t("hyrax.manifest.unknown_mime_text"),
|
30
|
+
'label' => I18n.t("hyrax.manifest.download_text") + (rendering.label || '') }
|
31
|
+
end.flatten
|
32
|
+
end
|
33
|
+
|
34
|
+
# OVERRIDE: Hyrax v3.x
|
35
|
+
module DisplayImagePresenterBehavior
|
36
|
+
# Extending the presenter to the base url which includes the protocol.
|
37
|
+
# We need the base url to render the facet links and normalize the interface.
|
38
|
+
attr_accessor :base_url
|
39
|
+
|
40
|
+
# Extending this class because there is an #ability= but not #ability and this definition
|
41
|
+
# mirrors the Hyrax::IiifManifestPresenter#ability.
|
42
|
+
def ability
|
43
|
+
@ability ||= NullAbility.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def display_image
|
47
|
+
return nil unless latest_file_id
|
48
|
+
return nil unless model.image?
|
49
|
+
return nil unless IiifPrint.config.default_iiif_manifest_version == 2
|
50
|
+
|
51
|
+
IIIFManifest::DisplayImage
|
52
|
+
.new(display_image_url(hostname),
|
53
|
+
format: image_format(alpha_channels),
|
54
|
+
width: width,
|
55
|
+
height: height,
|
56
|
+
iiif_endpoint: iiif_endpoint(latest_file_id, base_url: hostname))
|
57
|
+
end
|
58
|
+
|
59
|
+
# OVERRIDE: IIIF Hyrax AV v0.2 #display_content for prez 3 manifests
|
60
|
+
def display_content
|
61
|
+
return nil unless latest_file_id
|
62
|
+
return super unless model.image?
|
63
|
+
|
64
|
+
IIIFManifest::V3::DisplayContent
|
65
|
+
.new(display_image_url(hostname),
|
66
|
+
format: image_format(alpha_channels),
|
67
|
+
width: width,
|
68
|
+
height: height,
|
69
|
+
type: 'Image',
|
70
|
+
iiif_endpoint: iiif_endpoint(latest_file_id, base_url: hostname))
|
71
|
+
end
|
72
|
+
|
73
|
+
def display_image_url(base_url)
|
74
|
+
if ENV['EXTERNAL_IIIF_URL'].present?
|
75
|
+
# At the moment we are only concerned about Hyrax's default image url builder
|
76
|
+
iiif_image_url_builder(url_builder: Hyrax.config.iiif_image_url_builder)
|
77
|
+
else
|
78
|
+
super
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def iiif_endpoint(file_id, base_url: request.base_url)
|
83
|
+
if ENV['EXTERNAL_IIIF_URL'].present?
|
84
|
+
IIIFManifest::IIIFEndpoint.new(
|
85
|
+
File.join(ENV['EXTERNAL_IIIF_URL'], file_id),
|
86
|
+
profile: Hyrax.config.iiif_image_compliance_level_uri
|
87
|
+
)
|
88
|
+
else
|
89
|
+
super
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def hostname
|
94
|
+
@hostname || 'localhost'
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# @return [Boolean] false
|
99
|
+
def work?
|
100
|
+
false
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def latest_file_id
|
106
|
+
if ENV['EXTERNAL_IIIF_URL'].present?
|
107
|
+
external_latest_file_id
|
108
|
+
else
|
109
|
+
super
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def external_latest_file_id
|
114
|
+
@latest_file_id ||= digest_sha1
|
115
|
+
end
|
116
|
+
|
117
|
+
def iiif_image_url_builder(url_builder:)
|
118
|
+
args = [
|
119
|
+
latest_file_id,
|
120
|
+
ENV['EXTERNAL_IIIF_URL'],
|
121
|
+
Hyrax.config.iiif_image_size_default
|
122
|
+
]
|
123
|
+
# In Hyrax 3, Hyrax.config.iiif_image_url_builder takes an additional argument
|
124
|
+
args << image_format(alpha_channels) if url_builder.arity == 4
|
125
|
+
|
126
|
+
url_builder.call(*args).gsub(%r{images/}, '')
|
127
|
+
end
|
128
|
+
end
|
9
129
|
end
|
10
130
|
end
|
@@ -14,7 +14,7 @@ module IiifPrint
|
|
14
14
|
presenter_class.for(solr_doc)
|
15
15
|
elsif Hyrax.config.curation_concerns.include?(solr_doc.hydra_model)
|
16
16
|
# look up file set ids and loop through those
|
17
|
-
file_set_docs = load_file_set_docs(solr_doc.
|
17
|
+
file_set_docs = load_file_set_docs(solr_doc.try(:member_ids) || solr_doc.try(:[], 'member_ids_ssim'))
|
18
18
|
file_set_docs.map { |doc| presenter_class.for(doc) } if file_set_docs.length
|
19
19
|
end
|
20
20
|
end.flatten.compact
|
@@ -2,28 +2,40 @@
|
|
2
2
|
|
3
3
|
module IiifPrint
|
4
4
|
module WorkShowPresenterDecorator
|
5
|
-
delegate :
|
5
|
+
delegate :member_ids, to: :solr_document
|
6
|
+
alias file_set_ids member_ids
|
6
7
|
|
7
|
-
# OVERRIDE Hyrax 2.9.6 to remove check for representative_presenter.image?
|
8
|
-
# a fallback to check for images on the child works
|
8
|
+
# OVERRIDE Hyrax 2.9.6 to remove check for representative_presenter.image?
|
9
9
|
# @return [Boolean] render a IIIF viewer
|
10
10
|
def iiif_viewer?
|
11
|
-
|
11
|
+
Hyrax.config.iiif_image_server? &&
|
12
|
+
representative_id.present? &&
|
13
|
+
representative_presenter.present? &&
|
14
|
+
members_include_viewable_image?
|
12
15
|
end
|
13
16
|
|
14
17
|
alias universal_viewer? iiif_viewer?
|
15
18
|
|
16
19
|
private
|
17
20
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
21
|
+
# overriding Hyrax to include file sets for both work and child works (file set ids include both)
|
22
|
+
# process each id, short-circuiting the loop once one true value is found. This speeds up the test
|
23
|
+
# by not loading more member_presenters than needed.
|
24
|
+
#
|
25
|
+
# @todo Review if this is necessary for Hyrax 5.
|
26
|
+
def members_include_viewable_image?
|
27
|
+
all_member_ids = solr_document.try(:member_ids) || solr_document.try(:[], 'member_ids_ssim')
|
28
|
+
Array.wrap(all_member_ids).each do |id|
|
29
|
+
return true if file_type_and_permissions_valid?(member_presenters_for([id]).first)
|
30
|
+
end
|
31
|
+
false
|
23
32
|
end
|
24
33
|
|
25
|
-
|
26
|
-
|
34
|
+
# This method allows for overriding to add additional file types to mix in with IiifAv
|
35
|
+
# TODO: add configuration setting for file types to loop through so an override is unneeded.
|
36
|
+
def file_type_and_permissions_valid?(presenter)
|
37
|
+
current_ability.can?(:read, presenter.id) &&
|
38
|
+
(presenter.try(:image?) || presenter.try(:solr_document).try(:image?))
|
27
39
|
end
|
28
40
|
end
|
29
41
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IiifPrint
|
4
|
+
module AllinsonFlexFields
|
5
|
+
def include_allinson_flex_fields(solr_parameters)
|
6
|
+
return unless defined?(AllinsonFlex)
|
7
|
+
|
8
|
+
query_fields = solr_parameters[:qf].split(' ') + IiifPrint.allinson_flex_fields
|
9
|
+
.each_with_object([]) do |field, arr|
|
10
|
+
arr << (field.name + '_tesim') if field.is_a?(AllinsonFlex::ProfileProperty)
|
11
|
+
end
|
12
|
+
solr_parameters[:qf] = query_fields.uniq.join(' ')
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -6,9 +6,10 @@ module IiifPrint
|
|
6
6
|
def highlight_search_params(solr_parameters = {})
|
7
7
|
return unless solr_parameters[:q] || solr_parameters[:all_fields]
|
8
8
|
solr_parameters[:hl] = true
|
9
|
-
solr_parameters[:'hl.fl'] = '
|
9
|
+
solr_parameters[:'hl.fl'] = '*'
|
10
10
|
solr_parameters[:'hl.fragsize'] = 100
|
11
11
|
solr_parameters[:'hl.snippets'] = 5
|
12
|
+
solr_parameters[:'hl.requiredFieldMatch'] = true
|
12
13
|
end
|
13
14
|
end
|
14
15
|
end
|