Commit 3abcb2223493962a909b7c26f9c208ec1ca583b0

Authored by AntonioTerceiro
1 parent 66518fce

ActionItem392: adding attachment_fu from upstream with piston; still breaks with ruby 1.8.7



git-svn-id: https://svn.colivre.coop.br/svn/noosfero/trunk@1852 3f533792-8f58-4932-b0fe-aaf55b0a4547
Showing 44 changed files with 3180 additions and 0 deletions   Show diff stats
vendor/plugins/attachment_fu/.gitignore 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +test/amazon_s3.yml
  2 +test/debug.log
... ...
vendor/plugins/attachment_fu/CHANGELOG 0 → 100644
... ... @@ -0,0 +1,35 @@
  1 +* Apr 17 2008 *
  2 +* amazon_s3.yml is now passed through ERB before being passed to AWS::S3 [François Beausoleil]
  3 +
  4 +* Mar 22 2008 *
  5 +* Some tweaks to support Rails 2.0 and Rails 2.1 due to ActiveSupport::Callback changes.
  6 + Thanks to http://blog.methodmissing.com/2008/1/19/edge-callback-refactorings-attachment_fu/
  7 +
  8 +* Feb. 26, 2008 *
  9 +* remove breakpoint from test_helper, makes test suite crazy (at least Rails 2+) [Rob Sanheim]
  10 +* make S3 test really optional [Rob Sanheim]
  11 +
  12 +* Nov 27, 2007 *
  13 +* Handle properly ImageScience thumbnails resized from a gif file [Matt Aimonetti]
  14 +* Save thumbnails file size properly when using ImageScience [Matt Aimonetti]
  15 +* fixed s3 config file loading with latest versions of Rails [Matt Aimonetti]
  16 +
  17 +* April 2, 2007 *
  18 +
  19 +* don't copy the #full_filename to the default #temp_paths array if it doesn't exist
  20 +* add default ID partitioning for attachments
  21 +* add #binmode call to Tempfile (note: ruby should be doing this!) [Eric Beland]
  22 +* Check for current type of :thumbnails option.
  23 +* allow customization of the S3 configuration file path with the :s3_config_path option.
  24 +* Don't try to remove thumbnails if there aren't any. Closes #3 [ben stiglitz]
  25 +
  26 +* BC * (before changelog)
  27 +
  28 +* add default #temp_paths entry [mattly]
  29 +* add MiniMagick support to attachment_fu [Isacc]
  30 +* update #destroy_file to clear out any empty directories too [carlivar]
  31 +* fix references to S3Backend module [Hunter Hillegas]
  32 +* make #current_data public with db_file and s3 backends [ebryn]
  33 +* oops, actually svn add the files for s3 backend. [Jeffrey Hardy]
  34 +* experimental s3 support, egad, no tests.... [Jeffrey Hardy]
  35 +* doh, fix a few bad references to ActsAsAttachment [sixty4bit]
