From 6b1143730b95e7c088030a26eea11155a42b179a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esm=C3=A9=20Cowles?= Date: Tue, 12 Jan 2016 17:03:53 -0500 Subject: [PATCH] Adding IIIF resource and ingest job --- Gemfile | 1 + Gemfile.lock | 1 + app/jobs/iiif_ingest_job.rb | 15 ++++++++ app/models/iiif_resource.rb | 43 ++++++++++++++++++++++ spec/jobs/iiif_ingest_job_spec.rb | 22 +++++++++++ spec/models/iiif_resource_spec.rb | 61 +++++++++++++++++++++++++++++++ 6 files changed, 143 insertions(+) create mode 100644 app/jobs/iiif_ingest_job.rb create mode 100644 app/models/iiif_resource.rb create mode 100644 spec/jobs/iiif_ingest_job_spec.rb create mode 100644 spec/models/iiif_resource_spec.rb diff --git a/Gemfile b/Gemfile index 7d1fc2e5..4ff58091 100644 --- a/Gemfile +++ b/Gemfile @@ -62,5 +62,6 @@ gem 'blacklight-gallery', '>= 0.3.0' gem 'blacklight-oembed' gem 'social-share-button' gem 'devise_invitable' +gem 'faraday' gem 'rubocop', require: false gem 'rubocop-rspec', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 36d85f9e..5541b50d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -435,6 +435,7 @@ DEPENDENCIES devise devise-guests (~> 0.3) devise_invitable + faraday friendly_id jbuilder (~> 2.0) jettywrapper (>= 2.0) diff --git a/app/jobs/iiif_ingest_job.rb b/app/jobs/iiif_ingest_job.rb new file mode 100644 index 00000000..3cf0e72c --- /dev/null +++ b/app/jobs/iiif_ingest_job.rb @@ -0,0 +1,15 @@ +class IIIFIngestJob < ActiveJob::Base + # Ingest one or more IIIF manfiest URLs. Each manifest is ingested as its + # own resource. + def perform(urls) + arr = urls.is_a?(Array) ? urls : Array(urls) + arr.each do |url| + ingest url + end + end + + # Ingest a single IIIF manifest URL as a resource. + def ingest(url) + IIIFResource.new(manifest_url: url).save + end +end diff --git a/app/models/iiif_resource.rb b/app/models/iiif_resource.rb new file mode 100644 index 00000000..f50ea28e --- /dev/null +++ b/app/models/iiif_resource.rb @@ -0,0 +1,43 @@ +class IIIFResource < Spotlight::Resource + # If a manifest_url if provided, it is retrieved, parsed and indexed + def initialize(manifest_url: nil) + super() + return if manifest_url.blank? + manifest = IIIFResource.parse_manifest(manifest_url) + @title = manifest['label'] + self.url = manifest_url + + self.data ||= {} + self.data['thumbnail'] = manifest['thumbnail']['@id'] if manifest['thumbnail'] + manifest['metadata'].each do |h| + self.data[h['label'].parameterize('_')] = h['value'].map { |v| v["@value"] } + end + end + + def title_field + :"#{solr_fields.prefix}spotlight_title#{solr_fields.string_suffix}" + end + + def to_solr + solr_doc = super + solr_doc[title_field] = @title + + data.each do |k, v| + solr_doc[(k + solr_fields.string_suffix).to_sym] = v + end + + solr_doc + end + + def solr_fields + Spotlight::Engine.config.solr_fields + end + + # Retrieve a IIIF manifest and parse the resulting JSON + def self.parse_manifest(manifest_url) + conn = Faraday.new(manifest_url) do |builder| + builder.headers['Content-Type'] = 'application/json' + end + JSON.parse conn.get(manifest_url) + end +end diff --git a/spec/jobs/iiif_ingest_job_spec.rb b/spec/jobs/iiif_ingest_job_spec.rb new file mode 100644 index 00000000..0c87174e --- /dev/null +++ b/spec/jobs/iiif_ingest_job_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' + +describe IIIFIngestJob do + let(:url1) { 'http://example.com/1/manifest' } + let(:url2) { 'http://example.com/2/manifest' } + let(:resource) { IIIFResource.new } + + it 'ingests a single url' do + allow_any_instance_of(IIIFResource).to receive(:save) + expect(IIIFResource).to receive(:new).with(manifest_url: url1).and_return(resource) + + described_class.new.perform(url1) + end + + it 'ingests each of an array of urls' do + allow_any_instance_of(IIIFResource).to receive(:save) + expect(IIIFResource).to receive(:new).with(manifest_url: url1).and_return(resource) + expect(IIIFResource).to receive(:new).with(manifest_url: url2).and_return(resource) + + described_class.new.perform([url1, url2]) + end +end diff --git a/spec/models/iiif_resource_spec.rb b/spec/models/iiif_resource_spec.rb new file mode 100644 index 00000000..90aebe92 --- /dev/null +++ b/spec/models/iiif_resource_spec.rb @@ -0,0 +1,61 @@ +require 'rails_helper' + +describe IIIFResource do + let(:url) { 'http://example.com/1/manifest' } + let(:json) { '{ + "@context":"http://iiif.io/api/presentation/2/context.json", + "@id":"http://example.com/1/manifest", + "@type":"sc:Manifest", + "label":"Sample Manifest", + "thumbnail":{ + "@id":"http://example.com/loris/1.jp2/full/100,/0/default.jpg", + "service":{ + "@context":"http://iiif.io/api/image/2/context.json", + "@id":"https://example.com/loris/1.jp2", + "profile":"http://iiif.io/api/image/2/level2.json" }}, + "metadata":[ + { "label": "Creator", "value": [{ "@value": "Author, Alice, 1954-" }] }, + { "label": "Date created", "value": [{ "@value": "1985" }] } + ]}' + } + + before do + allow_any_instance_of(Faraday::Connection).to receive(:get).and_return(json) + end + + describe '#initialize' do + it 'loads metadata from the IIIF manifest' do + resource = described_class.new(manifest_url: url) + expect(resource.url).to eq(url) + expect(resource.data['thumbnail']).to eq('http://example.com/loris/1.jp2/full/100,/0/default.jpg') + expect(resource.data['creator']).to eq(['Author, Alice, 1954-']) + end + end + + describe '#parse_manifest' do + it 'retrieves and parses an IIIF manifest' do + expect_any_instance_of(Faraday::Connection).to receive(:get).with(url) + manifest = described_class.parse_manifest(url) + expect(manifest['@id']).to eq(url) + expect(manifest['label']).to eq('Sample Manifest') + expect(manifest['thumbnail']['@id']).to eq('http://example.com/loris/1.jp2/full/100,/0/default.jpg') + end + end + + describe '#to_solr' do + subject { described_class.new(manifest_url: url) } + before do + exhibit = Spotlight::Exhibit.new + allow(exhibit).to receive(:blacklight_config).and_return(Blacklight::Configuration.new) + subject.exhibit = exhibit + end + + it 'indexes iiif metadata' do + solr_doc = subject.to_solr + expect(solr_doc[:spotlight_title_ssim]).to eq('Sample Manifest') + expect(solr_doc[:thumbnail_ssim]).to eq('http://example.com/loris/1.jp2/full/100,/0/default.jpg') + expect(solr_doc[:creator_ssim]).to eq(['Author, Alice, 1954-']) + expect(solr_doc[:date_created_ssim]).to eq(['1985']) + end + end +end