Skip to content

Instantly share code, notes, and snippets.

@ahoward
Created May 3, 2012 14:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ahoward/2585895 to your computer and use it in GitHub Desktop.
Save ahoward/2585895 to your computer and use it in GitHub Desktop.
require "mongo"
require "open-uri"
require "mime/types"
require "digest/md5"
class Upload
##
#
include Mongoid::Document
include Mongoid::Timestamps
##
#
belongs_to(:context, :polymorphic => true)
##
#
field(:basename)
field(:tmp, :type => Boolean, :default => false)
##
#
validates_presence_of(:basename)
##
#
class Variant
##
#
include Mongoid::Document
include Mongoid::Timestamps
##
#
field(:name, :default => 'original', :type => String)
field(:default, :default => false)
field(:content_type, :type => String)
field(:length, :type => Integer)
field(:md5, :type => String)
field(:grid_fs, :type => Hash)
field(:s3, :type => Hash)
##
#
validates_presence_of(:name)
validates_presence_of(:content_type)
validates_presence_of(:length)
validates_presence_of(:md5)
##
#
embedded_in(:upload, :class_name => '::Upload')
##
#
before_destroy do |variant|
if variant.grid_fs?
variant.grid_fs_file.destroy rescue nil
end
if variant.s3?
variant.s3_object.destroy rescue nil
end
true
end
##
#
def grid_fs_file
if grid_fs?
namespace = GridFS.namespace_for(grid_fs['prefix'])
namespace.get(grid_fs['file_id'])
end
end
def grid_fs?
!grid_fs.blank?
end
##
#
def s3?
!s3.blank?
end
def s3_object
nil
end
##
#
def url_for(*args, &block)
case
when grid_fs?
"#{ Upload.route }/#{ upload.id }/#{ name }/#{ basename }"
end
end
def url(*args, &block)
url_for(*args, &block)
end
def to_s(*args, &block)
if grid_fs?
return grid_fs_file.to_s(*args, &block)
end
if s3?
return s3_object.to_s(*args, &block)
end
end
def image?
content_type.to_s.split('/').include?('image')
end
end
##
#
embeds_many(:variants, :class_name => '::Upload::Variant') do
def find_by_name(name)
all.detect{|variant| variant.name.to_s == name.to_s}
end
alias_method(:for, :find_by_name)
def original
find_by_name(:original)
end
def default
all.detect{|variant| variant.default} || original
end
def url_for(*args, &block)
options = args.extract_options!.to_options!
name = args.shift || options.delete(:name)
variant = find_by_name(name) || original
variant.url_for(options) if variant
end
def url(*args, &block)
url_for(*args, &block)
end
end
before_destroy do |upload|
upload.variants.destroy_all
true
end
##
# upload support
#
# /system/uploads/ 1234567890/original/logo.png
# /system/uploads/ 1234567890/small/logo.png
#
def add_variant(name, io_or_path, options = {})
name = name.to_s.downcase.to_sym
basename = Upload.extract_basename(io_or_path)
content_type = Upload.extract_content_type(basename)
filename = "#{ Upload.route }/#{ id }/#{ name }/#{ basename }"
options.to_options!
options[:filename] = filename
grid_fs_file = Upload.grid.put(io_or_path, options)
attributes = {
'name' => name,
'basename' => basename,
'grid_fs' => {
'prefix' => grid_fs_file.namespace.prefix,
'file_id' => grid_fs_file.id
},
'content_type' => grid_fs_file.content_type,
'length' => grid_fs_file.length,
'md5' => grid_fs_file.md5
}
variant = variants.build(attributes)
self.basename = variant.basename if self.basename.blank?
variant
end
def Upload.upload(*args, &block)
new.tap{|u| u.upload(*args, &block)}
end
def Upload.upload!(*args, &block)
new.tap{|u| u.upload!(*args, &block)}
end
def upload(*args, &block)
variants.destroy_all
add_variant(:original, *args, &block)
end
def upload!(*args, &block)
upload(*args, &block)
ensure
save!
end
def io=(io)
upload(io)
end
def path=(path)
upload(path)
end
def url_for(*args, &block)
variants.url_for(*args, &block)
end
def url(*args, &block)
url_for(*args, &block)
end
def urls(*args, &block)
variants.map{|variant| variant.url(*args, &block)}
end
def grid_fs_files
variants.map{|variant| variant.grid_fs_file}.compact
end
def grid_fs_file_chunks
grid_fs_files.map{|grid_fs_file| grid_fs_file.chunks}.compact.flatten
end
def image?
variants.any?{|variant| variant.image?}
end
def Upload.route
"/system/uploads"
end
def Upload.id
new.id
end
def Upload.grid
@grid ||= GridFS.namespace_for(:fs)
end
def Upload.extract_basename(object)
filename = nil
[:original_path, :original_filename, :path, :filename, :pathname].each do |msg|
if object.respond_to?(msg)
filename = object.send(msg)
break
end
end
cleanname(filename || object.to_s)
end
def Upload.cleanname(pathname)
basename = ::File.basename(pathname.to_s)
CGI.unescape(basename).gsub(%r/[^0-9a-zA-Z_@)(~.-]/, '_').gsub(%r/_+/,'_')
end
def Upload.extract_content_type(filename)
content_type = MIME::Types.type_for(::File.basename(filename.to_s)).first
content_type.to_s if content_type
end
##
# tmpwatch support - nukes old files...
#
def Upload.tmpwatch!(conditions = {})
conditions.to_options!
if conditions.empty?
conditions.update(:updated_at.lt => 1.week.ago)
end
Upload.where(conditions.merge(:tmp => true)).each do |upload|
upload.destroy unless upload.context_id
end
end
at_exit{ Upload.tmpwatch! } if Rails.env.production?
end
class Upload
class SIO < StringIO
attr_accessor :pathname
def initialize(string, options = {})
super(string)
ensure
@pathname = options[:pathname]||options['pathname']
end
def basename
File.basename(pathname)
end
def dirname
File.dirname(pathname)
end
end
def Upload.sio(*args, &block)
SIO.new(*args, &block)
end
end
class Upload
##
# dao upload cache support
#
# mount(::Upload::Cache, :upload, :placeholder => 'han-solo.jpg')
#
def Upload.cache!(upload)
case
when upload.respond_to?(:read)
create!(:io => upload, :tmp => true)
else
create!(:path => upload.to_s, :tmp => true)
end
end
def cache!(io)
self.tmp = true
upload(io)
save!
self
end
def cache
id.to_s if persisted?
end
class Cache < ::Map
attr :conducer
attr :upload
attr :key
class << self
def mount(*args, &block)
new(*args, &block)
end
end
def initialize(conducer, *args)
@conducer = conducer
@options = Map.options_for!(args)
@key = args.flatten
@upload = Upload.new :placeholder => @options[:placeholder]
update(:url => @upload.placeholder.url, :file => nil, :cache => nil)
end
def _set(params = {})
if !params.blank?
if !params[:file].blank?
file = params[:file]
if !params[:cache].blank?
cache = params[:cache]
begin
@upload = Upload.find(cache).cache!(file)
rescue
@upload = Upload.cache!(file)
end
else
@upload = Upload.cache!(file)
end
else
if !params[:cache].blank?
cache = params[:cache]
begin
@upload = Upload.find(cache)
rescue
nil
end
end
end
end
unless @upload.new_record?
update(
:url => @upload.url,
:cache => @upload.cache
)
end
end
def value
@upload.id unless @upload.new_record?
end
def clear!
@upload.set(:tmp => false) if @upload.persisted?
end
def object
@upload
end
def _key
@key
end
def _value
@upload
end
def _clear
clear!
end
end
##
# placeholder support
#
class Placeholder < ::String
def Placeholder.route
"/assets"
end
def Placeholder.root
File.join(Rails.root, "app", "assets", "placeholders")
end
attr_accessor :url
attr_accessor :path
def initialize(placeholder = '', options = {})
replace(placeholder.to_s)
options.to_options!
@url = options[:url] || default_url
@path = options[:path] || default_path
end
def default_url
return nil if blank?
absolute? ? self : File.join(Placeholder.route, self)
end
def default_path
return nil if blank?
absolute? ? nil : File.join(Placeholder.root, self)
end
def basename
File.basename(self)
end
def absolute?
self =~ %r|\A([^:/]++:/)?/|
end
end
def placeholder
@placeholder ||= Placeholder.new
end
def placeholder=(placeholder)
@placeholder = placeholder.is_a?(Placeholder) ? placeholder : Placeholder.new(placeholder)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment