module ActionController
module Macros
module ImageMagick
DEFAULT_OPTIONS = { :action_name=>:imagemagick,
:cache=>nil,
:prerender=>false,
:max_recipe_level=>:builtin,
:commands_param=>:commands }
def self.append_features(base)
super
base.extend(ClassMethods)
end
module ClassMethods
def imagemagick_for_directory(image_path, options = {})
imagemagick_for(image_path.to_s, options)
end
def imagemagick_filter_for(action_name, options = {})
imagemagick_for(action_name.to_sym, options)
end
def imagemagick_for(image_source, options = {})
require 'RMagick'
configuration = Hash.new.merge!(ActionController::Macros::ImageMagick::DEFAULT_OPTIONS)
configuration.update(options)
if defined?(RAILS_ROOT)
configuration[:cache] = File.expand_path(configuration[:cache], RAILS_ROOT) unless configuration[:cache].nil?
end
configuration[:max_recipe_level] = configuration[:max_recipe_level].to_sym
configuration[:max_recipe_level] = :builtin unless [:builtin, :global, :local].member?(configuration[:max_recipe_level])
configuration[:local_recipes] = RecipeSet.new
if image_source.is_a?(String)
configuration[:image_path] = image_source
configuration[:image_path] = image_source.sub(/\/$/, '') if image_source =~ /\/$/
configuration[:type] = :action
if defined?(RAILS_ROOT)
configuration[:image_path] = File.expand_path(configuration[:image_path], RAILS_ROOT)
end
configuration[:action_name] = :imagemagick unless configuration.has_key?(:action_name)
configuration[:action_name] = configuration[:action_name].to_sym
else
configuration[:type] = :filter
configuration[:for_action] = image_source.to_sym
end
@imagemagick_macro_helper = ImageMagickMacroHelper.new(configuration)
define_method(:imagemagick_macro_helper) do
return self.class.instance_variable_get(:@imagemagick_macro_helper)
end
protected :imagemagick_macro_helper
define_method(:imagemagick_local_recipes) do
return imagemagick_macro_helper.local_recipes
end
protected :imagemagick_local_recipes
define_method(:url_for_imagemagick) do |filename_or_params, p_commands|
imagemagick_macro_helper.controller = self
url_for(imagemagick_macro_helper.url_options_for_imagemagick(filename_or_params, p_commands))
end
if configuration[:type]==:action
implement_as_action(configuration)
elsif configuration[:type]==:filter
implement_as_filter(configuration)
end
end
def imagemagick_recipe(name, recipe, version = nil)
if @imagemagick_macro_helper.nil?
raise(ActionControllerError, "Use imagemagick_for before you use imagemagick_recipe.")
end
@imagemagick_macro_helper.add_recipe(name, recipe, version)
end
private
def implement_as_action(configuration)
define_method(configuration[:action_name]) do
begin
imagemagick_macro_helper.controller = self
identification = @params["id"].to_s
commands = @params["#{configuration[:commands_param]}"]
if imagemagick_macro_helper.prerender? && !imagemagick_macro_helper.cached?(identification, commands)
render :text=>"Image not found.", :status=>404
end
result = imagemagick_macro_helper.render(identification, identification, commands)
if result.nil?
render :text=>"Image not found.", :status=>404
else
@response.headers["Content-type"] = result.mime_type
render :text=>result.to_blob
end
rescue Exception=>e
render :text=>"Image could not be processed. "+e.to_s, :status=>500
end
end
end
def implement_as_filter(configuration)
define_method(:imagemagick_after_filter) do
image = Magick::Image.from_blob(@response.body)
if !image.empty?
imagemagick_macro_helper.controller = self
identification = MD5.md5(@response.body).to_s
commands = @params["#{configuration[:commands_param]}"]
result = imagemagick_macro_helper.render(image.first, identification, commands)
begin
if result.nil?
@response.headers["Status"] = 404
@response.body = "Image not found."
else
@response.headers["Content-type"] = result.mime_type
@response.body = result.to_blob
end
rescue Exception=>e
@response.headers["Status"] = 500
@response.body = "Image could not be processed. "+e.to_s
end
end
end
module_eval do
after_filter :imagemagick_after_filter, :only=>"#{configuration[:for_action]}"
end
end
end
class ImageMagickMacroHelper
def initialize(configuration)
@configuration = configuration
end
def caching?
return !@configuration[:cache].nil?
end
def prerender?
return @configuration[:type]==:action && caching? && @configuration[:prerender]
end
def controller=(controller)
@configuration[:controller] = controller
end
def add_recipe(name, action, version = nil)
@configuration[:local_recipes].add(name, action, version)
end
def local_recipes
@configuration[:local_recipes]
end
def valid_filename?(filename)
pathname = expand_filename(filename)
if pathname =~ /^
return File.readable?(pathname)
else
return false
end
end
def expand_filename(filename)
return File.expand_path(filename, @configuration[:image_path] + "/")
end
def cache_filename(identification, commands)
commands = MagickCommandList.from_anything(commands)
return File.expand_path(@configuration[:controller].class.to_s.gsub(/[^A-Za-z0-9]/, '') + "." +
commands.to_cache_s(@configuration) + identification.gsub(/\//, '.'), @configuration[:cache]+"/")
end
def cached?(identification, commands)
clear_cache_if_stale(identification, commands)
return caching? && File.exists?(cache_filename(identification, commands))
end
def clear_cache_if_stale(filename, commands)
if caching? && @configuration[:type]==:action
source_path = expand_filename(filename)
cache_path = cache_filename(filename, commands)
if File.exists?(cache_path) && File.mtime(cache_path) < File.mtime(source_path)
FileUtils.rm(source_path)
end
end
end
def url_options_for_imagemagick(filename_or_params, p_commands)
if p_commands.nil? && filename_or_params.is_a?(Hash)
p_commands = filename_or_params.delete(@configuration[:commands_param])
elsif p_commands.nil?
p_commands = []
end
commands = MagickCommandList.from_anything(p_commands)
path_options = nil
if @configuration[:type]==:action
path_options = { :action=>@configuration[:action_name].to_s }
path_options[:id] = filename_or_params
path_options[@configuration[:commands_param]] = commands.to_s unless commands.commands.empty?
render(filename_or_params, filename_or_params, commands) if prerender?
elsif @configuration[:type]==:filter
path_options = { }
path_options.merge!(filename_or_params)
path_options[@configuration[:commands_param]] = commands.to_s unless commands.commands.empty?
end
return path_options
end
def render(image, identification, commands)
commands = ImageMagickMacroHelper::MagickCommandList.from_anything(commands)
clear_cache_if_stale(identification, commands)
if