Skip to content

Instantly share code, notes, and snippets.

@patrickmcelwee
Created June 19, 2014 17:57
Show Gist options
  • Save patrickmcelwee/8c5d5b452afc7f520a1f to your computer and use it in GitHub Desktop.
Save patrickmcelwee/8c5d5b452afc7f520a1f to your computer and use it in GitHub Desktop.
Automated Vivo Image Uploads
require 'vivo_mapper/resource'
require 'app/maps/image_map'
module VivoMapper
class Image < Resource
map_with(ImageMap)
attr_reader :type, :download_location, :thumb, :content, :image
def initialize(image,type='image')
@type=type
@image = image
@download_location = VivoMapper::ImageFileLocation.new(image,type)
if type == 'image'
@thumb = VivoMapper::Image.new(image.thumb,'thumb')
end
super()
end
def uid
"file_" + download_location.uid
end
def person
download_location.person
end
def vivo_id
download_location.vivo_id
end
def mime_type
return nil unless image.file
case image.file.extension
when 'jpg' || 'jpeg' || 'jpe'
'image/jpeg'
when 'png'
'image/png'
when 'gif'
'image/gif'
else
'image/jpeg'
end
end
def content
# What does this do?
image
end
def file_name
download_location.file_name
end
protected
def full_path_and_name
download_location.full_path_and_name
end
end
end
require 'vivo_mapper/resource'
require 'app/maps/image_file_location_map'
module VivoMapper
class ImageFileLocation < Resource
map_with(ImageFileLocationMap)
attr_reader :type, :image
def initialize(image,type)
@type=type
@image = image
super()
end
def vivo_id
@vivo_id ||= person.vivo_id
end
def person
@person ||= image.model
end
def uid
"#{type[0]}#{vivo_id}"
end
def file_name
prefix = "#{image.version_name}_" if image.version_name
"#{prefix}#{image.filename}"
end
def full_path_and_name
"/file/#{uid}/#{file_name}"
end
end
end
require 'vivo_mapper/rdf_prefixes'
require 'date'
module ImageFileLocationMap
include RdfPrefixes
extend self
def uri(namespace,f)
"#{namespace}#{f.uid}"
end
def most_specific_type(f)
vpublic("FileByteStream")
end
def properties(f)
{ vpublic("directDownloadUrl") => f.full_path_and_name,
vitro("modTime") => DateTime.now }
end
end
require 'vivo_mapper/rdf_prefixes'
require "diffable"
require 'date'
module ImageMap
include RdfPrefixes
extend Diffable
extend self
def uri(namespace,f)
"#{namespace}#{f.uid}"
end
def most_specific_type(f)
vpublic("File")
end
def properties(f)
properties = {
vpublic("filename") => f.file_name,
vpublic("downloadLocation") => f.download_location,
vpublic("mimeType") => f.mime_type,
vitro("modTime") => DateTime.now }
if f.type == 'image'
properties.merge! ({
vpublic("thumbnailImage") => f.thumb,
duke("imageOf") => f.person.as_stub })
end
properties
end
def sparql_constructs(person_uri)
sparql =<<-EOS
PREFIX vitro-public: <http://vitro.mannlib.cornell.edu/ns/vitro/public#>
CONSTRUCT { ?image ?p ?o.
?s ?p2 ?image.
?location ?p3 ?o2.
?s2 ?p4 ?location.
?thumb ?p5 ?o3.
?s3 ?p6 ?thumb.
?t_location ?p7 ?o4.
?s4 ?p8 ?t_location.
}
WHERE { <#{person_uri}> vitro-public:mainImage ?image.
?image vitro-public:thumbnailImage ?thumb.
?image ?p ?o.
?s ?p2 ?image.
?image vitro-public:downloadLocation ?location.
?location ?p3 ?o2.
?s2 ?p4 ?location.
?image vitro-public:thumbnailImage ?thumb.
?thumb ?p5 ?o3.
?s3 ?p6 ?thumb.
?thumb vitro-public:downloadLocation ?t_location.
?t_location ?p7 ?o4.
?s4 ?p8 ?t_location.
}
EOS
Array(sparql)
end
end
class PersonProfileImageCropper
constructor: ->
$('#cropbox').Jcrop
aspectRatio: 1
setSelect: [0, 0, 600, 600]
onSelect: @update
onChange: @update
update: (coords) =>
$('#profile_image_crop_x').val(coords.x)
$('#profile_image_crop_y').val(coords.y)
$('#profile_image_crop_w').val(coords.w)
$('#profile_image_crop_h').val(coords.h)
@updatePreview(coords)
updatePreview: (coords) =>
$('#preview').css
width: Math.round(160/coords.w * $('#cropbox').width()) + 'px'
height: Math.round(160/coords.h * $('#cropbox').height()) + 'px'
marginLeft: '-' + Math.round(160/coords.w * coords.x) + 'px'
marginTop: '-' + Math.round(160/coords.h * coords.y) + 'px'
shared_context "profile_image" do
let(:krucoff) { FactoryGirl.create(:krucoff) }
let(:image_file_name) { "image_#{krucoff.vivo_id}.jpg" }
let(:thumb_file_name) { "thumb_image_#{krucoff.vivo_id}.jpg" }
let(:dir_path) { "PATH_TO_VIVO_DATA/data/uploads/file_storage_root" }
let(:image_base_path) { "#{dir_path}/a~i" }
let(:thumb_base_path) { "#{dir_path}/a~t" }
let(:image_path) { "#{image_base_path}/036/525/2/#{image_file_name}" }
let(:thumb_path) { "#{thumb_base_path}/036/525/2/#{thumb_file_name}" }
let(:namespace) { "https://scholars.duke.edu/individual/" }
let(:image_location_uid) { "i#{krucoff.vivo_id}" }
let(:image_uid) { "file_#{image_location_uid}" }
let(:image_uri) { namespace + image_uid }
let(:image_location_uri) { namespace + image_location_uid }
let(:thumb_location_uid) { "t#{krucoff.vivo_id}" }
let(:thumbnail_uid) { "file_#{thumb_location_uid}" }
let(:thumbnail_uri) { namespace + thumbnail_uid }
let(:thumb_location_uri) { namespace + thumb_location_uid }
let(:krucoff_uri) { namespace + "per0365252" }
let(:date_regex) { /http:\/\/www.w3.org\/2001\/XMLSchema#dateTime/ }
after(:each) do
clear_rdf_models %w(Image)
[image_base_path, thumb_base_path].each do |base_path|
FileUtils.rm_r(base_path + "/036") if Dir.exists?(base_path + "/036")
end
end
def image_attributes_should_be_loaded_correctly_in_rdf
image_attrs = query(file_sparql(image_uri)).first
image_attrs[:type].split('#')[1].should == "File"
image_attrs[:filename].should == image_file_name
image_attrs[:download_location].should == image_location_uri
image_attrs[:mime_type].should == "image/jpeg"
image_attrs[:thumb].should == thumbnail_uri
image_attrs[:person].should == krucoff_uri
image_attrs[:mod_time].should =~ date_regex
end
def image_location_attributes_should_be_loaded_correctly_in_rdf
image_location_attrs = query(location_sparql(image_location_uri)).first
image_location_attrs[:type].should == "http://vitro.mannlib.cornell.edu/ns/vitro/public#FileByteStream"
image_location_attrs[:download_url].should == "/file/#{image_location_uid}/#{image_file_name}"
image_location_attrs[:mod_time].should =~ date_regex
end
def thumbnail_attributes_should_be_loaded_correctly_in_rdf
thumbnail_attrs = query(file_sparql(thumbnail_uri, false)).first
thumbnail_attrs[:type].split('#')[1].should == "File"
thumbnail_attrs[:filename].should == thumb_file_name
thumbnail_attrs[:download_location].should == thumb_location_uri
thumbnail_attrs[:mime_type].should == "image/jpeg"
thumbnail_attrs[:mod_time].should =~ date_regex
end
def thumbnail_location_attributes_should_be_loaded_correctly_in_rdf
thumb_loc_attrs = query(location_sparql(thumb_location_uri)).first
thumb_loc_attrs[:type].should == "http://vitro.mannlib.cornell.edu/ns/vitro/public#FileByteStream"
thumb_loc_attrs[:download_url].should == "/file/#{thumb_location_uid}/#{thumb_file_name}"
thumb_loc_attrs[:mod_time].should =~ date_regex
end
def prefixes
<<-EOS
PREFIX vitro-public: <http://vitro.mannlib.cornell.edu/ns/vitro/public#>
PREFIX vitro: <http://vitro.mannlib.cornell.edu/ns/vitro/0.7#>
PREFIX duke: <http://vivo.duke.edu/vivo/ontology/duke-extension#>
EOS
end
def main_image_triples(image_uri, is_main_image)
if is_main_image
<<-EOS
OPTIONAL { <#{image_uri}> vitro-public:thumbnailImage ?thumb.}
OPTIONAL {<#{image_uri}> duke:imageOf ?person.}
EOS
end
end
def file_sparql(image_uri, is_main_image=true)
<<-EOS
#{prefixes}
SELECT *
WHERE {
OPTIONAL {<#{image_uri}> vitro:mostSpecificType ?type.}
OPTIONAL {<#{image_uri}> vitro-public:filename ?filename.}
OPTIONAL {<#{image_uri}> vitro-public:downloadLocation ?download_location.}
OPTIONAL {<#{image_uri}> vitro-public:mimeType ?mime_type.}
OPTIONAL {<#{image_uri}> vitro:modTime ?mod_time.}
OPTIONAL {#{main_image_triples(image_uri, is_main_image)}}
}
EOS
end
def location_sparql(image_location_uri)
<<-EOS
#{prefixes}
SELECT *
WHERE {
OPTIONAL {<#{image_location_uri}> vitro:mostSpecificType ?type.}
OPTIONAL {<#{image_location_uri}> vitro-public:directDownloadUrl ?download_url.}
OPTIONAL {<#{image_location_uri}> vitro:modTime ?mod_time.}
}
EOS
end
end
require 'carrierwave_imagevoodoo'
class ProfileImageUploader < CarrierWave::Uploader::Base
include CarrierWave::ImageVoodoo
# Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
# include Sprockets::Helpers::RailsHelper
# include Sprockets::Helpers::IsolatedHelper
storage :file
def store_dir(for_file=filename)
if version_name == :large_for_cropping
"vivo_admin/uploads/"
else
literal_root_path + namespacing + vivo_id_to_dir
end
end
version :large_for_cropping do
process :resize_to_limit => [600, 600]
end
version :thumb, from_version: :large_for_cropping do
process :crop
process :resize_to_fill => [200, 200]
end
def extension_white_list
%w(jpg jpeg gif png)
end
def crop
if model.crop_x.present?
manipulate! do |img|
x1 = model.crop_x.to_i
y1 = model.crop_y.to_i
x2 = model.crop_w.to_i + x1
y2 = model.crop_h.to_i + y1
img.with_crop(x1, y1, x2, y2) do |file|
file.save(self.current_path)
end
end
end
model.import_profile_image
end
# Override the filename of the uploaded files:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
def filename
"image_#{vivo_id}.#{original_extension}" if original_filename
end
def url
if version_name == :large_for_cropping
super
else
file ? vivo_url : default_url
end
end
private
def original_extension
original_filename.split(".").last
end
def default_url
'person_thumbnail.jpg'
end
def vivo_url
"#{VivoAdmin::Application.config.base_url}/file/#{version_char}#{vivo_id}/#{file.filename}"
end
def literal_root_path
'/srv/web/apps/vivo/shared/data/uploads/file_storage_root/'
end
def namespacing
"a~#{version_char}/"
end
def version_char
version_name ? version_name.to_s[0] : 'i'
end
def vivo_id_to_dir
vivo_id.scan(/.{1,3}/).join('/') + '/'
end
def vivo_id
model.vivo_id
end
end
require 'rubygems'
require 'carrierwave'
require 'active_record'
require 'spec_helper_lite'
require_relative '../../app/uploaders/profile_image_uploader'
stub_class "VivoAdmin::Application"
describe ProfileImageUploader do
before(:each) do
@professor = double(vivo_id: '0365252')
described_class.enable_processing = false
@uploader = described_class.new(@professor, :profile_image)
end
context "before image uploaded" do
it "gives the correct default url" do
@uploader.url.should =~ /person_thumbnail\.jpg/
end
end
context "when image uploaded" do
before(:each) do
@base_dir = "PATH_TO_VIVO_DATA/data/uploads/file_storage_root/"
@vivo_id_dir = "036/525/2/"
FileUtils.mkdir_p @base_dir + 'a~i' + @vivo_id_dir
FileUtils.mkdir_p @base_dir + 'a~t' + @vivo_id_dir
@uploader.store!(File.open("spec/support/images/person_profile/0117469.jpg"))
end
after(:each) do
@uploader.remove!
end
it "stores the main image file in the correct place" do
image_file_name = "image_#{@professor.vivo_id}.jpg"
@uploader.file.file.should == @base_dir + 'a~i/' + @vivo_id_dir + image_file_name
end
it "stores the thumbnail file in the correct place" do
thumb_file_name = "thumb_image_#{@professor.vivo_id}.jpg"
@uploader.thumb.file.file.should == @base_dir + 'a~t/' + @vivo_id_dir + thumb_file_name
end
it "gives the image the correct url" do
config = double(base_url: '')
VivoAdmin::Application.stub!(:config).and_return(config)
@uploader.url.should == "/file/i#{@professor.vivo_id}/image_#{@professor.vivo_id}.jpg"
end
end
end
@patrickmcelwee
Copy link
Author

Here are some of the files that handle the automated image uploads - the 'uploader' files are mostly concerned with cropping and placing the image files in the correct places in the filesystem, using the carrierwave ruby library. The spec files specify the two important parts of the process: 1, where we want those images saved; and 2, what triples we want to create. Creating the large (600x600) and thumbnail (200x200) images is perhaps the third important part of the process.

The map files show the predicates and values we are putting into the RDF. 'image' and 'image_location' are the resources that the map file uses to get its values.

We also have a batch uploader (not included here) that pushes a directory of images through this code.

Hope that's helpful. Cleaner and clearer documentation of what is required to do this would certainly fill a gap. (Or perhaps the file storage could be configurable through the triples themselves, which would make this quite a bit easier, I think.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment