spotlight-resources-iiif 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2fbc962270af9f8879ad88cb7518450028149e9d
4
- data.tar.gz: d1e148ef76b96e482b7813d066880ed914d667b9
3
+ metadata.gz: 99f85b3ef79f2c569645668b1bb55318756c70a5
4
+ data.tar.gz: 54414056e0566e567250875a5dca68427c1dff7e
5
5
  SHA512:
6
- metadata.gz: c0d8eb4a499d156fbce45138f8766e73840859ec380e0dcb6f18e66573f448d39caa9de0b2756104e146aa570317c4899f9d5ed53a13cb56cacf1b3623af5141
7
- data.tar.gz: 4941537eb392d728e53e34c74bf98054ad984a7519c16af5b890f276fde927cdf2d47675a06f4989e0aa35d1817fe68ad98d6b07f74cbcab1a57273a03af750b
6
+ metadata.gz: 5472e66b31c7e6e2020967ab18e63a4960584302b93f0cf42665265522c91407308fb650f6aa243982c4ea7459da1ac67fb80769198a9051b1cdba91448e0269
7
+ data.tar.gz: 0e976a850c9822d501301e66a78b68a6f40b3280b22ef8f7af6bd2b58a3018cb40fac78a2fb76d506d3a46bcaf389ec829a8d55c64f4d66a29c529bd1ef9545b
data/.rubocop.yml CHANGED
@@ -3,6 +3,7 @@ inherit_from: .rubocop_todo.yml
3
3
  require: rubocop-rspec
4
4
 
5
5
  AllCops:
6
+ TargetRubyVersion: 2.2
6
7
  Exclude:
7
8
  - 'Gemfile'
8
9
  - 'bin/**/*'
@@ -0,0 +1,32 @@
1
+ module Spotlight
2
+ module Resources
3
+ class IiifHarvesterController < Spotlight::ApplicationController
4
+ before_action :authenticate_user!
5
+
6
+ load_and_authorize_resource :exhibit, class: Spotlight::Exhibit
7
+ before_action :build_resource
8
+
9
+ def create
10
+ if @resource.save_and_index
11
+ redirect_to spotlight.admin_exhibit_catalog_index_path(current_exhibit, sort: :timestamp)
12
+ else
13
+ flash[:error] = @resource.errors.values.join(', ') if @resource.errors.present?
14
+ redirect_to spotlight.new_exhibit_resource_path(current_exhibit)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def resource_params
21
+ params.require(:resources_iiif_harvester).permit(:url)
22
+ end
23
+
24
+ def build_resource
25
+ @resource ||= Spotlight::Resources::IiifHarvester.create(
26
+ url: resource_params[:url],
27
+ exhibit: current_exhibit
28
+ )
29
+ end
30
+ end
31
+ end
32
+ end
@@ -1,10 +1,43 @@
1
- module Spotlight::Resources
2
- # harvest Images from IIIF Manifest and turn them into a Spotlight::Resource
3
- class IiifHarvester < Spotlight::Resource
4
- self.weight = -5000
1
+ require 'iiif/presentation'
5
2
 
6
- after_save :harvest_resources
3
+ module Spotlight
4
+ module Resources
5
+ # harvest Images from IIIF Manifest and turn them into a Spotlight::Resource
6
+ # Note: IIIF API : http://iiif.io/api/presentation/2.0
7
+ class IiifHarvester < Spotlight::Resource
8
+ self.weight = -5000
7
9
 
10
+ validate :valid_url?
8
11
 
12
+ def to_solr
13
+ return to_enum(:to_solr) { 0 } unless block_given?
14
+
15
+ base_doc = super
16
+ iiif_manifests.each do |manifest|
17
+ manifest.with_exhibit(exhibit)
18
+ manifest_solr = manifest.to_solr
19
+ yield base_doc.merge(manifest_solr) if manifest_solr.present?
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def iiif_manifests
26
+ @iiif_manifests ||= IiifService.parse(url)
27
+ end
28
+
29
+ def valid_url?
30
+ errors.add(:url, 'Invalid IIIF URL') unless url_is_iiif?(url)
31
+ end
32
+
33
+ def url_is_iiif?(url)
34
+ valid_content_types = ['application/json', 'application/ld+json']
35
+ req = Faraday.head(url)
36
+ return unless req.success?
37
+ valid_content_types.any? do |valid_type|
38
+ req.headers['content-type'].include?(valid_type)
39
+ end
40
+ end
41
+ end
9
42
  end
