Skip to content

Commit

Permalink
[i335] - port over serverless code from nnp (#198)
Browse files Browse the repository at this point in the history
* [i335] - port over serverless code from nnp

* [i335] - implement serverless iiif, move code into  DisplayImagePresenterBehavior

TODO: Loading issue in dev; when we change code the app no longer hits our iiif_manifest_presenter_behavior.rb and reverts back to using the app instead of cloudfront. We also added the prepend in the after_intitialize because the app couldn't find the digest method otherwise.

* [i335] rubocop fix

* Add test and refactor

This commit will add a test for #display_image.  Also, a refactor was
introduced to account for both Hyrax 2 and 3.  The method signature
of the `Hyrax.config.iiif_image_url_builder` lambda changed to add an
additional argument in Hyrax 3.

ref: https://github.com/samvera/hyrax/blob/2.x-stable/lib/hyrax/configuration.rb#L397-L399
ref: https://github.com/samvera/hyrax/blob/3.x-stable/lib/hyrax/configuration.rb#L250-L252

* Refactor `DisplayImagePresenterBehavior`

This commit will add OVERRIDE comments where useful and add a blurb in
the README for this new feature.  The #display_content method was added
to support presentation 3 manifests.  `SERVERLESS` has been renamed to
`EXTERNAL`.  `DisplayImagePresenterBehavior` has beeen refactored to
incorporate existing code.  Specs have also been refactored.

* add EXTERNAL_IIIF_URL instructions to the README

---------

Co-authored-by: Kirk Wang <[email protected]>
Co-authored-by: Rob Kaufman <[email protected]>
  • Loading branch information
3 people authored Mar 24, 2023
1 parent 31ba877 commit cd0d582
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 14 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ IiifPrint supports:
* splitting of PDFs to LZW compressed TIFFs for viewing
* adding metadata fields to the manifest with faceted search links and external links
* excluding specified work types to be found in the catalog search
* external IIIF image urls that work with services such as serverless-iiif or cantaloup

A complete list of features can be found [here](https://github.com/scientist-softserv/iiif_print/wiki/Features-List).

Expand Down Expand Up @@ -91,6 +92,10 @@ IiifPrint easily integrates with your Hyrax 2.x applications.
## Configuration to enable IiifPrint features
**NOTE: WorkTypes and models are used synonymously here.**

### IIIF URL configuration

If you set EXTERNAL_IIIF_URL in your environment, then IiifPrint will use that URL as the root for your IIIF URLs. It will also switch from using the file set ID to using the SHA1 of the file as the identifier. This enables using serverless_iiif or Cantaloupe (refered to as the service) by pointing the service to the same S3 bucket that FCREPO writes the uploaded files to. By setting it up that way you do not need the service to connect to FCREPO or Hyrax at all, both natively support connecting to an S3 bucket to get their data.

### Model level configurations

In `app/models/{work_type}.rb` add `include IiifPrint.model_configuration` to any work types which require IiifPrint processing features (such as PDF splitting or OCR derivatives). See [lib/iiif_print.rb](./lib/iiif_print.rb) for details on configuration options.
Expand Down
8 changes: 8 additions & 0 deletions app/indexers/concerns/iiif_print/file_set_indexer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,15 @@ def generate_solr_document
text = text.tr("\n", ' ').squeeze(' ')
solr_doc['all_text_timv'] = text
solr_doc['all_text_tsimv'] = text
solr_doc['digest_ssim'] = digest_from_content
end
end

private

def digest_from_content
return unless object.original_file
object.original_file.digest.first.to_s
end
end
end
5 changes: 5 additions & 0 deletions app/models/concerns/iiif_print/solr/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module IiifPrint::Solr::Document
def self.decorate(base)
base.prepend(self)
base.send(:attribute, :is_child, Hyrax::SolrDocument::Metadata::Solr::String, 'is_child_bsi')
base.send(:attribute, :digest, Hyrax::SolrDocument::Metadata::Solr::String, 'digest_ssim')

# @note These properties came from the newspaper_works gem. They are configurable.
base.class_attribute :iiif_print_solr_field_names, default: %w[alternative_title genre
Expand All @@ -31,6 +32,10 @@ def self.decorate(base)
base
end

def digest_sha1
digest[/urn:sha1:([\w]+)/, 1]
end

def method_missing(method_name, *args, &block)
super unless iiif_print_solr_field_names.include? method_name.to_s
self[::ActiveFedora.index_field_mapper.solr_name(method_name.to_s)]
Expand Down
102 changes: 101 additions & 1 deletion app/presenters/iiif_print/iiif_manifest_presenter_behavior.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ module IiifPrint
module IiifManifestPresenterBehavior
extend ActiveSupport::Concern

# Extending the presenter to the base url which includes the protocol.
# We need the base url to render the facet links and normalize the interface.
attr_accessor :base_url

def manifest_metadata
@manifest_metadata ||= IiifPrint.manifest_metadata_from(work: model, presenter: self)
end
Expand All @@ -11,7 +15,7 @@ def search_service
Rails.application.routes.url_helpers.solr_document_iiif_search_url(id, host: hostname)
end

# OVERRIDE Hyrax 3x, avoid nil returning to IIIF Manifest gem
# OVERRIDE: Hyrax 3x, avoid nil returning to IIIF Manifest gem
# @see https://github.com/samvera/iiif_manifest/blob/c408f90eba11bef908796c7236ba6bcf8d687acc/lib/iiif_manifest/v3/manifest_builder/record_property_builder.rb#L28
##
# @return [Array<Hash{String => String}>]
Expand All @@ -25,5 +29,101 @@ def sequence_rendering
'label' => I18n.t("hyrax.manifest.download_text") + (rendering.label || '') }
end.flatten
end

# OVERRIDE: Hyrax v3.x
module DisplayImagePresenterBehavior
# Extending the presenter to the base url which includes the protocol.
# We need the base url to render the facet links and normalize the interface.
attr_accessor :base_url

# Extending this class because there is an #ability= but not #ability and this definition
# mirrors the Hyrax::IiifManifestPresenter#ability.
def ability
@ability ||= NullAbility.new
end

def display_image
return nil unless latest_file_id
return nil unless model.image?
return nil unless IiifPrint.config.default_iiif_manifest_version == 2

IIIFManifest::DisplayImage
.new(display_image_url(hostname),
format: image_format(alpha_channels),
width: width,
height: height,
iiif_endpoint: iiif_endpoint(latest_file_id, base_url: hostname))
end

# OVERRIDE: IIIF Hyrax AV v0.2 #display_content for prez 3 manifests
def display_content
return nil unless latest_file_id
return super unless model.image?

IIIFManifest::V3::DisplayContent
.new(display_image_url(hostname),
format: image_format(alpha_channels),
width: width,
height: height,
type: 'Image',
iiif_endpoint: iiif_endpoint(latest_file_id, base_url: hostname))
end

def display_image_url(base_url)
if ENV['EXTERNAL_IIIF_URL'].present?
# At the moment we are only concerned about Hyrax's default image url builder
iiif_image_url_builder(url_builder: Hyrax.config.iiif_image_url_builder)
else
super
end
end

def iiif_endpoint(file_id, base_url: request.base_url)
if ENV['EXTERNAL_IIIF_URL'].present?
IIIFManifest::IIIFEndpoint.new(
File.join(ENV['EXTERNAL_IIIF_URL'], file_id),
profile: Hyrax.config.iiif_image_compliance_level_uri
)
else
super
end
end

def hostname
@hostname || 'localhost'
end

##
# @return [Boolean] false
def work?
false
end

private

def latest_file_id
if ENV['EXTERNAL_IIIF_URL'].present?
external_latest_file_id
else
super
end
end

def external_latest_file_id
@latest_file_id ||= digest_sha1
end

def iiif_image_url_builder(url_builder:)
args = [
latest_file_id,
ENV['EXTERNAL_IIIF_URL'],
Hyrax.config.iiif_image_size_default
]
# In Hyrax 3, Hyrax.config.iiif_image_url_builder takes an additional argument
args << image_format(alpha_channels) if url_builder.arity == 4

url_builder.call(*args).gsub(%r{images/}, '')
end
end
end
end
15 changes: 2 additions & 13 deletions lib/iiif_print/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,6 @@ class Engine < ::Rails::Engine
::BlacklightIiifSearch::IiifSearchAnnotation.prepend(IiifPrint::BlacklightIiifSearch::AnnotationDecorator)
Hyrax::Actors::FileSetActor.prepend(IiifPrint::Actors::FileSetActorDecorator)

# Extending the presenter to the base url which includes the protocol.
# We need the base url to render the facet links and normalize the interface.
Hyrax::IiifManifestPresenter.send(:attr_accessor, :base_url)
Hyrax::IiifManifestPresenter::DisplayImagePresenter.send(:attr_accessor, :base_url)
# Extending this class because there is an #ability= but not #ability and this definition
# mirrors the Hyrax::IiifManifestPresenter#ability.
module Hyrax::IiifManifestPresenter::DisplayImagePresenterDecorator
def ability
@ability ||= NullAbility.new
end
end
Hyrax::IiifManifestPresenter::DisplayImagePresenter.prepend(Hyrax::IiifManifestPresenter::DisplayImagePresenterDecorator)

Hyrax.config do |config|
config.callback.set(:after_create_fileset) do |file_set, user|
IiifPrint.config.handle_after_create_fileset(file_set, user)
Expand All @@ -71,6 +58,8 @@ def ability

config.after_initialize do
IiifPrint::Solr::Document.decorate(SolrDocument)
Hyrax::IiifManifestPresenter::DisplayImagePresenter
.prepend(IiifPrint::IiifManifestPresenterBehavior::DisplayImagePresenterBehavior)
end
# rubocop:enable Metrics/BlockLength
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,55 @@
expect(presenter.search_service).to include("#{solr_document.id}/iiif_search")
end
end

context 'with IIIF external support' do
let(:presenter) { Hyrax::IiifManifestPresenter::DisplayImagePresenter.new(solr_document) }
let(:id) { 'abc123' }
let(:url) { 'external_iiif_url' }
let(:iiif_info_url_builder) { ->(file_id, base_url) { "#{base_url}/#{file_id}" } }

before { allow(solr_document).to receive(:image?).and_return(true) }

context 'when external iiif is enabled' do
before do
allow(ENV).to receive(:[])
allow(ENV).to receive(:[]).with('EXTERNAL_IIIF_URL').and_return(url)
allow(presenter).to receive(:latest_file_id).and_return(id)
end

describe '#display_image' do
it 'renders a external url' do
expect(presenter.display_image.iiif_endpoint.url).to eq "#{url}/#{id}"
expect(presenter.display_image.iiif_endpoint.profile).to eq "http://iiif.io/api/image/2/level2.json"
end
end

describe '#display_content' do
it 'renders a external url' do
expect(presenter.display_content.iiif_endpoint.url).to eq "#{url}/#{id}"
expect(presenter.display_content.iiif_endpoint.profile).to eq "http://iiif.io/api/image/2/level2.json"
end
end
end

context 'when external iiif is not enabled' do
before do
allow(presenter).to receive(:latest_file_id).and_return(id)
allow(Hyrax.config).to receive(:iiif_image_server?).and_return(true)
allow(Hyrax.config).to receive(:iiif_info_url_builder).and_return(iiif_info_url_builder)
end

describe '#display_image' do
it 'does not render a external url' do
expect(presenter.display_image.iiif_endpoint.url).to eq "localhost/#{id}"
end
end

describe '#display_content' do
it 'does not render a external url' do
expect(presenter.display_content.iiif_endpoint.url).to eq "localhost/#{id}"
end
end
end
end
end

0 comments on commit cd0d582

Please sign in to comment.