... ...
vendor/plugins/attachment_fu/README 0 → 100644
... ... @@ -0,0 +1,162 @@
  1 +attachment-fu
  2 +=============
  3 +
  4 +attachment_fu is a plugin by Rick Olson (aka technoweenie <http://techno-weenie.net>) and is the successor to acts_as_attachment. To get a basic run-through of its capabilities, check out Mike Clark's tutorial <http://clarkware.com/cgi/blosxom/2007/02/24#FileUploadFu>.
  5 +
  6 +
  7 +attachment_fu functionality
  8 +===========================
  9 +
  10 +attachment_fu facilitates file uploads in Ruby on Rails. There are a few storage options for the actual file data, but the plugin always at a minimum stores metadata for each file in the database.
  11 +
  12 +There are three storage options for files uploaded through attachment_fu:
  13 + File system
  14 + Database file
  15 + Amazon S3
  16 +
  17 +Each method of storage many options associated with it that will be covered in the following section. Something to note, however, is that the Amazon S3 storage requires you to modify config/amazon_s3.yml and the Database file storage requires an extra table.
  18 +
  19 +
  20 +attachment_fu models
  21 +====================
  22 +
  23 +For all three of these storage options a table of metadata is required. This table will contain information about the file (hence the 'meta') and its location. This table has no restrictions on naming, unlike the extra table required for database storage, which must have a table name of db_files (and by convention a model of DbFile).
  24 +
  25 +In the model there are two methods made available by this plugins: has_attachment and validates_as_attachment.
  26 +
  27 +has_attachment(options = {})
  28 + This method accepts the options in a hash:
  29 + :content_type # Allowed content types.
  30 + # Allows all by default. Use :image to allow all standard image types.
  31 + :min_size # Minimum size allowed.
  32 + # 1 byte is the default.
  33 + :max_size # Maximum size allowed.
  34 + # 1.megabyte is the default.
  35 + :size # Range of sizes allowed.
  36 + # (1..1.megabyte) is the default. This overrides the :min_size and :max_size options.
  37 + :resize_to # Used by RMagick to resize images.
  38 + # Pass either an array of width/height, or a geometry string.
  39 + :thumbnails # Specifies a set of thumbnails to generate.
  40 + # This accepts a hash of filename suffixes and RMagick resizing options.
  41 + # This option need only be included if you want thumbnailing.
  42 + :thumbnail_class # Set which model class to use for thumbnails.
  43 + # This current attachment class is used by default.
  44 + :path_prefix # path to store the uploaded files.
  45 + # Uses public/#{table_name} by default for the filesystem, and just #{table_name} for the S3 backend.
  46 + # Setting this sets the :storage to :file_system.
  47 + :storage # Specifies the storage system to use..
  48 + # Defaults to :db_file. Options are :file_system, :db_file, and :s3.
  49 + :processor # Sets the image processor to use for resizing of the attached image.
  50 + # Options include ImageScience, Rmagick, and MiniMagick. Default is whatever is installed.
  51 +
  52 +
  53 + Examples:
  54 + has_attachment :max_size => 1.kilobyte
  55 + has_attachment :size => 1.megabyte..2.megabytes
  56 + has_attachment :content_type => 'application/pdf'
  57 + has_attachment :content_type => ['application/pdf', 'application/msword', 'text/plain']
  58 + has_attachment :content_type => :image, :resize_to => [50,50]
  59 + has_attachment :content_type => ['application/pdf', :image], :resize_to => 'x50'
  60 + has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
  61 + has_attachment :storage => :file_system, :path_prefix => 'public/files'
  62 + has_attachment :storage => :file_system, :path_prefix => 'public/files',
  63 + :content_type => :image, :resize_to => [50,50]
  64 + has_attachment :storage => :file_system, :path_prefix => 'public/files',
  65 + :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
  66 + has_attachment :storage => :s3
  67 +
  68 +validates_as_attachment
  69 + This method prevents files outside of the valid range (:min_size to :max_size, or the :size range) from being saved. It does not however, halt the upload of such files. They will be uploaded into memory regardless of size before validation.
  70 +
  71 + Example:
  72 + validates_as_attachment
  73 +
  74 +
  75 +attachment_fu migrations
  76 +========================
  77 +
  78 +Fields for attachment_fu metadata tables...
  79 + in general:
  80 + size, :integer # file size in bytes
  81 + content_type, :string # mime type, ex: application/mp3
  82 + filename, :string # sanitized filename
  83 + that reference images:
  84 + height, :integer # in pixels
  85 + width, :integer # in pixels
  86 + that reference images that will be thumbnailed:
  87 + parent_id, :integer # id of parent image (on the same table, a self-referencing foreign-key).
  88 + # Only populated if the current object is a thumbnail.
  89 + thumbnail, :string # the 'type' of thumbnail this attachment record describes.
  90 + # Only populated if the current object is a thumbnail.
  91 + # Usage:
  92 + # [ In Model 'Avatar' ]
  93 + # has_attachment :content_type => :image,
  94 + # :storage => :file_system,
  95 + # :max_size => 500.kilobytes,
  96 + # :resize_to => '320x200>',
  97 + # :thumbnails => { :small => '10x10>',
  98 + # :thumb => '100x100>' }
  99 + # [ Elsewhere ]
  100 + # @user.avatar.thumbnails.first.thumbnail #=> 'small'
  101 + that reference files stored in the database (:db_file):
  102 + db_file_id, :integer # id of the file in the database (foreign key)
  103 +
  104 +Field for attachment_fu db_files table:
  105 + data, :binary # binary file data, for use in database file storage
  106 +
  107 +
  108 +attachment_fu views
  109 +===================
  110 +
  111 +There are two main views tasks that will be directly affected by attachment_fu: upload forms and displaying uploaded images.
  112 +
  113 +There are two parts of the upload form that differ from typical usage.
  114 + 1. Include ':multipart => true' in the html options of the form_for tag.
  115 + Example:
  116 + <% form_for(:attachment_metadata, :url => { :action => "create" }, :html => { :multipart => true }) do |form| %>
  117 +
  118 + 2. Use the file_field helper with :uploaded_data as the field name.
  119 + Example:
  120 + <%= form.file_field :uploaded_data %>
  121 +
  122 +Displaying uploaded images is made easy by the public_filename method of the ActiveRecord attachment objects using file system and s3 storage.
  123 +
  124 +public_filename(thumbnail = nil)
  125 + Returns the public path to the file. If a thumbnail prefix is specified it will return the public file path to the corresponding thumbnail.
  126 + Examples:
  127 + attachment_obj.public_filename #=> /attachments/2/file.jpg
  128 + attachment_obj.public_filename(:thumb) #=> /attachments/2/file_thumb.jpg
  129 + attachment_obj.public_filename(:small) #=> /attachments/2/file_small.jpg
  130 +
  131 +When serving files from database storage, doing more than simply downloading the file is beyond the scope of this document.
  132 +
  133 +
  134 +attachment_fu controllers
  135 +=========================
  136 +
  137 +There are two considerations to take into account when using attachment_fu in controllers.
  138 +
  139 +The first is when the files have no publicly accessible path and need to be downloaded through an action.
  140 +
  141 +Example:
  142 + def readme
  143 + send_file '/path/to/readme.txt', :type => 'plain/text', :disposition => 'inline'
  144 + end
  145 +
  146 +See the possible values for send_file for reference.
  147 +
  148 +
  149 +The second is when saving the file when submitted from a form.
  150 +Example in view:
  151 + <%= form.file_field :attachable, :uploaded_data %>
  152 +
  153 +Example in controller:
  154 + def create
  155 + @attachable_file = AttachmentMetadataModel.new(params[:attachable])
  156 + if @attachable_file.save
  157 + flash[:notice] = 'Attachment was successfully created.'
  158 + redirect_to attachable_url(@attachable_file)
  159 + else
  160 + render :action => :new
  161 + end
  162 + end
... ...
vendor/plugins/attachment_fu/Rakefile 0 → 100644
... ... @@ -0,0 +1,22 @@
  1 +require 'rake'
  2 +require 'rake/testtask'
  3 +require 'rake/rdoctask'
  4 +
  5 +desc 'Default: run unit tests.'
  6 +task :default => :test
  7 +
  8 +desc 'Test the attachment_fu plugin.'
  9 +Rake::TestTask.new(:test) do |t|
  10 + t.libs << 'lib'
  11 + t.pattern = 'test/**/*_test.rb'
  12 + t.verbose = true
  13 +end
  14 +
  15 +desc 'Generate documentation for the attachment_fu plugin.'
  16 +Rake::RDocTask.new(:rdoc) do |rdoc|
  17 + rdoc.rdoc_dir = 'rdoc'
  18 + rdoc.title = 'ActsAsAttachment'
  19 + rdoc.options << '--line-numbers --inline-source'
  20 + rdoc.rdoc_files.include('README')
  21 + rdoc.rdoc_files.include('lib/**/*.rb')
  22 +end
... ...
vendor/plugins/attachment_fu/amazon_s3.yml.tpl 0 → 100644
... ... @@ -0,0 +1,14 @@
  1 +development:
  2 + bucket_name: appname_development
  3 + access_key_id:
  4 + secret_access_key:
  5 +
  6 +test:
  7 + bucket_name: appname_test
  8 + access_key_id:
  9 + secret_access_key:
  10 +
  11 +production:
  12 + bucket_name: appname
  13 + access_key_id:
  14 + secret_access_key:
... ...
vendor/plugins/attachment_fu/init.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +require 'tempfile'
  2 +
  3 +Tempfile.class_eval do
  4 + # overwrite so tempfiles use the extension of the basename. important for rmagick and image science
  5 + def make_tmpname(basename, n)
  6 + ext = nil
  7 + sprintf("%s%d-%d%s", basename.to_s.gsub(/\.\w+$/) { |s| ext = s; '' }, $$, n, ext)
  8 + end
  9 +end
  10 +
  11 +require 'geometry'
  12 +ActiveRecord::Base.send(:extend, Technoweenie::AttachmentFu::ActMethods)
  13 +Technoweenie::AttachmentFu.tempfile_path = ATTACHMENT_FU_TEMPFILE_PATH if Object.const_defined?(:ATTACHMENT_FU_TEMPFILE_PATH)
  14 +FileUtils.mkdir_p Technoweenie::AttachmentFu.tempfile_path
  15 +
  16 +$:.unshift(File.dirname(__FILE__) + '/vendor')
... ...
vendor/plugins/attachment_fu/install.rb 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +require 'fileutils'
  2 +
  3 +s3_config = File.dirname(__FILE__) + '/../../../config/amazon_s3.yml'
  4 +FileUtils.cp File.dirname(__FILE__) + '/amazon_s3.yml.tpl', s3_config unless File.exist?(s3_config)
  5 +puts IO.read(File.join(File.dirname(__FILE__), 'README'))
0 6 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/lib/geometry.rb 0 → 100644
... ... @@ -0,0 +1,93 @@
  1 +# This Geometry class was yanked from RMagick. However, it lets ImageMagick handle the actual change_geometry.
  2 +# Use #new_dimensions_for to get new dimensons
  3 +# Used so I can use spiffy RMagick geometry strings with ImageScience
  4 +class Geometry
  5 + # ! and @ are removed until support for them is added
  6 + FLAGS = ['', '%', '<', '>']#, '!', '@']
  7 + RFLAGS = { '%' => :percent,
  8 + '!' => :aspect,
  9 + '<' => :>,
  10 + '>' => :<,
  11 + '@' => :area }
  12 +
  13 + attr_accessor :width, :height, :x, :y, :flag
  14 +
  15 + def initialize(width=nil, height=nil, x=nil, y=nil, flag=nil)
  16 + # Support floating-point width and height arguments so Geometry
  17 + # objects can be used to specify Image#density= arguments.
  18 + raise ArgumentError, "width must be >= 0: #{width}" if width < 0
  19 + raise ArgumentError, "height must be >= 0: #{height}" if height < 0
  20 + @width = width.to_f
  21 + @height = height.to_f
  22 + @x = x.to_i
  23 + @y = y.to_i
  24 + @flag = flag
  25 + end
  26 +
  27 + # Construct an object from a geometry string
  28 + RE = /\A(\d*)(?:x(\d+))?([-+]\d+)?([-+]\d+)?([%!<>@]?)\Z/
  29 +
  30 + def self.from_s(str)
  31 + raise(ArgumentError, "no geometry string specified") unless str
  32 +
  33 + if m = RE.match(str)
  34 + new(m[1].to_i, m[2].to_i, m[3].to_i, m[4].to_i, RFLAGS[m[5]])
  35 + else
  36 + raise ArgumentError, "invalid geometry format"
  37 + end
  38 + end
  39 +
  40 + # Convert object to a geometry string
  41 + def to_s
  42 + str = ''
  43 + str << "%g" % @width if @width > 0
  44 + str << 'x' if (@width > 0 || @height > 0)
  45 + str << "%g" % @height if @height > 0
  46 + str << "%+d%+d" % [@x, @y] if (@x != 0 || @y != 0)
  47 + str << FLAGS[@flag.to_i]
  48 + end
  49 +
  50 + # attempts to get new dimensions for the current geometry string given these old dimensions.
  51 + # This doesn't implement the aspect flag (!) or the area flag (@). PDI
  52 + def new_dimensions_for(orig_width, orig_height)
  53 + new_width = orig_width
  54 + new_height = orig_height
  55 +
  56 + case @flag
  57 + when :percent
  58 + scale_x = @width.zero? ? 100 : @width
  59 + scale_y = @height.zero? ? @width : @height
  60 + new_width = scale_x.to_f * (orig_width.to_f / 100.0)
  61 + new_height = scale_y.to_f * (orig_height.to_f / 100.0)
  62 + when :<, :>, nil
  63 + scale_factor =
  64 + if new_width.zero? || new_height.zero?
  65 + 1.0
  66 + else
  67 + if @width.nonzero? && @height.nonzero?
  68 + [@width.to_f / new_width.to_f, @height.to_f / new_height.to_f].min
  69 + else
  70 + @width.nonzero? ? (@width.to_f / new_width.to_f) : (@height.to_f / new_height.to_f)
  71 + end
  72 + end
  73 + new_width = scale_factor * new_width.to_f
  74 + new_height = scale_factor * new_height.to_f
  75 + new_width = orig_width if @flag && orig_width.send(@flag, new_width)
  76 + new_height = orig_height if @flag && orig_height.send(@flag, new_height)
  77 + end
  78 +
  79 + [new_width, new_height].collect! { |v| v.round }
  80 + end
  81 +end
  82 +
  83 +class Array
  84 + # allows you to get new dimensions for the current array of dimensions with a given geometry string
  85 + #
  86 + # [50, 64] / '40>' # => [40, 51]
  87 + def /(geometry)
  88 + raise ArgumentError, "Only works with a [width, height] pair" if size != 2
  89 + raise ArgumentError, "Must pass a valid geometry string or object" unless geometry.is_a?(String) || geometry.is_a?(Geometry)
  90 + geometry = Geometry.from_s(geometry) if geometry.is_a?(String)
  91 + geometry.new_dimensions_for first, last
  92 + end
  93 +end
0 94 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu.rb 0 → 100644
... ... @@ -0,0 +1,465 @@
  1 +module Technoweenie # :nodoc:
  2 + module AttachmentFu # :nodoc:
  3 + @@default_processors = %w(ImageScience Rmagick MiniMagick Gd2 CoreImage)
  4 + @@tempfile_path = File.join(RAILS_ROOT, 'tmp', 'attachment_fu')
  5 + @@content_types = ['image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/x-png', 'image/jpg']
  6 + mattr_reader :content_types, :tempfile_path, :default_processors
  7 + mattr_writer :tempfile_path
  8 +
  9 + class ThumbnailError < StandardError; end
  10 + class AttachmentError < StandardError; end
  11 +
  12 + module ActMethods
  13 + # Options:
  14 + # * <tt>:content_type</tt> - Allowed content types. Allows all by default. Use :image to allow all standard image types.
  15 + # * <tt>:min_size</tt> - Minimum size allowed. 1 byte is the default.
  16 + # * <tt>:max_size</tt> - Maximum size allowed. 1.megabyte is the default.
  17 + # * <tt>:size</tt> - Range of sizes allowed. (1..1.megabyte) is the default. This overrides the :min_size and :max_size options.
  18 + # * <tt>:resize_to</tt> - Used by RMagick to resize images. Pass either an array of width/height, or a geometry string.
  19 + # * <tt>:thumbnails</tt> - Specifies a set of thumbnails to generate. This accepts a hash of filename suffixes and RMagick resizing options.
  20 + # * <tt>:thumbnail_class</tt> - Set what class to use for thumbnails. This attachment class is used by default.
  21 + # * <tt>:path_prefix</tt> - path to store the uploaded files. Uses public/#{table_name} by default for the filesystem, and just #{table_name}
  22 + # for the S3 backend. Setting this sets the :storage to :file_system.
  23 + # * <tt>:storage</tt> - Use :file_system to specify the attachment data is stored with the file system. Defaults to :db_system.
  24 +
  25 + # * <tt>:keep_profile</tt> By default image EXIF data will be stripped to minimize image size. For small thumbnails this proivides important savings. Picture quality is not affected. Set to false if you want to keep the image profile as is. ImageScience will allways keep EXIF data.
  26 + #
  27 + # Examples:
  28 + # has_attachment :max_size => 1.kilobyte
  29 + # has_attachment :size => 1.megabyte..2.megabytes
  30 + # has_attachment :content_type => 'application/pdf'
  31 + # has_attachment :content_type => ['application/pdf', 'application/msword', 'text/plain']
  32 + # has_attachment :content_type => :image, :resize_to => [50,50]
  33 + # has_attachment :content_type => ['application/pdf', :image], :resize_to => 'x50'
  34 + # has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
  35 + # has_attachment :storage => :file_system, :path_prefix => 'public/files'
  36 + # has_attachment :storage => :file_system, :path_prefix => 'public/files',
  37 + # :content_type => :image, :resize_to => [50,50]
  38 + # has_attachment :storage => :file_system, :path_prefix => 'public/files',
  39 + # :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
  40 + # has_attachment :storage => :s3
  41 + def has_attachment(options = {})
  42 + # this allows you to redefine the acts' options for each subclass, however
  43 + options[:min_size] ||= 1
  44 + options[:max_size] ||= 1.megabyte
  45 + options[:size] ||= (options[:min_size]..options[:max_size])
  46 + options[:thumbnails] ||= {}
  47 + options[:thumbnail_class] ||= self
  48 + options[:s3_access] ||= :public_read
  49 + options[:content_type] = [options[:content_type]].flatten.collect! { |t| t == :image ? Technoweenie::AttachmentFu.content_types : t }.flatten unless options[:content_type].nil?
  50 +
  51 + unless options[:thumbnails].is_a?(Hash)
  52 + raise ArgumentError, ":thumbnails option should be a hash: e.g. :thumbnails => { :foo => '50x50' }"
  53 + end
  54 +
  55 + extend ClassMethods unless (class << self; included_modules; end).include?(ClassMethods)
  56 + include InstanceMethods unless included_modules.include?(InstanceMethods)
  57 +
  58 + parent_options = attachment_options || {}
  59 + # doing these shenanigans so that #attachment_options is available to processors and backends
  60 + self.attachment_options = options
  61 +
  62 + attr_accessor :thumbnail_resize_options
  63 +
  64 + attachment_options[:storage] ||= (attachment_options[:file_system_path] || attachment_options[:path_prefix]) ? :file_system : :db_file
  65 + attachment_options[:storage] ||= parent_options[:storage]
  66 + attachment_options[:path_prefix] ||= attachment_options[:file_system_path]
  67 + if attachment_options[:path_prefix].nil?
  68 + attachment_options[:path_prefix] = attachment_options[:storage] == :s3 ? table_name : File.join("public", table_name)
  69 + end
  70 + attachment_options[:path_prefix] = attachment_options[:path_prefix][1..-1] if options[:path_prefix].first == '/'
  71 +
  72 + with_options :foreign_key => 'parent_id' do |m|
  73 + m.has_many :thumbnails, :class_name => "::#{attachment_options[:thumbnail_class]}"
  74 + m.belongs_to :parent, :class_name => "::#{base_class}" unless options[:thumbnails].empty?
  75 + end
  76 +
  77 + storage_mod = Technoweenie::AttachmentFu::Backends.const_get("#{options[:storage].to_s.classify}Backend")
  78 + include storage_mod unless included_modules.include?(storage_mod)
  79 +
  80 + case attachment_options[:processor]
  81 + when :none, nil
  82 + processors = Technoweenie::AttachmentFu.default_processors.dup
  83 + begin
  84 + if processors.any?
  85 + attachment_options[:processor] = "#{processors.first}Processor"
  86 + processor_mod = Technoweenie::AttachmentFu::Processors.const_get(attachment_options[:processor])
  87 + include processor_mod unless included_modules.include?(processor_mod)
  88 + end
  89 + rescue Object, Exception
  90 + raise unless load_related_exception?($!)
  91 +
  92 + processors.shift
  93 + retry
  94 + end
  95 + else
  96 + begin
  97 + processor_mod = Technoweenie::AttachmentFu::Processors.const_get("#{attachment_options[:processor].to_s.classify}Processor")
  98 + include processor_mod unless included_modules.include?(processor_mod)
  99 + rescue Object, Exception
  100 + raise unless load_related_exception?($!)
  101 +
  102 + puts "Problems loading #{options[:processor]}Processor: #{$!}"
  103 + end
  104 + end unless parent_options[:processor] # Don't let child override processor
  105 + end
  106 +
  107 + def load_related_exception?(e) #:nodoc: implementation specific
  108 + case
  109 + when e.kind_of?(LoadError), e.kind_of?(MissingSourceFile), $!.class.name == "CompilationError"
  110 + # We can't rescue CompilationError directly, as it is part of the RubyInline library.
  111 + # We must instead rescue RuntimeError, and check the class' name.
  112 + true
  113 + else
  114 + false
  115 + end
  116 + end
  117 + private :load_related_exception?
  118 + end
  119 +
  120 + module ClassMethods
  121 + delegate :content_types, :to => Technoweenie::AttachmentFu
  122 +
  123 + # Performs common validations for attachment models.
  124 + def validates_as_attachment
  125 + validates_presence_of :size, :content_type, :filename
  126 + validate :attachment_attributes_valid?
  127 + end
  128 +
  129 + # Returns true or false if the given content type is recognized as an image.
  130 + def image?(content_type)
  131 + content_types.include?(content_type)
  132 + end
  133 +
  134 + def self.extended(base)
  135 + base.class_inheritable_accessor :attachment_options
  136 + base.before_destroy :destroy_thumbnails
  137 + base.before_validation :set_size_from_temp_path
  138 + base.after_save :after_process_attachment
  139 + base.after_destroy :destroy_file
  140 + base.after_validation :process_attachment
  141 + if defined?(::ActiveSupport::Callbacks)
  142 + base.define_callbacks :after_resize, :after_attachment_saved, :before_thumbnail_saved
  143 + end
  144 + end
  145 +
  146 + unless defined?(::ActiveSupport::Callbacks)
  147 + # Callback after an image has been resized.
  148 + #
  149 + # class Foo < ActiveRecord::Base
  150 + # acts_as_attachment
  151 + # after_resize do |record, img|
  152 + # record.aspect_ratio = img.columns.to_f / img.rows.to_f
  153 + # end
  154 + # end
  155 + def after_resize(&block)
  156 + write_inheritable_array(:after_resize, [block])
  157 + end
  158 +
  159 + # Callback after an attachment has been saved either to the file system or the DB.
  160 + # Only called if the file has been changed, not necessarily if the record is updated.
  161 + #
  162 + # class Foo < ActiveRecord::Base
  163 + # acts_as_attachment
  164 + # after_attachment_saved do |record|
  165 + # ...
  166 + # end
  167 + # end
  168 + def after_attachment_saved(&block)
  169 + write_inheritable_array(:after_attachment_saved, [block])
  170 + end
  171 +
  172 + # Callback before a thumbnail is saved. Use this to pass any necessary extra attributes that may be required.
  173 + #
  174 + # class Foo < ActiveRecord::Base
  175 + # acts_as_attachment
  176 + # before_thumbnail_saved do |thumbnail|
  177 + # record = thumbnail.parent
  178 + # ...
  179 + # end
  180 + # end
  181 + def before_thumbnail_saved(&block)
  182 + write_inheritable_array(:before_thumbnail_saved, [block])
  183 + end
  184 + end
  185 +
  186 + # Get the thumbnail class, which is the current attachment class by default.
  187 + # Configure this with the :thumbnail_class option.
  188 + def thumbnail_class
  189 + attachment_options[:thumbnail_class] = attachment_options[:thumbnail_class].constantize unless attachment_options[:thumbnail_class].is_a?(Class)
  190 + attachment_options[:thumbnail_class]
  191 + end
  192 +
  193 + # Copies the given file path to a new tempfile, returning the closed tempfile.
  194 + def copy_to_temp_file(file, temp_base_name)
  195 + returning Tempfile.new(temp_base_name, Technoweenie::AttachmentFu.tempfile_path) do |tmp|
  196 + tmp.close
  197 + FileUtils.cp file, tmp.path
  198 + end
  199 + end
  200 +
  201 + # Writes the given data to a new tempfile, returning the closed tempfile.
  202 + def write_to_temp_file(data, temp_base_name)
  203 + returning Tempfile.new(temp_base_name, Technoweenie::AttachmentFu.tempfile_path) do |tmp|
  204 + tmp.binmode
  205 + tmp.write data
  206 + tmp.close
  207 + end
  208 + end
  209 + end
  210 +
  211 + module InstanceMethods
  212 + def self.included(base)
  213 + base.define_callbacks *[:after_resize, :after_attachment_saved, :before_thumbnail_saved] if base.respond_to?(:define_callbacks)
  214 + end
  215 +
  216 + # Checks whether the attachment's content type is an image content type
  217 + def image?
  218 + self.class.image?(content_type)
  219 + end
  220 +
  221 + # Returns true/false if an attachment is thumbnailable. A thumbnailable attachment has an image content type and the parent_id attribute.
  222 + def thumbnailable?
  223 + image? && respond_to?(:parent_id) && parent_id.nil?
  224 + end
  225 +
  226 + # Returns the class used to create new thumbnails for this attachment.
  227 + def thumbnail_class
  228 + self.class.thumbnail_class
  229 + end
  230 +
  231 + # Gets the thumbnail name for a filename. 'foo.jpg' becomes 'foo_thumbnail.jpg'
  232 + def thumbnail_name_for(thumbnail = nil)
  233 + return filename if thumbnail.blank?
  234 + ext = nil
  235 + basename = filename.gsub /\.\w+$/ do |s|
  236 + ext = s; ''
  237 + end
  238 + # ImageScience doesn't create gif thumbnails, only pngs
  239 + ext.sub!(/gif$/, 'png') if attachment_options[:processor] == "ImageScience"
  240 + "#{basename}_#{thumbnail}#{ext}"
  241 + end
  242 +
  243 + # Creates or updates the thumbnail for the current attachment.
  244 + def create_or_update_thumbnail(temp_file, file_name_suffix, *size)
  245 + thumbnailable? || raise(ThumbnailError.new("Can't create a thumbnail if the content type is not an image or there is no parent_id column"))
  246 + returning find_or_initialize_thumbnail(file_name_suffix) do |thumb|
  247 + thumb.attributes = {
  248 + :content_type => content_type,
  249 + :filename => thumbnail_name_for(file_name_suffix),
  250 + :temp_path => temp_file,
  251 + :thumbnail_resize_options => size
  252 + }
  253 + callback_with_args :before_thumbnail_saved, thumb
  254 + thumb.save!
  255 + end
  256 + end
  257 +
  258 + # Sets the content type.
  259 + def content_type=(new_type)
  260 + write_attribute :content_type, new_type.to_s.strip
  261 + end
  262 +
  263 + # Sanitizes a filename.
  264 + def filename=(new_name)
  265 + write_attribute :filename, sanitize_filename(new_name)
  266 + end
  267 +
  268 + # Returns the width/height in a suitable format for the image_tag helper: (100x100)
  269 + def image_size
  270 + [width.to_s, height.to_s] * 'x'
  271 + end
  272 +
  273 + # Returns true if the attachment data will be written to the storage system on the next save
  274 + def save_attachment?
  275 + File.file?(temp_path.to_s)
  276 + end
  277 +
  278 + # nil placeholder in case this field is used in a form.
  279 + def uploaded_data() nil; end
  280 +
  281 + # This method handles the uploaded file object. If you set the field name to uploaded_data, you don't need
  282 + # any special code in your controller.
  283 + #
  284 + # <% form_for :attachment, :html => { :multipart => true } do |f| -%>
  285 + # <p><%= f.file_field :uploaded_data %></p>
  286 + # <p><%= submit_tag :Save %>
  287 + # <% end -%>
  288 + #
  289 + # @attachment = Attachment.create! params[:attachment]
  290 + #
  291 + # TODO: Allow it to work with Merb tempfiles too.
  292 + def uploaded_data=(file_data)
  293 + return nil if file_data.nil? || file_data.size == 0
  294 + self.content_type = file_data.content_type
  295 + self.filename = file_data.original_filename if respond_to?(:filename)
  296 + if file_data.is_a?(StringIO)
  297 + file_data.rewind
  298 + self.temp_data = file_data.read
  299 + else
  300 + self.temp_path = file_data
  301 + end
  302 + end
  303 +
  304 + # Gets the latest temp path from the collection of temp paths. While working with an attachment,
  305 + # multiple Tempfile objects may be created for various processing purposes (resizing, for example).
  306 + # An array of all the tempfile objects is stored so that the Tempfile instance is held on to until
  307 + # it's not needed anymore. The collection is cleared after saving the attachment.
  308 + def temp_path
  309 + p = temp_paths.first
  310 + p.respond_to?(:path) ? p.path : p.to_s
  311 + end
  312 +
  313 + # Gets an array of the currently used temp paths. Defaults to a copy of #full_filename.
  314 + def temp_paths
  315 + @temp_paths ||= (new_record? || !respond_to?(:full_filename) || !File.exist?(full_filename) ?
  316 + [] : [copy_to_temp_file(full_filename)])
  317 + end
  318 +
  319 + # Adds a new temp_path to the array. This should take a string or a Tempfile. This class makes no
  320 + # attempt to remove the files, so Tempfiles should be used. Tempfiles remove themselves when they go out of scope.
  321 + # You can also use string paths for temporary files, such as those used for uploaded files in a web server.
  322 + def temp_path=(value)
  323 + temp_paths.unshift value
  324 + temp_path
  325 + end
  326 +
  327 + # Gets the data from the latest temp file. This will read the file into memory.
  328 + def temp_data
  329 + save_attachment? ? File.read(temp_path) : nil
  330 + end
  331 +
  332 + # Writes the given data to a Tempfile and adds it to the collection of temp files.
  333 + def temp_data=(data)
  334 + self.temp_path = write_to_temp_file data unless data.nil?
  335 + end
  336 +
  337 + # Copies the given file to a randomly named Tempfile.
  338 + def copy_to_temp_file(file)
  339 + self.class.copy_to_temp_file file, random_tempfile_filename
  340 + end
  341 +
  342 + # Writes the given file to a randomly named Tempfile.
  343 + def write_to_temp_file(data)
  344 + self.class.write_to_temp_file data, random_tempfile_filename
  345 + end
  346 +
  347 + # Stub for creating a temp file from the attachment data. This should be defined in the backend module.
  348 + def create_temp_file() end
  349 +
  350 + # Allows you to work with a processed representation (RMagick, ImageScience, etc) of the attachment in a block.
  351 + #
  352 + # @attachment.with_image do |img|
  353 + # self.data = img.thumbnail(100, 100).to_blob
  354 + # end
  355 + #
  356 + def with_image(&block)
  357 + self.class.with_image(temp_path, &block)
  358 + end
  359 +
  360 + protected
  361 + # Generates a unique filename for a Tempfile.
  362 + def random_tempfile_filename
  363 + "#{rand Time.now.to_i}#{filename || 'attachment'}"
  364 + end
  365 +
  366 + def sanitize_filename(filename)
  367 + returning filename.strip do |name|
  368 + # NOTE: File.basename doesn't work right with Windows paths on Unix
  369 + # get only the filename, not the whole path
  370 + name.gsub! /^.*(\\|\/)/, ''
  371 +
  372 + # Finally, replace all non alphanumeric, underscore or periods with underscore
  373 + name.gsub! /[^\w\.\-]/, '_'
  374 + end
  375 + end
  376 +
  377 + # before_validation callback.
  378 + def set_size_from_temp_path
  379 + self.size = File.size(temp_path) if save_attachment?
  380 + end
  381 +
  382 + # validates the size and content_type attributes according to the current model's options
  383 + def attachment_attributes_valid?
  384 + [:size, :content_type].each do |attr_name|
  385 + enum = attachment_options[attr_name]
  386 + errors.add attr_name, ActiveRecord::Errors.default_error_messages[:inclusion] unless enum.nil? || enum.include?(send(attr_name))
  387 + end
  388 + end
  389 +
  390 + # Initializes a new thumbnail with the given suffix.
  391 + def find_or_initialize_thumbnail(file_name_suffix)
  392 + respond_to?(:parent_id) ?
  393 + thumbnail_class.find_or_initialize_by_thumbnail_and_parent_id(file_name_suffix.to_s, id) :
  394 + thumbnail_class.find_or_initialize_by_thumbnail(file_name_suffix.to_s)
  395 + end
  396 +
  397 + # Stub for a #process_attachment method in a processor
  398 + def process_attachment
  399 + @saved_attachment = save_attachment?
  400 + end
  401 +
  402 + # Cleans up after processing. Thumbnails are created, the attachment is stored to the backend, and the temp_paths are cleared.
  403 + def after_process_attachment
  404 + if @saved_attachment
  405 + if respond_to?(:process_attachment_with_processing) && thumbnailable? && !attachment_options[:thumbnails].blank? && parent_id.nil?
  406 + temp_file = temp_path || create_temp_file
  407 + attachment_options[:thumbnails].each { |suffix, size| create_or_update_thumbnail(temp_file, suffix, *size) }
  408 + end
  409 + save_to_storage
  410 + @temp_paths.clear
  411 + @saved_attachment = nil
  412 + callback :after_attachment_saved
  413 + end
  414 + end
  415 +
  416 + # Resizes the given processed img object with either the attachment resize options or the thumbnail resize options.
  417 + def resize_image_or_thumbnail!(img)
  418 + if (!respond_to?(:parent_id) || parent_id.nil?) && attachment_options[:resize_to] # parent image
  419 + resize_image(img, attachment_options[:resize_to])
  420 + elsif thumbnail_resize_options # thumbnail
  421 + resize_image(img, thumbnail_resize_options)
  422 + end
  423 + end
  424 +
  425 + # Yanked from ActiveRecord::Callbacks, modified so I can pass args to the callbacks besides self.
  426 + # Only accept blocks, however
  427 + if ActiveSupport.const_defined?(:Callbacks)
  428 + # Rails 2.1 and beyond!
  429 + def callback_with_args(method, arg = self)
  430 + notify(method)
  431 +
  432 + result = run_callbacks(method, { :object => arg }) { |result, object| result == false }
  433 +
  434 + if result != false && respond_to_without_attributes?(method)
  435 + result = send(method)
  436 + end
  437 +
  438 + result
  439 + end
  440 +
  441 + def run_callbacks(kind, options = {}, &block)
  442 + options.reverse_merge!( :object => self )
  443 + self.class.send("#{kind}_callback_chain").run(options[:object], options, &block)
  444 + end
  445 + else
  446 + # Rails 2.0
  447 + def callback_with_args(method, arg = self)
  448 + notify(method)
  449 +
  450 + result = nil
  451 + callbacks_for(method).each do |callback|
  452 + result = callback.call(self, arg)
  453 + return false if result == false
  454 + end
  455 + result
  456 + end
  457 + end
  458 +
  459 + # Removes the thumbnails for the attachment, if it has any
  460 + def destroy_thumbnails
  461 + self.thumbnails.each { |thumbnail| thumbnail.destroy } if thumbnailable?
  462 + end
  463 + end
  464 + end
  465 +end
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/db_file_backend.rb 0 → 100644
... ... @@ -0,0 +1,39 @@
  1 +module Technoweenie # :nodoc:
  2 + module AttachmentFu # :nodoc:
  3 + module Backends
  4 + # Methods for DB backed attachments
  5 + module DbFileBackend
  6 + def self.included(base) #:nodoc:
  7 + Object.const_set(:DbFile, Class.new(ActiveRecord::Base)) unless Object.const_defined?(:DbFile)
  8 + base.belongs_to :db_file, :class_name => '::DbFile', :foreign_key => 'db_file_id'
  9 + end
  10 +
  11 + # Creates a temp file with the current db data.
  12 + def create_temp_file
  13 + write_to_temp_file current_data
  14 + end
  15 +
  16 + # Gets the current data from the database
  17 + def current_data
  18 + db_file.data
  19 + end
  20 +
  21 + protected
  22 + # Destroys the file. Called in the after_destroy callback
  23 + def destroy_file
  24 + db_file.destroy if db_file
  25 + end
  26 +
  27 + # Saves the data to the DbFile model
  28 + def save_to_storage
  29 + if save_attachment?
  30 + (db_file || build_db_file).data = temp_data
  31 + db_file.save!
  32 + self.class.update_all ['db_file_id = ?', self.db_file_id = db_file.id], ['id = ?', id]
  33 + end
  34 + true
  35 + end
  36 + end
  37 + end
  38 + end
  39 +end
0 40 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/file_system_backend.rb 0 → 100644
... ... @@ -0,0 +1,97 @@
  1 +require 'ftools'
  2 +module Technoweenie # :nodoc:
  3 + module AttachmentFu # :nodoc:
  4 + module Backends
  5 + # Methods for file system backed attachments
  6 + module FileSystemBackend
  7 + def self.included(base) #:nodoc:
  8 + base.before_update :rename_file
  9 + end
  10 +
  11 + # Gets the full path to the filename in this format:
  12 + #
  13 + # # This assumes a model name like MyModel
  14 + # # public/#{table_name} is the default filesystem path
  15 + # RAILS_ROOT/public/my_models/5/blah.jpg
  16 + #
  17 + # Overwrite this method in your model to customize the filename.
  18 + # The optional thumbnail argument will output the thumbnail's filename.
  19 + def full_filename(thumbnail = nil)
  20 + file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s
  21 + File.join(RAILS_ROOT, file_system_path, *partitioned_path(thumbnail_name_for(thumbnail)))
  22 + end
  23 +
  24 + # Used as the base path that #public_filename strips off full_filename to create the public path
  25 + def base_path
  26 + @base_path ||= File.join(RAILS_ROOT, 'public')
  27 + end
  28 +
  29 + # The attachment ID used in the full path of a file
  30 + def attachment_path_id
  31 + ((respond_to?(:parent_id) && parent_id) || id).to_i
  32 + end
  33 +
  34 + # overrwrite this to do your own app-specific partitioning.
  35 + # you can thank Jamis Buck for this: http://www.37signals.com/svn/archives2/id_partitioning.php
  36 + def partitioned_path(*args)
  37 + ("%08d" % attachment_path_id).scan(/..../) + args
  38 + end
  39 +
  40 + # Gets the public path to the file
  41 + # The optional thumbnail argument will output the thumbnail's filename.
  42 + def public_filename(thumbnail = nil)
  43 + full_filename(thumbnail).gsub %r(^#{Regexp.escape(base_path)}), ''
  44 + end
  45 +
  46 + def filename=(value)
  47 + @old_filename = full_filename unless filename.nil? || @old_filename
  48 + write_attribute :filename, sanitize_filename(value)
  49 + end
  50 +
  51 + # Creates a temp file from the currently saved file.
  52 + def create_temp_file
  53 + copy_to_temp_file full_filename
  54 + end
  55 +
  56 + protected
  57 + # Destroys the file. Called in the after_destroy callback
  58 + def destroy_file
  59 + FileUtils.rm full_filename
  60 + # remove directory also if it is now empty
  61 + Dir.rmdir(File.dirname(full_filename)) if (Dir.entries(File.dirname(full_filename))-['.','..']).empty?
  62 + rescue
  63 + logger.info "Exception destroying #{full_filename.inspect}: [#{$!.class.name}] #{$1.to_s}"
  64 + logger.warn $!.backtrace.collect { |b| " > #{b}" }.join("\n")
  65 + end
  66 +
  67 + # Renames the given file before saving
  68 + def rename_file
  69 + return unless @old_filename && @old_filename != full_filename
  70 + if save_attachment? && File.exists?(@old_filename)
  71 + FileUtils.rm @old_filename
  72 + elsif File.exists?(@old_filename)
  73 + FileUtils.mv @old_filename, full_filename
  74 + end
  75 + @old_filename = nil
  76 + true
  77 + end
  78 +
  79 + # Saves the file to the file system
  80 + def save_to_storage
  81 + if save_attachment?
  82 + # TODO: This overwrites the file if it exists, maybe have an allow_overwrite option?
  83 + FileUtils.mkdir_p(File.dirname(full_filename))
  84 + File.cp(temp_path, full_filename)
  85 + File.chmod(attachment_options[:chmod] || 0644, full_filename)
  86 + end
  87 + @old_filename = nil
  88 + true
  89 + end
  90 +
  91 + def current_data
  92 + File.file?(full_filename) ? File.read(full_filename) : nil
  93 + end
  94 + end
  95 + end
  96 + end
  97 +end
0 98 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/s3_backend.rb 0 → 100644
... ... @@ -0,0 +1,303 @@
  1 +module Technoweenie # :nodoc:
  2 + module AttachmentFu # :nodoc:
  3 + module Backends
  4 + # = AWS::S3 Storage Backend
  5 + #
  6 + # Enables use of {Amazon's Simple Storage Service}[http://aws.amazon.com/s3] as a storage mechanism
  7 + #
  8 + # == Requirements
  9 + #
  10 + # Requires the {AWS::S3 Library}[http://amazon.rubyforge.org] for S3 by Marcel Molina Jr. installed either
  11 + # as a gem or a as a Rails plugin.
  12 + #
  13 + # == Configuration
  14 + #
  15 + # Configuration is done via <tt>RAILS_ROOT/config/amazon_s3.yml</tt> and is loaded according to the <tt>RAILS_ENV</tt>.
  16 + # The minimum connection options that you must specify are a bucket name, your access key id and your secret access key.
  17 + # If you don't already have your access keys, all you need to sign up for the S3 service is an account at Amazon.
  18 + # You can sign up for S3 and get access keys by visiting http://aws.amazon.com/s3.
  19 + #
  20 + # Example configuration (RAILS_ROOT/config/amazon_s3.yml)
  21 + #
  22 + # development:
  23 + # bucket_name: appname_development
  24 + # access_key_id: <your key>
  25 + # secret_access_key: <your key>
  26 + #
  27 + # test:
  28 + # bucket_name: appname_test
  29 + # access_key_id: <your key>
  30 + # secret_access_key: <your key>
  31 + #
  32 + # production:
  33 + # bucket_name: appname
  34 + # access_key_id: <your key>
  35 + # secret_access_key: <your key>
  36 + #
  37 + # You can change the location of the config path by passing a full path to the :s3_config_path option.
  38 + #
  39 + # has_attachment :storage => :s3, :s3_config_path => (RAILS_ROOT + '/config/s3.yml')
  40 + #
  41 + # === Required configuration parameters
  42 + #
  43 + # * <tt>:access_key_id</tt> - The access key id for your S3 account. Provided by Amazon.
  44 + # * <tt>:secret_access_key</tt> - The secret access key for your S3 account. Provided by Amazon.
  45 + # * <tt>:bucket_name</tt> - A unique bucket name (think of the bucket_name as being like a database name).
  46 + #
  47 + # If any of these required arguments is missing, a MissingAccessKey exception will be raised from AWS::S3.
  48 + #
  49 + # == About bucket names
  50 + #
  51 + # Bucket names have to be globaly unique across the S3 system. And you can only have up to 100 of them,
  52 + # so it's a good idea to think of a bucket as being like a database, hence the correspondance in this
  53 + # implementation to the development, test, and production environments.
  54 + #
  55 + # The number of objects you can store in a bucket is, for all intents and purposes, unlimited.
  56 + #
  57 + # === Optional configuration parameters
  58 + #
  59 + # * <tt>:server</tt> - The server to make requests to. Defaults to <tt>s3.amazonaws.com</tt>.
  60 + # * <tt>:port</tt> - The port to the requests should be made on. Defaults to 80 or 443 if <tt>:use_ssl</tt> is set.
  61 + # * <tt>:use_ssl</tt> - If set to true, <tt>:port</tt> will be implicitly set to 443, unless specified otherwise. Defaults to false.
  62 + #
  63 + # == Usage
  64 + #
  65 + # To specify S3 as the storage mechanism for a model, set the acts_as_attachment <tt>:storage</tt> option to <tt>:s3</tt>.
  66 + #
  67 + # class Photo < ActiveRecord::Base
  68 + # has_attachment :storage => :s3
  69 + # end
  70 + #
  71 + # === Customizing the path
  72 + #
  73 + # By default, files are prefixed using a pseudo hierarchy in the form of <tt>:table_name/:id</tt>, which results
  74 + # in S3 urls that look like: http(s)://:server/:bucket_name/:table_name/:id/:filename with :table_name
  75 + # representing the customizable portion of the path. You can customize this prefix using the <tt>:path_prefix</tt>
  76 + # option:
  77 + #
  78 + # class Photo < ActiveRecord::Base
  79 + # has_attachment :storage => :s3, :path_prefix => 'my/custom/path'
  80 + # end
  81 + #
  82 + # Which would result in URLs like <tt>http(s)://:server/:bucket_name/my/custom/path/:id/:filename.</tt>
  83 + #
  84 + # === Permissions
  85 + #
  86 + # By default, files are stored on S3 with public access permissions. You can customize this using
  87 + # the <tt>:s3_access</tt> option to <tt>has_attachment</tt>. Available values are
  88 + # <tt>:private</tt>, <tt>:public_read_write</tt>, and <tt>:authenticated_read</tt>.
  89 + #
  90 + # === Other options
  91 + #
  92 + # Of course, all the usual configuration options apply, such as content_type and thumbnails:
  93 + #
  94 + # class Photo < ActiveRecord::Base
  95 + # has_attachment :storage => :s3, :content_type => ['application/pdf', :image], :resize_to => 'x50'
  96 + # has_attachment :storage => :s3, :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
  97 + # end
  98 + #
  99 + # === Accessing S3 URLs
  100 + #
  101 + # You can get an object's URL using the s3_url accessor. For example, assuming that for your postcard app
  102 + # you had a bucket name like 'postcard_world_development', and an attachment model called Photo:
  103 + #
  104 + # @postcard.s3_url # => http(s)://s3.amazonaws.com/postcard_world_development/photos/1/mexico.jpg
  105 + #
  106 + # The resulting url is in the form: http(s)://:server/:bucket_name/:table_name/:id/:file.
  107 + # The optional thumbnail argument will output the thumbnail's filename (if any).
  108 + #
  109 + # Additionally, you can get an object's base path relative to the bucket root using
  110 + # <tt>base_path</tt>:
  111 + #
  112 + # @photo.file_base_path # => photos/1
  113 + #
  114 + # And the full path (including the filename) using <tt>full_filename</tt>:
  115 + #
  116 + # @photo.full_filename # => photos/
  117 + #
  118 + # Niether <tt>base_path</tt> or <tt>full_filename</tt> include the bucket name as part of the path.
  119 + # You can retrieve the bucket name using the <tt>bucket_name</tt> method.
  120 + module S3Backend
  121 + class RequiredLibraryNotFoundError < StandardError; end
  122 + class ConfigFileNotFoundError < StandardError; end
  123 +
  124 + def self.included(base) #:nodoc:
  125 + mattr_reader :bucket_name, :s3_config
  126 +
  127 + begin
  128 + require 'aws/s3'
  129 + include AWS::S3
  130 + rescue LoadError
  131 + raise RequiredLibraryNotFoundError.new('AWS::S3 could not be loaded')
  132 + end
  133 +
  134 + begin
  135 + @@s3_config_path = base.attachment_options[:s3_config_path] || (RAILS_ROOT + '/config/amazon_s3.yml')
  136 + @@s3_config = @@s3_config = YAML.load(ERB.new(File.read(@@s3_config_path)).result)[RAILS_ENV].symbolize_keys
  137 + #rescue
  138 + # raise ConfigFileNotFoundError.new('File %s not found' % @@s3_config_path)
  139 + end
  140 +
  141 + @@bucket_name = s3_config[:bucket_name]
  142 +
  143 + Base.establish_connection!(s3_config.slice(:access_key_id, :secret_access_key, :server, :port, :use_ssl, :persistent, :proxy))
  144 +
  145 + # Bucket.create(@@bucket_name)
  146 +
  147 + base.before_update :rename_file
  148 + end
  149 +
  150 + def self.protocol
  151 + @protocol ||= s3_config[:use_ssl] ? 'https://' : 'http://'
  152 + end
  153 +
  154 + def self.hostname
  155 + @hostname ||= s3_config[:server] || AWS::S3::DEFAULT_HOST
  156 + end
  157 +
  158 + def self.port_string
  159 + @port_string ||= (s3_config[:port].nil? || s3_config[:port] == (s3_config[:use_ssl] ? 443 : 80)) ? '' : ":#{s3_config[:port]}"
  160 + end
  161 +
  162 + module ClassMethods
  163 + def s3_protocol
  164 + Technoweenie::AttachmentFu::Backends::S3Backend.protocol
  165 + end
  166 +
  167 + def s3_hostname
  168 + Technoweenie::AttachmentFu::Backends::S3Backend.hostname
  169 + end
  170 +
  171 + def s3_port_string
  172 + Technoweenie::AttachmentFu::Backends::S3Backend.port_string
  173 + end
  174 + end
  175 +
  176 + # Overwrites the base filename writer in order to store the old filename
  177 + def filename=(value)
  178 + @old_filename = filename unless filename.nil? || @old_filename
  179 + write_attribute :filename, sanitize_filename(value)
  180 + end
  181 +
  182 + # The attachment ID used in the full path of a file
  183 + def attachment_path_id
  184 + ((respond_to?(:parent_id) && parent_id) || id).to_s
  185 + end
  186 +
  187 + # The pseudo hierarchy containing the file relative to the bucket name
  188 + # Example: <tt>:table_name/:id</tt>
  189 + def base_path
  190 + File.join(attachment_options[:path_prefix], attachment_path_id)
  191 + end
  192 +
  193 + # The full path to the file relative to the bucket name
  194 + # Example: <tt>:table_name/:id/:filename</tt>
  195 + def full_filename(thumbnail = nil)
  196 + File.join(base_path, thumbnail_name_for(thumbnail))
  197 + end
  198 +
  199 + # All public objects are accessible via a GET request to the S3 servers. You can generate a
  200 + # url for an object using the s3_url method.
  201 + #
  202 + # @photo.s3_url
  203 + #
  204 + # The resulting url is in the form: <tt>http(s)://:server/:bucket_name/:table_name/:id/:file</tt> where
  205 + # the <tt>:server</tt> variable defaults to <tt>AWS::S3 URL::DEFAULT_HOST</tt> (s3.amazonaws.com) and can be
  206 + # set using the configuration parameters in <tt>RAILS_ROOT/config/amazon_s3.yml</tt>.
  207 + #
  208 + # The optional thumbnail argument will output the thumbnail's filename (if any).
  209 + def s3_url(thumbnail = nil)
  210 + File.join(s3_protocol + s3_hostname + s3_port_string, bucket_name, full_filename(thumbnail))
  211 + end
  212 + alias :public_filename :s3_url
  213 +
  214 + # All private objects are accessible via an authenticated GET request to the S3 servers. You can generate an
  215 + # authenticated url for an object like this:
  216 + #
  217 + # @photo.authenticated_s3_url
  218 + #
  219 + # By default authenticated urls expire 5 minutes after they were generated.
  220 + #
  221 + # Expiration options can be specified either with an absolute time using the <tt>:expires</tt> option,
  222 + # or with a number of seconds relative to now with the <tt>:expires_in</tt> option:
  223 + #
  224 + # # Absolute expiration date (October 13th, 2025)
  225 + # @photo.authenticated_s3_url(:expires => Time.mktime(2025,10,13).to_i)
  226 + #
  227 + # # Expiration in five hours from now
  228 + # @photo.authenticated_s3_url(:expires_in => 5.hours)
  229 + #
  230 + # You can specify whether the url should go over SSL with the <tt>:use_ssl</tt> option.
  231 + # By default, the ssl settings for the current connection will be used:
  232 + #
  233 + # @photo.authenticated_s3_url(:use_ssl => true)
  234 + #
  235 + # Finally, the optional thumbnail argument will output the thumbnail's filename (if any):
  236 + #
  237 + # @photo.authenticated_s3_url('thumbnail', :expires_in => 5.hours, :use_ssl => true)
  238 + def authenticated_s3_url(*args)
  239 + thumbnail = args.first.is_a?(String) ? args.first : nil
  240 + options = args.last.is_a?(Hash) ? args.last : {}
  241 + S3Object.url_for(full_filename(thumbnail), bucket_name, options)
  242 + end
  243 +
  244 + def create_temp_file
  245 + write_to_temp_file current_data
  246 + end
  247 +
  248 + def current_data
  249 + S3Object.value full_filename, bucket_name
  250 + end
  251 +
  252 + def s3_protocol
  253 + Technoweenie::AttachmentFu::Backends::S3Backend.protocol
  254 + end
  255 +
  256 + def s3_hostname
  257 + Technoweenie::AttachmentFu::Backends::S3Backend.hostname
  258 + end
  259 +
  260 + def s3_port_string
  261 + Technoweenie::AttachmentFu::Backends::S3Backend.port_string
  262 + end
  263 +
  264 + protected
  265 + # Called in the after_destroy callback
  266 + def destroy_file
  267 + S3Object.delete full_filename, bucket_name
  268 + end
  269 +
  270 + def rename_file
  271 + return unless @old_filename && @old_filename != filename
  272 +
  273 + old_full_filename = File.join(base_path, @old_filename)
  274 +
  275 + S3Object.rename(
  276 + old_full_filename,
  277 + full_filename,
  278 + bucket_name,
  279 + :access => attachment_options[:s3_access]
  280 + )
  281 +
  282 + @old_filename = nil
  283 + true
  284 + end
  285 +
  286 + def save_to_storage
  287 + if save_attachment?
  288 + S3Object.store(
  289 + full_filename,
  290 + (temp_path ? File.open(temp_path) : temp_data),
  291 + bucket_name,
  292 + :content_type => content_type,
  293 + :access => attachment_options[:s3_access]
  294 + )
  295 + end
  296 +
  297 + @old_filename = nil
  298 + true
  299 + end
  300 + end
  301 + end
  302 + end
  303 +end
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/core_image_processor.rb 0 → 100644
... ... @@ -0,0 +1,56 @@
  1 +require 'red_artisan/core_image/processor'
  2 +
  3 +module Technoweenie # :nodoc:
  4 + module AttachmentFu # :nodoc:
  5 + module Processors
  6 + module CoreImageProcessor
  7 + def self.included(base)
  8 + base.send :extend, ClassMethods
  9 + base.alias_method_chain :process_attachment, :processing
  10 + end
  11 +
  12 + module ClassMethods
  13 + def with_image(file, &block)
  14 + block.call OSX::CIImage.from(file)
  15 + end
  16 + end
  17 +
  18 + protected
  19 + def process_attachment_with_processing
  20 + return unless process_attachment_without_processing
  21 + with_image do |img|
  22 + self.width = img.extent.size.width if respond_to?(:width)
  23 + self.height = img.extent.size.height if respond_to?(:height)
  24 + resize_image_or_thumbnail! img
  25 + callback_with_args :after_resize, img
  26 + end if image?
  27 + end
  28 +
  29 + # Performs the actual resizing operation for a thumbnail
  30 + def resize_image(img, size)
  31 + processor = ::RedArtisan::CoreImage::Processor.new(img)
  32 + size = size.first if size.is_a?(Array) && size.length == 1
  33 + if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
  34 + if size.is_a?(Fixnum)
  35 + processor.fit(size)
  36 + else
  37 + processor.resize(size[0], size[1])
  38 + end
  39 + else
  40 + new_size = [img.extent.size.width, img.extent.size.height] / size.to_s
  41 + processor.resize(new_size[0], new_size[1])
  42 + end
  43 +
  44 + processor.render do |result|
  45 + self.width = result.extent.size.width if respond_to?(:width)
  46 + self.height = result.extent.size.height if respond_to?(:height)
  47 + result.save self.temp_path, OSX::NSJPEGFileType
  48 + self.size = File.size(self.temp_path)
  49 + end
  50 + end
  51 + end
  52 + end
  53 + end
  54 +end
  55 +
  56 +
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/gd2_processor.rb 0 → 100644
... ... @@ -0,0 +1,54 @@
  1 +require 'rubygems'
  2 +require 'gd2'
  3 +module Technoweenie # :nodoc:
  4 + module AttachmentFu # :nodoc:
  5 + module Processors
  6 + module Gd2Processor
  7 + def self.included(base)
  8 + base.send :extend, ClassMethods
  9 + base.alias_method_chain :process_attachment, :processing
  10 + end
  11 +
  12 + module ClassMethods
  13 + # Yields a block containing a GD2 Image for the given binary data.
  14 + def with_image(file, &block)
  15 + im = GD2::Image.import(file)
  16 + block.call(im)
  17 + end
  18 + end
  19 +
  20 + protected
  21 + def process_attachment_with_processing
  22 + return unless process_attachment_without_processing && image?
  23 + with_image do |img|
  24 + resize_image_or_thumbnail! img
  25 + self.width = img.width
  26 + self.height = img.height
  27 + callback_with_args :after_resize, img
  28 + end
  29 + end
  30 +
  31 + # Performs the actual resizing operation for a thumbnail
  32 + def resize_image(img, size)
  33 + size = size.first if size.is_a?(Array) && size.length == 1
  34 + if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
  35 + if size.is_a?(Fixnum)
  36 + # Borrowed from image science's #thumbnail method and adapted
  37 + # for this.
  38 + scale = size.to_f / (img.width > img.height ? img.width.to_f : img.height.to_f)
  39 + img.resize!((img.width * scale).round(1), (img.height * scale).round(1), false)
  40 + else
  41 + img.resize!(size.first, size.last, false)
  42 + end
  43 + else
  44 + w, h = [img.width, img.height] / size.to_s
  45 + img.resize!(w, h, false)
  46 + end
  47 + self.temp_path = random_tempfile_filename
  48 + self.size = img.export(self.temp_path)
  49 + end
  50 +
  51 + end
  52 + end
  53 + end
  54 +end
0 55 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/image_science_processor.rb 0 → 100644
... ... @@ -0,0 +1,61 @@
  1 +require 'image_science'
  2 +module Technoweenie # :nodoc:
  3 + module AttachmentFu # :nodoc:
  4 + module Processors
  5 + module ImageScienceProcessor
  6 + def self.included(base)
  7 + base.send :extend, ClassMethods
  8 + base.alias_method_chain :process_attachment, :processing
  9 + end
  10 +
  11 + module ClassMethods
  12 + # Yields a block containing an Image Science image for the given binary data.
  13 + def with_image(file, &block)
  14 + ::ImageScience.with_image file, &block
  15 + end
  16 + end
  17 +
  18 + protected
  19 + def process_attachment_with_processing
  20 + return unless process_attachment_without_processing && image?
  21 + with_image do |img|
  22 + self.width = img.width if respond_to?(:width)
  23 + self.height = img.height if respond_to?(:height)
  24 + resize_image_or_thumbnail! img
  25 + end
  26 + end
  27 +
  28 + # Performs the actual resizing operation for a thumbnail
  29 + def resize_image(img, size)
  30 + # create a dummy temp file to write to
  31 + # ImageScience doesn't handle all gifs properly, so it converts them to
  32 + # pngs for thumbnails. It has something to do with trying to save gifs
  33 + # with a larger palette than 256 colors, which is all the gif format
  34 + # supports.
  35 + filename.sub! /gif$/, 'png'
  36 + content_type.sub!(/gif$/, 'png')
  37 + self.temp_path = write_to_temp_file(filename)
  38 + grab_dimensions = lambda do |img|
  39 + self.width = img.width if respond_to?(:width)
  40 + self.height = img.height if respond_to?(:height)
  41 + img.save self.temp_path
  42 + self.size = File.size(self.temp_path)
  43 + callback_with_args :after_resize, img
  44 + end
  45 +
  46 + size = size.first if size.is_a?(Array) && size.length == 1
  47 + if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
  48 + if size.is_a?(Fixnum)
  49 + img.thumbnail(size, &grab_dimensions)
  50 + else
  51 + img.resize(size[0], size[1], &grab_dimensions)
  52 + end
  53 + else
  54 + new_size = [img.width, img.height] / size.to_s
  55 + img.resize(new_size[0], new_size[1], &grab_dimensions)
  56 + end
  57 + end
  58 + end
  59 + end
  60 + end
  61 +end
0 62 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb 0 → 100644
... ... @@ -0,0 +1,59 @@
  1 +require 'mini_magick'
  2 +module Technoweenie # :nodoc:
  3 + module AttachmentFu # :nodoc:
  4 + module Processors
  5 + module MiniMagickProcessor
  6 + def self.included(base)
  7 + base.send :extend, ClassMethods
  8 + base.alias_method_chain :process_attachment, :processing
  9 + end
  10 +
  11 + module ClassMethods
  12 + # Yields a block containing an MiniMagick Image for the given binary data.
  13 + def with_image(file, &block)
  14 + begin
  15 + binary_data = file.is_a?(MiniMagick::Image) ? file : MiniMagick::Image.from_file(file) unless !Object.const_defined?(:MiniMagick)
  16 + rescue
  17 + # Log the failure to load the image.
  18 + logger.debug("Exception working with image: #{$!}")
  19 + binary_data = nil
  20 + end
  21 + block.call binary_data if block && binary_data
  22 + ensure
  23 + !binary_data.nil?
  24 + end
  25 + end
  26 +
  27 + protected
  28 + def process_attachment_with_processing
  29 + return unless process_attachment_without_processing
  30 + with_image do |img|
  31 + resize_image_or_thumbnail! img
  32 + self.width = img[:width] if respond_to?(:width)
  33 + self.height = img[:height] if respond_to?(:height)
  34 + callback_with_args :after_resize, img
  35 + end if image?
  36 + end
  37 +
  38 + # Performs the actual resizing operation for a thumbnail
  39 + def resize_image(img, size)
  40 + size = size.first if size.is_a?(Array) && size.length == 1
  41 + img.combine_options do |commands|
  42 + commands.strip unless attachment_options[:keep_profile]
  43 + if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
  44 + if size.is_a?(Fixnum)
  45 + size = [size, size]
  46 + commands.resize(size.join('x'))
  47 + else
  48 + commands.resize(size.join('x') + '!')
  49 + end
  50 + else
  51 + commands.resize(size.to_s)
  52 + end
  53 + end
  54 + self.temp_path = img
  55 + end
  56 + end
  57 + end
  58 + end
  59 +end
... ...
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/rmagick_processor.rb 0 → 100644
... ... @@ -0,0 +1,54 @@
  1 +require 'RMagick'
  2 +module Technoweenie # :nodoc:
  3 + module AttachmentFu # :nodoc:
  4 + module Processors
  5 + module RmagickProcessor
  6 + def self.included(base)
  7 + base.send :extend, ClassMethods
  8 + base.alias_method_chain :process_attachment, :processing
  9 + end
  10 +
  11 + module ClassMethods
  12 + # Yields a block containing an RMagick Image for the given binary data.
  13 + def with_image(file, &block)
  14 + begin
  15 + binary_data = file.is_a?(Magick::Image) ? file : Magick::Image.read(file).first unless !Object.const_defined?(:Magick)
  16 + rescue
  17 + # Log the failure to load the image. This should match ::Magick::ImageMagickError
  18 + # but that would cause acts_as_attachment to require rmagick.
  19 + logger.debug("Exception working with image: #{$!}")
  20 + binary_data = nil
  21 + end
  22 + block.call binary_data if block && binary_data
  23 + ensure
  24 + !binary_data.nil?
  25 + end
  26 + end
  27 +
  28 + protected
  29 + def process_attachment_with_processing
  30 + return unless process_attachment_without_processing
  31 + with_image do |img|
  32 + resize_image_or_thumbnail! img
  33 + self.width = img.columns if respond_to?(:width)
  34 + self.height = img.rows if respond_to?(:height)
  35 + callback_with_args :after_resize, img
  36 + end if image?
  37 + end
  38 +
  39 + # Performs the actual resizing operation for a thumbnail
  40 + def resize_image(img, size)
  41 + size = size.first if size.is_a?(Array) && size.length == 1 && !size.first.is_a?(Fixnum)
  42 + if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
  43 + size = [size, size] if size.is_a?(Fixnum)
  44 + img.thumbnail!(*size)
  45 + else
  46 + img.change_geometry(size.to_s) { |cols, rows, image| image.resize!(cols<1 ? 1 : cols, rows<1 ? 1 : rows) }
  47 + end
  48 + img.strip! unless attachment_options[:keep_profile]
  49 + self.temp_path = write_to_temp_file(img.to_blob)
  50 + end
  51 + end
  52 + end
  53 + end
  54 +end
... ...
vendor/plugins/attachment_fu/test/backends/db_file_test.rb 0 → 100644
... ... @@ -0,0 +1,16 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
  2 +
  3 +class DbFileTest < Test::Unit::TestCase
  4 + include BaseAttachmentTests
  5 + attachment_model Attachment
  6 +
  7 + def test_should_call_after_attachment_saved(klass = Attachment)
  8 + attachment_model.saves = 0
  9 + assert_created do
  10 + upload_file :filename => '/files/rails.png'
  11 + end
  12 + assert_equal 1, attachment_model.saves
  13 + end
  14 +
  15 + test_against_subclass :test_should_call_after_attachment_saved, Attachment
  16 +end
0 17 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/backends/file_system_test.rb 0 → 100644
... ... @@ -0,0 +1,80 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
  2 +
  3 +class FileSystemTest < Test::Unit::TestCase
  4 + include BaseAttachmentTests
  5 + attachment_model FileAttachment
  6 +
  7 + def test_filesystem_size_for_file_attachment(klass = FileAttachment)
  8 + attachment_model klass
  9 + assert_created 1 do
  10 + attachment = upload_file :filename => '/files/rails.png'
  11 + assert_equal attachment.size, File.open(attachment.full_filename).stat.size
  12 + end
  13 + end
  14 +
  15 + test_against_subclass :test_filesystem_size_for_file_attachment, FileAttachment
  16 +
  17 + def test_should_not_overwrite_file_attachment(klass = FileAttachment)
  18 + attachment_model klass
  19 + assert_created 2 do
  20 + real = upload_file :filename => '/files/rails.png'
  21 + assert_valid real
  22 + assert !real.new_record?, real.errors.full_messages.join("\n")
  23 + assert !real.size.zero?
  24 +
  25 + fake = upload_file :filename => '/files/fake/rails.png'
  26 + assert_valid fake
  27 + assert !fake.size.zero?
  28 +
  29 + assert_not_equal File.open(real.full_filename).stat.size, File.open(fake.full_filename).stat.size
  30 + end
  31 + end
  32 +
  33 + test_against_subclass :test_should_not_overwrite_file_attachment, FileAttachment
  34 +
  35 + def test_should_store_file_attachment_in_filesystem(klass = FileAttachment)
  36 + attachment_model klass
  37 + attachment = nil
  38 + assert_created do
  39 + attachment = upload_file :filename => '/files/rails.png'
  40 + assert_valid attachment
  41 + assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
  42 + end
  43 + attachment
  44 + end
  45 +
  46 + test_against_subclass :test_should_store_file_attachment_in_filesystem, FileAttachment
  47 +
  48 + def test_should_delete_old_file_when_updating(klass = FileAttachment)
  49 + attachment_model klass
  50 + attachment = upload_file :filename => '/files/rails.png'
  51 + old_filename = attachment.full_filename
  52 + assert_not_created do
  53 + use_temp_file 'files/rails.png' do |file|
  54 + attachment.filename = 'rails2.png'
  55 + attachment.temp_path = File.join(fixture_path, file)
  56 + attachment.save!
  57 + assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
  58 + assert !File.exists?(old_filename), "#{old_filename} still exists"
  59 + end
  60 + end
  61 + end
  62 +
  63 + test_against_subclass :test_should_delete_old_file_when_updating, FileAttachment
  64 +
  65 + def test_should_delete_old_file_when_renaming(klass = FileAttachment)
  66 + attachment_model klass
  67 + attachment = upload_file :filename => '/files/rails.png'
  68 + old_filename = attachment.full_filename
  69 + assert_not_created do
  70 + attachment.filename = 'rails2.png'
  71 + attachment.save
  72 + assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
  73 + assert !File.exists?(old_filename), "#{old_filename} still exists"
  74 + assert !attachment.reload.size.zero?
  75 + assert_equal 'rails2.png', attachment.filename
  76 + end
  77 + end
  78 +
  79 + test_against_subclass :test_should_delete_old_file_when_renaming, FileAttachment
  80 +end
0 81 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/backends/remote/s3_test.rb 0 → 100644
... ... @@ -0,0 +1,107 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
  2 +require 'net/http'
  3 +
  4 +class S3Test < Test::Unit::TestCase
  5 + def self.test_S3?
  6 + true unless ENV["TEST_S3"] == "false"
  7 + end
  8 +
  9 + if test_S3? && File.exist?(File.join(File.dirname(__FILE__), '../../amazon_s3.yml'))
  10 + include BaseAttachmentTests
  11 + attachment_model S3Attachment
  12 +
  13 + def test_should_create_correct_bucket_name(klass = S3Attachment)
  14 + attachment_model klass
  15 + attachment = upload_file :filename => '/files/rails.png'
  16 + assert_equal attachment.s3_config[:bucket_name], attachment.bucket_name
  17 + end
  18 +
  19 + test_against_subclass :test_should_create_correct_bucket_name, S3Attachment
  20 +
  21 + def test_should_create_default_path_prefix(klass = S3Attachment)
  22 + attachment_model klass
  23 + attachment = upload_file :filename => '/files/rails.png'
  24 + assert_equal File.join(attachment_model.table_name, attachment.attachment_path_id), attachment.base_path
  25 + end
  26 +
  27 + test_against_subclass :test_should_create_default_path_prefix, S3Attachment
  28 +
  29 + def test_should_create_custom_path_prefix(klass = S3WithPathPrefixAttachment)
  30 + attachment_model klass
  31 + attachment = upload_file :filename => '/files/rails.png'
  32 + assert_equal File.join('some/custom/path/prefix', attachment.attachment_path_id), attachment.base_path
  33 + end
  34 +
  35 + test_against_subclass :test_should_create_custom_path_prefix, S3WithPathPrefixAttachment
  36 +
  37 + def test_should_create_valid_url(klass = S3Attachment)
  38 + attachment_model klass
  39 + attachment = upload_file :filename => '/files/rails.png'
  40 + assert_equal "#{s3_protocol}#{s3_hostname}#{s3_port_string}/#{attachment.bucket_name}/#{attachment.full_filename}", attachment.s3_url
  41 + end
  42 +
  43 + test_against_subclass :test_should_create_valid_url, S3Attachment
  44 +
  45 + def test_should_create_authenticated_url(klass = S3Attachment)
  46 + attachment_model klass
  47 + attachment = upload_file :filename => '/files/rails.png'
  48 + assert_match /^http.+AWSAccessKeyId.+Expires.+Signature.+/, attachment.authenticated_s3_url(:use_ssl => true)
  49 + end
  50 +
  51 + test_against_subclass :test_should_create_authenticated_url, S3Attachment
  52 +
  53 + def test_should_save_attachment(klass = S3Attachment)
  54 + attachment_model klass
  55 + assert_created do
  56 + attachment = upload_file :filename => '/files/rails.png'
  57 + assert_valid attachment
  58 + assert attachment.image?
  59 + assert !attachment.size.zero?
  60 + assert_kind_of Net::HTTPOK, http_response_for(attachment.s3_url)
  61 + end
  62 + end
  63 +
  64 + test_against_subclass :test_should_save_attachment, S3Attachment
  65 +
  66 + def test_should_delete_attachment_from_s3_when_attachment_record_destroyed(klass = S3Attachment)
  67 + attachment_model klass
  68 + attachment = upload_file :filename => '/files/rails.png'
  69 +
  70 + urls = [attachment.s3_url] + attachment.thumbnails.collect(&:s3_url)
  71 +
  72 + urls.each {|url| assert_kind_of Net::HTTPOK, http_response_for(url) }
  73 + attachment.destroy
  74 + urls.each do |url|
  75 + begin
  76 + http_response_for(url)
  77 + rescue Net::HTTPForbidden, Net::HTTPNotFound
  78 + nil
  79 + end
  80 + end
  81 + end
  82 +
  83 + test_against_subclass :test_should_delete_attachment_from_s3_when_attachment_record_destroyed, S3Attachment
  84 +
  85 + protected
  86 + def http_response_for(url)
  87 + url = URI.parse(url)
  88 + Net::HTTP.start(url.host, url.port) {|http| http.request_head(url.path) }
  89 + end
  90 +
  91 + def s3_protocol
  92 + Technoweenie::AttachmentFu::Backends::S3Backend.protocol
  93 + end
  94 +
  95 + def s3_hostname
  96 + Technoweenie::AttachmentFu::Backends::S3Backend.hostname
  97 + end
  98 +
  99 + def s3_port_string
  100 + Technoweenie::AttachmentFu::Backends::S3Backend.port_string
  101 + end
  102 + else
  103 + def test_flunk_s3
  104 + puts "s3 config file not loaded, tests not running"
  105 + end
  106 + end
  107 +end
0 108 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/base_attachment_tests.rb 0 → 100644
... ... @@ -0,0 +1,57 @@
  1 +module BaseAttachmentTests
  2 + def test_should_create_file_from_uploaded_file
  3 + assert_created do
  4 + attachment = upload_file :filename => '/files/foo.txt'
  5 + assert_valid attachment
  6 + assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file)
  7 + assert attachment.image?
  8 + assert !attachment.size.zero?
  9 + #assert_equal 3, attachment.size
  10 + assert_nil attachment.width
  11 + assert_nil attachment.height
  12 + end
  13 + end
  14 +
  15 + def test_reassign_attribute_data
  16 + assert_created 1 do
  17 + attachment = upload_file :filename => '/files/rails.png'
  18 + assert_valid attachment
  19 + assert attachment.size > 0, "no data was set"
  20 +
  21 + attachment.temp_data = 'wtf'
  22 + assert attachment.save_attachment?
  23 + attachment.save!
  24 +
  25 + assert_equal 'wtf', attachment_model.find(attachment.id).send(:current_data)
  26 + end
  27 + end
  28 +
  29 + def test_no_reassign_attribute_data_on_nil
  30 + assert_created 1 do
  31 + attachment = upload_file :filename => '/files/rails.png'
  32 + assert_valid attachment
  33 + assert attachment.size > 0, "no data was set"
  34 +
  35 + attachment.temp_data = nil
  36 + assert !attachment.save_attachment?
  37 + end
  38 + end
  39 +
  40 + def test_should_overwrite_old_contents_when_updating
  41 + attachment = upload_file :filename => '/files/rails.png'
  42 + assert_not_created do # no new db_file records
  43 + use_temp_file 'files/rails.png' do |file|
  44 + attachment.filename = 'rails2.png'
  45 + attachment.temp_path = File.join(fixture_path, file)
  46 + attachment.save!
  47 + end
  48 + end
  49 + end
  50 +
  51 + def test_should_save_without_updating_file
  52 + attachment = upload_file :filename => '/files/foo.txt'
  53 + assert_valid attachment
  54 + assert !attachment.save_attachment?
  55 + assert_nothing_raised { attachment.save! }
  56 + end
  57 +end
0 58 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/basic_test.rb 0 → 100644
... ... @@ -0,0 +1,64 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
  2 +
  3 +class BasicTest < Test::Unit::TestCase
  4 + def test_should_set_default_min_size
  5 + assert_equal 1, Attachment.attachment_options[:min_size]
  6 + end
  7 +
  8 + def test_should_set_default_max_size
  9 + assert_equal 1.megabyte, Attachment.attachment_options[:max_size]
  10 + end
  11 +
  12 + def test_should_set_default_size
  13 + assert_equal (1..1.megabyte), Attachment.attachment_options[:size]
  14 + end
  15 +
  16 + def test_should_set_default_thumbnails_option
  17 + assert_equal Hash.new, Attachment.attachment_options[:thumbnails]
  18 + end
  19 +
  20 + def test_should_set_default_thumbnail_class
  21 + assert_equal Attachment, Attachment.attachment_options[:thumbnail_class]
  22 + end
  23 +
  24 + def test_should_normalize_content_types_to_array
  25 + assert_equal %w(pdf), PdfAttachment.attachment_options[:content_type]
  26 + assert_equal %w(pdf doc txt), DocAttachment.attachment_options[:content_type]
  27 + assert_equal ['image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/x-png', 'image/jpg'], ImageAttachment.attachment_options[:content_type]
  28 + assert_equal ['pdf', 'image/jpeg', 'image/pjpeg', 'image/gif', 'image/png', 'image/x-png', 'image/jpg'], ImageOrPdfAttachment.attachment_options[:content_type]
  29 + end
  30 +
  31 + def test_should_sanitize_content_type
  32 + @attachment = Attachment.new :content_type => ' foo '
  33 + assert_equal 'foo', @attachment.content_type
  34 + end
  35 +
  36 + def test_should_sanitize_filenames
  37 + @attachment = Attachment.new :filename => 'blah/foo.bar'
  38 + assert_equal 'foo.bar', @attachment.filename
  39 +
  40 + @attachment.filename = 'blah\\foo.bar'
  41 + assert_equal 'foo.bar', @attachment.filename
  42 +
  43 + @attachment.filename = 'f o!O-.bar'
  44 + assert_equal 'f_o_O-.bar', @attachment.filename
  45 + end
  46 +
  47 + def test_should_convert_thumbnail_name
  48 + @attachment = FileAttachment.new :filename => 'foo.bar'
  49 + assert_equal 'foo.bar', @attachment.thumbnail_name_for(nil)
  50 + assert_equal 'foo.bar', @attachment.thumbnail_name_for('')
  51 + assert_equal 'foo_blah.bar', @attachment.thumbnail_name_for(:blah)
  52 + assert_equal 'foo_blah.blah.bar', @attachment.thumbnail_name_for('blah.blah')
  53 +
  54 + @attachment.filename = 'foo.bar.baz'
  55 + assert_equal 'foo.bar_blah.baz', @attachment.thumbnail_name_for(:blah)
  56 + end
  57 +
  58 + def test_should_require_valid_thumbnails_option
  59 + klass = Class.new(ActiveRecord::Base)
  60 + assert_raise ArgumentError do
  61 + klass.has_attachment :thumbnails => []
  62 + end
  63 + end
  64 +end
0 65 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/database.yml 0 → 100644
... ... @@ -0,0 +1,18 @@
  1 +sqlite:
  2 + :adapter: sqlite
  3 + :dbfile: attachment_fu_plugin.sqlite.db
  4 +sqlite3:
  5 + :adapter: sqlite3
  6 + :dbfile: attachment_fu_plugin.sqlite3.db
  7 +postgresql:
  8 + :adapter: postgresql
  9 + :username: postgres
  10 + :password: postgres
  11 + :database: attachment_fu_plugin_test
  12 + :min_messages: ERROR
  13 +mysql:
  14 + :adapter: mysql
  15 + :host: localhost
  16 + :username: rails
  17 + :password:
  18 + :database: attachment_fu_plugin_test
0 19 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/extra_attachment_test.rb 0 → 100644
... ... @@ -0,0 +1,57 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
  2 +
  3 +class OrphanAttachmentTest < Test::Unit::TestCase
  4 + include BaseAttachmentTests
  5 + attachment_model OrphanAttachment
  6 +
  7 + def test_should_create_image_from_uploaded_file
  8 + assert_created do
  9 + attachment = upload_file :filename => '/files/rails.png'
  10 + assert_valid attachment
  11 + assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file)
  12 + assert attachment.image?
  13 + assert !attachment.size.zero?
  14 + end
  15 + end
  16 +
  17 + def test_should_create_file_from_uploaded_file
  18 + assert_created do
  19 + attachment = upload_file :filename => '/files/foo.txt'
  20 + assert_valid attachment
  21 + assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file)
  22 + assert attachment.image?
  23 + assert !attachment.size.zero?
  24 + end
  25 + end
  26 +
  27 + def test_should_create_image_from_uploaded_file_with_custom_content_type
  28 + assert_created do
  29 + attachment = upload_file :content_type => 'foo/bar', :filename => '/files/rails.png'
  30 + assert_valid attachment
  31 + assert !attachment.image?
  32 + assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file)
  33 + assert !attachment.size.zero?
  34 + #assert_equal 1784, attachment.size
  35 + end
  36 + end
  37 +
  38 + def test_should_create_thumbnail
  39 + attachment = upload_file :filename => '/files/rails.png'
  40 +
  41 + assert_raise Technoweenie::AttachmentFu::ThumbnailError do
  42 + attachment.create_or_update_thumbnail(attachment.create_temp_file, 'thumb', 50, 50)
  43 + end
  44 + end
  45 +
  46 + def test_should_create_thumbnail_with_geometry_string
  47 + attachment = upload_file :filename => '/files/rails.png'
  48 +
  49 + assert_raise Technoweenie::AttachmentFu::ThumbnailError do
  50 + attachment.create_or_update_thumbnail(attachment.create_temp_file, 'thumb', 'x50')
  51 + end
  52 + end
  53 +end
  54 +
  55 +class MinimalAttachmentTest < OrphanAttachmentTest
  56 + attachment_model MinimalAttachment
  57 +end