10
- end
43
+ end
@@ -0,0 +1,186 @@
1
+ module Spotlight
2
+ module Resources
3
+ ##
4
+ # A PORO to construct a solr hash for a given IiifManifest
5
+ class IiifManifest
6
+ def initialize(attrs = {})
7
+ @url = attrs[:url]
8
+ @manifest = attrs[:manifest]
9
+ @solr_hash = {}
10
+ end
11
+
12
+ def to_solr
13
+ add_document_id
14
+ add_label
15
+ add_thumbnail_url
16
+ add_manifest_url
17
+ add_image_urls
18
+ add_metadata
19
+ solr_hash
20
+ end
21
+
22
+ def with_exhibit(e)
23
+ @exhibit = e
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :url, :manifest, :exhibit, :solr_hash
29
+ delegate :blacklight_config, to: :exhibit
30
+
31
+ def compound_id
32
+ Digest::MD5.hexdigest("#{exhibit.id}-#{url}")
33
+ end
34
+
35
+ def add_document_id
36
+ solr_hash[exhibit.blacklight_config.document_model.unique_key.to_sym] = compound_id
37
+ end
38
+
39
+ def add_manifest_url
40
+ solr_hash[Spotlight::Resources::Iiif::Engine.config.iiif_manifest_field] = url
41
+ end
42
+
43
+ def add_thumbnail_url
44
+ return unless thumbnail_field && manifest['thumbnail'].present?
45
+ solr_hash[thumbnail_field] = manifest['thumbnail']['@id']
46
+ end
47
+
48
+ def add_label
49
+ return unless title_field && manifest.try(:label)
50
+ solr_hash[title_field] = manifest.label
51
+ end
52
+
53
+ def add_image_urls
54
+ solr_hash[tile_source_field] = image_urls
55
+ end
56
+
57
+ def add_metadata
58
+ solr_hash.merge!(manifest_metadata)
59
+ sidecar.update(data: sidecar.data.merge(manifest_metadata))
60
+ end
61
+
62
+ def manifest_metadata
63
+ metadata = metadata_class.new(manifest).to_solr
64
+ return {} unless metadata.present?
65
+ create_sidecars_for(*metadata.keys)
66
+
67
+ metadata.each_with_object({}) do |(key, value), hash|
68
+ next unless (field = exhibit_custom_fields[key])
69
+ hash[field.field] = value
70
+ end
71
+ end
72
+
73
+ def create_sidecars_for(*keys)
74
+ missing_keys(keys).each do |k|
75
+ exhibit.custom_fields.create! label: k
76
+ end
77
+ @exhibit_custom_fields = nil
78
+ end
79
+
80
+ def missing_keys(keys)
81
+ custom_field_keys = exhibit_custom_fields.keys.map(&:downcase)
82
+ keys.reject do |key|
83
+ custom_field_keys.include?(key.downcase)
84
+ end
85
+ end
86
+
87
+ def exhibit_custom_fields
88
+ @exhibit_custom_fields ||= exhibit.custom_fields.each_with_object({}) do |value, hash|
89
+ hash[value.label] = value
90
+ end
91
+ end
92
+
93
+ def image_urls
94
+ @image_urls ||= resources.map do |resource|
95
+ next unless resource && !resource.service.empty?
96
+ image_url = resource.service['@id']
97
+ image_url << '/info.json' unless image_url.downcase.ends_with?('/info.json')
98
+ image_url
99
+ end
100
+ end
101
+
102
+ def resources
103
+ @resources ||= sequences
104
+ .flat_map(&:canvases)
105
+ .flat_map(&:images)
106
+ .flat_map(&:resource)
107
+ end
108
+
109
+ def sequences
110
+ manifest.try(:sequences) || []
111
+ end
112
+
113
+ def thumbnail_field
114
+ blacklight_config.index.try(:thumbnail_field)
115
+ end
116
+
117
+ def tile_source_field
118
+ blacklight_config.show.try(:tile_source_field)
119
+ end
120
+
121
+ def title_field
122
+ blacklight_config.index.try(:title_field)
123
+ end
124
+
125
+ def sidecar
126
+ @sidecar ||= document_model.new(id: compound_id).sidecar(exhibit)
127
+ end
128
+
129
+ def document_model
130
+ exhibit.blacklight_config.document_model
131
+ end
132
+
133
+ def metadata_class
134
+ Spotlight::Resources::Iiif::Engine.config.metadata_class.call
135
+ end
136
+
137
+ ###
138
+ # A simple class to map the metadata field
139
+ # in a IIIF document to label/value pairs
140
+ # This is intended to be overriden by an
141
+ # application if a different metadata
142
+ # strucure is used by the consumer
143
+ class Metadata
144
+ def initialize(manifest)
145
+ @manifest = manifest
146
+ end
147
+
148
+ def to_solr
149
+ metadata_hash.merge(manifest_level_metadata)
150
+ end
151
+
152
+ private
153
+
154
+ attr_reader :manifest
155
+
156
+ def metadata
157
+ manifest.try(:metadata) || []
158
+ end
159
+
160
+ def metadata_hash
161
+ return {} unless metadata.present?
162
+ return {} unless metadata.is_a?(Array)
163
+
164
+ metadata.each_with_object({}) do |md, hash|
165
+ next unless md['label'] && md['value']
166
+ hash[md['label']] ||= []
167
+ hash[md['label']] += Array(md['value'])
168
+ end
169
+ end
170
+
171
+ def manifest_level_metadata
172
+ manifest_fields.each_with_object({}) do |field, hash|
173
+ next unless manifest.respond_to?(field) &&
174
+ manifest.send(field).present?
175
+ hash[field.capitalize] ||= []
176
+ hash[field.capitalize] += Array(manifest.send(field))
177
+ end
178
+ end
179
+
180
+ def manifest_fields
181
+ %w(attribution description license)
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,85 @@
1
+ require 'iiif/presentation'
2
+ module Spotlight
3
+ module Resources
4
+ ###
5
+ # Wrapper around IIIIF-Presentation's IIIF::Service that provides the
6
+ # ability to recursively traverse through all collections and manifests
7
+ class IiifService
8
+ def initialize(url)
9
+ @url = url
10
+ end
11
+
12
+ def collections
13
+ @collections ||= (object.try(:collections) || []).map do |collection|
14
+ self.class.new(collection['@id'])
15
+ end
16
+ end
17
+
18
+ def manifests
19
+ @manifests ||= if manifest?
20
+ [create_iiif_manifest(object)]
21
+ else
22
+ things = []
23
+ things << create_iiif_manifest(object) if collection?
24
+ (object.try(:manifests) || []).each do |manifest|
25
+ things << create_iiif_manifest(
26
+ self.class.new(manifest['@id']).object
27
+ )
28
+ end
29
+ things
30
+ end
31
+ end
32
+
33
+ def self.parse(url)
34
+ recursive_manifests(new(url))
35
+ end
36
+
37
+ protected
38
+
39
+ def object
40
+ @object ||= IIIF::Service.parse(response)
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :url
46
+
47
+ class << self
48
+ private
49
+
50
+ def recursive_manifests(thing, &block)
51
+ return to_enum(:recursive_manifests, thing) unless block_given?
52
+
53
+ thing.manifests.each(&block)
54
+
55
+ thing.collections.each do |collection|
56
+ recursive_manifests(collection, &block)
57
+ end if thing.collections.present?
58
+ end
59
+
60
+ def iiif_response(url)
61
+ Faraday.get(url).body
62
+ rescue Faraday::Error::ConnectionFailed, Faraday::TimeoutError => e
63
+ Rails.logger.warn("HTTP GET for #{url} failed with #{e}")
64
+ {}.to_json
65
+ end
66
+ end
67
+
68
+ def create_iiif_manifest(manifest)
69
+ IiifManifest.new(url: manifest['@id'], manifest: manifest)
70
+ end
71
+
72
+ def manifest?
73
+ object.is_a?(IIIF::Presentation::Manifest)
74
+ end
75
+
76
+ def collection?
77
+ object.is_a?(IIIF::Presentation::Collection)
78
+ end
79
+
80
+ def response
81
+ @response ||= self.class.iiif_response(url)
82
+ end
83
+ end
84
+ end
85
+ end
@@ -1,4 +1,4 @@
1
- <%= bootstrap_form_for([current_exhibit, @resource], layout: :horizontal, label_col: 'col-md-2', control_col: 'col-sm-6 col-md-6', html: { class: 'item-upload-form', multipart: true } ) do |f| %>
1
+ <%= bootstrap_form_for([spotlight_resources_iiif_engine, current_exhibit, Spotlight::Resources::IiifHarvester.new], layout: :horizontal, label_col: 'col-md-2', control_col: 'col-sm-6 col-md-6' ) do |f| %>
2
2
  <%= f.text_field :url, help: t('.url-field.help'), label: t('.manifest') %>
