Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extract xml deserialization from the ResourceFile model #1775

Merged
merged 1 commit into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions app/models/embed/purl/file_xml_deserializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# frozen_string_literal: true

module Embed
class Purl
class FileXmlDeserializer
# @param [String] druid the identifier of the resource this file belongs to.
# @param [String] description the label for this file
# @param [Nokogiri::XML::Element] file
# @param [Dor::RightsAuth] rights
def initialize(druid, description, file, rights)
@druid = druid
@description = description
@file = file
@rights = rights
end

def deserialize
ResourceFile.new(
druid: @druid,
label: @description,
filename: @file.attributes['id'].value,
mimetype: @file.attributes['mimetype']&.value,
size: @file.attributes['size']&.value.to_i,
rights: @rights
) do |file|
if @file.xpath('./*/@duration').present?
file.duration = Embed::MediaDuration.new(@file.xpath('./*[@duration]').first).to_s
end
end
end
end
end
end
2 changes: 1 addition & 1 deletion app/models/embed/purl/resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def three_dimensional?
# @return [Array<ResourceFile>]
def files
@files ||= @resource.xpath('./file').map do |file|
ResourceFile.new(druid, description, file, @rights)
FileXmlDeserializer.new(druid, description, file, @rights).deserialize
end
end

Expand Down
40 changes: 11 additions & 29 deletions app/models/embed/purl/resource_file.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,20 @@
module Embed
class Purl
class ResourceFile
# @param [String] druid the identifier of the resource this file belongs to.
# @param [String] description the label for this file
# @param [Nokogiri::XML::Element] file
# @param [Dor::RightsAuth] rights
def initialize(druid, description, file, rights)
@druid = druid
@description = description
@file = file
@rights = rights
def initialize(attributes = {})
self.attributes = attributes
yield(self) if block_given?
end

def label
@description
def attributes=(hash)
hash.each do |key, value|
public_send("#{key}=", value)
end
end

def title
@file.attributes['id'].try(:value)
end
attr_accessor :druid, :label, :filename, :mimetype, :size, :duration, :rights

alias title filename

##
# Creates a file url for stacks
Expand Down Expand Up @@ -50,24 +46,10 @@ def pdf?
mimetype == 'application/pdf'
end

def mimetype
@file.attributes['mimetype'].try(:value)
end

def image?
mimetype =~ %r{image/jp2}i
end

# @return [Integer]
def size
@file.attributes['size']&.value.to_i
end

def duration
md = Embed::MediaDuration.new(@file.xpath('./*[@duration]').first) if @file.xpath('./*/@duration').present?
md&.to_s
end

def stanford_only?
value, _rule = @rights.stanford_only_rights_for_file(title)

Expand All @@ -79,7 +61,7 @@ def location_restricted?
end

def world_downloadable?
@rights.world_downloadable_file?(@file.attributes['id'])
@rights.world_downloadable_file?(title)
end
end
end
Expand Down
76 changes: 76 additions & 0 deletions spec/models/embed/purl/file_xml_deserializer_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Embed::Purl::FileXmlDeserializer do
describe '#deserialize' do
let(:file_node) do
Nokogiri::XML(
<<~XML
<file size="12345" mimetype="image/jp2" id="myvideo.mp4"/>
XML
).root
end

let(:resource_file) { described_class.new('abc123', 'desc', file_node, nil).deserialize }

it 'creates a resource file' do
expect(resource_file.druid).to eq 'abc123'
expect(resource_file.label).to eq 'desc'
expect(resource_file.mimetype).to eq 'image/jp2'
expect(resource_file.size).to eq 12_345
expect(resource_file.filename).to eq 'myvideo.mp4'
expect(resource_file.duration).to be_nil
end

context 'with duration in videoData' do
let(:file_node) do
Nokogiri::XML(
<<~XML
<file size="12345" mimetype="image/jp2" id="myvideo.mp4">
<videoData duration='P0DT1H2M3S'/>
</file>
XML
).root
end

it 'gets the duration string' do
expect(resource_file.duration).to eq '1:02:03'
end
end

context 'with duration in audioData' do
let(:file_node) do
Nokogiri::XML(
<<~XML
<file size="12345" mimetype="image/jp2" id="myvideo.mp4">
<audioData duration='PT43S'/>
</file>
XML
).root
end

it 'gets the duration string' do
expect(resource_file.duration).to eq '0:43'
end
end

