Skip to content

Commit

Permalink
Merge pull request #200 from scientist-softserv/conditional-iiif-print
Browse files Browse the repository at this point in the history
🎁   Add conditional PDF split or PDF.js
  • Loading branch information
jeremyf authored Nov 29, 2023
2 parents a07bb7c + a2d1d36 commit 2ad14dd
Show file tree
Hide file tree
Showing 12 changed files with 566 additions and 28 deletions.
22 changes: 16 additions & 6 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,15 @@ GIT

GIT
remote: https://github.com/scientist-softserv/iiif_print.git
revision: 8fdf56e151b5adb7e6aab1cd29d39b74554ed48a
revision: 50b2659f4584ac85f468c7fcec56398f6221a412
branch: main
specs:
iiif_print (1.0.0)
blacklight_iiif_search (~> 1.0)
dry-monads (~> 1.4.0)
hyrax (>= 2.5, < 4.0)
blacklight_iiif_search (>= 1.0, < 3.0)
derivative-rodeo (~> 0.5)
hyrax (>= 2.5, < 6)
nokogiri (>= 1.13.2)
rails (~> 5.0)
rdf-vocab (~> 3.0)
reform-rails (= 0.2.3)

GIT
remote: https://github.com/tawan/active-elastic-job.git
Expand Down Expand Up @@ -313,6 +311,15 @@ GEM
declarative-option (0.1.0)
deprecation (1.1.0)
activesupport
derivative-rodeo (0.5.2)
activesupport (>= 5)
aws-sdk-s3
aws-sdk-sqs
httparty
marcel
mime-types
mini_magick
nokogiri
devise (4.8.1)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
Expand Down Expand Up @@ -469,6 +476,9 @@ GEM
hiredis (0.6.3)
htmlentities (4.3.4)
http_logger (0.7.0)
httparty (0.21.0)
mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
hydra-access-controls (11.0.7)
active-fedora (>= 10.0.0)
Expand Down
5 changes: 5 additions & 0 deletions app/helpers/iiif_print_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

module IiifPrintHelper
include IiifPrint::IiifPrintHelperBehavior
end
5 changes: 5 additions & 0 deletions app/models/concerns/pdf_behavior.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ module PdfBehavior
end

after_initialize :set_default_show_pdf_viewer, :set_default_show_pdf_download_button

include IiifPrint.model_configuration(
pdf_split_child_model: GenericWork,
pdf_splitter_service: IiifPrint::TenantConfig::PdfSplitter
)
end

private
Expand Down
25 changes: 6 additions & 19 deletions app/presenters/hyku/work_show_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ class WorkShowPresenter < Hyrax::WorkShowPresenter
include Hyrax::IiifAv::DisplaysIiifAv
Hyrax::MemberPresenterFactory.file_presenter_class = Hyrax::IiifAv::IiifFileSetPresenter

##
# NOTE: IIIF Print prepends a IiifPrint::WorkShowPresenterDecorator to Hyrax::WorkShowPresenter
# However, with the above `include Hyrax::IiifAv::DisplaysIiifAv` we obliterate that logic. So
# we need to re-introduce that logic.
prepend IiifPrint::TenantConfig::WorkShowPresenterDecorator

delegate :title_or_label, :extent, :show_pdf_viewer, :show_pdf_download_button, to: :solr_document

# OVERRIDE Hyrax v2.9.0 here to make featured collections work
Expand Down Expand Up @@ -58,15 +64,6 @@ def user_can_feature_collection?
end
# End Featured Collections Methods

# @return [Boolean] render a IIIF viewer
def iiif_viewer?
Hyrax.config.iiif_image_server? &&
representative_id.present? &&
representative_presenter.present? &&
iiif_media? &&
members_include_viewable?
end

def video_embed_viewer?
extract_video_embed_presence
end
Expand Down Expand Up @@ -96,16 +93,6 @@ def extract_video_embed_presence
solr_document[:video_embed_tesim]&.all?(&:present?)
end

def iiif_media?(presenter: representative_presenter)
presenter.image? || presenter.video? || presenter.audio?
end

def members_include_viewable?
file_set_presenters.any? do |presenter|
iiif_media?(presenter: presenter) && current_ability.can?(:read, presenter.id)
end
end

def extract_from_identifier(rgx)
if solr_document['identifier_tesim'].present?
ref = solr_document['identifier_tesim'].map do |str|
Expand Down
202 changes: 202 additions & 0 deletions app/services/iiif_print/tenant_config.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
# frozen_string_literal: true

