iiif_print 1.0.0 → 1.1.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 +98 -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_indexer.rb +9 -3
- data/app/indexers/concerns/iiif_print/file_set_indexer.rb +17 -4
- data/app/models/concerns/iiif_print/set_child_flag.rb +9 -0
- data/app/models/concerns/iiif_print/solr/document.rb +14 -0
- 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/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 +19 -10
- 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 +88 -31
- data/app/services/iiif_print/pluggable_derivative_service.rb +3 -9
- 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/_actions.html.erb +2 -1
- data/app/views/hyrax/file_sets/_show_actions.html.erb +24 -0
- data/config/locales/iiif_print.en.yml +4 -0
- data/config/routes.rb +3 -0
- 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 +10 -9
- 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 +2 -1
- data/lib/iiif_print/blacklight_iiif_search/annotation_decorator.rb +57 -5
- data/lib/iiif_print/catalog_search_builder.rb +5 -1
- data/lib/iiif_print/configuration.rb +145 -8
- data/lib/iiif_print/data/fileset_helper.rb +1 -1
- data/lib/iiif_print/data/work_derivatives.rb +3 -3
- data/lib/iiif_print/engine.rb +7 -13
- 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/jobs/child_works_from_pdf_job.rb +74 -33
- data/lib/iiif_print/jobs/create_relationships_job.rb +80 -31
- data/lib/iiif_print/jobs/request_split_pdf_job.rb +31 -0
- data/lib/iiif_print/lineage_service.rb +29 -8
- data/lib/iiif_print/metadata.rb +67 -48
- data/lib/iiif_print/split_pdfs/base_splitter.rb +142 -0
- data/lib/iiif_print/split_pdfs/child_work_creation_from_pdf_service.rb +68 -32
- data/lib/iiif_print/split_pdfs/derivative_rodeo_splitter.rb +166 -0
- data/lib/iiif_print/split_pdfs/destroy_pdf_child_works_service.rb +33 -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/version.rb +1 -1
- data/lib/iiif_print.rb +167 -12
- data/lib/samvera/derivatives/configuration.rb +83 -0
- data/lib/samvera/derivatives/hyrax.rb +129 -0
- data/lib/samvera/derivatives.rb +238 -0
- data/spec/factories/newspaper_page_solr_document.rb +9 -1
- data/spec/fixtures/authorities/licenses.yml +4 -0
- data/spec/fixtures/authorities/rights_statements.yml +4 -0
- data/spec/iiif_print/base_derivative_service_spec.rb +20 -3
- data/spec/iiif_print/blacklight_iiif_search/annotation_decorator_spec.rb +11 -3
- data/spec/iiif_print/catalog_search_builder_spec.rb +1 -1
- data/spec/iiif_print/configuration_spec.rb +141 -15
- data/spec/iiif_print/jobs/child_works_from_pdf_job_spec.rb +7 -2
- data/spec/iiif_print/jobs/create_relationships_job_spec.rb +110 -9
- data/spec/iiif_print/lineage_service_spec.rb +1 -1
- data/spec/iiif_print/metadata_spec.rb +157 -23
- data/spec/iiif_print/split_pdfs/base_splitter_spec.rb +27 -0
- data/spec/iiif_print/split_pdfs/derivative_rodeo_splitter_spec.rb +80 -0
- data/spec/iiif_print/split_pdfs/destroy_pdf_child_works_service_spec.rb +92 -0
- data/spec/iiif_print/split_pdfs/pages_to_jpgs_splitter_spec.rb +22 -0
- data/spec/iiif_print/split_pdfs/pages_to_pngs_splitter_spec.rb +18 -0
- data/spec/iiif_print/split_pdfs/pages_to_tiffs_splitter_spec.rb +19 -0
- data/spec/iiif_print/text_extraction/hocr_reader_spec.rb +2 -2
- data/spec/iiif_print_spec.rb +125 -5
- data/spec/models/iiif_print/iiif_search_decorator_spec.rb +27 -0
- data/spec/presenters/iiif_print/iiif_manifest_presenter_behavior_spec.rb +51 -0
- data/spec/samvera/derivatives/configuration_spec.rb +41 -0
- data/spec/samvera/derivatives/hyrax_spec.rb +62 -0
- data/spec/samvera/derivatives_spec.rb +54 -0
- data/spec/services/iiif_print/derivative_rodeo_service_spec.rb +103 -0
- data/spec/services/iiif_print/manifest_builder_service_behavior_spec.rb +20 -0
- data/spec/services/iiif_print/pluggable_derivative_service_spec.rb +8 -11
- data/spec/test_app_templates/lib/generators/test_app_generator.rb +1 -1
- data/tasks/copy_authorities_to_test_app.rake +11 -0
- data/tasks/iiif_print_dev.rake +4 -4
- metadata +123 -35
- data/app/helpers/hyrax/iiif_helper.rb +0 -22
- data/lib/iiif_print/split_pdfs/pages_into_images_service.rb +0 -130
- data/spec/iiif_print/split_pdfs/pages_into_images_service_spec.rb +0 -6
data/lib/iiif_print.rb
CHANGED
@@ -17,10 +17,13 @@ require "iiif_print/works_controller_behavior"
|
|
17
17
|
require "iiif_print/jobs/application_job"
|
18
18
|
require "iiif_print/blacklight_iiif_search/annotation_decorator"
|
19
19
|
require "iiif_print/jobs/child_works_from_pdf_job"
|
20
|
-
require "iiif_print/jobs/
|
21
|
-
require "iiif_print/split_pdfs/
|
20
|
+
require "iiif_print/jobs/request_split_pdf_job"
|
21
|
+
require "iiif_print/split_pdfs/base_splitter"
|
22
22
|
require "iiif_print/split_pdfs/child_work_creation_from_pdf_service"
|
23
|
+
require "iiif_print/split_pdfs/derivative_rodeo_splitter"
|
24
|
+
require "iiif_print/split_pdfs/destroy_pdf_child_works_service"
|
23
25
|
|
26
|
+
# rubocop:disable Metrics/ModuleLength
|
24
27
|
module IiifPrint
|
25
28
|
extend ActiveSupport::Autoload
|
26
29
|
autoload :Configuration
|
@@ -28,9 +31,10 @@ module IiifPrint
|
|
28
31
|
|
29
32
|
##
|
30
33
|
# @api public
|
34
|
+
#
|
31
35
|
# Exposes the IiifPrint configuration.
|
32
36
|
#
|
33
|
-
# @
|
37
|
+
# @yieldparam [IiifPrint::Configuration] config if a block is passed
|
34
38
|
# @return [IiifPrint::Configuration]
|
35
39
|
# @see IiifPrint::Configuration for configuration options
|
36
40
|
def self.config(&block)
|
@@ -39,28 +43,65 @@ module IiifPrint
|
|
39
43
|
@config
|
40
44
|
end
|
41
45
|
|
46
|
+
class << self
|
47
|
+
delegate :skip_splitting_pdf_files_that_end_with_these_texts, to: :config
|
48
|
+
end
|
49
|
+
|
50
|
+
##
|
51
|
+
# Return the immediate parent of the given :file_set.
|
52
|
+
#
|
53
|
+
# @param file_set [FileSet]
|
54
|
+
# @return [#work?, Hydra::PCDM::Work]
|
55
|
+
# @return [NilClass] when no parent is found.
|
56
|
+
def self.parent_for(file_set)
|
57
|
+
# fallback to Fedora-stored relationships if work's aggregation of
|
58
|
+
# file set is not indexed in Solr
|
59
|
+
file_set.parent || file_set.member_of.find(&:work?)
|
60
|
+
end
|
61
|
+
|
62
|
+
##
|
63
|
+
# Return the parent's parent of the given :file_set.
|
64
|
+
#
|
65
|
+
# @param file_set [FileSet]
|
66
|
+
# @return [#work?, Hydra::PCDM::Work]
|
67
|
+
# @return [NilClass] when no grand parent is found.
|
68
|
+
def self.grandparent_for(file_set)
|
69
|
+
parent_of_file_set = parent_for(file_set)
|
70
|
+
# HACK: This is an assumption about the file_set structure, namely that an image page split from
|
71
|
+
# a PDF is part of a file set that is a child of a work that is a child of a single work. That
|
72
|
+
# is, it only has one grand parent. Which is a reasonable assumption for IIIF Print but is not
|
73
|
+
# valid when extended beyond IIIF Print. That is GenericWork does not have a parent method but
|
74
|
+
# does have a parents method.
|
75
|
+
parent_of_file_set.try(:parent_works).try(:first) ||
|
76
|
+
parent_of_file_set.try(:parents).try(:first) ||
|
77
|
+
parent_of_file_set&.member_of&.find(&:work?)
|
78
|
+
end
|
79
|
+
|
42
80
|
DEFAULT_MODEL_CONFIGURATION = {
|
43
81
|
# Split a PDF into individual page images and create a new child work for each image.
|
44
82
|
pdf_splitter_job: IiifPrint::Jobs::ChildWorksFromPdfJob,
|
45
|
-
pdf_splitter_service: IiifPrint::SplitPdfs::
|
83
|
+
pdf_splitter_service: IiifPrint::SplitPdfs::PagesToJpgsSplitter,
|
46
84
|
derivative_service_plugins: [
|
47
|
-
IiifPrint::
|
48
|
-
IiifPrint::PDFDerivativeService,
|
49
|
-
IiifPrint::TextExtractionDerivativeService,
|
50
|
-
IiifPrint::TIFFDerivativeService
|
85
|
+
IiifPrint::TextExtractionDerivativeService
|
51
86
|
]
|
52
87
|
}.freeze
|
53
88
|
|
54
89
|
# This is the record level configuration for PDF split handling.
|
55
90
|
ModelConfig = Struct.new(:pdf_split_child_model, *DEFAULT_MODEL_CONFIGURATION.keys, keyword_init: true)
|
91
|
+
private_constant :ModelConfig
|
56
92
|
|
57
|
-
|
93
|
+
##
|
94
|
+
# @api public
|
95
|
+
# This method is responsible for configuring a model for additional derivative generation.
|
58
96
|
#
|
59
97
|
# @example
|
60
98
|
# class Book < ActiveFedora::Base
|
61
99
|
# include IiifPrint.model_configuration(
|
62
100
|
# pdf_split_child_model: Page,
|
63
101
|
# derivative_service_plugins: [
|
102
|
+
# IiifPrint::JP2DerivativeService,
|
103
|
+
# IiifPrint::PDFDerivativeService,
|
104
|
+
# IiifPrint::TextExtractionDerivativeService,
|
64
105
|
# IiifPrint::TIFFDerivativeService
|
65
106
|
# ]
|
66
107
|
# )
|
@@ -68,6 +109,18 @@ module IiifPrint
|
|
68
109
|
#
|
69
110
|
# @param kwargs [Hash<Symbol,Object>] the configuration values that overrides the
|
70
111
|
# DEFAULT_MODEL_CONFIGURATION.
|
112
|
+
# @option kwargs [Array<Class>] derivative_service_plugins the various derivatives to run on the
|
113
|
+
# "original" files associated with this work. Options include:
|
114
|
+
# {IiifPrint::JP2DerivativeService}, {IiifPrint::PDFDerivativeService},
|
115
|
+
# {IiifPrint::TextExtractionDerivativeService}, {IiifPrint::TIFFDerivativeService}
|
116
|
+
# @option kwargs [Class] pdf_splitter_job responsible for handling the splitting of the original file
|
117
|
+
# @option kwargs [Class] pdf_split_child_model when we split the file into pages, what's the child model
|
118
|
+
# we want for those pages? Often times this is likely the same model as the parent.
|
119
|
+
# @option kwargs [Class] pdf_splitter_service the specific service that splits the PDF. Options are:
|
120
|
+
# {IiifPrint::SplitPdfs::PagesToJpgsSplitter},
|
121
|
+
# {IiifPrint::SplitPdfs::PagesToTiffsSplitter},
|
122
|
+
# {IiifPrint::SplitPdfs::PagesToPngsSplitter},
|
123
|
+
# {IiifPrint::SplitPdfs::DerivativeRodeoSplitter}
|
71
124
|
#
|
72
125
|
# @return [Module]
|
73
126
|
#
|
@@ -107,7 +160,7 @@ module IiifPrint
|
|
107
160
|
# @see Hyrax::IiifManifestPresenter#manifest_metadata
|
108
161
|
def self.manifest_metadata_for(work:,
|
109
162
|
version: config.default_iiif_manifest_version,
|
110
|
-
fields:
|
163
|
+
fields: defined?(AllinsonFlex) ? fields_for_allinson_flex : default_fields,
|
111
164
|
current_ability:,
|
112
165
|
base_url:)
|
113
166
|
Metadata.build_metadata_for(work: work,
|
@@ -117,6 +170,11 @@ module IiifPrint
|
|
117
170
|
base_url: base_url)
|
118
171
|
end
|
119
172
|
|
173
|
+
def self.manifest_metadata_from(work:, presenter:)
|
174
|
+
current_ability = presenter.try(:ability) || presenter.try(:current_ability)
|
175
|
+
base_url = presenter.try(:base_url) || presenter.try(:request)&.base_url
|
176
|
+
IiifPrint.manifest_metadata_for(work: work, current_ability: current_ability, base_url: base_url)
|
177
|
+
end
|
120
178
|
# Hash is an arbitrary attribute key/value pairs
|
121
179
|
# Struct is a defined set of attribute "keys". When we favor defined values,
|
122
180
|
# then we are naming the concept and defining the range of potential values.
|
@@ -124,13 +182,110 @@ module IiifPrint
|
|
124
182
|
|
125
183
|
# @api private
|
126
184
|
# @todo Figure out a way to use a custom label, right now it takes it get rendered from the title.
|
127
|
-
def self.
|
185
|
+
def self.default_fields(fields: config.metadata_fields)
|
128
186
|
fields.map do |field|
|
129
187
|
Field.new(
|
130
188
|
name: field.first,
|
131
|
-
label: Hyrax::Renderers::AttributeRenderer.new(field, nil).label,
|
189
|
+
label: Hyrax::Renderers::AttributeRenderer.new(field.first, nil).label,
|
132
190
|
options: field.last
|
133
191
|
)
|
134
192
|
end
|
135
193
|
end
|
194
|
+
|
195
|
+
##
|
196
|
+
# @param fields [Array<IiifPrint::Field>]
|
197
|
+
def self.fields_for_allinson_flex(fields: allinson_flex_fields, sort_order: IiifPrint.config.iiif_metadata_field_presentation_order)
|
198
|
+
fields = sort_af_fields!(fields, sort_order: sort_order)
|
199
|
+
fields.each_with_object({}) do |field, hash|
|
200
|
+
# filters out admin_only fields
|
201
|
+
next if field.indexing&.include?('admin_only')
|
202
|
+
|
203
|
+
# WARNING: This is assuming A LOT
|
204
|
+
# This is taking the Allinson Flex fields that have the same name and only
|
205
|
+
# using the first one while discarding the rest. There currently no way to
|
206
|
+
# controller which one(s) are discarded but this fits for the moment.
|
207
|
+
next if hash.key?(field.name)
|
208
|
+
|
209
|
+
# currently only supports the faceted option
|
210
|
+
# Why the `render_as:`? This was originally derived from Hyku default attributes
|
211
|
+
# @see https://github.com/samvera/hyku/blob/c702844de4c003eaa88eb5a7514c7a1eae1b289e/app/views/hyrax/base/_attribute_rows.html.erb#L3
|
212
|
+
hash[field.name] = Field.new(
|
213
|
+
name: field.name,
|
214
|
+
label: field.value,
|
215
|
+
options: field.indexing&.include?('facetable') ? { render_as: :faceted } : nil
|
216
|
+
)
|
217
|
+
end.values
|
218
|
+
end
|
219
|
+
|
220
|
+
CollectionFieldShim = Struct.new(:name, :value, :indexing, keyword_init: true)
|
221
|
+
|
222
|
+
##
|
223
|
+
# @return [Array<IiifPrint::Field>]
|
224
|
+
def self.allinson_flex_fields
|
225
|
+
return @allinson_flex_fields if defined?(@allinson_flex_fields)
|
226
|
+
|
227
|
+
allinson_flex_relation = AllinsonFlex::ProfileProperty
|
228
|
+
.joins(:texts)
|
229
|
+
.where(allinson_flex_profile_texts: { name: 'display_label' })
|
230
|
+
.distinct
|
231
|
+
.select(:name, :value, :indexing)
|
232
|
+
flex_fields = allinson_flex_relation.to_a
|
233
|
+
unless allinson_flex_relation.exists?(name: 'collection')
|
234
|
+
collection_field = CollectionFieldShim.new(name: :collection, value: 'Collection', indexing: [])
|
235
|
+
flex_fields << collection_field
|
236
|
+
end
|
237
|
+
@allinson_flex_fields = flex_fields
|
238
|
+
end
|
239
|
+
|
240
|
+
##
|
241
|
+
# @param fields [Array<IiifPrint::Field>]
|
242
|
+
# @param sort_order [Array<Symbol>]
|
243
|
+
def self.sort_af_fields!(fields, sort_order:)
|
244
|
+
return fields if sort_order.blank?
|
245
|
+
|
246
|
+
fields.sort_by do |field|
|
247
|
+
sort_order_index = sort_order.index(field.name.to_sym)
|
248
|
+
sort_order_index.nil? ? sort_order.length : sort_order_index
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
##
|
253
|
+
# @api public
|
254
|
+
#
|
255
|
+
# @param work [ActiveFedora::Base]
|
256
|
+
# @param file_set [FileSet]
|
257
|
+
# @param locations [Array<String>]
|
258
|
+
# @param user [User]
|
259
|
+
#
|
260
|
+
# @return [Symbol] when none of the locations are to be split.
|
261
|
+
def self.conditionally_submit_split_for(work:, file_set:, locations:, user:, skip_these_endings: skip_splitting_pdf_files_that_end_with_these_texts)
|
262
|
+
locations = locations.select { |location| split_for_path_suffix?(location, skip_these_endings: skip_these_endings) }
|
263
|
+
return :no_pdfs_for_splitting if locations.empty?
|
264
|
+
|
265
|
+
work.try(:iiif_print_config)&.pdf_splitter_job&.perform_later(
|
266
|
+
file_set,
|
267
|
+
locations,
|
268
|
+
user,
|
269
|
+
work.admin_set_id,
|
270
|
+
0 # A no longer used parameter; but we need to preserve the method signature (for now)
|
271
|
+
)
|
272
|
+
end
|
273
|
+
|
274
|
+
##
|
275
|
+
# @api public
|
276
|
+
#
|
277
|
+
# @param path [String] the path, hopefully with an extension, to the file we're considering
|
278
|
+
# splitting.
|
279
|
+
# @param skip_these_endings [Array<#downcase>] the endings that we should skip for splitting
|
280
|
+
# purposes.
|
281
|
+
# @return [TrueClass] when the path is one we should split
|
282
|
+
# @return [FalseClass] when the path is one we should not split
|
283
|
+
#
|
284
|
+
# @see .skip_splitting_pdf_files_that_end_with_these_texts
|
285
|
+
def self.split_for_path_suffix?(path, skip_these_endings: skip_splitting_pdf_files_that_end_with_these_texts)
|
286
|
+
return false unless path.downcase.end_with?('.pdf')
|
287
|
+
return true if skip_these_endings.empty?
|
288
|
+
!path.downcase.end_with?(*skip_these_endings.map(&:downcase))
|
289
|
+
end
|
136
290
|
end
|
291
|
+
# rubocop:enable Metrics/ModuleLength
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Samvera
|
4
|
+
module Derivatives
|
5
|
+
##
|
6
|
+
# The purpose of this class is to contain the explicit derivative generation directives for the
|
7
|
+
# upstream application.
|
8
|
+
#
|
9
|
+
# @note The implicit deriviate types for Hyrax are as follows:
|
10
|
+
# - type :extracted_text with sources [:pdf, :office_document]
|
11
|
+
# - type :thumbnail with sources [:pdf, :office_document, :thumbnail, :image]
|
12
|
+
# - type :mp3 with sources [:audio]
|
13
|
+
# - type :ogg with sources [:audio]
|
14
|
+
# - type :webm with sources [:video]
|
15
|
+
# - type :mp4 with sources [:video]
|
16
|
+
#
|
17
|
+
# @note A long-standing practice of Samvera's Hyrax has been to have assumptive and implicit
|
18
|
+
# derivative generation (see Hyrax::FileSetDerivativesService). In being implicit, a
|
19
|
+
# challenge arises, namely overriding and configuring. There exists a crease in the code
|
20
|
+
# to allow for a different derivative approach (see Hyrax::DerivativeService). Yet that
|
21
|
+
# approach continues the tradition of implicit work.
|
22
|
+
class Configuration
|
23
|
+
def initialize
|
24
|
+
# Favoring a Hash for ease of lookup as well as the concept that there can be only one entry
|
25
|
+
# per type.
|
26
|
+
@registered_types = {}
|
27
|
+
end
|
28
|
+
|
29
|
+
# TODO: Consider the appropriate extension
|
30
|
+
RegisteredType = Struct.new(:type, :locators, :applicators, :applicability, keyword_init: true) do
|
31
|
+
def applicable_for?(file_set:)
|
32
|
+
applicability.call(file_set)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# @api pulic
|
38
|
+
#
|
39
|
+
# @param type [Symbol] The named type of derivative
|
40
|
+
# @param locators [Array<Samvera::Derivatives::FileLocator::Strategy>] The strategies that
|
41
|
+
# we'll attempt in finding the derivative that we will later apply.
|
42
|
+
# @param applicators [Array<Samvera::Derivatives::FileApplicator::Strategy>] The strategies
|
43
|
+
# that we'll use to apply the found derivative to the {FileSet}
|
44
|
+
#
|
45
|
+
# @yieldparam applicability [#call]
|
46
|
+
#
|
47
|
+
# @return [RegisteredType]
|
48
|
+
#
|
49
|
+
# @note What is the best mechanism for naming the sources? At present we're doing a lot of
|
50
|
+
# assumption on the types.
|
51
|
+
def register(type:, locators:, applicators:, &applicability)
|
52
|
+
# Should the validator be required?
|
53
|
+
@registered_types[type.to_sym] = RegisteredType.new(
|
54
|
+
type: type.to_sym,
|
55
|
+
locators: Array(locators),
|
56
|
+
applicators: Array(applicators),
|
57
|
+
applicability: applicability || default_applicability
|
58
|
+
)
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# @api public
|
63
|
+
#
|
64
|
+
# @param type [Symbol]
|
65
|
+
#
|
66
|
+
# @return [RegisteredType]
|
67
|
+
def registry_for(type:)
|
68
|
+
@registered_types.fetch(type.to_sym) { empty_registry_for(type: type.to_sym) }
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def empty_registry_for(type:)
|
74
|
+
RegisteredType.new(type: type, locators: [], applicators: [], applicability: ->(_file_set) { false })
|
75
|
+
end
|
76
|
+
|
77
|
+
# We're going to assume this is true unless configured otherwise.
|
78
|
+
def default_applicability
|
79
|
+
->(_file_set) { true }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Samvera
|
4
|
+
module Derivatives
|
5
|
+
# The default behavior of {Hyrax::FileSetDerivativesService} is to create a derivative and then
|
6
|
+
# apply it to the FileSet. This module wraps that behavior such that we can leverage the
|
7
|
+
# {Samvera::Derivatives} module and interfaces to handle cases where some of the derivatives
|
8
|
+
# already exist.
|
9
|
+
module Hyrax
|
10
|
+
# @note This conforms to the {Hyrax::DerivativeService} interface. The intention of this
|
11
|
+
# class is to be the sole registered {Hyrax::DerivativeService.services}
|
12
|
+
class ServiceShim
|
13
|
+
# @param file_set [FileSet]
|
14
|
+
# @param candidate_derivative_types [Array<Symbol>] the possible types of derivatives that
|
15
|
+
# we could create for this file_set.
|
16
|
+
# @param config [#registry_for]
|
17
|
+
#
|
18
|
+
# @todo We will want some kind of lambda to determine the candidate_derivative_types for this
|
19
|
+
# file_set.
|
20
|
+
def initialize(file_set, candidate_derivative_types: [], config: Samvera::Derivatives.config)
|
21
|
+
@file_set = file_set
|
22
|
+
@config = config
|
23
|
+
@derivatives = candidate_derivative_types.map { |type| config.registry_for(type: type) }
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :file_set
|
27
|
+
|
28
|
+
# @return [Array<Samvera::Derivatives::Configuration::RegisteredType>]
|
29
|
+
attr_reader :derivatives
|
30
|
+
attr_reader :config
|
31
|
+
|
32
|
+
def valid?
|
33
|
+
# We have a file set, which also means a parent work. I believe we always want this to be
|
34
|
+
# valid, because we want to leverage the locator/applicator behavior instead of the
|
35
|
+
# implicit work.
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
def cleanup_derivatives; end
|
40
|
+
|
41
|
+
# We have two vectors of consideration for derivative generation:
|
42
|
+
#
|
43
|
+
# - The desired derivatives for a file_set's parent work (e.g. the candidate derivatives)
|
44
|
+
# - The available derivatives for a file_set's mime type
|
45
|
+
def create_derivatives(file_path)
|
46
|
+
derivatives.each do |derivative|
|
47
|
+
Samvera::Derivatives.locate_and_apply_derivative_for(
|
48
|
+
file_set: file_set,
|
49
|
+
file_path: file_path,
|
50
|
+
derivative: derivative
|
51
|
+
)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def derivative_url(_destination_name)
|
56
|
+
""
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class FileApplicatorStrategy < Samvera::Derivatives::FileApplicator::Strategy
|
61
|
+
# With this set to true, we're telling the applicator to use the from_location
|
62
|
+
# (e.g. {Samvera::Derivatives::Hyrax::FileSetDerivativesServiceWrapper}) to apply the
|
63
|
+
# derivatives.
|
64
|
+
self.delegate_apply_to_given_from_location = true
|
65
|
+
end
|
66
|
+
|
67
|
+
class FileLocatorStrategy < Samvera::Derivatives::FileLocator::Strategy
|
68
|
+
# Implements {Samvera::Derivatives::FileLocator::Strategy} interface.
|
69
|
+
#
|
70
|
+
# @see Samvera::Derivatives::FileLocator::Strategy
|
71
|
+
#
|
72
|
+
# @return [Samvera::Derivatives::Hyrax::FileSetDerivativesServiceWrapper]
|
73
|
+
def self.locate(file_set:, file_path:, **)
|
74
|
+
file_set.samvera_derivatives_default_from_location_wrapper(file_path: file_path)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class FileSetDerivativesServiceWrapper
|
79
|
+
class_attribute :wrapped_derivative_service_class, default: ::Hyrax::FileSetDerivativesService
|
80
|
+
|
81
|
+
# @param file_set [FileSet]
|
82
|
+
# @param file_path [String]
|
83
|
+
def initialize(file_set:, file_path:)
|
84
|
+
@file_set = file_set
|
85
|
+
@file_path = file_path
|
86
|
+
@wrapped_derivative_service = wrapped_derivative_service_class.new(file_set)
|
87
|
+
end
|
88
|
+
attr_reader :file_set, :wrapped_derivative_service, :file_path
|
89
|
+
|
90
|
+
# @see Samvera::Derivatives::FileLocator.call
|
91
|
+
def present?
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
# @see Samvera::Derivatives::FileApplicator::Strategy
|
96
|
+
def apply!(*)
|
97
|
+
# Why the short-circuit? By the nature of the underlying
|
98
|
+
# ::Hyrax::FileSetDerivativesService, we generate multiple derivatives in one pass. But
|
99
|
+
# with the implementation of Samvera::Derivatives, we declare the derivatives and then
|
100
|
+
# iterate on locating and applying them. With this short-circuit, we will only apply the
|
101
|
+
# derivatives once.
|
102
|
+
return true if defined?(@already_applied)
|
103
|
+
|
104
|
+
return false unless wrapped_derivative_service.valid?
|
105
|
+
|
106
|
+
wrapped_derivative_service.create_derivatives(file_path)
|
107
|
+
@already_applied = true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# The purpose of this module is to preserve the existing Hyrax derivative behavior while also
|
113
|
+
# allowing for the two-step tango of locator and applicator.
|
114
|
+
#
|
115
|
+
# @see Samvera::Derivatives.locate_and_apply_derivative_for
|
116
|
+
module FileSetDecorator
|
117
|
+
# @return [Samvera::Derivatives::Hyrax::FileSetDerivativesServiceWrapper]
|
118
|
+
def samvera_derivatives_default_from_location_wrapper(file_path:)
|
119
|
+
@samvera_derivatives_default_from_location_wrapper ||=
|
120
|
+
Samvera::Derivatives::Hyrax::FileSetDerivativesServiceWrapper.new(file_set: self, file_path: file_path)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# TODO: We are likely going to want that.
|
128
|
+
# Hyrax::DerivativeService.services = [Samvera::Derivatives::Hyrax::ServiceShim]
|
129
|
+
FileSet.prepend(Samvera::Derivatives::Hyrax::FileSetDecorator)
|