move processors and backends to separate modules
git-svn-id: http://svn.techno-weenie.net/projects/plugins/attachment_fu@2569 567b1171-46fb-0310-a4c9-b4bef9110e78
This commit is contained in:
Родитель
79f29133f3
Коммит
c3b5889fb4
9
init.rb
9
init.rb
|
@ -1 +1,10 @@
|
|||
require 'tempfile'
|
||||
|
||||
class Tempfile
|
||||
# overwrite so tempfiles have no extension
|
||||
def make_tmpname(basename, n)
|
||||
sprintf("%s%d-%d", basename, $$, n)
|
||||
end
|
||||
end
|
||||
|
||||
ActiveRecord::Base.send(:extend, Technoweenie::AttachmentFu::ActMethods)
|
|
@ -1,19 +1,9 @@
|
|||
require File.join(File.dirname(__FILE__), 'attachment_fu', 'backends')
|
||||
require File.join(File.dirname(__FILE__), 'attachment_fu', 'processors')
|
||||
require 'tempfile'
|
||||
|
||||
class Tempfile
|
||||
# overwrite so tempfiles have no extension
|
||||
def make_tmpname(basename, n)
|
||||
sprintf("%s%d-%d", basename, $$, n)
|
||||
end
|
||||
end
|
||||
|
||||
module Technoweenie # :nodoc:
|
||||
module AttachmentFu # :nodoc:
|
||||
@@default_processors = %w(Rmagick)
|
||||
@@tempfile_path = File.join(RAILS_ROOT, 'tmp', 'attachment_fu')
|
||||
@@content_types = ['image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/x-png']
|
||||
mattr_reader :content_types, :tempfile_path
|
||||
mattr_reader :content_types, :tempfile_path, :default_processors
|
||||
|
||||
class ThumbnailError < StandardError; end
|
||||
class AttachmentError < StandardError; end
|
||||
|
@ -57,7 +47,6 @@ module Technoweenie # :nodoc:
|
|||
class_inheritable_accessor :attachment_options
|
||||
attr_accessor :thumbnail_resize_options
|
||||
|
||||
options[:processor] ||= :rmagick
|
||||
options[:storage] ||= options[:file_system_path] ? :file_system : :db_file
|
||||
options[:file_system_path] ||= File.join("public", table_name)
|
||||
options[:file_system_path] = options[:file_system_path][1..-1] if options[:file_system_path].first == '/'
|
||||
|
@ -72,8 +61,20 @@ module Technoweenie # :nodoc:
|
|||
after_destroy :destroy_file
|
||||
extend ClassMethods
|
||||
include InstanceMethods
|
||||
include Technoweenie::AttachmentFu::const_get("#{options[:storage].to_s.classify}Backend")
|
||||
include Technoweenie::AttachmentFu::const_get("#{options[:processor].to_s.classify}Processor")
|
||||
include Technoweenie::AttachmentFu::Backends.const_get("#{options[:storage].to_s.classify}")
|
||||
case options[:processor]
|
||||
when :none
|
||||
when nil
|
||||
processors = Technoweenie::AttachmentFu.default_processors.dup
|
||||
begin
|
||||
include Technoweenie::AttachmentFu::Processors.const_get(processors.first) if processors.any?
|
||||
rescue LoadError
|
||||
processors.shift
|
||||
retry
|
||||
end
|
||||
else
|
||||
include Technoweenie::AttachmentFu::Processors.const_get("#{options[:processor].to_s.classify}")
|
||||
end
|
||||
before_save :process_attachment
|
||||
end
|
||||
|
||||
|
|
|
@ -1,110 +0,0 @@
|
|||
module Technoweenie # :nodoc:
|
||||
module AttachmentFu # :nodoc:
|
||||
# Methods for file system backed attachments
|
||||
module FileSystemBackend
|
||||
def self.included(base) #:nodoc:
|
||||
base.before_update :rename_file
|
||||
end
|
||||
|
||||
# Gets the full path to the filename in this format:
|
||||
#
|
||||
# # This assumes a model name like MyModel
|
||||
# # public/#{table_name} is the default filesystem path
|
||||
# RAILS_ROOT/public/my_models/5/blah.jpg
|
||||
#
|
||||
# Overwrite this method in your model to customize the filename.
|
||||
# The optional thumbnail argument will output the thumbnail's filename.
|
||||
def full_filename(thumbnail = nil)
|
||||
file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:file_system_path].to_s
|
||||
File.join(RAILS_ROOT, file_system_path, attachment_path_id, thumbnail_name_for(thumbnail))
|
||||
end
|
||||
|
||||
# Used as the base path that #public_filename strips off full_filename to create the public path
|
||||
def base_path
|
||||
@base_path ||= File.join(RAILS_ROOT, 'public')
|
||||
end
|
||||
|
||||
# The attachment ID used in the full path of a file
|
||||
def attachment_path_id
|
||||
((respond_to?(:parent_id) && parent_id) || id).to_s
|
||||
end
|
||||
|
||||
# Gets the public path to the file
|
||||
# The optional thumbnail argument will output the thumbnail's filename.
|
||||
def public_filename(thumbnail = nil)
|
||||
full_filename(thumbnail).gsub %r(^#{Regexp.escape(base_path)}), ''
|
||||
end
|
||||
|
||||
def filename=(value)
|
||||
@old_filename = full_filename unless filename.nil? || @old_filename
|
||||
write_attribute :filename, sanitize_filename(value)
|
||||
end
|
||||
|
||||
def create_temp_file
|
||||
copy_to_temp_file full_filename
|
||||
end
|
||||
|
||||
# Destroys the file. Called in the after_destroy callback
|
||||
def destroy_file
|
||||
FileUtils.rm full_filename rescue nil
|
||||
end
|
||||
|
||||
def rename_file
|
||||
return unless @old_filename && @old_filename != full_filename
|
||||
if save_attachment? && File.exists?(@old_filename)
|
||||
FileUtils.rm @old_filename
|
||||
elsif File.exists?(@old_filename)
|
||||
FileUtils.mv @old_filename, full_filename
|
||||
end
|
||||
@old_filename = nil
|
||||
true
|
||||
end
|
||||
|
||||
# Saves the file to the file system
|
||||
def save_to_storage
|
||||
if save_attachment?
|
||||
# TODO: This overwrites the file if it exists, maybe have an allow_overwrite option?
|
||||
FileUtils.mkdir_p(File.dirname(full_filename))
|
||||
FileUtils.mv temp_path, full_filename
|
||||
end
|
||||
@old_filename = nil
|
||||
true
|
||||
end
|
||||
|
||||
def current_data
|
||||
File.file?(full_filename) ? File.read(full_filename) : nil
|
||||
end
|
||||
end
|
||||
|
||||
# Methods for DB backed attachments
|
||||
module DbFileBackend
|
||||
def self.included(base) #:nodoc:
|
||||
Object.const_set(:DbFile, Class.new(ActiveRecord::Base)) unless Object.const_defined?(:DbFile)
|
||||
base.belongs_to :db_file, :class_name => '::DbFile', :foreign_key => 'db_file_id'
|
||||
end
|
||||
|
||||
def create_temp_file
|
||||
write_to_temp_file db_file.data
|
||||
end
|
||||
|
||||
# Destroys the file. Called in the after_destroy callback
|
||||
def destroy_file
|
||||
db_file.destroy if db_file
|
||||
end
|
||||
|
||||
# Saves the data to the DbFile model
|
||||
def save_to_storage
|
||||
if save_attachment?
|
||||
(db_file || build_db_file).data = temp_data
|
||||
db_file.save!
|
||||
self.class.update_all ['db_file_id = ?', self.db_file_id = db_file.id], ['id = ?', id]
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def current_data
|
||||
db_file.data
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,36 @@
|
|||
module Technoweenie # :nodoc:
|
||||
module AttachmentFu # :nodoc:
|
||||
module Backends
|
||||
# Methods for DB backed attachments
|
||||
module DbFile
|
||||
def self.included(base) #:nodoc:
|
||||
Object.const_set(:DbFile, Class.new(ActiveRecord::Base)) unless Object.const_defined?(:DbFile)
|
||||
base.belongs_to :db_file, :class_name => '::DbFile', :foreign_key => 'db_file_id'
|
||||
end
|
||||
|
||||
def create_temp_file
|
||||
write_to_temp_file db_file.data
|
||||
end
|
||||
|
||||
# Destroys the file. Called in the after_destroy callback
|
||||
def destroy_file
|
||||
db_file.destroy if db_file
|
||||
end
|
||||
|
||||
# Saves the data to the DbFile model
|
||||
def save_to_storage
|
||||
if save_attachment?
|
||||
(db_file || build_db_file).data = temp_data
|
||||
db_file.save!
|
||||
self.class.update_all ['db_file_id = ?', self.db_file_id = db_file.id], ['id = ?', id]
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def current_data
|
||||
db_file.data
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,81 @@
|
|||
module Technoweenie # :nodoc:
|
||||
module AttachmentFu # :nodoc:
|
||||
module Backends
|
||||
# Methods for file system backed attachments
|
||||
module FileSystem
|
||||
def self.included(base) #:nodoc:
|
||||
base.before_update :rename_file
|
||||
end
|
||||
|
||||
# Gets the full path to the filename in this format:
|
||||
#
|
||||
# # This assumes a model name like MyModel
|
||||
# # public/#{table_name} is the default filesystem path
|
||||
# RAILS_ROOT/public/my_models/5/blah.jpg
|
||||
#
|
||||
# Overwrite this method in your model to customize the filename.
|
||||
# The optional thumbnail argument will output the thumbnail's filename.
|
||||
def full_filename(thumbnail = nil)
|
||||
file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:file_system_path].to_s
|
||||
File.join(RAILS_ROOT, file_system_path, attachment_path_id, thumbnail_name_for(thumbnail))
|
||||
end
|
||||
|
||||
# Used as the base path that #public_filename strips off full_filename to create the public path
|
||||
def base_path
|
||||
@base_path ||= File.join(RAILS_ROOT, 'public')
|
||||
end
|
||||
|
||||
# The attachment ID used in the full path of a file
|
||||
def attachment_path_id
|
||||
((respond_to?(:parent_id) && parent_id) || id).to_s
|
||||
end
|
||||
|
||||
# Gets the public path to the file
|
||||
# The optional thumbnail argument will output the thumbnail's filename.
|
||||
def public_filename(thumbnail = nil)
|
||||
full_filename(thumbnail).gsub %r(^#{Regexp.escape(base_path)}), ''
|
||||
end
|
||||
|
||||
def filename=(value)
|
||||
@old_filename = full_filename unless filename.nil? || @old_filename
|
||||
write_attribute :filename, sanitize_filename(value)
|
||||
end
|
||||
|
||||
def create_temp_file
|
||||
copy_to_temp_file full_filename
|
||||
end
|
||||
|
||||
# Destroys the file. Called in the after_destroy callback
|
||||
def destroy_file
|
||||
FileUtils.rm full_filename rescue nil
|
||||
end
|
||||
|
||||
def rename_file
|
||||
return unless @old_filename && @old_filename != full_filename
|
||||
if save_attachment? && File.exists?(@old_filename)
|
||||
FileUtils.rm @old_filename
|
||||
elsif File.exists?(@old_filename)
|
||||
FileUtils.mv @old_filename, full_filename
|
||||
end
|
||||
@old_filename = nil
|
||||
true
|
||||
end
|
||||
|
||||
# Saves the file to the file system
|
||||
def save_to_storage
|
||||
if save_attachment?
|
||||
# TODO: This overwrites the file if it exists, maybe have an allow_overwrite option?
|
||||
FileUtils.mkdir_p(File.dirname(full_filename))
|
||||
FileUtils.mv temp_path, full_filename
|
||||
end
|
||||
@old_filename = nil
|
||||
true
|
||||
end
|
||||
|
||||
def current_data
|
||||
File.file?(full_filename) ? File.read(full_filename) : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,100 +0,0 @@
|
|||
module Technoweenie # :nodoc:
|
||||
module AttachmentFu # :nodoc:
|
||||
module RmagickProcessor
|
||||
def self.included(base)
|
||||
begin
|
||||
require 'RMagick'
|
||||
rescue LoadError
|
||||
# boo hoo no rmagick
|
||||
end
|
||||
base.send :extend, ClassMethods
|
||||
base.alias_method_chain :process_attachment, :processing
|
||||
base.alias_method_chain :after_process_attachment, :processing
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Yields a block containing an RMagick Image for the given binary data.
|
||||
def with_image(file, &block)
|
||||
begin
|
||||
binary_data = file.is_a?(Magick::Image) ? file : Magick::Image.read(file).first unless !Object.const_defined?(:Magick)
|
||||
rescue
|
||||
# Log the failure to load the image. This should match ::Magick::ImageMagickError
|
||||
# but that would cause acts_as_attachment to require rmagick.
|
||||
logger.debug("Exception working with image: #{$!}")
|
||||
binary_data = nil
|
||||
end
|
||||
block.call binary_data if block && binary_data
|
||||
ensure
|
||||
!binary_data.nil?
|
||||
end
|
||||
end
|
||||
|
||||
# Allows you to work with an RMagick representation of the attachment in a block.
|
||||
#
|
||||
# @attachment.with_image do |img|
|
||||
# self.data = img.thumbnail(100, 100).to_blob
|
||||
# end
|
||||
#
|
||||
def with_image(&block)
|
||||
self.class.with_image(temp_path, &block)
|
||||
end
|
||||
|
||||
# Creates or updates the thumbnail for the current attachment.
|
||||
def create_or_update_thumbnail(temp_file, file_name_suffix, *size)
|
||||
thumbnailable? || raise(ThumbnailError.new("Can't create a thumbnail if the content type is not an image or there is no parent_id column"))
|
||||
returning find_or_initialize_thumbnail(file_name_suffix) do |thumb|
|
||||
thumb.attributes = {
|
||||
:content_type => content_type,
|
||||
:filename => thumbnail_name_for(file_name_suffix),
|
||||
:temp_path => temp_file,
|
||||
:thumbnail_resize_options => size
|
||||
}
|
||||
callback_with_args :before_thumbnail_saved, thumb
|
||||
thumb.save!
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def process_attachment_with_processing
|
||||
return unless process_attachment_without_processing
|
||||
with_image do |img|
|
||||
if !respond_to?(:parent_id) || parent_id.nil? # parent image
|
||||
thumbnail_for_image(img, attachment_options[:resize_to]) if attachment_options[:resize_to]
|
||||
else # thumbnail
|
||||
thumbnail_for_image(img, thumbnail_resize_options) if thumbnail_resize_options
|
||||
end
|
||||
self.width = img.columns if respond_to?(:width)
|
||||
self.height = img.rows if respond_to?(:height)
|
||||
callback_with_args :after_resize, img
|
||||
end if image?
|
||||
end
|
||||
|
||||
def after_process_attachment_with_processing
|
||||
return unless @saved_attachment
|
||||
if thumbnailable? && !attachment_options[:thumbnails].blank? && parent_id.nil?
|
||||
temp_file = temp_path || create_temp_file
|
||||
attachment_options[:thumbnails].each { |suffix, size| create_or_update_thumbnail(temp_file, suffix, *size) }
|
||||
end
|
||||
after_process_attachment_without_processing
|
||||
end
|
||||
|
||||
# Performs the actual resizing operation for a thumbnail
|
||||
def thumbnail_for_image(img, size)
|
||||
size = size.first if size.is_a?(Array) && size.length == 1 && !size.first.is_a?(Fixnum)
|
||||
if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
|
||||
size = [size, size] if size.is_a?(Fixnum)
|
||||
img.thumbnail!(*size)
|
||||
else
|
||||
img.change_geometry(size.to_s) { |cols, rows, image| image.resize!(cols, rows) }
|
||||
end
|
||||
self.temp_path = write_to_temp_file(img.to_blob)
|
||||
end
|
||||
|
||||
def find_or_initialize_thumbnail(file_name_suffix)
|
||||
respond_to?(:parent_id) ?
|
||||
thumbnail_class.find_or_initialize_by_thumbnail_and_parent_id(file_name_suffix.to_s, id) :
|
||||
thumbnail_class.find_or_initialize_by_thumbnail(file_name_suffix.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,98 @@
|
|||
require 'RMagick'
|
||||
module Technoweenie # :nodoc:
|
||||
module AttachmentFu # :nodoc:
|
||||
module Processors
|
||||
module Rmagick
|
||||
def self.included(base)
|
||||
base.send :extend, ClassMethods
|
||||
base.alias_method_chain :process_attachment, :processing
|
||||
base.alias_method_chain :after_process_attachment, :processing
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Yields a block containing an RMagick Image for the given binary data.
|
||||
def with_image(file, &block)
|
||||
begin
|
||||
binary_data = file.is_a?(Magick::Image) ? file : Magick::Image.read(file).first unless !Object.const_defined?(:Magick)
|
||||
rescue
|
||||
# Log the failure to load the image. This should match ::Magick::ImageMagickError
|
||||
# but that would cause acts_as_attachment to require rmagick.
|
||||
logger.debug("Exception working with image: #{$!}")
|
||||
binary_data = nil
|
||||
end
|
||||
block.call binary_data if block && binary_data
|
||||
ensure
|
||||
!binary_data.nil?
|
||||
end
|
||||
end
|
||||
|
||||
# Allows you to work with an RMagick representation of the attachment in a block.
|
||||
#
|
||||
# @attachment.with_image do |img|
|
||||
# self.data = img.thumbnail(100, 100).to_blob
|
||||
# end
|
||||
#
|
||||
def with_image(&block)
|
||||
self.class.with_image(temp_path, &block)
|
||||
end
|
||||
|
||||
# Creates or updates the thumbnail for the current attachment.
|
||||
def create_or_update_thumbnail(temp_file, file_name_suffix, *size)
|
||||
thumbnailable? || raise(ThumbnailError.new("Can't create a thumbnail if the content type is not an image or there is no parent_id column"))
|
||||
returning find_or_initialize_thumbnail(file_name_suffix) do |thumb|
|
||||
thumb.attributes = {
|
||||
:content_type => content_type,
|
||||
:filename => thumbnail_name_for(file_name_suffix),
|
||||
:temp_path => temp_file,
|
||||
:thumbnail_resize_options => size
|
||||
}
|
||||
callback_with_args :before_thumbnail_saved, thumb
|
||||
thumb.save!
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def process_attachment_with_processing
|
||||
return unless process_attachment_without_processing
|
||||
with_image do |img|
|
||||
if !respond_to?(:parent_id) || parent_id.nil? # parent image
|
||||
thumbnail_for_image(img, attachment_options[:resize_to]) if attachment_options[:resize_to]
|
||||
else # thumbnail
|
||||
thumbnail_for_image(img, thumbnail_resize_options) if thumbnail_resize_options
|
||||
end
|
||||
self.width = img.columns if respond_to?(:width)
|
||||
self.height = img.rows if respond_to?(:height)
|
||||
callback_with_args :after_resize, img
|
||||
end if image?
|
||||
end
|
||||
|
||||
def after_process_attachment_with_processing
|
||||
return unless @saved_attachment
|
||||
if thumbnailable? && !attachment_options[:thumbnails].blank? && parent_id.nil?
|
||||
temp_file = temp_path || create_temp_file
|
||||
attachment_options[:thumbnails].each { |suffix, size| create_or_update_thumbnail(temp_file, suffix, *size) }
|
||||
end
|
||||
after_process_attachment_without_processing
|
||||
end
|
||||
|
||||
# Performs the actual resizing operation for a thumbnail
|
||||
def thumbnail_for_image(img, size)
|
||||
size = size.first if size.is_a?(Array) && size.length == 1 && !size.first.is_a?(Fixnum)
|
||||
if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
|
||||
size = [size, size] if size.is_a?(Fixnum)
|
||||
img.thumbnail!(*size)
|
||||
else
|
||||
img.change_geometry(size.to_s) { |cols, rows, image| image.resize!(cols, rows) }
|
||||
end
|
||||
self.temp_path = write_to_temp_file(img.to_blob)
|
||||
end
|
||||
|
||||
def find_or_initialize_thumbnail(file_name_suffix)
|
||||
respond_to?(:parent_id) ?
|
||||
thumbnail_class.find_or_initialize_by_thumbnail_and_parent_id(file_name_suffix.to_s, id) :
|
||||
thumbnail_class.find_or_initialize_by_thumbnail(file_name_suffix.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Загрузка…
Ссылка в новой задаче