0 58 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/fixtures/attachment.rb 0 → 100644
... ... @@ -0,0 +1,148 @@
  1 +class Attachment < ActiveRecord::Base
  2 + @@saves = 0
  3 + cattr_accessor :saves
  4 + has_attachment :processor => :rmagick
  5 + validates_as_attachment
  6 + after_attachment_saved do |record|
  7 + self.saves += 1
  8 + end
  9 +end
  10 +
  11 +class SmallAttachment < Attachment
  12 + has_attachment :max_size => 1.kilobyte
  13 +end
  14 +
  15 +class BigAttachment < Attachment
  16 + has_attachment :size => 1.megabyte..2.megabytes
  17 +end
  18 +
  19 +class PdfAttachment < Attachment
  20 + has_attachment :content_type => 'pdf'
  21 +end
  22 +
  23 +class DocAttachment < Attachment
  24 + has_attachment :content_type => %w(pdf doc txt)
  25 +end
  26 +
  27 +class ImageAttachment < Attachment
  28 + has_attachment :content_type => :image, :resize_to => [50,50]
  29 +end
  30 +
  31 +class ImageOrPdfAttachment < Attachment
  32 + has_attachment :content_type => ['pdf', :image], :resize_to => 'x50'
  33 +end
  34 +
  35 +class ImageWithThumbsAttachment < Attachment
  36 + has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }, :resize_to => [55,55]
  37 + after_resize do |record, img|
  38 + record.aspect_ratio = img.columns.to_f / img.rows.to_f
  39 + end
  40 +end
  41 +
  42 +class FileAttachment < ActiveRecord::Base
  43 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', :processor => :rmagick
  44 + validates_as_attachment
  45 +end
  46 +
  47 +class ImageFileAttachment < FileAttachment
  48 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  49 + :content_type => :image, :resize_to => [50,50]
  50 +end
  51 +
  52 +class ImageWithThumbsFileAttachment < FileAttachment
  53 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  54 + :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }, :resize_to => [55,55]
  55 + after_resize do |record, img|
  56 + record.aspect_ratio = img.columns.to_f / img.rows.to_f
  57 + end
  58 +end
  59 +
  60 +class ImageWithThumbsClassFileAttachment < FileAttachment
  61 + # use file_system_path to test backwards compatibility
  62 + has_attachment :file_system_path => 'vendor/plugins/attachment_fu/test/files',
  63 + :thumbnails => { :thumb => [50, 50] }, :resize_to => [55,55],
  64 + :thumbnail_class => 'ImageThumbnail'
  65 +end
  66 +
  67 +class ImageThumbnail < FileAttachment
  68 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files/thumbnails'
  69 +end
  70 +
  71 +# no parent
  72 +class OrphanAttachment < ActiveRecord::Base
  73 + has_attachment :processor => :rmagick
  74 + validates_as_attachment
  75 +end
  76 +
  77 +# no filename, no size, no content_type
  78 +class MinimalAttachment < ActiveRecord::Base
  79 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', :processor => :rmagick
  80 + validates_as_attachment
  81 +
  82 + def filename
  83 + "#{id}.file"
  84 + end
  85 +end
  86 +
  87 +begin
  88 + class ImageScienceAttachment < ActiveRecord::Base
  89 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  90 + :processor => :image_science, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55
  91 + end
  92 +rescue MissingSourceFile
  93 + puts $!.message
  94 + puts "no ImageScience"
  95 +end
  96 +
  97 +begin
  98 + class CoreImageAttachment < ActiveRecord::Base
  99 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  100 + :processor => :core_image, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55
  101 + end
  102 +rescue MissingSourceFile
  103 + puts $!.message
  104 + puts "no CoreImage"
  105 +end
  106 +
  107 +begin
  108 + class MiniMagickAttachment < ActiveRecord::Base
  109 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  110 + :processor => :mini_magick, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55
  111 + end
  112 +rescue MissingSourceFile
  113 + puts $!.message
  114 + puts "no Mini Magick"
  115 +end
  116 +
  117 +begin
  118 + class GD2Attachment < ActiveRecord::Base
  119 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  120 + :processor => :gd2, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55
  121 + end
  122 +rescue MissingSourceFile
  123 + puts $!.message
  124 + puts "no GD2"
  125 +end
  126 +
  127 +
  128 +begin
  129 + class MiniMagickAttachment < ActiveRecord::Base
  130 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  131 + :processor => :mini_magick, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55
  132 + end
  133 +rescue MissingSourceFile
  134 +end
  135 +
  136 +begin
  137 + class S3Attachment < ActiveRecord::Base
  138 + has_attachment :storage => :s3, :processor => :rmagick, :s3_config_path => File.join(File.dirname(__FILE__), '../amazon_s3.yml')
  139 + validates_as_attachment
  140 + end
  141 +
  142 + class S3WithPathPrefixAttachment < S3Attachment
  143 + has_attachment :storage => :s3, :path_prefix => 'some/custom/path/prefix', :processor => :rmagick
  144 + validates_as_attachment
  145 + end
  146 +rescue
  147 + puts "S3 error: #{$!}"
  148 +end
