Skip to content

Commit

Permalink
🎁 Add conditional PDF split or PDF.js
Browse files Browse the repository at this point in the history
This is a preliminary commit to add conditional logic for either
rendering PDF.js or IIIF viewer for split pages.

There is a particular case where QA might appear to fail:

1. Set the Tenant to use UV
2. Create a work with a PDF (this will split the page)
3. Go to the work's page, you'll see the UV
4. Set the Tenant to use PDF.js
5. Refresh the work page, and you should see PDF.js but instead will see
   the UV.

To work around this (and realistically how most people will experience this):

1. Set the Tenant to use UV
2. Create a work with a PDF
3. Go to the Dashboard
4. Click the work's URL
5. You should see a UV for the PDF
6. Close the work's page
7. Change the tenant to use PDF.js
8. Go to the Dashboard
9. Click the work's URL

The conjecture is that there's some iframe and/or turbolinks caching.
Given that this is an edge case regarding toggling on and off a tenant
feature, we think the work around is adequate.

Related to:

- scientist-softserv/palni-palci#666
- scientist-softserv/palni-palci#675
- scientist-softserv/palni-palci#704
- scientist-softserv/palni-palci#705
- #162

Co-authored-by: Shana Moore <[email protected]>
Co-authored-by: Kirk Wang <[email protected]>
  • Loading branch information
3 people committed Nov 29, 2023
1 parent f98d95f commit a2d1d36
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 a2d1d36

Please sign in to comment.