# rubocop:disable Metrics/LineLength
module IiifPrint
##
# This module encapsulates the logic for whether or not we'll use the IIIF Print services for the
# current tenant/account. The IIIF Print services does the following:
#
# - Skipping IIIF Print based derivative generation
# - Skipping PDF Splitting
# - Ignoring showing PDFs in the UV
#
# @note I am specifically isolating as much of this code into one module as possible, so that it
# it is hopefully easier to understand the configuration requirements and scope to this
# change. At some point, this might make sense to bring into IIIF Print directly.
#
# @see https://github.com/scientist-softserv/palni-palci/issues/656 palni-palci#656
# @see https://github.com/scientist-softserv/palni-palci/issues/657 palni-palci#657
# @see https://github.com/scientist-softserv/palni-palci/issues/658 palni-palci#658
# @see https://github.com/scientist-softserv/palni-palci/issues/659 palni-palci#659
module TenantConfig
##
# When we were not planning on calling the underlying IiifPrint service but did due to some kind
# of faulty programming logic.
#
# @note This is raised as a guard to say "Hey, you thought you weren't using IIIF Print but your
# code's logic paths say otherwise."
class LeakyAbstractionError < StandardError
def initialize(klass:, method_name:)
super("Called #{klass}##{method_name} when we had said that #{klass} was not valid because we weren't using IIIF Print")
end
end

##
# If the default PDF viewer (PDF.js) is enabled, this method returns false,
# meaning the application should not use IIIF Print. If the default viewer is
# disabled, this method returns true, meaning the application should use IIIF Print.
def self.use_iiif_print?
!::Flipflop.default_pdf_viewer?
end

##
# This class implements the interface of the Hyrax::DerivativeService. It is responsible for
# negotiating whether or not the DerivativeService is "on" for the current tenant.
#
# @see https://github.com/samvera/hyrax/blob/08ef6c9a4fac489972eea9be53403e173f4ffb29/app/services/hyrax/derivative_service.rb Hyrax::DerivativeService
class DerivativeService
##
# This allows you to specify the IIIF derivative service to use when the tenant has chosen to
# use IIIF Print for processing PDFs.
#
# If you are using the DerivativeRodeo, you'd specify something else.
class_attribute :iiif_service_class, default: ::IiifPrint::PluggableDerivativeService

def initialize(file_set)
@file_set = file_set
end

delegate :use_iiif_print?, to: TenantConfig

def valid?
return false unless use_iiif_print?

iiif_print_service_instance.valid?
end

%i[create_derivatives cleanup_derivatives].each do |method_name|
define_method(method_name) do |*args|
raise LeakyAbstractionError.new(klass: self.class, method_name: method_name) unless use_iiif_print?

iiif_print_service_instance.public_send(method_name, *args)
end
end

##
# @api private
#
# @note Public to ease testing.
def iiif_print_service_instance
@iiif_print_service_instance ||= iiif_service_class.new(@file_set)
end
end

##
# This is the pdf_splitter_service that will be used. If the tenant does not allow PDF splitting
# we will return an empty array.
#
# @example
#
# class MyWork
# include IiifPrint.model_configuration(
# pdf_split_child_model: Attachment,
# pdf_splitter_service: IiifPrint::TenantConfig::PdfSplitter,
# derivative_service_plugins: [ IiifPrint::TextExtractionDerivativeService ])
# end
#
# @see https://github.com/scientist-softserv/iiif_print/blob/9e7837ce4bd08bf8fff9126455d0e0e2602f6018/lib/iiif_print.rb#L86-L138 Documentation for configuring
# @see https://github.com/scientist-softserv/adventist-dl/blob/d7676bdac2c672f09b28086d7145b68306978950/app/models/image.rb#L14-L20 Example implementation
module PdfSplitter
mattr_accessor :iiif_print_splitter
self.iiif_print_splitter = ::IiifPrint::SplitPdfs::PagesToJpgsSplitter

##
# @api public
def self.call(*args)
return [] unless TenantConfig.use_iiif_print?

iiif_print_splitter.call(*args)
end
end

##
# @see https://github.com/scientist-softserv/iiif_print/blob/9e7837ce4bd08bf8fff9126455d0e0e2602f6018/lib/iiif_print/split_pdfs/child_work_creation_from_pdf_service.rb#L10-L46 Interface of FileSetActor#service
module SkipSplittingPdfService
##
# @return [Symbol] Always :tenant_does_not_split_pdfs
def self.conditionally_enqueue(*_args)
:tenant_does_not_split_pdfs
end
end

##
# This decorator should ensure that we don't call model configured :pdf_splitter_service as
# documented in {TenantConfig::PdfSplitter} and the IIIF Print gem. It avoids the potentially
# expensive conditionally enqueue logic of the super class.
#
# Why not make an `app/actors/hyrax/actors/file_set_actor_decorator.rb`? It would be lost in that
# it is decorating the decoration of the IIIF Print gem. Beside, in bringing this here, we have
# a relatively singular place for all of the configurations.
module FileSetActorDecorator
##
# @see https://github.com/scientist-softserv/iiif_print/blob/9e7837ce4bd08bf8fff9126455d0e0e2602f6018/app/actors/iiif_print/actors/file_set_actor_decorator.rb#L33-L35 Method we're overriding
def service
return TenantConfig::SkipSplittingPdfService unless TenantConfig.use_iiif_print?