... ...
vendor/plugins/attachment_fu/test/fixtures/files/fake/rails.png 0 → 100644

4.12 KB

vendor/plugins/attachment_fu/test/fixtures/files/foo.txt 0 → 100644
... ... @@ -0,0 +1 @@
  1 +foo
0 2 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/fixtures/files/rails.png 0 → 100644

1.75 KB

vendor/plugins/attachment_fu/test/geometry_test.rb 0 → 100644
... ... @@ -0,0 +1,101 @@
  1 +require 'test/unit'
  2 +require File.expand_path(File.join(File.dirname(__FILE__), '../lib/geometry')) unless Object.const_defined?(:Geometry)
  3 +
  4 +class GeometryTest < Test::Unit::TestCase
  5 + def test_should_resize
  6 + assert_geometry 50, 64,
  7 + "50x50" => [39, 50],
  8 + "60x60" => [47, 60],
  9 + "100x100" => [78, 100]
  10 + end
  11 +
  12 + def test_should_resize_no_width
  13 + assert_geometry 50, 64,
  14 + "x50" => [39, 50],
  15 + "x60" => [47, 60],
  16 + "x100" => [78, 100]
  17 + end
  18 +
  19 + def test_should_resize_no_height
  20 + assert_geometry 50, 64,
  21 + "50" => [50, 64],
  22 + "60" => [60, 77],
  23 + "100" => [100, 128]
  24 + end
  25 +
  26 + def test_should_resize_with_percent
  27 + assert_geometry 50, 64,
  28 + "50x50%" => [25, 32],
  29 + "60x60%" => [30, 38],
  30 + "120x112%" => [60, 72]
  31 + end
  32 +
  33 + def test_should_resize_with_percent_and_no_width
  34 + assert_geometry 50, 64,
  35 + "x50%" => [50, 32],
  36 + "x60%" => [50, 38],
  37 + "x112%" => [50, 72]
  38 + end
  39 +
  40 + def test_should_resize_with_percent_and_no_height
  41 + assert_geometry 50, 64,
  42 + "50%" => [25, 32],
  43 + "60%" => [30, 38],
  44 + "120%" => [60, 77]
  45 + end
  46 +
  47 + def test_should_resize_with_less
  48 + assert_geometry 50, 64,
  49 + "50x50<" => [50, 64],
  50 + "60x60<" => [50, 64],
  51 + "100x100<" => [78, 100],
  52 + "100x112<" => [88, 112],
  53 + "40x70<" => [50, 64]
  54 + end
  55 +
  56 + def test_should_resize_with_less_and_no_width
  57 + assert_geometry 50, 64,
  58 + "x50<" => [50, 64],
  59 + "x60<" => [50, 64],
  60 + "x100<" => [78, 100]
  61 + end
  62 +
  63 + def test_should_resize_with_less_and_no_height
  64 + assert_geometry 50, 64,
  65 + "50<" => [50, 64],
  66 + "60<" => [60, 77],
  67 + "100<" => [100, 128]
  68 + end
  69 +
  70 + def test_should_resize_with_greater
  71 + assert_geometry 50, 64,
  72 + "50x50>" => [39, 50],
  73 + "60x60>" => [47, 60],
  74 + "100x100>" => [50, 64],
  75 + "100x112>" => [50, 64],
  76 + "40x70>" => [40, 51]
  77 + end
  78 +
  79 + def test_should_resize_with_greater_and_no_width
  80 + assert_geometry 50, 64,
  81 + "x40>" => [31, 40],
  82 + "x60>" => [47, 60],
  83 + "x100>" => [50, 64]
  84 + end
  85 +
  86 + def test_should_resize_with_greater_and_no_height
  87 + assert_geometry 50, 64,
  88 + "40>" => [40, 51],
  89 + "60>" => [50, 64],
  90 + "100>" => [50, 64]
  91 + end
  92 +
  93 + protected
  94 + def assert_geometry(width, height, values)
  95 + values.each do |geo, result|
  96 + # run twice to verify the Geometry string isn't modified after a run
  97 + geo = Geometry.from_s(geo)
  98 + 2.times { assert_equal result, [width, height] / geo }
  99 + end
  100 + end
  101 +end
