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
@@ -0,0 +1,238 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'samvera/derivatives/configuration'
|
4
|
+
|
5
|
+
##
|
6
|
+
# Why Samvera and not Hyrax? Because there are folks creating non-Hyrax applications that will
|
7
|
+
# almost certainly want to leverage this work. And there might be switches for `defined?(Hyrax)`.
|
8
|
+
#
|
9
|
+
# Further the following four gems have interest in the interfaces of this module:
|
10
|
+
#
|
11
|
+
# - Hyrax
|
12
|
+
# - Hydra::Derivatives
|
13
|
+
# - Bulkrax
|
14
|
+
# - IiifPrint
|
15
|
+
#
|
16
|
+
# As such, it makes some sense to isolate the module and begin defining interfaces.
|
17
|
+
module Samvera
|
18
|
+
##
|
19
|
+
# This module separates the finding/creation of a derivative binary (via {FileLocator}) and
|
20
|
+
# applying that derivative to the FileSet (via {FileApplicator}).
|
21
|
+
#
|
22
|
+
# In working on the interface and objects there is an effort to preserve backwards functionality
|
23
|
+
# while also allowing for a move away from that functionality.
|
24
|
+
#
|
25
|
+
# There are three primary concepts to consider:
|
26
|
+
#
|
27
|
+
# - Locator :: responsible for knowing where the derivative is
|
28
|
+
# - Location :: responsible for encapsulating the location
|
29
|
+
# - Applicator :: responsible for applying the located derivative to the FileSet
|
30
|
+
#
|
31
|
+
# The "trick" in this is in the polymorphism of the Location. Let's say we have the following
|
32
|
+
# desired functionality for the thumbnail derivative:
|
33
|
+
#
|
34
|
+
# ```gherkin
|
35
|
+
# Given a FileSet
|
36
|
+
# When I provide a thumbnail derivative
|
37
|
+
# Then I want to add that as the thumbnail for the FileSet
|
38
|
+
#
|
39
|
+
# Given a FileSet
|
40
|
+
# When I do not provide a thumbnail derivative
|
41
|
+
# Then I want to generate a thumbnail
|
42
|
+
# And add the generated as the thumbnail for the FileSet
|
43
|
+
# ```
|
44
|
+
#
|
45
|
+
# In the above case we would have two Locator strategies:
|
46
|
+
#
|
47
|
+
# - Find Existing One
|
48
|
+
# - Will Generate One (e.g. Hyrax::FileSetDerivativesService with Hydra::Derivative behavior)
|
49
|
+
#
|
50
|
+
# And we would have two Applicator strategies:
|
51
|
+
#
|
52
|
+
# - Apply an Existing One
|
53
|
+
# - Generate One and Apply (e.g. Hyrax::FileSetDerivativesService with Hydra::Derivative behavior)
|
54
|
+
#
|
55
|
+
# The Location from the first successful Locator will dictate how the ApplicatorStrategies do
|
56
|
+
# their work.
|
57
|
+
module Derivatives
|
58
|
+
##
|
59
|
+
# @api public
|
60
|
+
#
|
61
|
+
# Responsible for configuration of derivatives.
|
62
|
+
#
|
63
|
+
# @example
|
64
|
+
# Samvera::Derivative.config do |config|
|
65
|
+
# config.register(type: :thumbnail, applicators: [CustomApplicator], locators: [CustomLocator]) do |file_set|
|
66
|
+
# file_set.video? || file_set.audio? || file_set.image?
|
67
|
+
# end
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# @yield [Configuration]
|
71
|
+
#
|
72
|
+
# @return [Configuration]
|
73
|
+
def self.config
|
74
|
+
@config ||= Configuration.new
|
75
|
+
yield(@config) if block_given?
|
76
|
+
@config
|
77
|
+
end
|
78
|
+
|
79
|
+
##
|
80
|
+
# @api public
|
81
|
+
#
|
82
|
+
# Locate the derivative for the given :file_set and apply it to that :file_set.
|
83
|
+
#
|
84
|
+
# @param file_set [FileSet]
|
85
|
+
# @param derivative [Samvera::Derivatives::Configuration::RegisteredType]
|
86
|
+
# @param file_path [String]
|
87
|
+
#
|
88
|
+
# @note As a concession to existing implementations of creating derivatives, file_path is
|
89
|
+
# included as a parameter.
|
90
|
+
def self.locate_and_apply_derivative_for(file_set:, derivative:, file_path:)
|
91
|
+
return false unless derivative.applicable_for?(file_set: file_set)
|
92
|
+
|
93
|
+
from_location = FileLocator.call(
|
94
|
+
file_set: file_set,
|
95
|
+
file_path: file_path,
|
96
|
+
derivative: derivative
|
97
|
+
)
|
98
|
+
|
99
|
+
FileApplicator.call(
|
100
|
+
from_location: from_location,
|
101
|
+
file_set: file_set,
|
102
|
+
derivative: derivative
|
103
|
+
)
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# The purpose of this module is to find the derivative file path for a FileSet and a given
|
108
|
+
# derivative type (e.g. :thumbnail).
|
109
|
+
#
|
110
|
+
# @see https://github.com/samvera-labs/bulkrax/issues/760 Design Document
|
111
|
+
#
|
112
|
+
# @note Ideally, this module would be part of Hyrax or Hydra::Derivatives
|
113
|
+
# @see https://github.com/samvera/hyrax
|
114
|
+
# @see https://github.com/samvera/hydra-derivatives
|
115
|
+
module FileLocator
|
116
|
+
##
|
117
|
+
# @api public
|
118
|
+
#
|
119
|
+
# This method is responsible for finding the correct file names for the given file set and
|
120
|
+
# derivative type.
|
121
|
+
#
|
122
|
+
# @param file_set [FileSet]
|
123
|
+
# @param file_path [String]
|
124
|
+
# @param derivative [Samvera::Derivatives::Configuration::RegisteredType]
|
125
|
+
#
|
126
|
+
# @return [Samvera::Derivatives::FromLocation]
|
127
|
+
#
|
128
|
+
# @note Why {.call}? This allows for a simple lambda interface, which can greatly ease testing
|
129
|
+
# and composition.
|
130
|
+
def self.call(file_set:, file_path:, derivative:)
|
131
|
+
from_location = nil
|
132
|
+
|
133
|
+
derivative.locators.each do |locator|
|
134
|
+
from_location = locator.locate(
|
135
|
+
file_set: file_set,
|
136
|
+
file_path: file_path,
|
137
|
+
derivative_type: derivative.type
|
138
|
+
)
|
139
|
+
break if from_location.present?
|
140
|
+
end
|
141
|
+
|
142
|
+
from_location
|
143
|
+
end
|
144
|
+
|
145
|
+
##
|
146
|
+
# @abstract
|
147
|
+
#
|
148
|
+
# The purpose of this abstract class is to provide the public interface for strategies.
|
149
|
+
#
|
150
|
+
# @see {.find}
|
151
|
+
class Strategy
|
152
|
+
##
|
153
|
+
# @api public
|
154
|
+
# @param file_set [FileSet]
|
155
|
+
# @param file_path [String]
|
156
|
+
# @param derivative_type [#to_sym]
|
157
|
+
#
|
158
|
+
# @return [Samvera::Derivatives::FromLocation] when this is a valid strategy
|
159
|
+
# @return [NilClass] when this is not a valid strategy
|
160
|
+
def self.locate(file_set:, file_path:, derivative_type:)
|
161
|
+
raise NotImplementedError
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
module FileApplicator
|
167
|
+
##
|
168
|
+
# @api public
|
169
|
+
#
|
170
|
+
# @param file_set [FileSet]
|
171
|
+
# @param from_location [#present?]
|
172
|
+
# @param derivative [Array<#apply!>]
|
173
|
+
def self.call(file_set:, from_location:, derivative:)
|
174
|
+
# rubocop:disable Rails/Blank
|
175
|
+
return false unless from_location.present?
|
176
|
+
# rubocop:enable Rails/Blank
|
177
|
+
|
178
|
+
derivative.applicators.each do |applicator|
|
179
|
+
applicator.apply!(file_set: file_set, derivative_type: derivative.type, from_location: from_location)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
##
|
184
|
+
# @abstract
|
185
|
+
#
|
186
|
+
# The purpose of this abstract class is to provide the public interface for strategies.
|
187
|
+
#
|
188
|
+
# @see {.find}
|
189
|
+
class Strategy
|
190
|
+
# In some cases the FromLocation knows how to write itself; this is the case when we wrap
|
191
|
+
# the Hyrax::FileSetDerivativesService.
|
192
|
+
class_attribute :delegate_apply_to_given_from_location, default: false
|
193
|
+
|
194
|
+
##
|
195
|
+
# @param file_set [FileSet]
|
196
|
+
# @param derivative_type [#to_sym]
|
197
|
+
# @param from_location [Object]
|
198
|
+
def self.apply!(file_set:, derivative_type:, from_location:)
|
199
|
+
new(file_set: file_set, derivative_type: derivative_type, from_location: from_location).apply!
|
200
|
+
end
|
201
|
+
|
202
|
+
def initialize(file_set:, derivative_type:, from_location:)
|
203
|
+
@file_set = file_set
|
204
|
+
@derivative_type = derivative_type
|
205
|
+
@from_location = from_location
|
206
|
+
end
|
207
|
+
attr_reader :file_set, :derivative_type, :from_location
|
208
|
+
|
209
|
+
# @note What's going on with this logic? To continue to leverage
|
210
|
+
# Hyrax::FileSetDerivativesService, we want to let that wrapped service (as a
|
211
|
+
# FromLocation) to do it's original work. However, we might have multiple strategies
|
212
|
+
# in play for application. That case is when we want to first check for an existing
|
213
|
+
# thumbnail and failing that generate the thumbnail. The from_location could either
|
214
|
+
# be the found thumbnail...or it could be the wrapped Hyrax::FileSetDerivativesService
|
215
|
+
# that will create the thumbnail and write it to the location. The two applicator
|
216
|
+
# strategies in that case would be the wrapper and logic that will write the found
|
217
|
+
# file to the correct derivative path.
|
218
|
+
def apply!
|
219
|
+
if delegate_apply_to_given_from_location?
|
220
|
+
return false unless from_location.respond_to?(:apply!)
|
221
|
+
|
222
|
+
from_location.apply!(file_set: file_set, derivative_type: derivative_type)
|
223
|
+
else
|
224
|
+
return false if from_location.respond_to?(:apply!)
|
225
|
+
|
226
|
+
perform_apply!
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
|
232
|
+
def perform_apply!
|
233
|
+
raise NotImplementedError
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
@@ -1,11 +1,19 @@
|
|
1
1
|
FactoryBot.define do
|
2
|
+
factory :file_set_solr_document, class: SolrDocument do
|
3
|
+
initialize_with do
|
4
|
+
new(id: 'fs123456',
|
5
|
+
has_model_ssim: ['FileSet'])
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
2
9
|
factory :newspaper_page_solr_document, class: SolrDocument do
|
3
10
|
initialize_with do
|
11
|
+
file_set = build(:file_set_solr_document)
|
4
12
|
new(id: '123456',
|
5
13
|
title_tesim: ['Page 1'],
|
6
14
|
has_model_ssim: ['NewspaperPage'],
|
7
15
|
issue_id_ssi: 'abc123',
|
8
|
-
file_set_ids_ssim: [
|
16
|
+
file_set_ids_ssim: [file_set.id],
|
9
17
|
thumbnail_path_ss: '/downloads/123456?file=thumbnail')
|
10
18
|
end
|
11
19
|
end
|
@@ -1,11 +1,28 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe IiifPrint::BaseDerivativeService do
|
4
|
+
let(:file_set) { double(FileSet) }
|
5
|
+
let(:service) { described_class.new(file_set) }
|
6
|
+
|
4
7
|
describe '#valid?' do
|
5
|
-
let(:file_set) { double(FileSet) }
|
6
|
-
let(:service) { described_class.new(file_set) }
|
7
8
|
subject { service.valid? }
|
8
9
|
|
9
|
-
|
10
|
+
context 'when given an image file' do
|
11
|
+
let(:file_set) { double(FileSet, mime_type: 'image/tiff', class: FileSet) }
|
12
|
+
|
13
|
+
it { is_expected.to be_truthy }
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when given a non-image file' do
|
17
|
+
let(:file_set) { double(FileSet, mime_type: 'audio/mpeg', class: FileSet) }
|
18
|
+
|
19
|
+
it { is_expected.to be_falsey }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "instance" do
|
24
|
+
subject { service }
|
25
|
+
|
26
|
+
it { is_expected.to respond_to :target_extension }
|
10
27
|
end
|
11
28
|
end
|
@@ -1,20 +1,27 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe IiifPrint::BlacklightIiifSearch::AnnotationDecorator do
|
4
|
-
let(:
|
4
|
+
let(:parent_id) { 'abc123' }
|
5
|
+
let(:page_document) do
|
6
|
+
doc = build(:newspaper_page_solr_document)
|
7
|
+
doc['is_page_of_ssim'] = [parent_id]
|
8
|
+
doc
|
9
|
+
end
|
5
10
|
let(:controller) { CatalogController.new }
|
6
11
|
let(:coordinates) do
|
7
12
|
JSON.parse("{\"coords\":{\"software\":[[2641,4102,512,44]]}}")
|
8
13
|
end
|
9
14
|
let(:parent_document) do
|
10
|
-
SolrDocument.new('id' =>
|
15
|
+
SolrDocument.new('id' => parent_id,
|
11
16
|
'has_model_ssim' => ['NewspaperIssue'])
|
12
17
|
end
|
18
|
+
let(:query) { "software AND (is_page_of_ssim:#{parent_id} OR id:#{parent_id})" }
|
13
19
|
let(:iiif_search_annotation) do
|
14
|
-
BlacklightIiifSearch::IiifSearchAnnotation.new(page_document,
|
20
|
+
BlacklightIiifSearch::IiifSearchAnnotation.new(page_document, query,
|
15
21
|
0, nil, controller,
|
16
22
|
parent_document)
|
17
23
|
end
|
24
|
+
let(:file_set) { build(:file_set_solr_document) }
|
18
25
|
let(:test_request) { ActionDispatch::TestRequest.new({}) }
|
19
26
|
|
20
27
|
before do
|
@@ -22,6 +29,7 @@ RSpec.describe IiifPrint::BlacklightIiifSearch::AnnotationDecorator do
|
|
22
29
|
allow(controller).to receive(:polymorphic_url)
|
23
30
|
.with(parent_document, host: test_request.base_url, locale: nil)
|
24
31
|
.and_return("/#{page_document[:issue_id_ssi]}")
|
32
|
+
allow(SolrDocument).to receive(:find).with(file_set.id).and_return(file_set)
|
25
33
|
end
|
26
34
|
|
27
35
|
describe '#annotation_id' do
|
@@ -13,7 +13,7 @@ RSpec.describe IiifPrint::CatalogSearchBuilder do
|
|
13
13
|
before { subject.highlight_search_params(solr_parameters) }
|
14
14
|
it 'adds the highlight fields to solr_parameters' do
|
15
15
|
expect(solr_parameters[:hl]).to be_truthy
|
16
|
-
expect(solr_parameters[:'hl.fl']).to eq('
|
16
|
+
expect(solr_parameters[:'hl.fl']).to eq('*')
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -3,6 +3,48 @@ require 'spec_helper'
|
|
3
3
|
RSpec.describe IiifPrint::Configuration do
|
4
4
|
let(:config) { described_class.new }
|
5
5
|
|
6
|
+
describe '#ancestory_identifier_function' do
|
7
|
+
subject(:function) { config.ancestory_identifier_function }
|
8
|
+
it "is expected to be a lambda with an arity of one" do
|
9
|
+
expect(function.arity).to eq(1)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "is configurable" do
|
13
|
+
expect do
|
14
|
+
config.ancestory_identifier_function = ->(w) { w.object_id }
|
15
|
+
end.to change { config.ancestory_identifier_function.object_id }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#unique_child_title_generator_function' do
|
20
|
+
subject(:function) { config.unique_child_title_generator_function }
|
21
|
+
|
22
|
+
it "is expected to be a lambda with keyword args" do
|
23
|
+
expect(function.parameters).to eq([[:keyreq, :original_pdf_path],
|
24
|
+
[:keyreq, :image_path],
|
25
|
+
[:keyreq, :parent_work],
|
26
|
+
[:keyreq, :page_number],
|
27
|
+
[:keyreq, :page_padding]])
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'works as originally designed' do
|
31
|
+
work = double(title: ["My Title"], id: '1234')
|
32
|
+
expect(function.call(
|
33
|
+
original_pdf_path: "/hello/world/nice.pdf",
|
34
|
+
image_path: __FILE__,
|
35
|
+
parent_work: work,
|
36
|
+
page_number: 23,
|
37
|
+
page_padding: 5
|
38
|
+
)).to eq("1234 - nice.pdf Page 00024")
|
39
|
+
end
|
40
|
+
|
41
|
+
it "is configurable" do
|
42
|
+
expect do
|
43
|
+
config.unique_child_title_generator_function = ->(**kwargs) { kwargs }
|
44
|
+
end.to change { config.unique_child_title_generator_function.object_id }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
6
48
|
describe "#metadata_fields" do
|
7
49
|
subject { config.metadata_fields }
|
8
50
|
|
@@ -14,17 +56,6 @@ RSpec.describe IiifPrint::Configuration do
|
|
14
56
|
end
|
15
57
|
end
|
16
58
|
|
17
|
-
describe "#sort_iiif_manifest_canvases_by" do
|
18
|
-
subject { config.sort_iiif_manifest_canvases_by }
|
19
|
-
|
20
|
-
it { is_expected.to be_a Symbol }
|
21
|
-
it "allows for an override" do
|
22
|
-
original = config.sort_iiif_manifest_canvases_by
|
23
|
-
config.sort_iiif_manifest_canvases_by = :title
|
24
|
-
expect(config.metadata_fields).not_to eq original
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
59
|
describe "#handle_after_create_fileset" do
|
29
60
|
let(:file_set) { double(FileSet) }
|
30
61
|
let(:user) { double(User) }
|
@@ -50,18 +81,113 @@ RSpec.describe IiifPrint::Configuration do
|
|
50
81
|
end
|
51
82
|
end
|
52
83
|
|
53
|
-
describe '#
|
84
|
+
describe '#additional_tesseract_options' do
|
54
85
|
context "by default" do
|
55
|
-
subject { config.
|
86
|
+
subject { config.additional_tesseract_options }
|
56
87
|
it { is_expected.not_to be_present }
|
57
88
|
end
|
58
89
|
|
59
90
|
it "can be configured" do
|
60
91
|
expect do
|
61
|
-
config.
|
62
|
-
end.to change(config, :
|
92
|
+
config.additional_tesseract_options = "-l esperanto"
|
93
|
+
end.to change(config, :additional_tesseract_options)
|
63
94
|
.from("")
|
64
95
|
.to("-l esperanto")
|
65
96
|
end
|
66
97
|
end
|
98
|
+
|
99
|
+
describe '#default_iiif_manifest_version' do
|
100
|
+
subject { config.default_iiif_manifest_version }
|
101
|
+
|
102
|
+
context 'default' do
|
103
|
+
it { is_expected.to eq 2 }
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'when set to empty' do
|
107
|
+
before { config.default_iiif_manifest_version = '' }
|
108
|
+
it { is_expected.to eq 2 }
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'can be set' do
|
112
|
+
expect { config.default_iiif_manifest_version = 3 }
|
113
|
+
.to change(config, :default_iiif_manifest_version)
|
114
|
+
.from(2)
|
115
|
+
.to(3)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
describe '#child_work_attributes_function' do
|
120
|
+
subject(:function) { config.child_work_attributes_function }
|
121
|
+
|
122
|
+
it "is expected to be a lambda with keyword args" do
|
123
|
+
expect(function.parameters).to eq([[:keyreq, :parent_work],
|
124
|
+
[:keyreq, :admin_set_id]])
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe "#sort_iiif_manifest_canvases_by" do
|
129
|
+
subject { config.sort_iiif_manifest_canvases_by }
|
130
|
+
|
131
|
+
it { is_expected.to be_a NilClass }
|
132
|
+
it "allows for an override" do
|
133
|
+
original = config.sort_iiif_manifest_canvases_by
|
134
|
+
config.sort_iiif_manifest_canvases_by = :title
|
135
|
+
expect(config.metadata_fields).not_to eq original
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
describe "#ocr_coords_from_json_function" do
|
140
|
+
subject(:function) { config.ocr_coords_from_json_function }
|
141
|
+
|
142
|
+
it "is expected to be a lambda with one keyword arg and optional args" do
|
143
|
+
expect(function.parameters).to eq([[:keyreq, :file_set_id], [:keyrest]])
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
describe "#all_text_generator_function" do
|
148
|
+
subject(:function) { config.all_text_generator_function }
|
149
|
+
|
150
|
+
it "is expected to be a lambda with one keyword arg" do
|
151
|
+
expect(function.parameters).to eq([[:keyreq, :object]])
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "#iiif_metadata_field_presentation_order" do
|
156
|
+
subject { config.iiif_metadata_field_presentation_order }
|
157
|
+
|
158
|
+
it { is_expected.to be_a NilClass }
|
159
|
+
it "allows for an override" do
|
160
|
+
original = config.iiif_metadata_field_presentation_order
|
161
|
+
config.iiif_metadata_field_presentation_order = :title
|
162
|
+
expect(config.iiif_metadata_field_presentation_order).not_to eq original
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe "#questioning_authority_fields" do
|
167
|
+
subject { config.questioning_authority_fields }
|
168
|
+
|
169
|
+
it { is_expected.to be_a Array }
|
170
|
+
context "by default" do
|
171
|
+
it { is_expected.to eq ['rights_statement', 'license'] }
|
172
|
+
end
|
173
|
+
|
174
|
+
it "allows for an override" do
|
175
|
+
expect do
|
176
|
+
config.questioning_authority_fields = ['rights_statement', 'license', 'subject']
|
177
|
+
end.to change(config, :questioning_authority_fields).from(['rights_statement', 'license']).to(['rights_statement', 'license', 'subject'])
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe '#skip_splitting_pdf_files_that_end_with_these_texts' do
|
182
|
+
subject { config.skip_splitting_pdf_files_that_end_with_these_texts }
|
183
|
+
context 'by default' do
|
184
|
+
it { is_expected.to be_empty }
|
185
|
+
end
|
186
|
+
|
187
|
+
context 'is configurable' do
|
188
|
+
before { config.skip_splitting_pdf_files_that_end_with_these_texts = ['.READER.pdf'] }
|
189
|
+
|
190
|
+
it { is_expected.not_to be_empty }
|
191
|
+
end
|
192
|
+
end
|
67
193
|
end
|
@@ -3,7 +3,7 @@ require 'misc_shared'
|
|
3
3
|
|
4
4
|
RSpec.describe IiifPrint::Jobs::ChildWorksFromPdfJob do
|
5
5
|
# TODO: add specs
|
6
|
-
let(:work) { WorkWithIiifPrintConfig.new(title: ['required title']) }
|
6
|
+
let(:work) { WorkWithIiifPrintConfig.new(title: ['required title'], id: '123') }
|
7
7
|
let(:my_user) { build(:user) }
|
8
8
|
let(:uploaded_pdf_file) { create(:uploaded_pdf_file) }
|
9
9
|
let(:uploaded_file_ids) { [uploaded_pdf_file.id] }
|
@@ -15,7 +15,7 @@ RSpec.describe IiifPrint::Jobs::ChildWorksFromPdfJob do
|
|
15
15
|
let(:admin_set_id) { "admin_set/default" }
|
16
16
|
let(:prior_pdfs) { 0 }
|
17
17
|
|
18
|
-
let(:subject) { described_class.
|
18
|
+
let(:subject) { described_class.perform_now(work, pdf_paths, my_user, admin_set_id, prior_pdfs) }
|
19
19
|
|
20
20
|
describe '#perform' do
|
21
21
|
xit 'calls pdf splitter service with path' do
|
@@ -26,5 +26,10 @@ RSpec.describe IiifPrint::Jobs::ChildWorksFromPdfJob do
|
|
26
26
|
|
27
27
|
xit 'submits IiifPrint::Jobs::CreateRelationshipsJob' do
|
28
28
|
end
|
29
|
+
|
30
|
+
context 'with more than 9 pages' do
|
31
|
+
xit 'pads the page number with a zero' do
|
32
|
+
end
|
33
|
+
end
|
29
34
|
end
|
30
35
|
end
|
@@ -1,17 +1,118 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
require 'misc_shared'
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
module IiifPrint::Jobs
|
7
|
+
RSpec.describe CreateRelationshipsJob, type: :job do
|
8
|
+
let(:create_relationships_job) { described_class.new }
|
9
|
+
|
10
|
+
let(:parent_model) { WorkWithIiifPrintConfig.to_s }
|
11
|
+
let(:child_model) { WorkWithIiifPrintConfig.to_s }
|
12
|
+
let(:file) { FileSet.new.tap { |fs| fs.save!(validate: false) } }
|
13
|
+
let(:parent_record) { WorkWithIiifPrintConfig.new(title: ['required title']) }
|
14
|
+
let(:child_record1) { WorkWithIiifPrintConfig.new(title: ["Child of #{parent_record.id} page 01"]) }
|
15
|
+
let(:child_record2) { WorkWithIiifPrintConfig.new(title: ["Child of #{parent_record.id} page 02"]) }
|
16
|
+
let(:pending_rel1) do
|
17
|
+
IiifPrint::PendingRelationship.new(
|
18
|
+
parent_id: parent_record.id,
|
19
|
+
child_title: "Child of #{parent_record.id} page 01",
|
20
|
+
child_order: "Child of #{parent_record.id} page 01",
|
21
|
+
parent_model: parent_model,
|
22
|
+
child_model: child_model,
|
23
|
+
file_id: file.id
|
24
|
+
)
|
25
|
+
end
|
26
|
+
let(:pending_rel2) do
|
27
|
+
IiifPrint::PendingRelationship.new(
|
28
|
+
parent_id: parent_record.id,
|
29
|
+
child_title: "Child of #{parent_record.id} page 02",
|
30
|
+
child_order: "Child of #{parent_record.id} page 02",
|
31
|
+
parent_model: parent_model,
|
32
|
+
child_model: child_model,
|
33
|
+
file_id: file.id
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#perform' do
|
38
|
+
before do
|
39
|
+
allow(create_relationships_job).to receive(:acquire_lock_for).and_yield
|
40
|
+
allow(create_relationships_job).to receive(:reschedule_job)
|
41
|
+
allow(parent_record).to receive(:save!)
|
42
|
+
|
43
|
+
parent_record.save
|
44
|
+
pending_rel1.save
|
45
|
+
pending_rel2.save
|
46
|
+
end
|
47
|
+
|
48
|
+
subject(:perform) do
|
49
|
+
create_relationships_job.perform(
|
50
|
+
parent_id: parent_record.id,
|
51
|
+
parent_model: parent_model,
|
52
|
+
child_model: child_model,
|
53
|
+
retries: 0
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'when adding a child work to a parent work' do
|
58
|
+
before do
|
59
|
+
child_record1.save
|
60
|
+
child_record2.save
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'assigns the child to the parent\'s #ordered_members' do
|
64
|
+
perform
|
65
|
+
expect(parent_record.reload.ordered_member_ids).to eq([child_record1.id, child_record2.id])
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'deletes the pending relationships' do
|
69
|
+
expect { perform }.to change(IiifPrint::PendingRelationship, :count).by(-2)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'does not reschedule the job' do
|
73
|
+
perform
|
74
|
+
expect(create_relationships_job).not_to have_received(:reschedule_job)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
context 'when a relationship fails' do
|
79
|
+
before do
|
80
|
+
child_record1.save
|
81
|
+
child_record2.save
|
82
|
+
end
|
83
|
+
|
84
|
+
before do
|
85
|
+
expect_any_instance_of(CreateRelationshipsJob).to receive(:add_to_work).and_raise('error')
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'does not save the parent' do
|
89
|
+
expect { perform }.to raise_error(RuntimeError)
|
90
|
+
expect(parent_record).not_to have_received(:save!)
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'does not delete the pending relationships' do
|
94
|
+
expect { perform }.to raise_error(RuntimeError)
|
95
|
+
expect(IiifPrint::PendingRelationship.where(parent_id: parent_record.id).count).to eq(2)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
context 'when any child record is not found' do
|
100
|
+
let(:child_record2) { nil }
|
101
|
+
|
102
|
+
before do
|
103
|
+
child_record1.save
|
104
|
+
end
|
10
105
|
|
11
|
-
|
106
|
+
it 'does not save the parent' do
|
107
|
+
perform
|
108
|
+
expect(parent_record).not_to have_received(:save!)
|
109
|
+
end
|
12
110
|
|
13
|
-
|
14
|
-
|
111
|
+
it 'reschedules the job' do
|
112
|
+
perform
|
113
|
+
expect(create_relationships_job).to have_received(:reschedule_job)
|
114
|
+
end
|
115
|
+
end
|
15
116
|
end
|
16
117
|
end
|
17
118
|
end
|