diff --git a/app/controllers/playlists_controller.rb b/app/controllers/playlists_controller.rb index e93ed6a690..e7dd2786d4 100644 --- a/app/controllers/playlists_controller.rb +++ b/app/controllers/playlists_controller.rb @@ -247,10 +247,14 @@ def manifest authorize! :read, @playlist canvas_presenters = @playlist.items.collect do |item| - next if item.clip.master_file.nil? + cannot_read_item = cannot? :read, @master_file @master_file = item.clip.master_file - stream_info = secure_streams(@master_file.stream_details, @master_file.media_object_id) - IiifPlaylistCanvasPresenter.new(playlist_item: item, stream_info: stream_info) + stream_info = if @master_file.nil? + nil + else + secure_streams(@master_file.stream_details, @master_file.media_object_id) + end + IiifPlaylistCanvasPresenter.new(playlist_item: item, stream_info: stream_info, cannot_read_item: cannot_read_item) end presenter = IiifPlaylistManifestPresenter.new(playlist: @playlist, items: canvas_presenters) diff --git a/app/models/iiif_playlist_canvas_presenter.rb b/app/models/iiif_playlist_canvas_presenter.rb index c3a2ec53b9..852107ffd6 100644 --- a/app/models/iiif_playlist_canvas_presenter.rb +++ b/app/models/iiif_playlist_canvas_presenter.rb @@ -13,19 +13,26 @@ # --- END LICENSE_HEADER BLOCK --- class IiifPlaylistCanvasPresenter - attr_reader :playlist_item, :stream_info + attr_reader :playlist_item, :stream_info, :cannot_read_item attr_accessor :media_fragment - def initialize(playlist_item:, stream_info:, media_fragment: nil) + def initialize(playlist_item:, stream_info:, cannot_read_item: false, media_fragment: nil) @playlist_item = playlist_item @stream_info = stream_info + @cannot_read_item = cannot_read_item @media_fragment = media_fragment end delegate :id, to: :playlist_item def to_s - playlist_item.title + if cannot_read_item + "Restricted item" + elsif master_file.nil? + "Deleted item" + else + playlist_item.title + end end def master_file @@ -45,15 +52,31 @@ def range # @return [IIIFManifest::V3::DisplayContent] the display content required by the manifest builder. def display_content + return if cannot_read_item || master_file.nil? master_file.is_video? ? video_content : audio_content end def annotation_content + return if cannot_read_item || master_file.nil? playlist_item.marker.collect { |m| marker_content(m) } end - private + def placeholder_content + if cannot_read_item + IIIFManifest::V3::DisplayContent.new(nil, + label: 'You do not have permission to playback this item.', + type: 'Text', + format: 'text/plain') + elsif master_file.nil? + IIIFManifest::V3::DisplayContent.new(nil, + label: 'The source for this playlist item has been deleted.', + type: 'Text', + format: 'text/plain') + end + end + private + def video_content # @see https://github.com/samvera-labs/iiif_manifest stream_urls.collect { |quality, _url| video_display_content(quality) } diff --git a/spec/models/iiif_playlist_canvas_presenter_spec.rb b/spec/models/iiif_playlist_canvas_presenter_spec.rb index e336e86e3f..371d46bbda 100644 --- a/spec/models/iiif_playlist_canvas_presenter_spec.rb +++ b/spec/models/iiif_playlist_canvas_presenter_spec.rb @@ -23,7 +23,30 @@ let(:stream_info) { master_file.stream_details } let(:presenter) { described_class.new(playlist_item: playlist_item, stream_info: stream_info) } - context 'auth_service' do + describe '#to_s' do + it 'returns the playlist_item label' do + expect(presenter.to_s).to eq playlist_item.title + end + + context 'when a user does not have access to a restricted item' do + let(:presenter) { described_class.new(playlist_item: playlist_item, stream_info: stream_info, cannot_read_item: true) } + + it 'return "Restricted item"' do + expect(presenter.to_s).to eq "Restricted item" + end + end + + context 'when an item has been deleted' do + let(:master_file) { nil } + let(:stream_info) { nil } + + it 'returns "Deleted item"' do + expect(presenter.to_s).to eq "Deleted item" + end + end + end + + describe '#auth_service' do subject { presenter.display_content.first.auth_service } it 'provides a cookie auth service' do @@ -73,11 +96,28 @@ expect(subject.url).to include("#t=#{playlist_item.start_time / 1000},#{playlist_item.end_time / 1000}") end end + + context 'when a user does not have access to a restricted item' do + let(:presenter) { described_class.new(playlist_item: playlist_item, stream_info: stream_info, cannot_read_item: true) } + + it 'does not serialize the content' do + expect(presenter.display_content).to be_nil + end + end + + context 'when an item has been deleted' do + let(:master_file) { nil } + let(:stream_info) { nil } + + it 'does not serialize the content' do + expect(presenter.display_content).to be_nil + end + end end describe '#annotation_content' do let(:marker) { FactoryBot.create(:avalon_marker) } - let(:playlist_item) { FactoryBot.create(:playlist_item, marker: [marker]) } + let(:playlist_item) { FactoryBot.build(:playlist_item, clip: playlist_clip, marker: [marker]) } subject { presenter.annotation_content } it "serializes playlist markers as iiif annotations" do @@ -95,6 +135,23 @@ it "includes 'highlighting' motivation" do expect(subject.first.motivation).to eq 'highlighting' end + + context 'when a user does not have access to a restricted item' do + let(:presenter) { described_class.new(playlist_item: playlist_item, stream_info: stream_info, cannot_read_item: true) } + + it 'does not serialize the content' do + expect(presenter.annotation_content).to be_nil + end + end + + context 'when an item has been deleted' do + let(:master_file) { nil } + let(:stream_info) { nil } + + it 'does not serialize the content' do + expect(presenter.annotation_content).to be_nil + end + end end describe '#range' do @@ -115,4 +172,53 @@ expect(subject.any? { |po| po["@id"] =~ /media_objects\/#{media_object.id}\/manifest/ }).to eq true end end + + describe '#placeholder_contetnt' do + subject { presenter.placeholder_content } + + it 'does not generate a placeholder for non-restricted items' do + expect(subject).to be_nil + end + + context 'when a user does not have access to a restricted item' do + let(:presenter) { described_class.new(playlist_item: playlist_item, stream_info: stream_info, cannot_read_item: true) } + + it 'generates placeholder canvas' do + expect(subject).to be_present + end + + it 'has format' do + expect(subject.format).to eq "text/plain" + end + + it 'has type' do + expect(subject.type).to eq "Text" + end + + it 'has label' do + expect(subject.label).to eq "You do not have permission to playback this item." + end + end + + context 'when an item has been deleted' do + let(:master_file) { nil } + let(:stream_info) { nil } + + it 'generated placeholder canvas' do + expect(subject).to be_present + end + + it 'has format' do + expect(subject.format).to eq "text/plain" + end + + it 'has type' do + expect(subject.type).to eq "Text" + end + + it 'has label' do + expect(subject.label).to eq "The source for this playlist item has been deleted." + end + end + end end