super
end
end

##
# OVERRIDE IiifPrint::WorkShowPresenterDecorator
# OVERRIDE Hyrax::WorkShowPresenter
#
# In IiifPrint we overrided #members_include_viewable_image? to query for both file sets and
# child works. (Child works being the pages split off of a PDF)
#
# In Hyrax::WorkShowPresenter we're only looking at the underlying file_sets. But IiifPrint
# needs to look at multiple places.
module WorkShowPresenterDecorator
##
# @return [Array<Symbol>] predicate methods (e.g. ending in "?") that reflect the types
# of files we want to consider for showing in the IIIF Viewer.
def iiif_media_predicates
if TenantConfig.use_iiif_print?
%i[image? audio? video? pdf?]
else
%i[image? audio? video?]
end
end

def iiif_media?(presenter: representative_presenter)
iiif_media_predicates.any? { |predicate| presenter.try(predicate) || presenter.try(:solr_document).try(predicate) }
end

##
# @return [Boolean] render a IIIF viewer
#
# OVERRIDE Hyrax::WorkShowPresenter; this override introduces behavior to handle over-rides.
def iiif_viewer?
Hyrax.config.iiif_image_server? &&
representative_id.present? &&
representative_presenter.present? &&
iiif_media? &&
members_include_iiif_viewable?
end

def members_include_iiif_viewable?
iiif_presentable_member_presenters.any? do |presenter|
iiif_media?(presenter: presenter) && current_ability.can?(:read, presenter.id)
end
end

##
# @return [Array<Object>] An array of presenter objects
#
# In a non-IIIF Print using scenario, we use the file_set_presenters value; that is for
# objects that are very specifically file_sets.
#
# In a IIIF Print using scenario, we use the ill-named 'file_set_ids_ssim', because a
# long-standing decision is that this field will have both file_set IDs and child work IDs.
def iiif_presentable_member_presenters
if TenantConfig.use_iiif_print?
presentable_member_ids = Array.wrap(solr_document.try(:file_set_ids) || solr_document.try(:[], 'file_set_ids_ssim'))
member_presenters_for(presentable_member_ids)
else
file_set_presenters
end
end
end
end
end
# rubocop:enable Metrics/LineLength
16 changes: 16 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,21 @@ class Application < Rails::Application

Object.include(AccountSwitch)
end

config.after_initialize do
##
# The first "#valid?" service is the one that we'll use for generating derivatives.
Hyrax::DerivativeService.services = [
IiifPrint::TenantConfig::DerivativeService,
Hyrax::FileSetDerivativesService
]

##
# This needs to be in the after initialize so that the IiifPrint gem can do it's decoration.
#
# @see https://github.com/scientist-softserv/iiif_print/blob/9e7837ce4bd08bf8fff9126455d0e0e2602f6018/lib/iiif_print/engine.rb#L54 Where we do the override.
Hyrax::Actors::FileSetActor.prepend(IiifPrint::TenantConfig::FileSetActorDecorator)
Hyrax::WorkShowPresenter.prepend(IiifPrint::TenantConfig::WorkShowPresenterDecorator)
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# This migration comes from iiif_print (originally 20231110163052)
class AddModelDetailsToIiifPrintPendingRelationships < ActiveRecord::Migration[5.2]
def change
add_column :iiif_print_pending_relationships, :parent_model, :string
add_column :iiif_print_pending_relationships, :child_model, :string
add_column :iiif_print_pending_relationships, :file_id, :string
end
end
5 changes: 4 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2023_06_08_153601) do
ActiveRecord::Schema.define(version: 2023_11_28_191136) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -382,6 +382,9 @@
t.string "child_order", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "parent_model"
t.string "child_model"
t.string "file_id"
t.index ["parent_id"], name: "index_iiif_print_pending_relationships_on_parent_id"
end

Expand Down
9 changes: 9 additions & 0 deletions docker-compose.bundle.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copy file to docker-compose.override.yml to override docker-compose.yml
# Only use for local development
version: '3.8'
services:
web:
command: sh -l -c "bundle && bundle exec puma -v -b tcp://0.0.0.0:3000"
worker:
command: sh -l -c "bundle && bundle exec sidekiq"

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Hyrax::IiifAv::DisplaysContentDecorator do
# We're prepending the DisplaysContentDecorator to the Hyrax::IiifAv::DisplaysContent
describe Hyrax::IiifAv::DisplaysContent do
describe '.public_instance_methods' do
subject { Hyrax::IiifAv::DisplaysContent.public_instance_methods }

it { is_expected.to include(:solr_document) }
it { is_expected.to include(:current_ability) }
end
end
end
Loading

0 comments on commit 2ad14dd

Please sign in to comment.