0 102 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/processors/core_image_test.rb 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
  2 +
  3 +class CoreImageTest < Test::Unit::TestCase
  4 + attachment_model CoreImageAttachment
  5 +
  6 + if Object.const_defined?(:OSX)
  7 + def test_should_resize_image
  8 + attachment = upload_file :filename => '/files/rails.png'
  9 + assert_valid attachment
  10 + assert attachment.image?
  11 + # test core image thumbnail
  12 + assert_equal 42, attachment.width
  13 + assert_equal 55, attachment.height
  14 +
  15 + thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
  16 + geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  17 +
  18 + # test exact resize dimensions
  19 + assert_equal 50, thumb.width
  20 + assert_equal 51, thumb.height
  21 +
  22 + # test geometry string
  23 + assert_equal 31, geo.width
  24 + assert_equal 41, geo.height
  25 + end
  26 + else
  27 + def test_flunk
  28 + puts "CoreImage not loaded, tests not running"
  29 + end
  30 + end
  31 +end
0 32 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/processors/gd2_test.rb 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
  2 +
  3 +class GD2Test < Test::Unit::TestCase
  4 + attachment_model GD2Attachment
  5 +
  6 + if Object.const_defined?(:GD2)
  7 + def test_should_resize_image
  8 + attachment = upload_file :filename => '/files/rails.png'
  9 + assert_valid attachment
  10 + assert attachment.image?
  11 + # test gd2 thumbnail
  12 + assert_equal 43, attachment.width
  13 + assert_equal 55, attachment.height
  14 +
  15 + thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
  16 + geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  17 +
  18 + # test exact resize dimensions
  19 + assert_equal 50, thumb.width
  20 + assert_equal 51, thumb.height
  21 +
  22 + # test geometry string
  23 + assert_equal 31, geo.width
  24 + assert_equal 40, geo.height
  25 + end
  26 + else
  27 + def test_flunk
  28 + puts "GD2 not loaded, tests not running"
  29 + end
  30 + end
  31 +end