3
3
  <div class="form-actions">
4
4
  <div class="primary-actions">
@@ -2,10 +2,9 @@ en:
2
2
  spotlight:
3
3
  resources:
4
4
  iiif:
5
- tabbed_form:
6
- manifest: "IIIF Manifest"
7
- manifest:
8
- manifest: "Manifest"
9
- add_item: "Add IIIF manifest URLs"
5
+ form:
6
+ title: 'IIIF URL'
7
+ manifest: "URL"
8
+ add_item: "Add IIIF items"
10
9
  url-field:
11
- help: "Add the URL of a IIIF manifest."
10
+ help: "Add the URL of a IIIF manifest or collection."
data/config/routes.rb ADDED
@@ -0,0 +1,5 @@
1
+ Spotlight::Resources::Iiif::Engine.routes.draw do
2
+ resources :exhibits, only: [] do
3
+ resource :iiif_harvester, controller: :"spotlight/resources/iiif_harvester", only: :create, as: :resources_iiif_harvesters
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ require 'rails/generators'
2
+
3
+ module Spotlight
4
+ module Resources
5
+ module Iiif
6
+ class InstallGenerator < Rails::Generators::Base
7
+ desc 'This generator mounts the Spotlight::Resources::Iiif engine'
8
+
9
+ def inject_spotlight_iiif_routes
10
+ route "mount Spotlight::Resources::Iiif::Engine, at: 'spotlight_resources_iiif'"
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -1,4 +1,6 @@
1
- require "spotlight/resources/iiif/version"
1
+ require 'spotlight/resources/iiif/version'
2
+ require 'spotlight'
3
+ require 'faraday'
2
4
 
