Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allows adding URL redirects as files
This creates a service class called AddExternalFileToFileSet which acts very similar to AddFileToFileSet, but instead of expecting binary data, it expects a URL. This URL will go into the external-content header of the Fedora resource, and Fedora will automatically redirect to that URL. The motivation is to provide the basis for more advanced external storage features higher up in the Hydra stack.
- Loading branch information
Showing
4 changed files
with
171 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
108 changes: 108 additions & 0 deletions
108
lib/hydra/works/services/add_external_file_to_file_set.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
module Hydra::Works | ||
class AddExternalFileToFileSet | ||
# Adds a file to the file_set | ||
# @param [Hydra::PCDM::FileSet] file_set the file will be added to | ||
# @param [String] external_file_url URL representing the externally stored file | ||
# @param [RDF::URI or String] type URI for the RDF.type that identifies the file's role within the file_set | ||
# @param [Hash] opts Options | ||
# @option opts [Boolean] :update_existing When set to true, performs a create_or_update. When set to false, always creates a new file within file_set.files. | ||
# @option opts [Boolean] :versioning Whether to create new version entries (only applicable if +type+ corresponds to a versionable file) | ||
# @option opts [String] :filename A string for the original file name. Defaults to the same value as external_file_url | ||
def self.call(file_set, external_file_url, type, opts = {}) | ||
fail ArgumentError, 'supplied object must be a file set' unless file_set.file_set? | ||
|
||
update_existing = opts.fetch(:update_existing, true) | ||
versioning = opts.fetch(:versioning, true) | ||
filename = opts.fetch(:filename, external_file_url) | ||
|
||
# TODO: required as a workaround for https://github.com/projecthydra/active_fedora/pull/858 | ||
file_set.save unless file_set.persisted? | ||
|
||
updater_class = versioning ? VersioningUpdater : Updater | ||
updater = updater_class.new(file_set, type, update_existing) | ||
status = updater.update(external_file_url, filename) | ||
status ? file_set : false | ||
end | ||
|
||
class Updater | ||
attr_reader :file_set, :current_file | ||
|
||
def initialize(file_set, type, update_existing) | ||
@file_set = file_set | ||
@current_file = find_or_create_file(type, update_existing) | ||
end | ||
|
||
# @param [#read] file object that will be interrogated using the methods: :path, :original_name, :original_filename, :mime_type, :content_type | ||
# None of the attribute description methods are required. | ||
def update(external_file_url, filename = nil) | ||
attach_attributes(external_file_url, filename) | ||
persist | ||
end | ||
|
||
private | ||
|
||
# Persist a new file with its containing file set; otherwise, just save the file itself | ||
def persist | ||
if current_file.new_record? | ||
file_set.save | ||
else | ||
current_file.save | ||
end | ||
end | ||
|
||
def attach_attributes(external_file_url, filename = nil) | ||
current_file.content = StringIO.new('') | ||
current_file.original_name = filename | ||
current_file.mime_type = "message/external-body; access-type=URL; URL=\"#{external_file_url}\"" | ||
end | ||
|
||
# @param [Symbol, RDF::URI] the type of association or filter to use | ||
# @param [true, false] update_existing when true, try to retrieve existing element before building one | ||
def find_or_create_file(type, update_existing) | ||
if type.instance_of? Symbol | ||
find_or_create_file_for_symbol(type, update_existing) | ||
else | ||
find_or_create_file_for_rdf_uri(type, update_existing) | ||
end | ||
end | ||
|
||
def find_or_create_file_for_symbol(type, update_existing) | ||
association = file_set.association(type) | ||
fail ArgumentError, "you're attempting to add a file to a file_set using '#{type}' association but the file_set does not have an association called '#{type}''" unless association | ||
current_file = association.reader if update_existing | ||
current_file || association.build | ||
end | ||
|
||
def find_or_create_file_for_rdf_uri(type, update_existing) | ||
current_file = file_set.filter_files_by_type(type_to_uri(type)).first if update_existing | ||
unless current_file | ||
file_set.files.build | ||
current_file = file_set.files.last | ||
Hydra::PCDM::AddTypeToFile.call(current_file, type_to_uri(type)) | ||
end | ||
current_file | ||
end | ||
|
||
# Returns appropriate URI for the requested type | ||
# * Converts supported symbols to corresponding URIs | ||
# * Converts URI strings to RDF::URI | ||
# * Returns RDF::URI objects as-is | ||
def type_to_uri(type) | ||
case type | ||
when ::RDF::URI | ||
type | ||
when String | ||
::RDF::URI(type) | ||
else | ||
fail ArgumentError, 'Invalid file type. You must submit a URI or a symbol.' | ||
end | ||
end | ||
end | ||
|
||
class VersioningUpdater < Updater | ||
def update(*) | ||
super && current_file.create_version | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
spec/hydra/works/services/add_external_file_to_file_set_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
require 'spec_helper' | ||
|
||
describe Hydra::Works::AddExternalFileToFileSet do | ||
let(:file_set) { Hydra::Works::FileSet.new } | ||
let(:file_set2) { Hydra::Works::FileSet.new } | ||
let(:filename) { 'sample-file.pdf' } | ||
let(:filename2) { 'updated-file.txt' } | ||
let(:original_name) { 'original-name.pdf' } | ||
# let(:file) { File.open(File.join(fixture_path, filename)) } | ||
# let(:file2) { File.open(File.join(fixture_path, filename2)) } | ||
let(:external_file_url) { "http://foo.org/abc1234" } | ||
let(:type) { ::RDF::URI('http://pcdm.org/use#ExtractedText') } | ||
let(:update_existing) { true } | ||
# let(:mime_type) { 'application/pdf' } | ||
|
||
context 'when file_set is not persisted' do | ||
let(:file_set) { Hydra::Works::FileSet.new } | ||
it 'saves file_set' do | ||
described_class.call(file_set, external_file_url, type) | ||
expect(file_set.persisted?).to be true | ||
end | ||
end | ||
|
||
context 'when file_set is not valid' do | ||
before do | ||
file_set.save | ||
allow(file_set).to receive(:valid?).and_return(false) | ||
end | ||
it 'returns false' do | ||
expect(described_class.call(file_set, external_file_url, type)).to be false | ||
end | ||
end | ||
|
||
context 'when file set is valid' do | ||
before do | ||
described_class.call(file_set, external_file_url, type, filename: original_name) | ||
end | ||
|
||
subject(:file) { file_set.filter_files_by_type(type).first } | ||
|
||
it 'sets mime type of the File object to message/external body containing external file URL' do | ||
expect(file.mime_type).to eq "message/external-body;access-type=URL;url=\"http://foo.org/abc1234\"" | ||
end | ||
|
||
it 'assigns value of :filename option to the File object' do | ||
expect(file.original_name).to eq original_name | ||
end | ||
|
||
context 'when no filename is passed in' do | ||
before do | ||
described_class.call(file_set, external_file_url, type) | ||
end | ||
|
||
subject(:file) { file_set.filter_files_by_type(type).last } | ||
|
||
it 'sets filename of File objectd to be the same as the external file url' do | ||
expect(file.original_name).to eq external_file_url | ||
end | ||
end | ||
end | ||
end |