0 32 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/processors/image_science_test.rb 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
  2 +
  3 +class ImageScienceTest < Test::Unit::TestCase
  4 + attachment_model ImageScienceAttachment
  5 +
  6 + if Object.const_defined?(:ImageScience)
  7 + def test_should_resize_image
  8 + attachment = upload_file :filename => '/files/rails.png'
  9 + assert_valid attachment
  10 + assert attachment.image?
  11 + # test image science thumbnail
  12 + assert_equal 42, attachment.width
  13 + assert_equal 55, attachment.height
  14 +
  15 + thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
  16 + geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  17 +
  18 + # test exact resize dimensions
  19 + assert_equal 50, thumb.width
  20 + assert_equal 51, thumb.height
  21 +
  22 + # test geometry string
  23 + assert_equal 31, geo.width
  24 + assert_equal 41, geo.height
  25 + end
  26 + else
  27 + def test_flunk
  28 + puts "ImageScience not loaded, tests not running"
  29 + end
  30 + end
  31 +end
0 32 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/processors/mini_magick_test.rb 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
  2 +
  3 +class MiniMagickTest < Test::Unit::TestCase
  4 + attachment_model MiniMagickAttachment
  5 +
  6 + if Object.const_defined?(:MiniMagick)
  7 + def test_should_resize_image
  8 + attachment = upload_file :filename => '/files/rails.png'
  9 + assert_valid attachment
  10 + assert attachment.image?
  11 + # test MiniMagick thumbnail
  12 + assert_equal 43, attachment.width
  13 + assert_equal 55, attachment.height
  14 +
  15 + thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
  16 + geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  17 +
  18 + # test exact resize dimensions
  19 + assert_equal 50, thumb.width
  20 + assert_equal 51, thumb.height
  21 +
  22 + # test geometry string
  23 + assert_equal 31, geo.width
  24 + assert_equal 40, geo.height
  25 + end
  26 + else
  27 + def test_flunk
  28 + puts "MiniMagick not loaded, tests not running"
  29 + end
  30 + end
  31 +end