context 'when the duration in audioData is invalid' do
let(:file_node) do
Nokogiri::XML(
<<~XML
<file size="12345" mimetype="image/jp2" id="myvideo.mp4">
<audioData duration='invalid'/>
</file>
XML
).root
end

it 'returns nil and logs an error' do
allow(Honeybadger).to receive(:notify)
expect(resource_file.duration).to be_nil
expect(Honeybadger).to have_received(:notify).with("ResourceFile#media duration ISO8601::Errors::UnknownPattern: 'invalid'")
end
end
end
end
57 changes: 6 additions & 51 deletions spec/models/embed/purl/resource_file_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,9 @@
end

describe '#file_url' do
let(:file_node) do
Nokogiri::XML(<<~XML
<file size="12345" mimetype="image/jp2" id="#{file_name}"/>
XML
).root
end

let(:file_name) { 'cool_file' }

let(:filename) { 'cool_file' }
let(:resource) { instance_double(Embed::Purl::Resource, druid: 'abc123') }
let(:resource_file) { described_class.new('abc123', 'desc', file_node, nil) }
let(:resource_file) { described_class.new(druid: 'abc123', filename:) }

it 'creates a stacks file url' do
expect(resource_file.file_url).to eq 'https://stacks.stanford.edu/file/druid:abc123/cool_file'
Expand All @@ -48,15 +40,15 @@
end

context 'when there are special characters in the file name' do
let(:file_name) { '[Dissertation] micro-TEC vfinal (for submission)-augmented.pdf' }
let(:filename) { '[Dissertation] micro-TEC vfinal (for submission)-augmented.pdf' }

it 'escapes them' do
expect(resource_file.file_url).to eq 'https://stacks.stanford.edu/file/druid:abc123/%5BDissertation%5D%20micro-TEC%20vfinal%20%28for%20submission%29-augmented.pdf'
end
end

context 'when there are literal slashes in the file name' do
let(:file_name) { 'path/to/[Dissertation] micro-TEC vfinal (for submission)-augmented.pdf' }
let(:filename) { 'path/to/[Dissertation] micro-TEC vfinal (for submission)-augmented.pdf' }

it 'allows them' do
expect(resource_file.file_url)
Expand All @@ -76,11 +68,10 @@
end

describe '#label' do
let(:resource_file) { instance_double(Nokogiri::XML::Element, attributes: { 'id' => double(value: 'The File ID') }) }
let(:resource_file) { described_class.new(label: 'The Resource Description') }

it 'is the resource description' do
file = described_class.new('abc123', 'The Resource Description', resource_file, double('rights'))
expect(file.label).to eq 'The Resource Description'
expect(resource_file.label).to eq 'The Resource Description'
end
end

Expand Down Expand Up @@ -162,42 +153,6 @@
end
end

describe 'duration' do
let(:rf) { described_class.new('123', 'desc', f, double('Rights')) }
let(:f) { double('File') }

it 'gets duration string from videoData' do
video_data_el = Nokogiri::XML("<videoData duration='P0DT1H2M3S'/>").root
expect(f).to receive(:xpath).with('./*/@duration').and_return(['something'])
expect(f).to receive(:xpath).with('./*[@duration]').and_return([video_data_el])
expect(Embed::MediaDuration).to receive(:new).and_call_original
expect(rf.duration).to eq '1:02:03'
end

it 'gets duration string from audioData' do
audio_data_el = Nokogiri::XML("<audioData duration='PT43S'/>").root
expect(f).to receive(:xpath).with('./*/@duration').and_return(['something'])
expect(f).to receive(:xpath).with('./*[@duration]').and_return([audio_data_el])
expect(Embed::MediaDuration).to receive(:new).and_call_original
expect(rf.duration).to eq '0:43'
end

it 'nil when missing media data element' do
allow(f).to receive(:xpath)
expect(Embed::MediaDuration).not_to receive(:new)
expect(rf.duration).to be_nil
end

it 'invalid format returns nil and logs an error' do
audio_data_el = Nokogiri::XML("<audioData duration='invalid'/>").root
expect(f).to receive(:xpath).with('./*/@duration').and_return(['something'])
expect(f).to receive(:xpath).with('./*[@duration]').and_return([audio_data_el])
expect(Honeybadger).to receive(:notify).with("ResourceFile#media duration ISO8601::Errors::UnknownPattern: 'invalid'")
expect(Embed::MediaDuration).to receive(:new).and_call_original
expect(rf.duration).to be_nil
end
end

describe '#vtt?' do
subject { file.vtt? }

Expand Down