3
5
  module Spotlight
4
6
  module Resources
@@ -2,11 +2,14 @@ require 'spotlight/engine'
2
2
 
3
3
  module Spotlight::Resources::Iiif
4
4
  class Engine < ::Rails::Engine
5
- Spotlight::Resources::Iiif::Engine.config.resource_partials = ['spotlight/resources/iiif/manifest']
5
+ Spotlight::Resources::Iiif::Engine.config.resource_partial = 'spotlight/resources/iiif/form'
6
+ Spotlight::Resources::Iiif::Engine.config.metadata_class = -> { Spotlight::Resources::IiifManifest::Metadata }
7
+ Spotlight::Resources::Iiif::Engine.config.iiif_manifest_field = :content_metadata_iiif_manifest_ssm
8
+
9
+
6
10
  initializer 'spotlight.resources.iiif.initialize' do
7
- Spotlight::Engine.config.resource_providers << Spotlight::Resources::IiifHarvester
8
- Spotlight::Engine.config.new_resource_partials ||= []
9
- Spotlight::Engine.config.new_resource_partials << 'spotlight/resources/iiif/tabbed_form'
11
+ Spotlight::Engine.config.external_resources_partials ||= []
12
+ Spotlight::Engine.config.external_resources_partials << Spotlight::Resources::Iiif::Engine.config.resource_partial
10
13
  end
11
14
  end
12
- end
15
+ end
@@ -1,7 +1,7 @@
1
1
  module Spotlight
2
2
  module Resources
3
3
  module Iiif
4
- VERSION = "0.0.1"
4
+ VERSION = "0.1.0"
5
5
  end
6
6
  end
7
7
  end
@@ -6,8 +6,8 @@ require 'spotlight/resources/iiif/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "spotlight-resources-iiif"
8
8
  spec.version = Spotlight::Resources::Iiif::VERSION
9
- spec.authors = ["Naomi Dushay"]
10
- spec.email = ["ndushay@stanford.edu"]
9
+ spec.authors = ["Naomi Dushay", "Peter Mangiafico"]
10
+ spec.email = ["ndushay@stanford.edu", "petucket@stanford.edu"]
11
11
 
12
12
  spec.summary = 'Spotlight Resource Indexer for IIIF manifests or collections.'
13
13
  spec.homepage = "https://github.com/sul-dlss/spotlight-resources-iiif"
@@ -19,12 +19,14 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ["lib"]
20
20
 
21
21
  spec.add_dependency "blacklight-spotlight"
22
+ spec.add_dependency "faraday"
23
+ spec.add_dependency "iiif-presentation"
22
24
 
23
25
  spec.add_development_dependency "bundler", "~> 1.10"
24
26
  spec.add_development_dependency "rake", "~> 10.0"
25
27
  spec.add_development_dependency "rspec-rails"