... ...
vendor/plugins/attachment_fu/test/processors/rmagick_test.rb 0 → 100644
... ... @@ -0,0 +1,255 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
  2 +
  3 +class RmagickTest < Test::Unit::TestCase
  4 + attachment_model Attachment
  5 +
  6 + if Object.const_defined?(:Magick)
  7 + def test_should_create_image_from_uploaded_file
  8 + assert_created do
  9 + attachment = upload_file :filename => '/files/rails.png'
  10 + assert_valid attachment
  11 + assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file)
  12 + assert attachment.image?
  13 + assert !attachment.size.zero?
  14 + #assert_equal 1784, attachment.size
  15 + assert_equal 50, attachment.width
  16 + assert_equal 64, attachment.height
  17 + assert_equal '50x64', attachment.image_size
  18 + end
  19 + end
  20 +
  21 + def test_should_create_image_from_uploaded_file_with_custom_content_type
  22 + assert_created do
  23 + attachment = upload_file :content_type => 'foo/bar', :filename => '/files/rails.png'
  24 + assert_valid attachment
  25 + assert !attachment.image?
  26 + assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file)
  27 + assert !attachment.size.zero?
  28 + #assert_equal 1784, attachment.size
  29 + assert_nil attachment.width
  30 + assert_nil attachment.height
  31 + assert_equal [], attachment.thumbnails
  32 + end
  33 + end
  34 +
  35 + def test_should_create_thumbnail
  36 + attachment = upload_file :filename => '/files/rails.png'
  37 +
  38 + assert_created do
  39 + basename, ext = attachment.filename.split '.'
  40 + thumbnail = attachment.create_or_update_thumbnail(attachment.create_temp_file, 'thumb', 50, 50)
  41 + assert_valid thumbnail
  42 + assert !thumbnail.size.zero?
  43 + #assert_in_delta 4673, thumbnail.size, 2
  44 + assert_equal 50, thumbnail.width
  45 + assert_equal 50, thumbnail.height
  46 + assert_equal [thumbnail.id], attachment.thumbnails.collect(&:id)
  47 + assert_equal attachment.id, thumbnail.parent_id if thumbnail.respond_to?(:parent_id)
  48 + assert_equal "#{basename}_thumb.#{ext}", thumbnail.filename
  49 + end
  50 + end
  51 +
  52 + def test_should_create_thumbnail_with_geometry_string
  53 + attachment = upload_file :filename => '/files/rails.png'
  54 +
  55 + assert_created do
  56 + basename, ext = attachment.filename.split '.'
  57 + thumbnail = attachment.create_or_update_thumbnail(attachment.create_temp_file, 'thumb', 'x50')
  58 + assert_valid thumbnail
  59 + assert !thumbnail.size.zero?
  60 + #assert_equal 3915, thumbnail.size
  61 + assert_equal 39, thumbnail.width
  62 + assert_equal 50, thumbnail.height
  63 + assert_equal [thumbnail], attachment.thumbnails
  64 + assert_equal attachment.id, thumbnail.parent_id if thumbnail.respond_to?(:parent_id)
  65 + assert_equal "#{basename}_thumb.#{ext}", thumbnail.filename
  66 + end
  67 + end
  68 +
  69 + def test_should_resize_image(klass = ImageAttachment)
  70 + attachment_model klass
  71 + assert_equal [50, 50], attachment_model.attachment_options[:resize_to]
  72 + attachment = upload_file :filename => '/files/rails.png'
  73 + assert_valid attachment
  74 + assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file)
  75 + assert attachment.image?
  76 + assert !attachment.size.zero?
  77 + #assert_in_delta 4673, attachment.size, 2
  78 + assert_equal 50, attachment.width
  79 + assert_equal 50, attachment.height
  80 + end
  81 +
  82 + test_against_subclass :test_should_resize_image, ImageAttachment
  83 +
  84 + def test_should_resize_image_with_geometry(klass = ImageOrPdfAttachment)
  85 + attachment_model klass
  86 + assert_equal 'x50', attachment_model.attachment_options[:resize_to]
  87 + attachment = upload_file :filename => '/files/rails.png'
  88 + assert_valid attachment
  89 + assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file)
  90 + assert attachment.image?
  91 + assert !attachment.size.zero?
  92 + #assert_equal 3915, attachment.size
  93 + assert_equal 39, attachment.width
  94 + assert_equal 50, attachment.height
  95 + end
  96 +
  97 + test_against_subclass :test_should_resize_image_with_geometry, ImageOrPdfAttachment
  98 +
  99 + def test_should_give_correct_thumbnail_filenames(klass = ImageWithThumbsFileAttachment)
  100 + attachment_model klass
  101 + assert_created 3 do
  102 + attachment = upload_file :filename => '/files/rails.png'
  103 + thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
  104 + geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  105 +
  106 + [attachment, thumb, geo].each { |record| assert_valid record }
  107 +
  108 + assert_match /rails\.png$/, attachment.full_filename
  109 + assert_match /rails_geometry\.png$/, attachment.full_filename(:geometry)
  110 + assert_match /rails_thumb\.png$/, attachment.full_filename(:thumb)
  111 + end
  112 + end
  113 +
  114 + test_against_subclass :test_should_give_correct_thumbnail_filenames, ImageWithThumbsFileAttachment
  115 +
  116 + def test_should_automatically_create_thumbnails(klass = ImageWithThumbsAttachment)
  117 + attachment_model klass
  118 + assert_created 3 do
  119 + attachment = upload_file :filename => '/files/rails.png'
  120 + assert_valid attachment
  121 + assert !attachment.size.zero?
  122 + #assert_equal 1784, attachment.size
  123 + assert_equal 55, attachment.width
  124 + assert_equal 55, attachment.height
  125 + assert_equal 2, attachment.thumbnails.length
  126 + assert_equal 1.0, attachment.aspect_ratio
  127 +
  128 + thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
  129 + assert !thumb.new_record?, thumb.errors.full_messages.join("\n")
  130 + assert !thumb.size.zero?
  131 + #assert_in_delta 4673, thumb.size, 2
  132 + assert_equal 50, thumb.width
  133 + assert_equal 50, thumb.height
  134 + assert_equal 1.0, thumb.aspect_ratio
  135 +
  136 + geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  137 + assert !geo.new_record?, geo.errors.full_messages.join("\n")
  138 + assert !geo.size.zero?
  139 + #assert_equal 3915, geo.size
  140 + assert_equal 50, geo.width
  141 + assert_equal 50, geo.height
  142 + assert_equal 1.0, geo.aspect_ratio
  143 + end
  144 + end
  145 +
  146 + test_against_subclass :test_should_automatically_create_thumbnails, ImageWithThumbsAttachment
  147 +
  148 + # same as above method, but test it on a file model
  149 + test_against_class :test_should_automatically_create_thumbnails, ImageWithThumbsFileAttachment
  150 + test_against_subclass :test_should_automatically_create_thumbnails_on_class, ImageWithThumbsFileAttachment
  151 +
  152 + def test_should_use_thumbnail_subclass(klass = ImageWithThumbsClassFileAttachment)
  153 + attachment_model klass
  154 + attachment = nil
  155 + assert_difference ImageThumbnail, :count do
  156 + attachment = upload_file :filename => '/files/rails.png'
  157 + assert_valid attachment
  158 + end
  159 + assert_kind_of ImageThumbnail, attachment.thumbnails.first
  160 + if attachment.thumbnails.first.respond_to?(:parent)
  161 + assert_equal attachment.id, attachment.thumbnails.first.parent.id
  162 + assert_kind_of FileAttachment, attachment.thumbnails.first.parent
  163 + end
  164 + assert_equal 'rails_thumb.png', attachment.thumbnails.first.filename
  165 + assert_equal attachment.thumbnails.first.full_filename, attachment.full_filename(attachment.thumbnails.first.thumbnail),
  166 + "#full_filename does not use thumbnail class' path."
  167 + assert_equal attachment.destroy, attachment
  168 + end
  169 +
  170 + test_against_subclass :test_should_use_thumbnail_subclass, ImageWithThumbsClassFileAttachment
  171 +
  172 + def test_should_remove_old_thumbnail_files_when_updating(klass = ImageWithThumbsFileAttachment)
  173 + attachment_model klass
  174 + attachment = nil
  175 + assert_created 3 do
  176 + attachment = upload_file :filename => '/files/rails.png'
  177 + end
  178 +
  179 + old_filenames = [attachment.full_filename] + attachment.thumbnails.collect(&:full_filename)
  180 +
  181 + assert_not_created do
  182 + use_temp_file "files/rails.png" do |file|
  183 + attachment.filename = 'rails2.png'
  184 + attachment.temp_path = File.join(fixture_path, file)
  185 + attachment.save
  186 + new_filenames = [attachment.reload.full_filename] + attachment.thumbnails.collect { |t| t.reload.full_filename }
  187 + new_filenames.each { |f| assert File.exists?(f), "#{f} does not exist" }
  188 + old_filenames.each { |f| assert !File.exists?(f), "#{f} still exists" }
  189 + end
  190 + end
  191 + end
  192 +
  193 + test_against_subclass :test_should_remove_old_thumbnail_files_when_updating, ImageWithThumbsFileAttachment
  194 +
  195 + def test_should_delete_file_when_in_file_system_when_attachment_record_destroyed(klass = ImageWithThumbsFileAttachment)
  196 + attachment_model klass
  197 + attachment = upload_file :filename => '/files/rails.png'
  198 + filenames = [attachment.full_filename] + attachment.thumbnails.collect(&:full_filename)
  199 + filenames.each { |f| assert File.exists?(f), "#{f} never existed to delete on destroy" }
  200 + attachment.destroy
  201 + filenames.each { |f| assert !File.exists?(f), "#{f} still exists" }
  202 + end
  203 +
  204 + test_against_subclass :test_should_delete_file_when_in_file_system_when_attachment_record_destroyed, ImageWithThumbsFileAttachment
  205 +
  206 + def test_should_have_full_filename_method(klass = FileAttachment)
  207 + attachment_model klass
  208 + attachment = upload_file :filename => '/files/rails.png'
  209 + assert_respond_to attachment, :full_filename
  210 + end
  211 +
  212 + test_against_subclass :test_should_have_full_filename_method, FileAttachment
  213 +
  214 + def test_should_overwrite_old_thumbnail_records_when_updating(klass = ImageWithThumbsAttachment)
  215 + attachment_model klass
  216 + attachment = nil
  217 + assert_created 3 do
  218 + attachment = upload_file :filename => '/files/rails.png'
  219 + end
  220 + assert_not_created do # no new db_file records
  221 + use_temp_file "files/rails.png" do |file|
  222 + attachment.filename = 'rails2.png'
  223 + # The above test (#test_should_have_full_filename_method) to pass before be can set the temp_path below --
  224 + # #temp_path calls #full_filename, which is not getting mixed into the attachment. Maybe we don't need to
  225 + # set temp_path at all?
  226 + #
  227 + # attachment.temp_path = File.join(fixture_path, file)
  228 + attachment.save!
  229 + end
  230 + end
  231 + end
  232 +
  233 + test_against_subclass :test_should_overwrite_old_thumbnail_records_when_updating, ImageWithThumbsAttachment
  234 +
  235 + def test_should_overwrite_old_thumbnail_records_when_renaming(klass = ImageWithThumbsAttachment)
  236 + attachment_model klass
  237 + attachment = nil
  238 + assert_created 3 do
  239 + attachment = upload_file :class => klass, :filename => '/files/rails.png'
  240 + end
  241 + assert_not_created do # no new db_file records
  242 + attachment.filename = 'rails2.png'
  243 + attachment.save
  244 + assert !attachment.reload.size.zero?
  245 + assert_equal 'rails2.png', attachment.filename
  246 + end
  247 + end
  248 +
  249 + test_against_subclass :test_should_overwrite_old_thumbnail_records_when_renaming, ImageWithThumbsAttachment
  250 + else
  251 + def test_flunk
  252 + puts "RMagick not installed, no tests running"
  253 + end
  254 + end
  255 +end
... ...
vendor/plugins/attachment_fu/test/schema.rb 0 → 100644
... ... @@ -0,0 +1,108 @@
  1 +ActiveRecord::Schema.define(:version => 0) do
  2 + create_table :attachments, :force => true do |t|
  3 + t.column :db_file_id, :integer
  4 + t.column :parent_id, :integer
  5 + t.column :thumbnail, :string
  6 + t.column :filename, :string, :limit => 255
  7 + t.column :content_type, :string, :limit => 255
  8 + t.column :size, :integer
  9 + t.column :width, :integer
  10 + t.column :height, :integer
  11 + t.column :aspect_ratio, :float
  12 + end
  13 +
  14 + create_table :file_attachments, :force => true do |t|
  15 + t.column :parent_id, :integer
  16 + t.column :thumbnail, :string
  17 + t.column :filename, :string, :limit => 255
  18 + t.column :content_type, :string, :limit => 255
  19 + t.column :size, :integer
  20 + t.column :width, :integer
  21 + t.column :height, :integer
  22 + t.column :type, :string
  23 + t.column :aspect_ratio, :float
  24 + end
  25 +
  26 + create_table :gd2_attachments, :force => true do |t|
  27 + t.column :parent_id, :integer
  28 + t.column :thumbnail, :string
  29 + t.column :filename, :string, :limit => 255
  30 + t.column :content_type, :string, :limit => 255
  31 + t.column :size, :integer
  32 + t.column :width, :integer
  33 + t.column :height, :integer
  34 + t.column :type, :string
  35 + end
  36 +
  37 + create_table :image_science_attachments, :force => true do |t|
  38 + t.column :parent_id, :integer
  39 + t.column :thumbnail, :string
  40 + t.column :filename, :string, :limit => 255
  41 + t.column :content_type, :string, :limit => 255
  42 + t.column :size, :integer
  43 + t.column :width, :integer
  44 + t.column :height, :integer
  45 + t.column :type, :string
  46 + end
  47 +
  48 + create_table :core_image_attachments, :force => true do |t|
  49 + t.column :parent_id, :integer
  50 + t.column :thumbnail, :string
  51 + t.column :filename, :string, :limit => 255
  52 + t.column :content_type, :string, :limit => 255
  53 + t.column :size, :integer
  54 + t.column :width, :integer
  55 + t.column :height, :integer
  56 + t.column :type, :string
  57 + end
  58 +
  59 + create_table :mini_magick_attachments, :force => true do |t|
  60 + t.column :parent_id, :integer
  61 + t.column :thumbnail, :string
  62 + t.column :filename, :string, :limit => 255
  63 + t.column :content_type, :string, :limit => 255
  64 + t.column :size, :integer
  65 + t.column :width, :integer
  66 + t.column :height, :integer
  67 + t.column :type, :string
  68 + end
  69 +
  70 + create_table :mini_magick_attachments, :force => true do |t|
  71 + t.column :parent_id, :integer
  72 + t.column :thumbnail, :string
  73 + t.column :filename, :string, :limit => 255
  74 + t.column :content_type, :string, :limit => 255
  75 + t.column :size, :integer
  76 + t.column :width, :integer
  77 + t.column :height, :integer
  78 + t.column :type, :string
  79 + end
  80 +
  81 + create_table :orphan_attachments, :force => true do |t|
  82 + t.column :db_file_id, :integer
  83 + t.column :filename, :string, :limit => 255
  84 + t.column :content_type, :string, :limit => 255
  85 + t.column :size, :integer
  86 + end
  87 +
  88 + create_table :minimal_attachments, :force => true do |t|
  89 + t.column :size, :integer
  90 + t.column :content_type, :string, :limit => 255
  91 + end
  92 +
  93 + create_table :db_files, :force => true do |t|
  94 + t.column :data, :binary
  95 + end
  96 +
  97 + create_table :s3_attachments, :force => true do |t|
  98 + t.column :parent_id, :integer
  99 + t.column :thumbnail, :string
  100 + t.column :filename, :string, :limit => 255
  101 + t.column :content_type, :string, :limit => 255
  102 + t.column :size, :integer
  103 + t.column :width, :integer
  104 + t.column :height, :integer
  105 + t.column :type, :string
  106 + t.column :aspect_ratio, :float
  107 + end
  108 +end
0 109 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/test_helper.rb 0 → 100644
... ... @@ -0,0 +1,142 @@
  1 +$:.unshift(File.dirname(__FILE__) + '/../lib')
  2 +
  3 +ENV['RAILS_ENV'] = 'test'
  4 +ENV['RAILS_ROOT'] ||= File.dirname(__FILE__) + '/../../../..'
  5 +
  6 +require 'test/unit'
  7 +require File.expand_path(File.join(ENV['RAILS_ROOT'], 'config/environment.rb'))
  8 +require 'active_record/fixtures'
  9 +require 'action_controller/test_process'
  10 +
  11 +config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
  12 +ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
  13 +
  14 +db_adapter = ENV['DB']
  15 +
  16 +# no db passed, try one of these fine config-free DBs before bombing.
  17 +db_adapter ||=
  18 + begin
  19 + require 'rubygems'
  20 + require 'sqlite'
  21 + 'sqlite'
  22 + rescue MissingSourceFile
  23 + begin
  24 + require 'sqlite3'
  25 + 'sqlite3'
  26 + rescue MissingSourceFile
  27 + end
  28 + end
  29 +
  30 +if db_adapter.nil?
  31 + raise "No DB Adapter selected. Pass the DB= option to pick one, or install Sqlite or Sqlite3."
  32 +end
  33 +
  34 +ActiveRecord::Base.establish_connection(config[db_adapter])
  35 +
  36 +load(File.dirname(__FILE__) + "/schema.rb")
  37 +
  38 +Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures"
  39 +$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
  40 +
  41 +class Test::Unit::TestCase #:nodoc:
  42 + include ActionController::TestProcess
  43 + def create_fixtures(*table_names)
  44 + if block_given?
  45 + Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
  46 + else
  47 + Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
  48 + end
  49 + end
  50 +
  51 + def setup
  52 + Attachment.saves = 0
  53 + DbFile.transaction { [Attachment, FileAttachment, OrphanAttachment, MinimalAttachment, DbFile].each { |klass| klass.delete_all } }
  54 + attachment_model self.class.attachment_model
  55 + end
  56 +
  57 + def teardown
  58 + FileUtils.rm_rf File.join(File.dirname(__FILE__), 'files')
  59 + end
  60 +
  61 + self.use_transactional_fixtures = true
  62 + self.use_instantiated_fixtures = false
  63 +
  64 + def self.attachment_model(klass = nil)
  65 + @attachment_model = klass if klass
  66 + @attachment_model
  67 + end
  68 +
  69 + def self.test_against_class(test_method, klass, subclass = false)
  70 + define_method("#{test_method}_on_#{:sub if subclass}class") do
  71 + klass = Class.new(klass) if subclass
  72 + attachment_model klass
  73 + send test_method, klass
  74 + end
  75 + end
  76 +
  77 + def self.test_against_subclass(test_method, klass)
  78 + test_against_class test_method, klass, true
  79 + end
  80 +
  81 + protected
  82 + def upload_file(options = {})
  83 + use_temp_file options[:filename] do |file|
  84 + att = attachment_model.create :uploaded_data => fixture_file_upload(file, options[:content_type] || 'image/png')
  85 + att.reload unless att.new_record?
  86 + return att
  87 + end
  88 + end
  89 +
  90 + def use_temp_file(fixture_filename)
  91 + temp_path = File.join('/tmp', File.basename(fixture_filename))
  92 + FileUtils.mkdir_p File.join(fixture_path, 'tmp')
  93 + FileUtils.cp File.join(fixture_path, fixture_filename), File.join(fixture_path, temp_path)
  94 + yield temp_path
  95 + ensure
  96 + FileUtils.rm_rf File.join(fixture_path, 'tmp')
  97 + end
  98 +
  99 + def assert_created(num = 1)
  100 + assert_difference attachment_model.base_class, :count, num do
  101 + if attachment_model.included_modules.include? DbFile
  102 + assert_difference DbFile, :count, num do
  103 + yield
  104 + end
  105 + else
  106 + yield
  107 + end
  108 + end
  109 + end
  110 +
  111 + def assert_not_created
  112 + assert_created(0) { yield }
  113 + end
  114 +
  115 + def should_reject_by_size_with(klass)
  116 + attachment_model klass
  117 + assert_not_created do
  118 + attachment = upload_file :filename => '/files/rails.png'
  119 + assert attachment.new_record?
  120 + assert attachment.errors.on(:size)
  121 + assert_nil attachment.db_file if attachment.respond_to?(:db_file)
  122 + end
  123 + end
  124 +
  125 + def assert_difference(object, method = nil, difference = 1)
  126 + initial_value = object.send(method)
  127 + yield
  128 + assert_equal initial_value + difference, object.send(method)
  129 + end
  130 +
  131 + def assert_no_difference(object, method, &block)
  132 + assert_difference object, method, 0, &block
  133 + end
  134 +
  135 + def attachment_model(klass = nil)
  136 + @attachment_model = klass if klass
  137 + @attachment_model
  138 + end
  139 +end
  140 +
  141 +require File.join(File.dirname(__FILE__), 'fixtures/attachment')
  142 +require File.join(File.dirname(__FILE__), 'base_attachment_tests')
