diff --git a/app/controllers/object_controller.rb b/app/controllers/object_controller.rb index df55c748..5d37abd3 100644 --- a/app/controllers/object_controller.rb +++ b/app/controllers/object_controller.rb @@ -8,7 +8,8 @@ class ObjectController < ApplicationController # Return a zip of all the files if they have access to all the files. # This will force a login if any of the files is not access=world def show - files = Purl.files(druid) + cocina = Cocina.find(druid) + files = cocina.files raise ActionController::RoutingError, 'No downloadable files' if files.none? files.each do |file| diff --git a/app/models/cocina.rb b/app/models/cocina.rb new file mode 100644 index 00000000..73eb2dd0 --- /dev/null +++ b/app/models/cocina.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +# Retrieves the public JSON (cocina) and extracts data from it. +class Cocina + extend ActiveSupport::Benchmarkable + + THUMBNAIL_MIME_TYPE = 'image/jp2' + + def self.find(druid) + Rails.cache.fetch("purl/#{druid}.json", expires_in: 10.minutes) do + benchmark "Fetching public json for #{druid}" do + response = Faraday.get(public_json_url(druid)) + raise Purl::Exception, response.status unless response.success? + + new(JSON.parse(response.body)) + end + end + end + + def self.public_json_url(druid) + "#{Settings.purl.url}#{druid}.json" + end + + def self.logger + Rails.logger + end + + def initialize(data) + @data = data + end + + attr_accessor :data + + def find_file(file_name) + file_sets = data.dig('structural', 'contains') + raise(ActionController::MissingFile, "File not found '#{file_name}'") unless file_sets # Trap for Collections + + file_sets.lazy.flat_map { |file_set| file_set.dig('structural', 'contains') } + .find { |file| file['filename'] == file_name } || raise(ActionController::MissingFile, "File not found '#{file_name}'") + end + + def thumbnail_file + data.dig('structural', 'contains') + .lazy.flat_map { |file_set| file_set.dig('structural', 'contains') } + .find { |file| file['hasMimeType'] == THUMBNAIL_MIME_TYPE } + end + + def embargo_release_date + data.dig('access', 'embargo', 'releaseDate') + end + + def files(&) + return to_enum(:files) unless block_given? + + files_from_json(&) + end + + private + + def files_from_json + data.dig('structural', 'contains').each do |fileset| + fileset.dig('structural', 'contains').each do |file| + file = StacksFile.new(id: data.fetch('externalIdentifier').delete_prefix('druid:'), file_name: file['filename']) + yield file + end + end + end +end diff --git a/app/models/purl.rb b/app/models/purl.rb index c9a64fe8..b954dc76 100644 --- a/app/models/purl.rb +++ b/app/models/purl.rb @@ -2,53 +2,5 @@ # PURL API service class Purl - include ActiveSupport::Benchmarkable - class Exception < ::RuntimeError; end - - def self.instance - @instance ||= new - end - - class << self - delegate :public_json, :files, to: :instance - end - - def public_json(druid) - Rails.cache.fetch("purl/#{druid}.json", expires_in: 10.minutes) do - benchmark "Fetching public json for #{druid}" do - response = Faraday.get(public_json_url(druid)) - raise Purl::Exception, response.status unless response.success? - - JSON.parse(response.body) - end - end - end - - def files(druid, &) - return to_enum(:files, druid) unless block_given? - - files_from_json(druid, &) - end - - def files_from_json(druid) - doc = public_json(druid) - - doc.dig('structural', 'contains').each do |fileset| - fileset.dig('structural', 'contains').each do |file| - file = StacksFile.new(id: druid, file_name: file['filename']) - yield file - end - end - end - - private - - def public_json_url(druid) - "#{Settings.purl.url}#{druid}.json" - end - - def logger - Rails.logger - end end diff --git a/app/models/stacks_file.rb b/app/models/stacks_file.rb index 5a33bcb2..1f4ee6fd 100644 --- a/app/models/stacks_file.rb +++ b/app/models/stacks_file.rb @@ -41,8 +41,9 @@ def storage_root end def stacks_rights - @stacks_rights ||= StacksRights.new(id:, file_name:) + @stacks_rights ||= StacksRights.new(cocina: Cocina.find(id), file_name:) end + delegate :rights, :restricted_by_location?, :stanford_restricted?, :embargoed?, :embargo_release_date, :location, to: :stacks_rights diff --git a/app/models/stacks_image.rb b/app/models/stacks_image.rb index 5f5c5834..91be782f 100644 --- a/app/models/stacks_image.rb +++ b/app/models/stacks_image.rb @@ -60,8 +60,9 @@ def info_service end def stacks_rights - @stacks_rights ||= StacksRights.new(id:, file_name:) + @stacks_rights ||= StacksRights.new(cocina: Cocina.find(id), file_name:) end + delegate :rights, :maybe_downloadable?, :object_thumbnail?, :stanford_restricted?, :restricted_by_location?, to: :stacks_rights end diff --git a/app/models/stacks_media_stream.rb b/app/models/stacks_media_stream.rb index ae876903..791a928b 100644 --- a/app/models/stacks_media_stream.rb +++ b/app/models/stacks_media_stream.rb @@ -15,7 +15,7 @@ def file delegate :etag, :mtime, to: :file def stacks_rights - @stacks_rights ||= StacksRights.new(id:, file_name:) + @stacks_rights ||= StacksRights.new(cocina: Cocina.find(id), file_name:) end delegate :rights, :restricted_by_location?, :stanford_restricted?, :embargoed?, diff --git a/app/models/stacks_rights.rb b/app/models/stacks_rights.rb index 1eec6afc..26677464 100644 --- a/app/models/stacks_rights.rb +++ b/app/models/stacks_rights.rb @@ -3,15 +3,15 @@ ## # RightsMetadata interpretation class StacksRights - attr_reader :id, :file_name + attr_reader :cocina_file, :cocina - THUMBNAIL_MIME_TYPE = 'image/jp2' - - def initialize(id:, file_name:) - @id = id - @file_name = file_name + def initialize(file_name:, cocina:) + @cocina = cocina + @cocina_file = cocina.find_file(file_name) end + delegate :embargo_release_date, :thumbnail_file, to: :cocina + def maybe_downloadable? %w[world stanford].include?(rights.download) end @@ -26,23 +26,12 @@ def restricted_by_location? rights.view == 'location-based' || rights.download == 'location-based' end - def embargo_release_date - cocina_embargo_release_date - end - def embargoed? - cocina_embargo_release_date && Time.parse(cocina_embargo_release_date).getlocal > Time.now.getlocal - end - - def cocina_embargo_release_date - @cocina_embargo_release_date ||= public_json.dig('access', 'embargo', 'releaseDate') + embargo_release_date && Time.parse(embargo_release_date).getlocal > Time.now.getlocal end # Based on implementation of ThumbnailService in DSA def object_thumbnail? - thumbnail_file = public_json.dig('structural', 'contains') - .lazy.flat_map { |file_set| file_set.dig('structural', 'contains') } - .find { |file| file['hasMimeType'] == THUMBNAIL_MIME_TYPE } thumbnail_file == cocina_file end @@ -51,22 +40,4 @@ def rights end delegate :location, to: :rights - - private - - def cocina_file - @cocina_file ||= find_file - end - - def find_file - file_sets = public_json.dig('structural', 'contains') - raise(ActionController::MissingFile, "File not found '#{file_name}'") unless file_sets # Trap for Collections - - file_sets.lazy.flat_map { |file_set| file_set.dig('structural', 'contains') } - .find { |file| file['filename'] == file_name } || raise(ActionController::MissingFile, "File not found '#{file_name}'") - end - - def public_json - @public_json ||= Purl.public_json(id) - end end diff --git a/spec/abilities/cocina_ability_spec.rb b/spec/abilities/cocina_ability_spec.rb index e51bc611..a9b67e01 100644 --- a/spec/abilities/cocina_ability_spec.rb +++ b/spec/abilities/cocina_ability_spec.rb @@ -7,7 +7,7 @@ subject(:ability) { described_class.new(user) } before do - allow(Purl).to receive(:public_json).and_return(public_json) + allow(Cocina).to receive(:find).and_return(Cocina.new(public_json)) allow(image).to receive_messages(image_width: 11_957, image_height: 15_227) end diff --git a/spec/controllers/file_controller_spec.rb b/spec/controllers/file_controller_spec.rb index 2316144d..1eb7c5bf 100644 --- a/spec/controllers/file_controller_spec.rb +++ b/spec/controllers/file_controller_spec.rb @@ -5,7 +5,7 @@ RSpec.describe FileController do before do allow(StacksFile).to receive(:new).and_return(file) - allow(Purl).to receive(:public_json).and_return(public_json) + allow(Cocina).to receive(:find).and_return(Cocina.new(public_json)) end let(:public_json) do diff --git a/spec/controllers/object_controller_spec.rb b/spec/controllers/object_controller_spec.rb index fff45b96..c4cffa54 100644 --- a/spec/controllers/object_controller_spec.rb +++ b/spec/controllers/object_controller_spec.rb @@ -26,6 +26,7 @@ context "with downloadable files" do let(:json) do { + 'externalIdentifier' => 'druid:fd063dh3727', 'structural' => { 'contains' => [ { @@ -102,6 +103,7 @@ context "with a stanford access file" do let(:json) do { + 'externalIdentifier' => "druid:bb142ws0723", 'structural' => { 'contains' => [ { @@ -165,6 +167,7 @@ context "with a stanford access file" do let(:json) do { + 'externalIdentifier' => "druid:bb142ws0723", 'structural' => { 'contains' => [ { diff --git a/spec/models/purl_spec.rb b/spec/models/cocina_spec.rb similarity index 78% rename from spec/models/purl_spec.rb rename to spec/models/cocina_spec.rb index de913616..81adb401 100644 --- a/spec/models/purl_spec.rb +++ b/spec/models/cocina_spec.rb @@ -2,14 +2,15 @@ require 'rails_helper' -RSpec.describe Purl do +RSpec.describe Cocina do before do Rails.cache.clear end - describe '.files' do + describe '#files' do let(:json) do { + 'externalIdentifier' => 'abc', 'structural' => { 'contains' => [ { @@ -44,12 +45,12 @@ end before do - allow(Faraday).to receive(:get).with('https://purl.stanford.edu/abc.json') - .and_return(double(success?: true, body: json)) + stub_request(:get, "https://purl.stanford.edu/abc.json") + .to_return(status: 200, body: json) end it 'gets all the files for a resource' do - actual = described_class.files('abc').map { |file| "#{file.id}/#{file.file_name}" } + actual = described_class.find('abc').files.map { |file| "#{file.id}/#{file.file_name}" } expect(actual).to contain_exactly('abc/26855.jp2', 'abc/123.jp2') end diff --git a/spec/requests/file_auth_request_spec.rb b/spec/requests/file_auth_request_spec.rb index 53992b68..f8a1d6e5 100644 --- a/spec/requests/file_auth_request_spec.rb +++ b/spec/requests/file_auth_request_spec.rb @@ -16,7 +16,7 @@ let(:sf) { StacksFile.new(id: druid, file_name:) } before(:each) do - allow(Purl).to receive(:public_json).and_return(public_json) + allow(Cocina).to receive(:find).and_return(Cocina.new(public_json)) allow(File).to receive(:world_readable?).with(path).and_return(perms) end diff --git a/spec/requests/file_spec.rb b/spec/requests/file_spec.rb index ff17b8ee..28ad7d41 100644 --- a/spec/requests/file_spec.rb +++ b/spec/requests/file_spec.rb @@ -4,7 +4,7 @@ RSpec.describe "File requests" do before do - allow(Purl).to receive_messages(public_json:) + allow(Cocina).to receive(:find).and_return(Cocina.new(public_json)) end let(:druid) { 'nr349ct7889' } diff --git a/spec/requests/iiif/auth/v2/probe_service_spec.rb b/spec/requests/iiif/auth/v2/probe_service_spec.rb index e2cb0307..47b516bd 100644 --- a/spec/requests/iiif/auth/v2/probe_service_spec.rb +++ b/spec/requests/iiif/auth/v2/probe_service_spec.rb @@ -13,7 +13,7 @@ # rubocop:disable RSpec/AnyInstance before do - allow(Purl).to receive(:public_json).and_return(public_json) + allow(Cocina).to receive(:find).and_return(Cocina.new(public_json)) allow(File).to receive(:world_readable?).and_return('420') allow(StacksFile).to receive(:new).and_call_original end diff --git a/spec/requests/iiif_auth_request_spec.rb b/spec/requests/iiif_auth_request_spec.rb index b0aeb1d0..bf61ce68 100644 --- a/spec/requests/iiif_auth_request_spec.rb +++ b/spec/requests/iiif_auth_request_spec.rb @@ -37,7 +37,7 @@ allow(http_client).to receive(:get).and_return(instance_double(HTTP::Response, status: 200, body: StringIO.new)) allow_any_instance_of(IiifController).to receive(:current_user).and_return(current_user) allow_any_instance_of(IiifController).to receive(:current_image).and_return(current_image) - allow(Purl).to receive(:public_json).and_return(public_json) + allow(Cocina).to receive(:find).and_return(Cocina.new(public_json)) end context 'with a public item' do diff --git a/spec/requests/iiif_spec.rb b/spec/requests/iiif_spec.rb index 15b05133..24e5a41a 100644 --- a/spec/requests/iiif_spec.rb +++ b/spec/requests/iiif_spec.rb @@ -44,7 +44,7 @@ end before do - allow(Purl).to receive(:public_json).and_return(public_json) + allow(Cocina).to receive(:find).and_return(Cocina.new(public_json)) # stubbing Rails.cache.fetch is required because you can't dump a singleton (double) # which is what happens when writing to the cache. diff --git a/spec/requests/media_auth_request_spec.rb b/spec/requests/media_auth_request_spec.rb index f3399f01..aa0e985d 100644 --- a/spec/requests/media_auth_request_spec.rb +++ b/spec/requests/media_auth_request_spec.rb @@ -33,7 +33,7 @@ end before do - allow(Purl).to receive(:public_json).and_return(public_json) + allow(Cocina).to receive(:find).and_return(Cocina.new(public_json)) allow_any_instance_of(MediaController).to receive(:current_user).and_return(user) allow_any_instance_of(MediaController).to receive(:current_media).and_return(mock_media) end diff --git a/spec/requests/media_headers_request_spec.rb b/spec/requests/media_headers_request_spec.rb index e542c132..cfddf299 100644 --- a/spec/requests/media_headers_request_spec.rb +++ b/spec/requests/media_headers_request_spec.rb @@ -4,7 +4,7 @@ RSpec.describe "CORS headers for Media requests" do before do - allow(Purl).to receive(:public_json).and_return(public_json) + allow(Cocina).to receive(:find).and_return(Cocina.new(public_json)) end let(:public_json) do diff --git a/spec/requests/metrics_spec.rb b/spec/requests/metrics_spec.rb index 136b003e..a844b7e2 100644 --- a/spec/requests/metrics_spec.rb +++ b/spec/requests/metrics_spec.rb @@ -8,6 +8,7 @@ let(:file_name) { 'image.jp2' } let(:json) do { + 'externalIdentifier' => "druid:#{druid}", 'structural' => { 'contains' => [ { diff --git a/spec/requests/remote_iiif_image_delivery_spec.rb b/spec/requests/remote_iiif_image_delivery_spec.rb index 793f72dc..bbb5c0df 100644 --- a/spec/requests/remote_iiif_image_delivery_spec.rb +++ b/spec/requests/remote_iiif_image_delivery_spec.rb @@ -35,7 +35,7 @@ end before do - allow(Purl).to receive(:public_json).and_return(public_json) + allow(Cocina).to receive(:find).and_return(Cocina.new(public_json)) stub_request(:get, info_request) .to_return(status: 200, body: info_response) stub_request(:get, image_response) diff --git a/spec/services/iiif_info_service_spec.rb b/spec/services/iiif_info_service_spec.rb index 94159789..545396ce 100644 --- a/spec/services/iiif_info_service_spec.rb +++ b/spec/services/iiif_info_service_spec.rb @@ -174,7 +174,7 @@ end before do - allow(Purl).to receive(:public_json).and_return(public_json) + allow(Cocina).to receive(:find).and_return(Cocina.new(public_json)) allow(image).to receive(:restricted_by_location?).and_return(true) end @@ -222,7 +222,7 @@ end before do - allow(Purl).to receive(:public_json).and_return(public_json) + allow(Cocina).to receive(:find).and_return(Cocina.new(public_json)) allow(image).to receive_messages(stanford_restricted?: true, restricted_by_location?: true) end