26
28
  spec.add_development_dependency 'capybara'
27
- spec.add_development_dependency 'factory_girl', '~> 4.5'
29
+ spec.add_development_dependency 'factory_girl_rails'
28
30
  spec.add_development_dependency 'database_cleaner', '~> 1.3'
29
31
  # spec.add_development_dependency 'poltergeist', '>= 1.5.0' # for js testing using phantomjs
30
32
  spec.add_development_dependency "coveralls"
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spotlight-resources-iiif
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Naomi Dushay
8
+ - Peter Mangiafico
8
9
  autorequire:
9
10
  bindir: exe
10
11
  cert_chain: []
11
- date: 2015-11-25 00:00:00.000000000 Z
12
+ date: 2016-01-28 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: blacklight-spotlight
@@ -24,6 +25,34 @@ dependencies:
24
25
  - - ">="
25
26
  - !ruby/object:Gem::Version
26
27
  version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: faraday
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: iiif-presentation
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
27
56
  - !ruby/object:Gem::Dependency
28
57
  name: bundler
29
58
  requirement: !ruby/object:Gem::Requirement
@@ -81,19 +110,19 @@ dependencies:
81
110
  - !ruby/object:Gem::Version
82
111
  version: '0'
83
112
  - !ruby/object:Gem::Dependency
84
- name: factory_girl
113
+ name: factory_girl_rails
85
114
  requirement: !ruby/object:Gem::Requirement
86
115
  requirements:
87
- - - "~>"
116
+ - - ">="
88
117
  - !ruby/object:Gem::Version
89
- version: '4.5'
118
+ version: '0'
90
119
  type: :development
91
120
  prerelease: false
92
121
  version_requirements: !ruby/object:Gem::Requirement
93
122
  requirements:
94
- - - "~>"
123
+ - - ">="
95
124
  - !ruby/object:Gem::Version
96
- version: '4.5'
125
+ version: '0'
97
126
  - !ruby/object:Gem::Dependency
98
127
  name: database_cleaner
99
128
  requirement: !ruby/object:Gem::Requirement
@@ -223,6 +252,7 @@ dependencies:
223
252
  description:
224
253
  email:
225
254
  - ndushay@stanford.edu
255
+ - petucket@stanford.edu
226
256
  executables: []
227
257
  extensions: []
228
258
  extra_rdoc_files: []
@@ -237,13 +267,17 @@ files:
237
267
  - LICENSE.txt
238
268
  - README.md
239
269
  - Rakefile
270
+ - app/controllers/spotlight/resources/iiif_harvester_controller.rb
240
271
  - app/models/spotlight/resources/iiif_harvester.rb
241
- - app/views/spotlight/resources/iiif/_manifest.html.erb
242
- - app/views/spotlight/resources/iiif/_tabbed_form.html.erb
272
+ - app/models/spotlight/resources/iiif_manifest.rb
273
+ - app/models/spotlight/resources/iiif_service.rb
274
+ - app/views/spotlight/resources/iiif/_form.html.erb
243
275
  - bin/console
244
276
  - bin/setup
245
277
  - config/jetty.yml
246
278
  - config/locales/spotlight-resources-iiif.en.yml
279
+ - config/routes.rb
280
+ - lib/generators/spotlight/resources/iiif/install_generator.rb
247
281
  - lib/spotlight/resources/iiif.rb
248
282
  - lib/spotlight/resources/iiif/engine.rb
249
283
  - lib/spotlight/resources/iiif/version.rb
@@ -1,18 +0,0 @@
1
- <div role="tabpanel" class="item-upload-tabs">
2
- <ul class="nav nav-tabs" role="tablist">
3
- <% Spotlight::Resources::Iiif::Engine.config.resource_partials.each_with_index do |p, i| %>
4
- <% name = p.split('/').last %>
5
- <li role="presentation" class="<%= "active" if i ==0 %>">
6
- <%= link_to t(".#{name}"), "##{name}", role: 'tab', 'data-toggle' => 'tab', 'aria-controls' => 'single' %>
7
- </li>
8
- <% end %>
9
- </ul>
10
- <div class="tab-content">
11
- <% Spotlight::Resources::Iiif::Engine.config.resource_partials.each_with_index do |p, i| %>
12
- <% name = p.split('/').last %>
13
- <%= content_tag :div, id: name, role: 'tabpanel', class: "tab-pane #{"active" if i == 0}" do %>
14
- <%= render p %>
15
- <% end %>
16
- <% end %>
17
- </div>
18
- </div>