0 143 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/test/validation_test.rb 0 → 100644
... ... @@ -0,0 +1,55 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
  2 +
  3 +class ValidationTest < Test::Unit::TestCase
  4 + def test_should_invalidate_big_files
  5 + @attachment = SmallAttachment.new
  6 + assert !@attachment.valid?
  7 + assert @attachment.errors.on(:size)
  8 +
  9 + @attachment.size = 2000
  10 + assert !@attachment.valid?
  11 + assert @attachment.errors.on(:size), @attachment.errors.full_messages.to_sentence
  12 +
  13 + @attachment.size = 1000
  14 + assert !@attachment.valid?
  15 + assert_nil @attachment.errors.on(:size)
  16 + end
  17 +
  18 + def test_should_invalidate_small_files
  19 + @attachment = BigAttachment.new
  20 + assert !@attachment.valid?
  21 + assert @attachment.errors.on(:size)
  22 +
  23 + @attachment.size = 2000
  24 + assert !@attachment.valid?
  25 + assert @attachment.errors.on(:size), @attachment.errors.full_messages.to_sentence
  26 +
  27 + @attachment.size = 1.megabyte
  28 + assert !@attachment.valid?
  29 + assert_nil @attachment.errors.on(:size)
  30 + end
  31 +
  32 + def test_should_validate_content_type
  33 + @attachment = PdfAttachment.new
  34 + assert !@attachment.valid?
  35 + assert @attachment.errors.on(:content_type)
  36 +
  37 + @attachment.content_type = 'foo'
  38 + assert !@attachment.valid?
  39 + assert @attachment.errors.on(:content_type)
  40 +
  41 + @attachment.content_type = 'pdf'
  42 + assert !@attachment.valid?
  43 + assert_nil @attachment.errors.on(:content_type)
  44 + end
  45 +
  46 + def test_should_require_filename
  47 + @attachment = Attachment.new
  48 + assert !@attachment.valid?
  49 + assert @attachment.errors.on(:filename)
  50 +
  51 + @attachment.filename = 'foo'
  52 + assert !@attachment.valid?
  53 + assert_nil @attachment.errors.on(:filename)
  54 + end
  55 +end
0 56 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/vendor/red_artisan/core_image/filters/color.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +module RedArtisan
  2 + module CoreImage
  3 + module Filters
  4 + module Color
  5 +
  6 + def greyscale(color = nil, intensity = 1.00)
  7 + create_core_image_context(@original.extent.size.width, @original.extent.size.height)
  8 +
  9 + color = OSX::CIColor.colorWithString("1.0 1.0 1.0 1.0") unless color
  10 +
  11 + @original.color_monochrome :inputColor => color, :inputIntensity => intensity do |greyscale|
  12 + @target = greyscale
  13 + end
  14 + end
  15 +
  16 + def sepia(intensity = 1.00)
  17 + create_core_image_context(@original.extent.size.width, @original.extent.size.height)
  18 +
  19 + @original.sepia_tone :inputIntensity => intensity do |sepia|
  20 + @target = sepia
  21 + end
  22 + end
  23 +
  24 + end
  25 + end
  26 + end
  27 +end
0 28 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/vendor/red_artisan/core_image/filters/effects.rb 0 → 100644
... ... @@ -0,0 +1,31 @@
  1 +module RedArtisan
  2 + module CoreImage
  3 + module Filters
  4 + module Effects
  5 +
  6 + def spotlight(position, points_at, brightness, concentration, color)
  7 + create_core_image_context(@original.extent.size.width, @original.extent.size.height)
  8 +
  9 + @original.spot_light :inputLightPosition => vector3(*position), :inputLightPointsAt => vector3(*points_at),
  10 + :inputBrightness => brightness, :inputConcentration => concentration, :inputColor => color do |spot|
  11 + @target = spot
  12 + end
  13 + end
  14 +
  15 + def edges(intensity = 1.00)
  16 + create_core_image_context(@original.extent.size.width, @original.extent.size.height)
  17 +
  18 + @original.edges :inputIntensity => intensity do |edged|
  19 + @target = edged
  20 + end
  21 + end
  22 +
  23 + private
  24 +
  25 + def vector3(x, y, w)
  26 + OSX::CIVector.vectorWithX_Y_Z(x, y, w)
  27 + end
  28 + end
  29 + end
  30 + end
  31 +end
... ...
vendor/plugins/attachment_fu/vendor/red_artisan/core_image/filters/perspective.rb 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +module RedArtisan
  2 + module CoreImage
  3 + module Filters
  4 + module Perspective
  5 +
  6 + def perspective(top_left, top_right, bottom_left, bottom_right)
  7 + create_core_image_context(@original.extent.size.width, @original.extent.size.height)
  8 +
  9 + @original.perspective_transform :inputTopLeft => top_left, :inputTopRight => top_right, :inputBottomLeft => bottom_left, :inputBottomRight => bottom_right do |transformed|
  10 + @target = transformed
  11 + end
  12 + end
  13 +
  14 + def perspective_tiled(top_left, top_right, bottom_left, bottom_right)
  15 + create_core_image_context(@original.extent.size.width, @original.extent.size.height)
  16 +
  17 + @original.perspective_tile :inputTopLeft => top_left, :inputTopRight => top_right, :inputBottomLeft => bottom_left, :inputBottomRight => bottom_right do |tiled|
  18 + @target = tiled
  19 + end
  20 + end
  21 +
  22 + end
  23 + end
  24 + end
  25 +end
0 26 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/vendor/red_artisan/core_image/filters/quality.rb 0 → 100644
... ... @@ -0,0 +1,25 @@
  1 +module RedArtisan
  2 + module CoreImage
  3 + module Filters
  4 + module Quality
  5 +
  6 + def reduce_noise(level = 0.02, sharpness = 0.4)
  7 + create_core_image_context(@original.extent.size.width, @original.extent.size.height)
  8 +
  9 + @original.noise_reduction :inputNoiseLevel => level, :inputSharpness => sharpness do |noise_reduced|
  10 + @target = noise_reduced
  11 + end
  12 + end
  13 +
  14 + def adjust_exposure(input_ev = 0.5)
  15 + create_core_image_context(@original.extent.size.width, @original.extent.size.height)
  16 +
  17 + @original.exposure_adjust :inputEV => input_ev do |adjusted|
  18 + @target = adjusted
  19 + end
  20 + end
  21 +
  22 + end
  23 + end
  24 + end
  25 +end
0 26 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/vendor/red_artisan/core_image/filters/scale.rb 0 → 100644
... ... @@ -0,0 +1,47 @@
  1 +module RedArtisan
  2 + module CoreImage
  3 + module Filters
  4 + module Scale
  5 +
  6 + def resize(width, height)
  7 + create_core_image_context(width, height)
  8 +
  9 + scale_x, scale_y = scale(width, height)
  10 +
  11 + @original.affine_clamp :inputTransform => OSX::NSAffineTransform.transform do |clamped|
  12 + clamped.lanczos_scale_transform :inputScale => scale_x > scale_y ? scale_x : scale_y, :inputAspectRatio => scale_x / scale_y do |scaled|
  13 + scaled.crop :inputRectangle => vector(0, 0, width, height) do |cropped|
  14 + @target = cropped
  15 + end
  16 + end
  17 + end
  18 + end
  19 +
  20 + def thumbnail(width, height)
  21 + create_core_image_context(width, height)
  22 +
  23 + transform = OSX::NSAffineTransform.transform
  24 + transform.scaleXBy_yBy *scale(width, height)
  25 +
  26 + @original.affine_transform :inputTransform => transform do |scaled|
  27 + @target = scaled
  28 + end
  29 + end
  30 +
  31 + def fit(size)
  32 + original_size = @original.extent.size
  33 + scale = size.to_f / (original_size.width > original_size.height ? original_size.width : original_size.height)
  34 + resize (original_size.width * scale).to_i, (original_size.height * scale).to_i
  35 + end
  36 +
  37 + private
  38 +
  39 + def scale(width, height)
  40 + original_size = @original.extent.size
  41 + return width.to_f / original_size.width.to_f, height.to_f / original_size.height.to_f
  42 + end
  43 +
  44 + end
  45 + end
  46 + end
  47 +end
0 48 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/vendor/red_artisan/core_image/filters/watermark.rb 0 → 100644
... ... @@ -0,0 +1,32 @@
  1 +module RedArtisan
  2 + module CoreImage
  3 + module Filters
  4 + module Watermark
  5 +
  6 + def watermark(watermark_image, tile = false, strength = 0.1)
  7 + create_core_image_context(@original.extent.size.width, @original.extent.size.height)
  8 +
  9 + if watermark_image.respond_to? :to_str
  10 + watermark_image = OSX::CIImage.from(watermark_image.to_str)
  11 + end
  12 +
  13 + if tile
  14 + tile_transform = OSX::NSAffineTransform.transform
  15 + tile_transform.scaleXBy_yBy 1.0, 1.0
  16 +
  17 + watermark_image.affine_tile :inputTransform => tile_transform do |tiled|
  18 + tiled.crop :inputRectangle => vector(0, 0, @original.extent.size.width, @original.extent.size.height) do |tiled_watermark|
  19 + watermark_image = tiled_watermark
  20 + end
  21 + end
  22 + end
  23 +
  24 + @original.dissolve_transition :inputTargetImage => watermark_image, :inputTime => strength do |watermarked|
  25 + @target = watermarked
  26 + end
  27 + end
  28 +
  29 + end
  30 + end
  31 + end
  32 +end
0 33 \ No newline at end of file
... ...
vendor/plugins/attachment_fu/vendor/red_artisan/core_image/processor.rb 0 → 100644
... ... @@ -0,0 +1,123 @@
  1 +require 'rubygems'
  2 +require 'osx/cocoa'
  3 +require 'active_support'
  4 +
  5 +require 'red_artisan/core_image/filters/scale'
  6 +require 'red_artisan/core_image/filters/color'
  7 +require 'red_artisan/core_image/filters/watermark'
  8 +require 'red_artisan/core_image/filters/quality'
  9 +require 'red_artisan/core_image/filters/perspective'
  10 +require 'red_artisan/core_image/filters/effects'
  11 +
  12 +# Generic image processor for scaling images based on CoreImage via RubyCocoa.
  13 +#
  14 +# Example usage:
  15 +#
  16 +# p = Processor.new OSX::CIImage.from(path_to_image)
  17 +# p.resize(640, 480)
  18 +# p.render do |result|
  19 +# result.save('resized.jpg', OSX::NSJPEGFileType)
  20 +# end
  21 +#
  22 +# This will resize the image to the given dimensions exactly, if you'd like to ensure that aspect ratio is preserved:
  23 +#
  24 +# p = Processor.new OSX::CIImage.from(path_to_image)
  25 +# p.fit(640)
  26 +# p.render do |result|
  27 +# result.save('resized.jpg', OSX::NSJPEGFileType)
  28 +# end
  29 +#
  30 +# fit(size) will attempt its best to resize the image so that the longest width/height (depending on image orientation) will match
  31 +# the given size. The second axis will be calculated automatically based on the aspect ratio.
  32 +#
  33 +# Scaling is performed by first clamping the image so that its external bounds become infinite, this helps when scaling so that any
  34 +# rounding discrepencies in dimensions don't affect the resultant image. We then perform a Lanczos transform on the image which scales
  35 +# it to the target size. We then crop the image to the traget dimensions.
  36 +#
  37 +# If you are generating smaller images such as thumbnails where high quality rendering isn't as important, an additional method is
  38 +# available:
  39 +#
  40 +# p = Processor.new OSX::CIImage.from(path_to_image)
  41 +# p.thumbnail(100, 100)
  42 +# p.render do |result|
  43 +# result.save('resized.jpg', OSX::NSJPEGFileType)
  44 +# end
  45 +#
  46 +# This will perform a straight affine transform and scale the X and Y boundaries to the requested size. Generally, this will be faster
  47 +# than a lanczos scale transform, but with a scaling quality trade.
  48 +#
  49 +# More than welcome to intregrate any patches, improvements - feel free to mail me with ideas.
  50 +#
  51 +# Thanks to
  52 +# * Satoshi Nakagawa for working out that OCObjWrapper needs inclusion when aliasing method_missing on existing OSX::* classes.
  53 +# * Vasantha Crabb for general help and inspiration with Cocoa
  54 +# * Ben Schwarz for example image data and collaboration during performance testing
  55 +#
  56 +# Copyright (c) Marcus Crafter <crafterm@redartisan.com> released under the MIT license
  57 +#
  58 +module RedArtisan
  59 + module CoreImage
  60 + class Processor
  61 +
  62 + def initialize(original)
  63 + if original.respond_to? :to_str
  64 + @original = OSX::CIImage.from(original.to_str)
  65 + else
  66 + @original = original
  67 + end
  68 + end
  69 +
  70 + def render(&block)
  71 + raise "unprocessed image: #{@original}" unless @target
  72 + block.call @target
  73 + end
  74 +
  75 + include Filters::Scale, Filters::Color, Filters::Watermark, Filters::Quality, Filters::Perspective, Filters::Effects
  76 +
  77 + private
  78 +
  79 + def create_core_image_context(width, height)
  80 + output = OSX::NSBitmapImageRep.alloc.initWithBitmapDataPlanes_pixelsWide_pixelsHigh_bitsPerSample_samplesPerPixel_hasAlpha_isPlanar_colorSpaceName_bytesPerRow_bitsPerPixel(nil, width, height, 8, 4, true, false, OSX::NSDeviceRGBColorSpace, 0, 0)
  81 + context = OSX::NSGraphicsContext.graphicsContextWithBitmapImageRep(output)
  82 + OSX::NSGraphicsContext.setCurrentContext(context)
  83 + @ci_context = context.CIContext
  84 + end
  85 +
  86 + def vector(x, y, w, h)
  87 + OSX::CIVector.vectorWithX_Y_Z_W(x, y, w, h)
  88 + end
  89 + end
  90 + end
  91 +end
  92 +
  93 +module OSX
  94 + class CIImage
  95 + include OCObjWrapper
  96 +
  97 + def method_missing_with_filter_processing(sym, *args, &block)
  98 + f = OSX::CIFilter.filterWithName("CI#{sym.to_s.camelize}")
  99 + return method_missing_without_filter_processing(sym, *args, &block) unless f
  100 +
  101 + f.setDefaults if f.respond_to? :setDefaults
  102 + f.setValue_forKey(self, 'inputImage')
  103 + options = args.last.is_a?(Hash) ? args.last : {}
  104 + options.each { |k, v| f.setValue_forKey(v, k.to_s) }
  105 +
  106 + block.call f.valueForKey('outputImage')
  107 + end
  108 +
  109 + alias_method_chain :method_missing, :filter_processing
  110 +
  111 + def save(target, format = OSX::NSJPEGFileType, properties = nil)
  112 + bitmapRep = OSX::NSBitmapImageRep.alloc.initWithCIImage(self)
  113 + blob = bitmapRep.representationUsingType_properties(format, properties)
  114 + blob.writeToFile_atomically(target, false)
  115 + end
  116 +
  117 + def self.from(filepath)
  118 + raise Errno::ENOENT, "No such file or directory - #{filepath}" unless File.exists?(filepath)
  119 + OSX::CIImage.imageWithContentsOfURL(OSX::NSURL.fileURLWithPath(filepath))
  120 + end
  121 + end
  122 +end
  123 +
... ...