Commit d948f58184c5d793bb95a2ea87eea9314c5649a4

Authored by Rodrigo Souto
1 parent 92bbe720

rails3: updating attachment_fu

Using https://github.com/pothoven/attachment_fu.git. This repository is
an integration of attachment_fu for ruby 1.9.2 and rails 3.2.
Showing 44 changed files with 2163 additions and 283 deletions   Show diff stats
vendor/plugins/attachment_fu/.gitignore
@@ -1,2 +0,0 @@ @@ -1,2 +0,0 @@
1 -test/amazon_s3.yml  
2 -test/debug.log  
vendor/plugins/attachment_fu/CHANGELOG
  1 +* Aug 22, 2013 *
  2 +* Added cache-control header option and Ruby 1.9 fix for S3 from tricycle
  3 +
  4 +* Aug 21, 2013 *
  5 +* Added S3 :encrypted_storage option support from cschulte22
  6 +
  7 +* Jul 5, 2013
  8 +* Pull in changes from lchimonji10 to reformat README to RDoc format
  9 +
  10 +* Apr 10, 2013 *
  11 +* Ruby 2 compatibility fix
  12 +* Removed some lingering occurrences of RAILS_ROOT and RAILS_ENV
  13 +* clean up of gemspec file
  14 +* no longer package unnecessary files like the test files
  15 +
  16 +* Mar 12, 2013 *
  17 +* Pull in changes from https://github.com/aalong/attachment_fu to use Ruby 1.9.2 Tempfile naming strategy to fix conflicts with Sprockets (see https://github.com/aalong/attachment_fu/commit/938ec3b8597fbf82b1de8c98de12c4688463450a)
  18 +
  19 +* Feb 19, 2013 *
  20 +* Pull in changes from https://github.com/itkin/attachment_fu fork to fix mini magick
  21 +
  22 +* Nov 15, 2012 *
  23 +* Removed 'crop:' option as already included when prefixing geometry with a 'c'. Add note to docs.
  24 +* Added option to sharpen resized images ':sharpen_on_resize'
  25 +
  26 +* Oct 26, 2012 *
  27 +* Merged with https://github.com/tdd/attachment_fu fork to include GEM support for use in Rails 3.2, and bumped version to 3.2.x to indicate Rails 3.2 support.
  28 +
  29 +* Oct 25, 2012 *
  30 +* Fix to support Ruby 1.9.3
  31 +* Image cropping support (append 'crop:' to desired file size when resizing)
  32 +
  33 +* Mar 15, 2010 *
  34 +* Added a symbol syntax for parent-type-based size spec: calls the corresponding method on the current asset instance to
  35 + get a Hash of required thumbnails. Lets us dynamically specify what would otherwise be hard-coded, which is useful
  36 + when the set of thumbnails for a given parent type varies depending on the usage context.
  37 +
  38 +* Aug 6, 2009 *
  39 +* JPEG quality control finalized across processors (although CoreImage applies it in a rather fuzzy-logic way), with tests.
  40 +
  41 +* Aug 4, 2009 *
  42 +* Supports the :aspect/'!' geometry flag in all processors
  43 + (thanks to http://www.deepcalm.com/writing/cropped-thumbnails-in-attachment_fu-using-imagescience)
  44 +* JPEG quality control on thumbnailing/resizing (still buggy on Rmagick/MiniMagick though).
  45 +* Moves from GIF to PNG regardless of the source file extension's case (used to require lowercase)
  46 +* Auto-orients image (if EXIF suggests it) prior to processing with RMagickProcessor
  47 +* Fixes non-image upload tests (both regular files and Merb files)
  48 +* Fixes obsolete failures on RMagick tests (aspect_ratio tested but not initialized anymore due to new callback architecture
  49 +
1 * Apr 17 2008 * 50 * Apr 17 2008 *
2 * amazon_s3.yml is now passed through ERB before being passed to AWS::S3 [François Beausoleil] 51 * amazon_s3.yml is now passed through ERB before being passed to AWS::S3 [François Beausoleil]
3 52
vendor/plugins/attachment_fu/Gemfile 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +source 'http://rubygems.org'
  2 +
  3 +
  4 +group :test, :development do
  5 + gem 'rails', '~> 3.2'
  6 + gem 'sqlite3'
  7 + gem 'pothoven-attachment_fu', :path => '.'
  8 + gem 'rmagick'
  9 + gem 'core_image'
  10 + gem 'mini_magick'
  11 + gem 'aws-s3', :require => 'aws/s3'
  12 + gem 'test-unit'
  13 +end
vendor/plugins/attachment_fu/Gemfile.lock 0 → 100644
@@ -0,0 +1,109 @@ @@ -0,0 +1,109 @@
  1 +PATH
  2 + remote: .
  3 + specs:
  4 + pothoven-attachment_fu (3.2.10)
  5 +
  6 +GEM
  7 + remote: http://rubygems.org/
  8 + specs:
  9 + actionmailer (3.2.14)
  10 + actionpack (= 3.2.14)
  11 + mail (~> 2.5.4)
  12 + actionpack (3.2.14)
  13 + activemodel (= 3.2.14)
  14 + activesupport (= 3.2.14)
  15 + builder (~> 3.0.0)
  16 + erubis (~> 2.7.0)
  17 + journey (~> 1.0.4)
  18 + rack (~> 1.4.5)
  19 + rack-cache (~> 1.2)
  20 + rack-test (~> 0.6.1)
  21 + sprockets (~> 2.2.1)
  22 + activemodel (3.2.14)
  23 + activesupport (= 3.2.14)
  24 + builder (~> 3.0.0)
  25 + activerecord (3.2.14)
  26 + activemodel (= 3.2.14)
  27 + activesupport (= 3.2.14)
  28 + arel (~> 3.0.2)
  29 + tzinfo (~> 0.3.29)
  30 + activeresource (3.2.14)
  31 + activemodel (= 3.2.14)
  32 + activesupport (= 3.2.14)
  33 + activesupport (3.2.14)
  34 + i18n (~> 0.6, >= 0.6.4)
  35 + multi_json (~> 1.0)
  36 + arel (3.0.2)
  37 + aws-s3 (0.6.3)
  38 + builder
  39 + mime-types
  40 + xml-simple
  41 + builder (3.0.4)
  42 + core_image (0.0.3.5)
  43 + erubis (2.7.0)
  44 + hike (1.2.3)
  45 + i18n (0.6.5)
  46 + journey (1.0.4)
  47 + json (1.8.0)
  48 + mail (2.5.4)
  49 + mime-types (~> 1.16)
  50 + treetop (~> 1.4.8)
  51 + mime-types (1.24)
  52 + mini_magick (3.6.0)
  53 + subexec (~> 0.2.1)
  54 + multi_json (1.7.9)
  55 + polyglot (0.3.3)
  56 + rack (1.4.5)
  57 + rack-cache (1.2)
  58 + rack (>= 0.4)
  59 + rack-ssl (1.3.3)
  60 + rack
  61 + rack-test (0.6.2)
  62 + rack (>= 1.0)
  63 + rails (3.2.14)
  64 + actionmailer (= 3.2.14)
  65 + actionpack (= 3.2.14)
  66 + activerecord (= 3.2.14)
  67 + activeresource (= 3.2.14)
  68 + activesupport (= 3.2.14)
  69 + bundler (~> 1.0)
  70 + railties (= 3.2.14)
  71 + railties (3.2.14)
  72 + actionpack (= 3.2.14)
  73 + activesupport (= 3.2.14)
  74 + rack-ssl (~> 1.3.2)
  75 + rake (>= 0.8.7)
  76 + rdoc (~> 3.4)
  77 + thor (>= 0.14.6, < 2.0)
  78 + rake (10.1.0)
  79 + rdoc (3.12.2)
  80 + json (~> 1.4)
  81 + rmagick (2.13.2)
  82 + sprockets (2.2.2)
  83 + hike (~> 1.2)
  84 + multi_json (~> 1.0)
  85 + rack (~> 1.0)
  86 + tilt (~> 1.1, != 1.3.0)
  87 + sqlite3 (1.3.8)
  88 + subexec (0.2.3)
  89 + test-unit (2.5.5)
  90 + thor (0.18.1)
  91 + tilt (1.4.1)
  92 + treetop (1.4.15)
  93 + polyglot
  94 + polyglot (>= 0.3.1)
  95 + tzinfo (0.3.37)
  96 + xml-simple (1.1.2)
  97 +
  98 +PLATFORMS
  99 + ruby
  100 +
  101 +DEPENDENCIES
  102 + aws-s3
  103 + core_image
  104 + mini_magick
  105 + pothoven-attachment_fu!
  106 + rails (~> 3.2)
  107 + rmagick
  108 + sqlite3
  109 + test-unit
vendor/plugins/attachment_fu/LICENSE 0 → 100644
@@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
  1 +Copyright (c) 2009 rick olson
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining
  4 +a copy of this software and associated documentation files (the
  5 +"Software"), to deal in the Software without restriction, including
  6 +without limitation the rights to use, copy, modify, merge, publish,
  7 +distribute, sublicense, and/or sell copies of the Software, and to
  8 +permit persons to whom the Software is furnished to do so, subject to
  9 +the following conditions:
  10 +
  11 +The above copyright notice and this permission notice shall be
  12 +included in all copies or substantial portions of the Software.
  13 +
  14 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  15 +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  16 +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  17 +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  18 +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  19 +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  20 +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
vendor/plugins/attachment_fu/README
@@ -9,12 +9,13 @@ attachment_fu functionality @@ -9,12 +9,13 @@ attachment_fu functionality
9 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. 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 11
12 -There are three storage options for files uploaded through attachment_fu: 12 +There are four storage options for files uploaded through attachment_fu:
13 File system 13 File system
14 Database file 14 Database file
15 Amazon S3 15 Amazon S3
  16 + Rackspace (Mosso) Cloud Files
16 17
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 +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, the Rackspace Cloud Files storage requires you to modify config/rackspace_cloudfiles.yml, and the Database file storage requires an extra table.
18 19
19 20
20 attachment_fu models 21 attachment_fu models
@@ -41,13 +42,18 @@ has_attachment(options = {}) @@ -41,13 +42,18 @@ has_attachment(options = {})
41 # This option need only be included if you want thumbnailing. 42 # This option need only be included if you want thumbnailing.
42 :thumbnail_class # Set which model class to use for thumbnails. 43 :thumbnail_class # Set which model class to use for thumbnails.
43 # This current attachment class is used by default. 44 # 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. 45 + :path_prefix # Path to store the uploaded files in.
  46 + # Uses public/#{table_name} by default for the filesystem, and just #{table_name} for the S3 and Cloud Files backend.
46 # Setting this sets the :storage to :file_system. 47 # Setting this sets the :storage to :file_system.
  48 + :partition # Whether to partiton files in directories like /0000/0001/image.jpg. Default is true. Only applicable to the :file_system backend.
47 :storage # Specifies the storage system to use.. 49 :storage # Specifies the storage system to use..
48 - # Defaults to :db_file. Options are :file_system, :db_file, and :s3. 50 + # Defaults to :db_file. Options are :file_system, :db_file, :s3, and :cloud_files.
  51 + :cloudfront # If using S3 for storage, this option allows for serving the files via Amazon CloudFront.
  52 + # Defaults to false.
49 :processor # Sets the image processor to use for resizing of the attached image. 53 :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. 54 # Options include ImageScience, Rmagick, and MiniMagick. Default is whatever is installed.
  55 + :uuid_primary_key # If your model's primary key is a 128-bit UUID in hexadecimal format, then set this to true.
  56 + :association_options # attachment_fu automatically defines associations with thumbnails with has_many and belongs_to. If there are any additional options that you want to pass to these methods, then specify them here.
51 57
52 58
53 Examples: 59 Examples:
@@ -60,10 +66,12 @@ has_attachment(options = {}) @@ -60,10 +66,12 @@ has_attachment(options = {})
60 has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' } 66 has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
61 has_attachment :storage => :file_system, :path_prefix => 'public/files' 67 has_attachment :storage => :file_system, :path_prefix => 'public/files'
62 has_attachment :storage => :file_system, :path_prefix => 'public/files', 68 has_attachment :storage => :file_system, :path_prefix => 'public/files',
63 - :content_type => :image, :resize_to => [50,50] 69 + :content_type => :image, :resize_to => [50,50], :partition => false
64 has_attachment :storage => :file_system, :path_prefix => 'public/files', 70 has_attachment :storage => :file_system, :path_prefix => 'public/files',
65 :thumbnails => { :thumb => [50, 50], :geometry => 'x50' } 71 :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
66 has_attachment :storage => :s3 72 has_attachment :storage => :s3
  73 + has_attachment :store => :s3, :cloudfront => true
  74 + has_attachment :storage => :cloud_files
67 75
68 validates_as_attachment 76 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. 77 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.
@@ -119,7 +127,7 @@ There are two parts of the upload form that differ from typical usage. @@ -119,7 +127,7 @@ There are two parts of the upload form that differ from typical usage.
119 Example: 127 Example:
120 <%= form.file_field :uploaded_data %> 128 <%= form.file_field :uploaded_data %>
121 129
122 -Displaying uploaded images is made easy by the public_filename method of the ActiveRecord attachment objects using file system and s3 storage. 130 +Displaying uploaded images is made easy by the public_filename method of the ActiveRecord attachment objects using file system, s3, and Cloud Files storage.
123 131
124 public_filename(thumbnail = nil) 132 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. 133 Returns the public path to the file. If a thumbnail prefix is specified it will return the public file path to the corresponding thumbnail.
@@ -160,3 +168,26 @@ Example in controller: @@ -160,3 +168,26 @@ Example in controller:
160 render :action => :new 168 render :action => :new
161 end 169 end
162 end 170 end
  171 +
  172 +attachement_fu scripting
  173 +====================================
  174 +
  175 +You may wish to import a large number of images or attachments.
  176 +The following example shows how to upload a file from a script.
  177 +
  178 +#!/usr/bin/env ./script/runner
  179 +
  180 +# required to use ActionController::TestUploadedFile
  181 +require 'action_controller'
  182 +require 'action_controller/test_process.rb'
  183 +
  184 +path = "./public/images/x.jpg"
  185 +
  186 +# mimetype is a string like "image/jpeg". One way to get the mimetype for a given file on a UNIX system
  187 +# mimetype = `file -ib #{path}`.gsub(/\n/,"")
  188 +
  189 +mimetype = "image/jpeg"
  190 +
  191 +# This will "upload" the file at path and create the new model.
  192 +@attachable = AttachmentMetadataModel.new(:uploaded_data => ActionController::TestUploadedFile.new(path, mimetype))
  193 +@attachable.save
vendor/plugins/attachment_fu/README.rdoc 0 → 100644
@@ -0,0 +1,352 @@ @@ -0,0 +1,352 @@
  1 += attachment-fu
  2 +
  3 +attachment_fu is a plugin by Rick Olson (aka technoweenie
  4 +http://techno-weenie.net) and is the successor to acts_as_attachment. To get a
  5 +basic run-through of its capabilities, check out {Mike Clark's
  6 +tutorial}[http://clarkware.com/cgi/blosxom/2007/02/24#FileUploadFu].
  7 +
  8 += attachment_fu functionality
  9 +
  10 +attachment_fu facilitates file uploads in Ruby on Rails. There are a few
  11 +storage options for the actual file data, but the plugin always at a minimum
  12 +stores metadata for each file in the database.
  13 +
  14 +There are four storage options for files uploaded through attachment_fu:
  15 +
  16 +* File system
  17 +* Database file
  18 +* Amazon S3
  19 +* Rackspace (Mosso) Cloud Files
  20 +
  21 +Each method of storage many options associated with it that will be covered in
  22 +the following section. Something to note, however, is that the Amazon S3 storage
  23 +requires you to modify +config/amazon_s3.yml+, the Rackspace Cloud Files storage
  24 +requires you to modify +config/rackspace_cloudfiles.yml+, and the Database file
  25 +storage requires an extra table.
  26 +
  27 += attachment_fu models
  28 +
  29 +For all three of these storage options a table of metadata is required. This
  30 +table will contain information about the file (hence the 'meta') and its
  31 +location. This table has no restrictions on naming, unlike the extra table
  32 +required for database storage, which must have a table name of +db_files+ (and
  33 +by convention a model of +DbFile+).
  34 +
  35 +Two methods are available to models: +has_attachment+ and
  36 ++validates_as_attachment+.
  37 +
  38 +== has_attachment(options = {})
  39 +
  40 +This method accepts the options in a hash:
  41 +
  42 +[:content_type]
  43 + Allowed content types.
  44 +
  45 + By default, all content types are allowed. Use +:image+ to allow all
  46 + standard image types.
  47 +
  48 +[:min_size]
  49 + Minimum file size.
  50 +
  51 + By default, set to +1.byte+.
  52 +
  53 +[:max_size]
  54 + Maximum file size.
  55 +
  56 + By default, set to +1.megabyte+.
  57 +
  58 +[:size]
  59 + Minimum and maximum file size.
  60 +
  61 + By default, set to +1..1.megabyte+. Overrides +:min_size+ and
  62 + +:max_size+.
  63 +
  64 +[:resize_to]
  65 + Used by RMagick.
  66 +
  67 + Tells RMagick how to resize images. Pass either an array specifying
  68 + width and height or a geometry string. Prefixing the geometry string
  69 + with a 'c' will crop the image to the specified size.
  70 +
  71 +[:sharpen_on_resize]
  72 + Used by RMagick.
  73 +
  74 + If set to true, images are sharpened after being resized.
  75 +
  76 +[:thumbnails]
  77 + A set of thumbnails to generate.
  78 +
  79 + This accepts a hash of filename suffixes and RMagick resizing options. This
  80 + option need only be included if you want thumbnailing.
  81 +
  82 + If you have a polymorphic parent relationship, you can provide
  83 + parent-type-specific thumbnail settings by using a pair with the type string
  84 + as key and a Hash of thumbnail definitions, or a method symbol, as value.
  85 + The method symbol will call the named method in order to get a
  86 + dynamically-built Hash of thumbnail definitions, which gives you full
  87 + flexibility. AttachmentFu automatically detects your first polymorphic
  88 + +belongs_to+ relationship.
  89 +
  90 +[:thumbnail_class]
  91 + Which model class to use for thumbnails.
  92 +
  93 + By default, the current attachment class is used.
  94 +
  95 +[:jpeg_quality]
  96 + JPEG quality settings for thumbnail resizes.
  97 +
  98 + Arguments can be in multiple formats:
  99 +
  100 + * Integer from 0 (basically crap) to 100 (basically lossless, fat files).
  101 +
  102 + * When relying on tdd-image_science, you can also use one of its +JPEG_xxx+
  103 + constants for predefined ratios/settings.
  104 +
  105 + * You can also use a Hash, with keys being either thumbnail symbols (I
  106 + repeat: _symbols_) or surface boundaries. A surface boundary is a string
  107 + starting with either '<' or '>=', followed by a number of pixels. This
  108 + lets you specify per-thumbnail or per-general-thumbnail-"size" JPEG
  109 + qualities. (which can be useful when you have a _lot_ of thumbnail
  110 + options). Surface example: <code>{'<2000' => 90, '>=2000' => 75}</code>.
  111 +
  112 + Defaults vary depending on the processor (ImageScience: 100%,
  113 + Rmagick/MiniMagick/Gd2: 75%, CoreImage: auto-adjust). Note that only
  114 + tdd-image_science (available from GitHub) currently supports explicit JPEG
  115 + quality; the default image_science currently forces 100%.
  116 +
  117 +[:path_prefix]
  118 + Path to store the uploaded files in. Uses <code>public/#{table_name}</code>
  119 + by default for the filesystem, and just <code>#{table_name}</code> for the
  120 + S3 and Cloud Files backend. Setting this sets the +:storage+ to
  121 + +:file_system+.
  122 +
  123 +[:partition]
  124 + Whether to partiton files in directories like +/0000/0001/image.jpg+.
  125 + Default is true. Only applicable to the +:file_system+ backend.
  126 +
  127 +[:storage]
  128 + Specifies the storage system to use. Defaults to +:db_file+. Options are
  129 + +:file_system+, +:db_file+, +:s3+, and +:cloud_files+.
  130 +
  131 +[:cloudfront]
  132 + If using S3 for storage, this option allows for serving the files via Amazon
  133 + CloudFront. Defaults to false.
  134 +
  135 +[:processor]
  136 + Sets the image processor to use for resizing of the attached image. Options
  137 + include ImageScience, Rmagick, MiniMagick, Gd2 and CoreImage. Default is
  138 + whatever is installed.
  139 +
  140 +[:uuid_primary_key]
  141 + If your model's primary key is a 128-bit UUID in hexadecimal format, then
  142 + set this to true.
  143 +
  144 +[:association_options]
  145 + attachment_fu automatically defines associations with thumbnails with
  146 + +has_many+ and +belongs_to+. If there are any additional options that you
  147 + want to pass to these methods, then specify them here.
  148 +
  149 +Examples:
  150 +
  151 + has_attachment(content_type: 'application/pdf')
  152 + has_attachment(
  153 + content_type: ['application/pdf', 'application/msword', 'text/plain']
  154 + )
  155 + has_attachment(content_type: ['application/pdf', :image], resize_to: 'x50')
  156 + has_attachment(content_type: :image, resize_to: [50,50])
  157 + has_attachment(max_size: 1.kilobyte)
  158 + has_attachment(size: 1.megabyte..2.megabytes)
  159 + has_attachment(storage: :cloud_files)
  160 + has_attachment(storage: :file_system, path_prefix: 'public/files')
  161 + has_attachment(
  162 + storage: :file_system,
  163 + path_prefix: 'public/files',
  164 + content_type: :image,
  165 + resize_to: [50, 50],
  166 + partition: false
  167 + )
  168 + has_attachment(
  169 + storage: :file_system,
  170 + path_prefix: 'public/files',
  171 + thumbnails: {thumb: [50, 50], geometry: 'x50'}
  172 + )
  173 + has_attachment(storage: :s3)
  174 + has_attachment(store: :s3, cloudfront: true)
  175 + has_attachment(thumbnails: {thumb: [50, 50], geometry: 'x50'})
  176 +
  177 + # Let's say we have a polymorphic belongs_to, e.g. called 'imageable', where
  178 + # imageable_type (or whatever the :foreign_type option was set to) can be,
  179 + # among other things, 'Product', 'User' or 'Editorial', each of which should
  180 + # have extra thumbnails:
  181 +
  182 + has_attachment(thumbnails: {
  183 + editorials: {fullsize: '150x100>'},
  184 + geometry: 'x50',
  185 + products: {large_thumb: '169x169!', zoomed: '500x500>'},
  186 + thumb: [50, 50],
  187 + users: {avatar: '64x64!'}
  188 + })
  189 +
  190 + # JPEG qualities…
  191 +
  192 + has_attachment(jpeg_quality: 75)
  193 + has_attachment(jpeg_quality: 80 | ImageScience::JPEG_PROGRESSIVE)
  194 + has_attachment(
  195 + thumbnails: {thumb: [50, 50], geometry: 'x50'},
  196 + jpeg_quality: {'<2000' => 90, '>=2000' => 75}
  197 + )
  198 + has_attachment(
  199 + thumbnails: {thumb: [50, 50], geometry: 'x50'},
  200 + jpeg_quality: {nil => 75, thumb: 90, geometry: 90}
  201 + )
  202 +
  203 +== validates_as_attachment
  204 +
  205 +This method prevents files outside of the valid range (+:min_size+ to
  206 ++:max_size+, or the +:size+ range) from being saved. It does not however, halt
  207 +the upload of such files. They will be uploaded into memory regardless of size
  208 +before validation.
  209 +
  210 +To perform this validation, simply add +validates_as_attachment+ to your model.
  211 +
  212 += attachment_fu migrations
  213 +
  214 +Fields for attachment_fu metadata tables…
  215 +
  216 +In general:
  217 +
  218 + size, :integer # file size in bytes
  219 + content_type, :string # mime type, ex: application/mp3
  220 + filename, :string # sanitized filename
  221 +
  222 +That reference images:
  223 +
  224 + height, :integer # in pixels
  225 + width, :integer # in pixels
  226 +
  227 +That reference images that will be thumbnailed:
  228 +
  229 + parent_id, :integer # id of parent image (on the same table, a
  230 + # self-referencing foreign-key). Only populated if
  231 + # the current object is a thumbnail.
  232 + thumbnail, :string # The type of thumbnail this attachment record
  233 + # describes. Only populated if the current object is
  234 + # a thumbnail. Example:
  235 + #
  236 + # (In Model 'Avatar')
  237 + # has_attachment(
  238 + # :content_type => :image,
  239 + # :storage => :file_system,
  240 + # :max_size => 500.kilobytes,
  241 + # :resize_to => '320x200>',
  242 + # :thumbnails => {
  243 + # :small => '10x10>',
  244 + # :thumb => '100x100>'
  245 + # }
  246 + # )
  247 + #
  248 + # (Elsewhere)
  249 + # @user.avatar.thumbnails.first.thumbnail # => 'small'
  250 + #
  251 + db_file_id, :integer # ID of the file in the database (foreign key) that
  252 + # reference files stored in the database (:db_file).
  253 +
  254 +Field for attachment_fu +db_files+ table:
  255 +
  256 + data, :binary # binary file data, for use in database file storage
  257 +
  258 += attachment_fu views
  259 +
  260 +There are two main views tasks that will be directly affected by attachment_fu:
  261 +upload forms and displaying uploaded images.
  262 +
  263 +There are two parts of the upload form that differ from typical usage.
  264 +
  265 +1. Include <code>multipart: true</code> in the html options of the +form_for+
  266 + tag. Example:
  267 +
  268 + <%=
  269 + form_for(
  270 + :attachment_metadata,
  271 + url: {action: "create"},
  272 + html: {multipart: true}
  273 + ) do |form|
  274 + %>
  275 +
  276 +2. Use the +file_field+ helper with +:uploaded_data+ as the field name. Example:
  277 +
  278 + <%= form.file_field(:uploaded_data) %>
  279 +
  280 +Displaying uploaded images is made easy by the +public_filename+ method of the
  281 +ActiveRecord attachment objects using file system, s3, and Cloud Files storage.
  282 +
  283 +== public_filename(thumbnail = nil)
  284 +
  285 +Returns the public path to the file. If a thumbnail prefix is specified it will
  286 +return the public file path to the corresponding thumbnail. Examples:
  287 +
  288 + attachment_obj.public_filename #=> /attachments/2/file.jpg
  289 + attachment_obj.public_filename(:thumb) #=> /attachments/2/file_thumb.jpg
  290 + attachment_obj.public_filename(:small) #=> /attachments/2/file_small.jpg
  291 +
  292 +When serving files from database storage, doing more than simply downloading the
  293 +file is beyond the scope of this document.
  294 +
  295 += attachment_fu controllers
  296 +
  297 +There are two considerations to take into account when using attachment_fu in
  298 +controllers.
  299 +
  300 +The first is when the files have no publicly accessible path and need to be
  301 +downloaded through an action. Example:
  302 +
  303 + def readme
  304 + send_file(
  305 + '/path/to/readme.txt',
  306 + type: 'plain/text',
  307 + disposition: 'inline'
  308 + )
  309 + end
  310 +
  311 +See the possible values for +send_file+ for reference.
  312 +
  313 +The second is when saving the file when submitted from a form. Example:
  314 +
  315 +In a view:
  316 +
  317 + <%= form.file_field(:attachable, :uploaded_data) %>
  318 +
  319 +In a controller:
  320 +
  321 + def create
  322 + @attachable_file = AttachmentMetadataModel.new(params[:attachable])
  323 + if @attachable_file.save
  324 + flash[:notice] = 'Attachment was successfully created.'
  325 + redirect_to(attachable_url(@attachable_file))
  326 + else
  327 + redirect_to(action: 'new')
  328 + end
  329 + end
  330 +
  331 += attachment_fu scripting
  332 +
  333 +You may wish to import a large number of images or attachments. The following
  334 +example shows how to upload a file from a script.
  335 +
  336 + #!/usr/bin/env ./script/runner
  337 +
  338 + # required to use ActionController::TestUploadedFile
  339 + require 'action_controller'
  340 + require 'action_controller/test_process.rb'
  341 +
  342 + path = "./public/images/x.jpg"
  343 +
  344 + # `mimetype` is a string like "image/jpeg". One way to get the mimetype for
  345 + # a given file on a UNIX system: mimetype = `file -ib #{path}`.gsub(/\n/,"")
  346 + mimetype = "image/jpeg"
  347 +
  348 + # This will "upload" the file at path and create the new model.
  349 + @attachable = AttachmentMetadataModel.new(
  350 + uploaded_data: ActionController::TestUploadedFile.new(path, mimetype)
  351 + )
  352 + @attachable.save
vendor/plugins/attachment_fu/Rakefile
1 require 'rake' 1 require 'rake'
2 require 'rake/testtask' 2 require 'rake/testtask'
3 -require 'rake/rdoctask' 3 +require 'rdoc/task'
4 4
5 desc 'Default: run unit tests.' 5 desc 'Default: run unit tests.'
6 task :default => :test 6 task :default => :test
@@ -16,6 +16,7 @@ desc &#39;Generate documentation for the attachment_fu plugin.&#39; @@ -16,6 +16,7 @@ desc &#39;Generate documentation for the attachment_fu plugin.&#39;
16 Rake::RDocTask.new(:rdoc) do |rdoc| 16 Rake::RDocTask.new(:rdoc) do |rdoc|
17 rdoc.rdoc_dir = 'rdoc' 17 rdoc.rdoc_dir = 'rdoc'
18 rdoc.title = 'ActsAsAttachment' 18 rdoc.title = 'ActsAsAttachment'
19 - rdoc.rdoc_files.include('README') 19 + rdoc.options << '--line-numbers --inline-source'
  20 + rdoc.rdoc_files.include('README.rdoc')
20 rdoc.rdoc_files.include('lib/**/*.rb') 21 rdoc.rdoc_files.include('lib/**/*.rb')
21 end 22 end
vendor/plugins/attachment_fu/amazon_s3.yml.tpl
@@ -2,13 +2,16 @@ development: @@ -2,13 +2,16 @@ development:
2 bucket_name: appname_development 2 bucket_name: appname_development
3 access_key_id: 3 access_key_id:
4 secret_access_key: 4 secret_access_key:
  5 + distribution_domain: XXXX.cloudfront.net
5 6
6 test: 7 test:
7 bucket_name: appname_test 8 bucket_name: appname_test
8 access_key_id: 9 access_key_id:
9 secret_access_key: 10 secret_access_key:
  11 + distribution_domain: XXXX.cloudfront.net
10 12
11 production: 13 production:
12 bucket_name: appname 14 bucket_name: appname
13 access_key_id: 15 access_key_id:
14 secret_access_key: 16 secret_access_key:
  17 + distribution_domain: XXXX.cloudfront.net
vendor/plugins/attachment_fu/attachment_fu.gemspec 0 → 100644
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +# -*- encoding: utf-8 -*-
  2 +
  3 +Gem::Specification.new do |s|
  4 + s.name = %q{pothoven-attachment_fu}
  5 + s.authors = ["Rick Olson", "Steven Pothoven"]
  6 + s.summary = %q{attachment_fu as a gem}
  7 + s.description = %q{This is a fork of Rick Olson's attachment_fu adding Ruby 1.9 and Rails 3.2 support as well as some other enhancements.}
  8 + s.email = %q{steven@pothoven.net}
  9 + s.homepage = %q{http://github.com/pothoven/attachment_fu}
  10 + s.version = "3.2.10"
  11 + s.date = %q{2013-08-22}
  12 +
  13 + s.files = Dir.glob("{lib,vendor}/**/*") + %w( CHANGELOG LICENSE README.rdoc amazon_s3.yml.tpl rackspace_cloudfiles.yml.tpl )
  14 + s.extra_rdoc_files = ["README.rdoc"]
  15 + s.rdoc_options = ["--inline-source", "--charset=UTF-8"]
  16 + s.require_paths = ["lib"]
  17 + s.rubyforge_project = "nowarning"
  18 + s.rubygems_version = %q{1.3.5}
  19 +
  20 + if s.respond_to? :specification_version then
  21 + s.specification_version = 2
  22 + end
  23 +end
vendor/plugins/attachment_fu/config/database.yml 0 → 100644
@@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
  1 +test:
  2 + adapter: sqlite3
  3 + database: ":memory:"
vendor/plugins/attachment_fu/config/environment.rb 0 → 100644
vendor/plugins/attachment_fu/init.rb
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' 1 require 'geometry'
12 ActiveRecord::Base.send(:extend, Technoweenie::AttachmentFu::ActMethods) 2 ActiveRecord::Base.send(:extend, Technoweenie::AttachmentFu::ActMethods)
13 Technoweenie::AttachmentFu.tempfile_path = ATTACHMENT_FU_TEMPFILE_PATH if Object.const_defined?(:ATTACHMENT_FU_TEMPFILE_PATH) 3 Technoweenie::AttachmentFu.tempfile_path = ATTACHMENT_FU_TEMPFILE_PATH if Object.const_defined?(:ATTACHMENT_FU_TEMPFILE_PATH)
vendor/plugins/attachment_fu/install.rb
@@ -2,4 +2,6 @@ require &#39;fileutils&#39; @@ -2,4 +2,6 @@ require &#39;fileutils&#39;
2 2
3 s3_config = File.dirname(__FILE__) + '/../../../config/amazon_s3.yml' 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) 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'))  
6 \ No newline at end of file 5 \ No newline at end of file
  6 +cloudfiles_config = File.dirname(__FILE__) + '/../../../config/rackspace_cloudfiles.yml'
  7 +FileUtils.cp File.dirname(__FILE__) + '/rackspace_cloudfiles.yml.tpl', cloudfiles_config unless File.exist?(cloudfiles_config)
  8 +puts IO.read(File.join(File.dirname(__FILE__), 'README.rdoc'))
vendor/plugins/attachment_fu/lib/geometry.rb
1 # This Geometry class was yanked from RMagick. However, it lets ImageMagick handle the actual change_geometry. 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 2 +# Use #new_dimensions_for to get new dimensions
3 # Used so I can use spiffy RMagick geometry strings with ImageScience 3 # Used so I can use spiffy RMagick geometry strings with ImageScience
4 class Geometry 4 class Geometry
5 - # ! and @ are removed until support for them is added  
6 - FLAGS = ['', '%', '<', '>']#, '!', '@'] 5 + # @ is removed until support for them is added
  6 + FLAGS = ['', '%', '<', '>', '!']#, '@']
7 RFLAGS = { '%' => :percent, 7 RFLAGS = { '%' => :percent,
8 '!' => :aspect, 8 '!' => :aspect,
9 '<' => :>, 9 '<' => :>,
@@ -25,7 +25,7 @@ class Geometry @@ -25,7 +25,7 @@ class Geometry
25 end 25 end
26 26
27 # Construct an object from a geometry string 27 # Construct an object from a geometry string
28 - RE = /\A(\d*)(?:x(\d+))?([-+]\d+)?([-+]\d+)?([%!<>@]?)\Z/ 28 + RE = /\A(\d*)(?:x(\d+)?)?([-+]\d+)?([-+]\d+)?([%!<>@]?)\Z/
29 29
30 def self.from_s(str) 30 def self.from_s(str)
31 raise(ArgumentError, "no geometry string specified") unless str 31 raise(ArgumentError, "no geometry string specified") unless str
@@ -59,6 +59,9 @@ class Geometry @@ -59,6 +59,9 @@ class Geometry
59 scale_y = @height.zero? ? @width : @height 59 scale_y = @height.zero? ? @width : @height
60 new_width = scale_x.to_f * (orig_width.to_f / 100.0) 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) 61 new_height = scale_y.to_f * (orig_height.to_f / 100.0)
  62 + when :aspect
  63 + new_width = @width unless @width.nil?
  64 + new_height = @height unless @height.nil?
62 when :<, :>, nil 65 when :<, :>, nil
63 scale_factor = 66 scale_factor =
64 if new_width.zero? || new_height.zero? 67 if new_width.zero? || new_height.zero?
@@ -76,7 +79,7 @@ class Geometry @@ -76,7 +79,7 @@ class Geometry
76 new_height = orig_height if @flag && orig_height.send(@flag, new_height) 79 new_height = orig_height if @flag && orig_height.send(@flag, new_height)
77 end 80 end
78 81
79 - [new_width, new_height].collect! { |v| v.round } 82 + [new_width, new_height].collect! { |v| [v.round, 1].max }
80 end 83 end
81 end 84 end
82 85
@@ -90,4 +93,4 @@ class Array @@ -90,4 +93,4 @@ class Array
90 geometry = Geometry.from_s(geometry) if geometry.is_a?(String) 93 geometry = Geometry.from_s(geometry) if geometry.is_a?(String)
91 geometry.new_dimensions_for first, last 94 geometry.new_dimensions_for first, last
92 end 95 end
93 -end  
94 \ No newline at end of file 96 \ No newline at end of file
  97 +end
vendor/plugins/attachment_fu/lib/pothoven-attachment_fu.rb 0 → 100644
@@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
  1 +class Engine < Rails::Engine
  2 + # Mimic old vendored plugin behavior, attachment_fu/lib is autoloaded.
  3 + config.autoload_paths << File.expand_path("..", __FILE__)
  4 +
  5 + initializer "attachment_fu" do
  6 + require 'geometry'
  7 +
  8 + ActiveRecord::Base.send(:extend, Technoweenie::AttachmentFu::ActMethods)
  9 + Technoweenie::AttachmentFu.tempfile_path = ATTACHMENT_FU_TEMPFILE_PATH if Object.const_defined?(:ATTACHMENT_FU_TEMPFILE_PATH)
  10 + FileUtils.mkdir_p Technoweenie::AttachmentFu.tempfile_path
  11 + end
  12 +end
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu.rb
@@ -2,7 +2,37 @@ module Technoweenie # :nodoc: @@ -2,7 +2,37 @@ module Technoweenie # :nodoc:
2 module AttachmentFu # :nodoc: 2 module AttachmentFu # :nodoc:
3 @@default_processors = %w(ImageScience Rmagick MiniMagick Gd2 CoreImage) 3 @@default_processors = %w(ImageScience Rmagick MiniMagick Gd2 CoreImage)
4 @@tempfile_path = File.join(Rails.root, 'tmp', 'attachment_fu') 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'] 5 + @@content_types = [
  6 + 'image/jpeg',
  7 + 'image/pjpeg',
  8 + 'image/jpg',
  9 + 'image/gif',
  10 + 'image/png',
  11 + 'image/x-png',
  12 + 'image/jpg',
  13 + 'image/x-ms-bmp',
  14 + 'image/bmp',
  15 + 'image/x-bmp',
  16 + 'image/x-bitmap',
  17 + 'image/x-xbitmap',
  18 + 'image/x-win-bitmap',
  19 + 'image/x-windows-bmp',
  20 + 'image/ms-bmp',
  21 + 'application/bmp',
  22 + 'application/x-bmp',
  23 + 'application/x-win-bitmap',
  24 + 'application/preview',
  25 + 'image/jp_',
  26 + 'application/jpg',
  27 + 'application/x-jpg',
  28 + 'image/pipeg',
  29 + 'image/vnd.swiftview-jpeg',
  30 + 'image/x-xbitmap',
  31 + 'application/png',
  32 + 'application/x-png',
  33 + 'image/gi_',
  34 + 'image/x-citrix-pjpeg'
  35 + ]
6 mattr_reader :content_types, :tempfile_path, :default_processors 36 mattr_reader :content_types, :tempfile_path, :default_processors
7 mattr_writer :tempfile_path 37 mattr_writer :tempfile_path
8 38
@@ -15,12 +45,32 @@ module Technoweenie # :nodoc: @@ -15,12 +45,32 @@ module Technoweenie # :nodoc:
15 # * <tt>:min_size</tt> - Minimum size allowed. 1 byte is the default. 45 # * <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. 46 # * <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. 47 # * <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. 48 + # * <tt>:resize_to</tt> - Used by RMagick to resize images. Pass either an array of width/height, or a geometry string. Prefix geometry string with 'c' to crop image, ex. 'c100x100'
  49 + # * <tt>:sharpen_on_resize</tt> - When using RMagick, setting to true will sharpen images after resizing.
  50 + # * <tt>:jpeg_quality</tt> - Used to provide explicit JPEG quality for thumbnail/resize saves. Can have multiple formats:
  51 + # * Integer from 0 (basically crap) to 100 (basically lossless, fat files).
  52 + # * When relying on ImageScience, you can also use one of its +JPEG_xxx+ constants for predefined ratios/settings.
  53 + # * You can also use a Hash, with keys being either thumbnail symbols (I repeat: _symbols_) or surface boundaries.
  54 + # A surface boundary is a string starting with either '<' or '>=', followed by a number of pixels. This lets you
  55 + # specify per-thumbnail or per-general-thumbnail-"size" JPEG qualities. (which can be useful when you have a
  56 + # _lot_ of thumbnail options). Surface example: +{ '<2000' => 90, '>=2000' => 75 }+.
  57 + # Defaults vary depending on the processor (ImageScience: 100%, Rmagick/MiniMagick/Gd2: 75%,
  58 + # CoreImage: auto-adjust). Note that only tdd-image_science (available from GitHub) currently supports explicit JPEG quality;
  59 + # the default image_science currently forces 100%.
  60 + # * <tt>:thumbnails</tt> - Specifies a set of thumbnails to generate. This accepts a hash of filename suffixes and
  61 + # RMagick resizing options. If you have a polymorphic parent relationship, you can provide parent-type-specific
  62 + # thumbnail settings by using a pair with the type string as key and a Hash of thumbnail definitions as value.
  63 + # AttachmentFu automatically detects your first polymorphic +belongs_to+ relationship.
20 # * <tt>:thumbnail_class</tt> - Set what class to use for thumbnails. This attachment class is used by default. 64 # * <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} 65 # * <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. 66 # for the S3 backend. Setting this sets the :storage to :file_system.
  67 +
23 # * <tt>:storage</tt> - Use :file_system to specify the attachment data is stored with the file system. Defaults to :db_system. 68 # * <tt>:storage</tt> - Use :file_system to specify the attachment data is stored with the file system. Defaults to :db_system.
  69 + # * <tt>:cloundfront</tt> - Set to true if you are using S3 storage and want to serve the files through CloudFront. You will need to
  70 + # set a distribution domain in the amazon_s3.yml config file. Defaults to false
  71 + # * <tt>:bucket_key</tt> - Use this to specify a different bucket key other than :bucket_name in the amazon_s3.yml file. This allows you to use
  72 + # different buckets for different models. An example setting would be :image_bucket and the you would need to define the name of the corresponding
  73 + # bucket in the amazon_s3.yml file.
24 74
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. 75 # * <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 # 76 #
@@ -46,7 +96,9 @@ module Technoweenie # :nodoc: @@ -46,7 +96,9 @@ module Technoweenie # :nodoc:
46 options[:thumbnails] ||= {} 96 options[:thumbnails] ||= {}
47 options[:thumbnail_class] ||= self 97 options[:thumbnail_class] ||= self
48 options[:s3_access] ||= :public_read 98 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? 99 + options[:cloudfront] ||= false
  100 + options[:content_type] = [options[:content_type]].flatten.collect! { |t| t == :image ? ::Technoweenie::AttachmentFu.content_types : t }.flatten unless options[:content_type].nil?
  101 + options[:cache_control] ||= "max-age=315360000" # 10 years
50 102
51 unless options[:thumbnails].is_a?(Hash) 103 unless options[:thumbnails].is_a?(Hash)
52 raise ArgumentError, ":thumbnails option should be a hash: e.g. :thumbnails => { :foo => '50x50' }" 104 raise ArgumentError, ":thumbnails option should be a hash: e.g. :thumbnails => { :foo => '50x50' }"
@@ -65,25 +117,33 @@ module Technoweenie # :nodoc: @@ -65,25 +117,33 @@ module Technoweenie # :nodoc:
65 attachment_options[:storage] ||= parent_options[:storage] 117 attachment_options[:storage] ||= parent_options[:storage]
66 attachment_options[:path_prefix] ||= attachment_options[:file_system_path] 118 attachment_options[:path_prefix] ||= attachment_options[:file_system_path]
67 if attachment_options[:path_prefix].nil? 119 if attachment_options[:path_prefix].nil?
68 - attachment_options[:path_prefix] = attachment_options[:storage] == :s3 ? table_name : File.join("public", table_name) 120 + attachment_options[:path_prefix] = case attachment_options[:storage]
  121 + when :s3 then table_name
  122 + when :cloud_files then table_name
  123 + else File.join("public", table_name)
  124 + end
69 end 125 end
70 attachment_options[:path_prefix] = attachment_options[:path_prefix][1..-1] if options[:path_prefix].first == '/' 126 attachment_options[:path_prefix] = attachment_options[:path_prefix][1..-1] if options[:path_prefix].first == '/'
71 127
72 - with_options :foreign_key => 'parent_id' do |m| 128 + association_options = { :foreign_key => 'parent_id' }
  129 + if attachment_options[:association_options]
  130 + association_options.merge!(attachment_options[:association_options])
  131 + end
  132 + with_options(association_options) do |m|
73 m.has_many :thumbnails, :class_name => "::#{attachment_options[:thumbnail_class]}" 133 m.has_many :thumbnails, :class_name => "::#{attachment_options[:thumbnail_class]}"
74 m.belongs_to :parent, :class_name => "::#{base_class}" unless options[:thumbnails].empty? 134 m.belongs_to :parent, :class_name => "::#{base_class}" unless options[:thumbnails].empty?
75 end 135 end
76 136
77 - storage_mod = Technoweenie::AttachmentFu::Backends.const_get("#{options[:storage].to_s.classify}Backend") 137 + storage_mod = ::Technoweenie::AttachmentFu::Backends.const_get("#{options[:storage].to_s.classify}Backend")
78 include storage_mod unless included_modules.include?(storage_mod) 138 include storage_mod unless included_modules.include?(storage_mod)
79 139
80 case attachment_options[:processor] 140 case attachment_options[:processor]
81 when :none, nil 141 when :none, nil
82 - processors = Technoweenie::AttachmentFu.default_processors.dup 142 + processors = ::Technoweenie::AttachmentFu.default_processors.dup
83 begin 143 begin
84 if processors.any? 144 if processors.any?
85 - attachment_options[:processor] = "#{processors.first}Processor"  
86 - processor_mod = Technoweenie::AttachmentFu::Processors.const_get(attachment_options[:processor]) 145 + attachment_options[:processor] = processors.first
  146 + processor_mod = ::Technoweenie::AttachmentFu::Processors.const_get("#{attachment_options[:processor].to_s.classify}Processor")
87 include processor_mod unless included_modules.include?(processor_mod) 147 include processor_mod unless included_modules.include?(processor_mod)
88 end 148 end
89 rescue Object, Exception 149 rescue Object, Exception
@@ -94,7 +154,7 @@ module Technoweenie # :nodoc: @@ -94,7 +154,7 @@ module Technoweenie # :nodoc:
94 end 154 end
95 else 155 else
96 begin 156 begin
97 - processor_mod = Technoweenie::AttachmentFu::Processors.const_get("#{attachment_options[:processor].to_s.classify}Processor") 157 + processor_mod = ::Technoweenie::AttachmentFu::Processors.const_get("#{attachment_options[:processor].to_s.classify}Processor")
98 include processor_mod unless included_modules.include?(processor_mod) 158 include processor_mod unless included_modules.include?(processor_mod)
99 rescue Object, Exception 159 rescue Object, Exception
100 raise unless load_related_exception?($!) 160 raise unless load_related_exception?($!)
@@ -118,7 +178,7 @@ module Technoweenie # :nodoc: @@ -118,7 +178,7 @@ module Technoweenie # :nodoc:
118 end 178 end
119 179
120 module ClassMethods 180 module ClassMethods
121 - delegate :content_types, :to => Technoweenie::AttachmentFu 181 + delegate :content_types, :to => ::Technoweenie::AttachmentFu
122 182
123 # Performs common validations for attachment models. 183 # Performs common validations for attachment models.
124 def validates_as_attachment 184 def validates_as_attachment
@@ -135,12 +195,12 @@ module Technoweenie # :nodoc: @@ -135,12 +195,12 @@ module Technoweenie # :nodoc:
135 base.class_attribute :attachment_options 195 base.class_attribute :attachment_options
136 base.before_destroy :destroy_thumbnails 196 base.before_destroy :destroy_thumbnails
137 base.before_validation :set_size_from_temp_path 197 base.before_validation :set_size_from_temp_path
138 - base.after_save :after_process_attachment  
139 base.after_destroy :destroy_file 198 base.after_destroy :destroy_file
140 base.after_validation :process_attachment 199 base.after_validation :process_attachment
141 - if defined?(::ActiveSupport::Callbacks)  
142 - base.define_callbacks :after_resize, :after_attachment_saved, :before_thumbnail_saved  
143 - end 200 + base.after_save :after_process_attachment
  201 + #if defined?(::ActiveSupport::Callbacks)
  202 + # base.define_callbacks :after_resize, :after_attachment_saved, :before_thumbnail_saved
  203 + #end
144 end 204 end
145 205
146 unless defined?(::ActiveSupport::Callbacks) 206 unless defined?(::ActiveSupport::Callbacks)
@@ -192,7 +252,7 @@ module Technoweenie # :nodoc: @@ -192,7 +252,7 @@ module Technoweenie # :nodoc:
192 252
193 # Copies the given file path to a new tempfile, returning the closed tempfile. 253 # Copies the given file path to a new tempfile, returning the closed tempfile.
194 def copy_to_temp_file(file, temp_base_name) 254 def copy_to_temp_file(file, temp_base_name)
195 - returning Tempfile.new(temp_base_name, Technoweenie::AttachmentFu.tempfile_path) do |tmp| 255 + Tempfile.new(temp_base_name, ::Technoweenie::AttachmentFu.tempfile_path).tap do |tmp|
196 tmp.close 256 tmp.close
197 FileUtils.cp file, tmp.path 257 FileUtils.cp file, tmp.path
198 end 258 end
@@ -200,12 +260,19 @@ module Technoweenie # :nodoc: @@ -200,12 +260,19 @@ module Technoweenie # :nodoc:
200 260
201 # Writes the given data to a new tempfile, returning the closed tempfile. 261 # Writes the given data to a new tempfile, returning the closed tempfile.
202 def write_to_temp_file(data, temp_base_name) 262 def write_to_temp_file(data, temp_base_name)
203 - returning Tempfile.new(temp_base_name, Technoweenie::AttachmentFu.tempfile_path) do |tmp| 263 + Tempfile.new(temp_base_name, ::Technoweenie::AttachmentFu.tempfile_path).tap do |tmp|
204 tmp.binmode 264 tmp.binmode
205 tmp.write data 265 tmp.write data
206 tmp.close 266 tmp.close
207 end 267 end
208 end 268 end
  269 +
  270 + def polymorphic_relation_type_column
  271 + return @@_polymorphic_relation_type_column if defined?(@@_polymorphic_relation_type_column)
  272 + # Checked against ActiveRecord 1.15.6 through Edge @ 2009-08-05.
  273 + ref = reflections.values.detect { |r| r.macro == :belongs_to && r.options[:polymorphic] }
  274 + @@_polymorphic_relation_type_column = ref && ref.options[:foreign_type]
  275 + end
209 end 276 end
210 277
211 module InstanceMethods 278 module InstanceMethods
@@ -220,11 +287,7 @@ module Technoweenie # :nodoc: @@ -220,11 +287,7 @@ module Technoweenie # :nodoc:
220 287
221 # Returns true/false if an attachment is thumbnailable. A thumbnailable attachment has an image content type and the parent_id attribute. 288 # Returns true/false if an attachment is thumbnailable. A thumbnailable attachment has an image content type and the parent_id attribute.
222 def thumbnailable? 289 def thumbnailable?
223 - image? && !is_thumbnail?  
224 - end  
225 -  
226 - def is_thumbnail?  
227 - (thumbnail_class == self.class) && !(respond_to?(:parent_id) && parent_id.nil?) 290 + image? && respond_to?(:parent_id) && parent_id.nil?
228 end 291 end
229 292
230 # Returns the class used to create new thumbnails for this attachment. 293 # Returns the class used to create new thumbnails for this attachment.
@@ -234,26 +297,33 @@ module Technoweenie # :nodoc: @@ -234,26 +297,33 @@ module Technoweenie # :nodoc:
234 297
235 # Gets the thumbnail name for a filename. 'foo.jpg' becomes 'foo_thumbnail.jpg' 298 # Gets the thumbnail name for a filename. 'foo.jpg' becomes 'foo_thumbnail.jpg'
236 def thumbnail_name_for(thumbnail = nil) 299 def thumbnail_name_for(thumbnail = nil)
237 - return filename if thumbnail.blank? 300 + if thumbnail.blank?
  301 + if filename.nil?
  302 + return ''
  303 + else
  304 + return filename
  305 + end
  306 + end
  307 +
238 ext = nil 308 ext = nil
239 basename = filename.gsub /\.\w+$/ do |s| 309 basename = filename.gsub /\.\w+$/ do |s|
240 ext = s; '' 310 ext = s; ''
241 end 311 end
242 # ImageScience doesn't create gif thumbnails, only pngs 312 # ImageScience doesn't create gif thumbnails, only pngs
243 - ext.sub!(/gif$/, 'png') if attachment_options[:processor] == "ImageScience" 313 + ext.sub!(/gif$/i, 'png') if attachment_options[:processor] == "ImageScience"
244 "#{basename}_#{thumbnail}#{ext}" 314 "#{basename}_#{thumbnail}#{ext}"
245 end 315 end
246 316
247 # Creates or updates the thumbnail for the current attachment. 317 # Creates or updates the thumbnail for the current attachment.
248 def create_or_update_thumbnail(temp_file, file_name_suffix, *size) 318 def create_or_update_thumbnail(temp_file, file_name_suffix, *size)
249 thumbnailable? || raise(ThumbnailError.new("Can't create a thumbnail if the content type is not an image or there is no parent_id column")) 319 thumbnailable? || raise(ThumbnailError.new("Can't create a thumbnail if the content type is not an image or there is no parent_id column"))
250 - returning find_or_initialize_thumbnail(file_name_suffix) do |thumb|  
251 - thumb.attributes = { 320 + find_or_initialize_thumbnail(file_name_suffix).tap do |thumb|
  321 + thumb.temp_paths.unshift temp_file
  322 + thumb.send(:assign_attributes, {
252 :content_type => content_type, 323 :content_type => content_type,
253 :filename => thumbnail_name_for(file_name_suffix), 324 :filename => thumbnail_name_for(file_name_suffix),
254 - :temp_path => temp_file,  
255 :thumbnail_resize_options => size 325 :thumbnail_resize_options => size
256 - } 326 + }, :without_protection => true)
257 callback_with_args :before_thumbnail_saved, thumb 327 callback_with_args :before_thumbnail_saved, thumb
258 thumb.save! 328 thumb.save!
259 end 329 end
@@ -276,7 +346,7 @@ module Technoweenie # :nodoc: @@ -276,7 +346,7 @@ module Technoweenie # :nodoc:
276 346
277 # Returns true if the attachment data will be written to the storage system on the next save 347 # Returns true if the attachment data will be written to the storage system on the next save
278 def save_attachment? 348 def save_attachment?
279 - File.file?(temp_path.to_s) 349 + File.file?(temp_path.class == String ? temp_path : temp_path.to_filename)
280 end 350 end
281 351
282 # nil placeholder in case this field is used in a form. 352 # nil placeholder in case this field is used in a form.
@@ -294,14 +364,21 @@ module Technoweenie # :nodoc: @@ -294,14 +364,21 @@ module Technoweenie # :nodoc:
294 # 364 #
295 # TODO: Allow it to work with Merb tempfiles too. 365 # TODO: Allow it to work with Merb tempfiles too.
296 def uploaded_data=(file_data) 366 def uploaded_data=(file_data)
297 - return nil if file_data.nil? || file_data.size == 0  
298 - self.content_type = file_data.content_type  
299 - self.filename = file_data.original_filename if respond_to?(:filename) 367 + if file_data.respond_to?(:content_type)
  368 + return nil if file_data.size == 0
  369 + self.content_type = file_data.content_type
  370 + self.filename = file_data.original_filename if respond_to?(:filename)
  371 + else
  372 + return nil if file_data.blank? || file_data['size'] == 0
  373 + self.content_type = file_data['content_type']
  374 + self.filename = file_data['filename']
  375 + file_data = file_data['tempfile']
  376 + end
300 if file_data.is_a?(StringIO) 377 if file_data.is_a?(StringIO)
301 file_data.rewind 378 file_data.rewind
302 - self.temp_data = file_data.read 379 + set_temp_data file_data.read
303 else 380 else
304 - self.temp_path = file_data 381 + file_data.respond_to?(:tempfile) ? self.temp_paths.unshift( file_data.tempfile.path ) : self.temp_paths.unshift( file_data.path )
305 end 382 end
306 end 383 end
307 384
@@ -320,22 +397,14 @@ module Technoweenie # :nodoc: @@ -320,22 +397,14 @@ module Technoweenie # :nodoc:
320 [] : [copy_to_temp_file(full_filename)]) 397 [] : [copy_to_temp_file(full_filename)])
321 end 398 end
322 399
323 - # Adds a new temp_path to the array. This should take a string or a Tempfile. This class makes no  
324 - # attempt to remove the files, so Tempfiles should be used. Tempfiles remove themselves when they go out of scope.  
325 - # You can also use string paths for temporary files, such as those used for uploaded files in a web server.  
326 - def temp_path=(value)  
327 - temp_paths.unshift value  
328 - temp_path  
329 - end  
330 -  
331 # Gets the data from the latest temp file. This will read the file into memory. 400 # Gets the data from the latest temp file. This will read the file into memory.
332 def temp_data 401 def temp_data
333 save_attachment? ? File.read(temp_path) : nil 402 save_attachment? ? File.read(temp_path) : nil
334 end 403 end
335 404
336 # Writes the given data to a Tempfile and adds it to the collection of temp files. 405 # Writes the given data to a Tempfile and adds it to the collection of temp files.
337 - def temp_data=(data)  
338 - self.temp_path = write_to_temp_file data unless data.nil? 406 + def set_temp_data(data)
  407 + temp_paths.unshift write_to_temp_file data unless data.nil?
339 end 408 end
340 409
341 # Copies the given file to a randomly named Tempfile. 410 # Copies the given file to a randomly named Tempfile.
@@ -364,17 +433,20 @@ module Technoweenie # :nodoc: @@ -364,17 +433,20 @@ module Technoweenie # :nodoc:
364 protected 433 protected
365 # Generates a unique filename for a Tempfile. 434 # Generates a unique filename for a Tempfile.
366 def random_tempfile_filename 435 def random_tempfile_filename
367 - "#{rand Time.now.to_i}#{filename || 'attachment'}" 436 + base_filename = filename ? filename.gsub(/\.\w+$/, '') : 'attachment'
  437 + ext = filename.slice(/\.\w+$/)
  438 + ["#{rand Time.now.to_i}#{base_filename}", ext || '']
368 end 439 end
369 440
370 def sanitize_filename(filename) 441 def sanitize_filename(filename)
371 - returning filename.strip do |name| 442 + return unless filename
  443 + filename.strip.tap do |name|
372 # NOTE: File.basename doesn't work right with Windows paths on Unix 444 # NOTE: File.basename doesn't work right with Windows paths on Unix
373 # get only the filename, not the whole path 445 # get only the filename, not the whole path
374 name.gsub! /^.*(\\|\/)/, '' 446 name.gsub! /^.*(\\|\/)/, ''
375 447
376 # Finally, replace all non alphanumeric, underscore or periods with underscore 448 # Finally, replace all non alphanumeric, underscore or periods with underscore
377 - name.gsub! /[^\w\.\-]/, '_' 449 + name.gsub! /[^A-Za-z0-9\.\-]/, '_'
378 end 450 end
379 end 451 end
380 452
@@ -387,13 +459,17 @@ module Technoweenie # :nodoc: @@ -387,13 +459,17 @@ module Technoweenie # :nodoc:
387 def attachment_attributes_valid? 459 def attachment_attributes_valid?
388 [:size, :content_type].each do |attr_name| 460 [:size, :content_type].each do |attr_name|
389 enum = attachment_options[attr_name] 461 enum = attachment_options[attr_name]
390 - errors.add attr_name, ActiveRecord::Errors.default_error_messages[:inclusion] unless enum.nil? || enum.include?(send(attr_name)) 462 + if Object.const_defined?(:I18n) # Rails >= 2.2
  463 + errors.add attr_name, I18n.translate("activerecord.errors.messages.inclusion", attr_name => enum) unless enum.nil? || enum.include?(send(attr_name))
  464 + else
  465 + errors.add attr_name, ActiveRecord::Errors.default_error_messages[:inclusion] unless enum.nil? || enum.include?(send(attr_name))
  466 + end
391 end 467 end
392 end 468 end
393 469
394 # Initializes a new thumbnail with the given suffix. 470 # Initializes a new thumbnail with the given suffix.
395 def find_or_initialize_thumbnail(file_name_suffix) 471 def find_or_initialize_thumbnail(file_name_suffix)
396 - thumbnail_class.columns.map(&:name).include?('parent_id') ? 472 + respond_to?(:parent_id) ?
397 thumbnail_class.find_or_initialize_by_thumbnail_and_parent_id(file_name_suffix.to_s, id) : 473 thumbnail_class.find_or_initialize_by_thumbnail_and_parent_id(file_name_suffix.to_s, id) :
398 thumbnail_class.find_or_initialize_by_thumbnail(file_name_suffix.to_s) 474 thumbnail_class.find_or_initialize_by_thumbnail(file_name_suffix.to_s)
399 end 475 end
@@ -406,29 +482,51 @@ module Technoweenie # :nodoc: @@ -406,29 +482,51 @@ module Technoweenie # :nodoc:
406 # Cleans up after processing. Thumbnails are created, the attachment is stored to the backend, and the temp_paths are cleared. 482 # Cleans up after processing. Thumbnails are created, the attachment is stored to the backend, and the temp_paths are cleared.
407 def after_process_attachment 483 def after_process_attachment
408 if @saved_attachment 484 if @saved_attachment
409 - if respond_to?(:process_attachment_with_processing) && thumbnailable? && !attachment_options[:thumbnails].blank? 485 + if respond_to?(:process_attachment_with_processing, true) && thumbnailable? && !attachment_options[:thumbnails].blank? && parent_id.nil?
410 temp_file = temp_path || create_temp_file 486 temp_file = temp_path || create_temp_file
411 - attachment_options[:thumbnails].each { |suffix, size| create_or_update_thumbnail(temp_file, suffix, *size) } 487 + attachment_options[:thumbnails].each { |suffix, size|
  488 + if size.is_a?(Symbol)
  489 + parent_type = polymorphic_parent_type
  490 + next unless parent_type && [parent_type, parent_type.tableize].include?(suffix.to_s) && respond_to?(size)
  491 + size = send(size)
  492 + end
  493 + if size.is_a?(Hash)
  494 + parent_type = polymorphic_parent_type
  495 + next unless parent_type && [parent_type, parent_type.tableize].include?(suffix.to_s)
  496 + size.each { |ppt_suffix, ppt_size|
  497 + create_or_update_thumbnail(temp_file, ppt_suffix, *ppt_size)
  498 + }
  499 + else
  500 + create_or_update_thumbnail(temp_file, suffix, *size)
  501 + end
  502 + }
412 end 503 end
413 save_to_storage 504 save_to_storage
414 @temp_paths.clear 505 @temp_paths.clear
415 @saved_attachment = nil 506 @saved_attachment = nil
416 - callback :after_attachment_saved 507 + #callback :after_attachment_saved
  508 + callback_with_args :after_attachment_saved, nil
417 end 509 end
418 end 510 end
419 511
420 # Resizes the given processed img object with either the attachment resize options or the thumbnail resize options. 512 # Resizes the given processed img object with either the attachment resize options or the thumbnail resize options.
421 def resize_image_or_thumbnail!(img) 513 def resize_image_or_thumbnail!(img)
422 - if !is_thumbnail? && attachment_options[:resize_to] # parent image 514 + if (!respond_to?(:parent_id) || parent_id.nil?) && attachment_options[:resize_to] # parent image
423 resize_image(img, attachment_options[:resize_to]) 515 resize_image(img, attachment_options[:resize_to])
424 elsif thumbnail_resize_options # thumbnail 516 elsif thumbnail_resize_options # thumbnail
425 resize_image(img, thumbnail_resize_options) 517 resize_image(img, thumbnail_resize_options)
426 end 518 end
427 end 519 end
428 520
  521 + if defined?(Rails) && Rails::VERSION::MAJOR >= 3
  522 + def callback_with_args(method, arg = self)
  523 + if respond_to?(method)
  524 + send(method, arg)
  525 + end
  526 + end
429 # Yanked from ActiveRecord::Callbacks, modified so I can pass args to the callbacks besides self. 527 # Yanked from ActiveRecord::Callbacks, modified so I can pass args to the callbacks besides self.
430 # Only accept blocks, however 528 # Only accept blocks, however
431 - if ActiveSupport.const_defined?(:Callbacks) 529 + elsif ActiveSupport.const_defined?(:Callbacks)
432 # Rails 2.1 and beyond! 530 # Rails 2.1 and beyond!
433 def callback_with_args(method, arg = self) 531 def callback_with_args(method, arg = self)
434 notify(method) 532 notify(method)
@@ -464,6 +562,27 @@ module Technoweenie # :nodoc: @@ -464,6 +562,27 @@ module Technoweenie # :nodoc:
464 def destroy_thumbnails 562 def destroy_thumbnails
465 self.thumbnails.each { |thumbnail| thumbnail.destroy } if thumbnailable? 563 self.thumbnails.each { |thumbnail| thumbnail.destroy } if thumbnailable?
466 end 564 end
  565 +
  566 + def polymorphic_parent_type
  567 + rel_name = self.class.polymorphic_relation_type_column
  568 + rel_name && send(rel_name)
  569 + end
  570 +
  571 + def get_jpeg_quality(require_0_to_100 = true)
  572 + quality = attachment_options[:jpeg_quality]
  573 + if quality.is_a?(Hash)
  574 + sbl_quality = thumbnail && quality[thumbnail.to_sym]
  575 + sbl_quality = nil if sbl_quality && require_0_to_100 && !sbl_quality.to_i.between?(0, 100)
  576 + surface = (width || 1) * (height || 1)
  577 + size_quality = quality.detect { |k, v|
  578 + next unless k.is_a?(String) && k =~ /^(<|>=)(\d+)$/
  579 + op, threshold = $1, $2.to_i
  580 + surface.send(op, threshold)
  581 + }
  582 + quality = sbl_quality || size_quality && size_quality[1]
  583 + end
  584 + return quality && (!require_0_to_100 || quality.to_i.between?(0, 100)) ? quality : nil
  585 + end
467 end 586 end
468 end 587 end
469 end 588 end
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/cloud_file_backend.rb 0 → 100644
@@ -0,0 +1,211 @@ @@ -0,0 +1,211 @@
  1 +module Technoweenie # :nodoc:
  2 + module AttachmentFu # :nodoc:
  3 + module Backends
  4 + # = CloudFiles Storage Backend
  5 + #
  6 + # Enables use of {Rackspace Cloud Files}[http://www.mosso.com/cloudfiles.jsp] as a storage mechanism
  7 + #
  8 + # Based heavily on the Amazon S3 backend.
  9 + #
  10 + # == Requirements
  11 + #
  12 + # Requires the {Cloud Files Gem}[http://www.mosso.com/cloudfiles.jsp] by Rackspace
  13 + #
  14 + # == Configuration
  15 + #
  16 + # Configuration is done via <tt>Rails.root.to_s/config/rackspace_cloudfiles.yml</tt> and is loaded according to the <tt>#{Rails.env}</tt>.
  17 + # The minimum connection options that you must specify are a container name, your Mosso login name and your Mosso API key.
  18 + # You can sign up for Cloud Files and get access keys by visiting https://www.mosso.com/buy.htm
  19 + #
  20 + # Example configuration (Rails.root.to_s/config/rackspace_cloudfiles.yml)
  21 + #
  22 + # development:
  23 + # container_name: appname_development
  24 + # username: <your key>
  25 + # api_key: <your key>
  26 + #
  27 + # test:
  28 + # container_name: appname_test
  29 + # username: <your key>
  30 + # api_key: <your key>
  31 + #
  32 + # production:
  33 + # container_name: appname
  34 + # username: <your key>
  35 + # apik_key: <your key>
  36 + #
  37 + # You can change the location of the config path by passing a full path to the :cloudfiles_config_path option.
  38 + #
  39 + # has_attachment :storage => :cloud_files, :cloudfiles_config_path => (Rails.root.to_s + '/config/mosso.yml')
  40 + #
  41 + # === Required configuration parameters
  42 + #
  43 + # * <tt>:username</tt> - The username for your Rackspace Cloud (Mosso) account. Provided by Rackspace.
  44 + # * <tt>:secret_access_key</tt> - The api key for your Rackspace Cloud account. Provided by Rackspace.
  45 + # * <tt>:container_name</tt> - The name of a container in your Cloud Files account.
  46 + #
  47 + # If any of these required arguments is missing, a AuthenticationException will be raised from CloudFiles::Connection.
  48 + #
  49 + # == Usage
  50 + #
  51 + # To specify Cloud Files as the storage mechanism for a model, set the acts_as_attachment <tt>:storage</tt> option to <tt>:cloud_files/tt>.
  52 + #
  53 + # class Photo < ActiveRecord::Base
  54 + # has_attachment :storage => :cloud_files
  55 + # end
  56 + #
  57 + # === Customizing the path
  58 + #
  59 + # By default, files are prefixed using a pseudo hierarchy in the form of <tt>:table_name/:id</tt>, which results
  60 + # in Cloud Files object names (and urls) that look like: http://:server/:container_name/:table_name/:id/:filename with :table_name
  61 + # representing the customizable portion of the path. You can customize this prefix using the <tt>:path_prefix</tt>
  62 + # option:
  63 + #
  64 + # class Photo < ActiveRecord::Base
  65 + # has_attachment :storage => :cloud_files, :path_prefix => 'my/custom/path'
  66 + # end
  67 + #
  68 + # Which would result in public URLs like <tt>http(s)://:server/:container_name/my/custom/path/:id/:filename.</tt>
  69 + #
  70 + # === Permissions
  71 + #
  72 + # File permisisons are determined by the permissions of the container. At present, the options are public (and distributed
  73 + # by the Limelight CDN), and private (only available to your login)
  74 + #
  75 + # === Other options
  76 + #
  77 + # Of course, all the usual configuration options apply, such as content_type and thumbnails:
  78 + #
  79 + # class Photo < ActiveRecord::Base
  80 + # has_attachment :storage => :cloud_files, :content_type => ['application/pdf', :image], :resize_to => 'x50'
  81 + # has_attachment :storage => :cloud_files, :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
  82 + # end
  83 + #
  84 + # === Accessing Cloud Files URLs
  85 + #
  86 + # You can get an object's public URL using the cloudfiles_url accessor. For example, assuming that for your postcard app
  87 + # you had a container name like 'postcard_world_development', and an attachment model called Photo:
  88 + #
  89 + # @postcard.cloudfiles_url # => http://cdn.cloudfiles.mosso.com/c45182/uploaded_files/20/london.jpg
  90 + #
  91 + # The resulting url is in the form: http://:server/:container_name/:table_name/:id/:file.
  92 + # The optional thumbnail argument will output the thumbnail's filename (if any).
  93 + #
  94 + # Additionally, you can get an object's base path relative to the container root using
  95 + # <tt>base_path</tt>:
  96 + #
  97 + # @photo.file_base_path # => uploaded_files/20
  98 + #
  99 + # And the full path (including the filename) using <tt>full_filename</tt>:
  100 + #
  101 + # @photo.full_filename # => uploaded_files/20/london.jpg
  102 + #
  103 + # Niether <tt>base_path</tt> or <tt>full_filename</tt> include the container name as part of the path.
  104 + # You can retrieve the container name using the <tt>container_name</tt> method.
  105 + module CloudFileBackend
  106 + class RequiredLibraryNotFoundError < StandardError; end
  107 + class ConfigFileNotFoundError < StandardError; end
  108 +
  109 + def self.included(base) #:nodoc:
  110 + mattr_reader :container_name, :cloudfiles_config
  111 +
  112 + begin
  113 + require 'cloudfiles'
  114 + rescue LoadError
  115 + raise RequiredLibraryNotFoundError.new('CloudFiles could not be loaded')
  116 + end
  117 +
  118 + begin
  119 + @@cloudfiles_config_path = base.attachment_options[:cloudfiles_config_path] || (Rails.root.to_s + '/config/rackspace_cloudfiles.yml')
  120 + @@cloudfiles_config = @@cloudfiles_config = YAML.load(ERB.new(File.read(@@cloudfiles_config_path)).result)[Rails.env].symbolize_keys
  121 + rescue
  122 + #raise ConfigFileNotFoundError.new('File %s not found' % @@cloudfiles_config_path)
  123 + end
  124 +
  125 + @@container_name = @@cloudfiles_config[:container_name]
  126 + @@cf = CloudFiles::Connection.new(@@cloudfiles_config[:username], @@cloudfiles_config[:api_key])
  127 + @@container = @@cf.container(@@container_name)
  128 +
  129 + base.before_update :rename_file
  130 + end
  131 +
  132 + # Overwrites the base filename writer in order to store the old filename
  133 + def filename=(value)
  134 + @old_filename = filename unless filename.nil? || @old_filename
  135 + write_attribute :filename, sanitize_filename(value)
  136 + end
  137 +
  138 + # The attachment ID used in the full path of a file
  139 + def attachment_path_id
  140 + ((respond_to?(:parent_id) && parent_id) || id).to_s
  141 + end
  142 +
  143 + # The pseudo hierarchy containing the file relative to the container name
  144 + # Example: <tt>:table_name/:id</tt>
  145 + def base_path
  146 + File.join(attachment_options[:path_prefix], attachment_path_id)
  147 + end
  148 +
  149 + # The full path to the file relative to the container name
  150 + # Example: <tt>:table_name/:id/:filename</tt>
  151 + def full_filename(thumbnail = nil)
  152 + File.join(base_path, thumbnail_name_for(thumbnail))
  153 + end
  154 +
  155 + # All public objects are accessible via a GET request to the Cloud Files servers. You can generate a
  156 + # url for an object using the cloudfiles_url method.
  157 + #
  158 + # @photo.cloudfiles_url
  159 + #
  160 + # The resulting url is in the CDN URL for the object
  161 + #
  162 + # The optional thumbnail argument will output the thumbnail's filename (if any).
  163 + #
  164 + # If you are trying to get the URL for a nonpublic container, nil will be returned.
  165 + def cloudfiles_url(thumbnail = nil)
  166 + if @@container.public?
  167 + File.join(@@container.cdn_url, full_filename(thumbnail))
  168 + else
  169 + nil
  170 + end
  171 + end
  172 + alias :public_filename :cloudfiles_url
  173 +
  174 + def create_temp_file
  175 + write_to_temp_file current_data
  176 + end
  177 +
  178 + def current_data
  179 + @@container.get_object(full_filename).data
  180 + end
  181 +
  182 + protected
  183 + # Called in the after_destroy callback
  184 + def destroy_file
  185 + @@container.delete_object(full_filename)
  186 + end
  187 +
  188 + def rename_file
  189 + # Cloud Files doesn't rename right now, so we'll just nuke.
  190 + return unless @old_filename && @old_filename != filename
  191 +
  192 + old_full_filename = File.join(base_path, @old_filename)
  193 + @@container.delete_object(old_full_filename)
  194 +
  195 + @old_filename = nil
  196 + true
  197 + end
  198 +
  199 + def save_to_storage
  200 + if save_attachment?
  201 + @object = @@container.create_object(full_filename)
  202 + @object.write((temp_path ? File.open(temp_path) : temp_data))
  203 + end
  204 +
  205 + @old_filename = nil
  206 + true
  207 + end
  208 + end
  209 + end
  210 + end
  211 +end
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/file_system_backend.rb
  1 +require 'fileutils'
  2 +require 'digest/sha2'
  3 +
1 module Technoweenie # :nodoc: 4 module Technoweenie # :nodoc:
2 module AttachmentFu # :nodoc: 5 module AttachmentFu # :nodoc:
3 module Backends 6 module Backends
@@ -6,12 +9,12 @@ module Technoweenie # :nodoc: @@ -6,12 +9,12 @@ module Technoweenie # :nodoc:
6 def self.included(base) #:nodoc: 9 def self.included(base) #:nodoc:
7 base.before_update :rename_file 10 base.before_update :rename_file
8 end 11 end
9 - 12 +
10 # Gets the full path to the filename in this format: 13 # Gets the full path to the filename in this format:
11 # 14 #
12 # # This assumes a model name like MyModel 15 # # This assumes a model name like MyModel
13 - # # public/#{table_name} is the default filesystem path  
14 - # Rails.root/public/my_models/5/blah.jpg 16 + # # public/#{table_name} is the default filesystem path
  17 + # #{Rails.root}/public/my_models/5/blah.jpg
15 # 18 #
16 # Overwrite this method in your model to customize the filename. 19 # Overwrite this method in your model to customize the filename.
17 # The optional thumbnail argument will output the thumbnail's filename. 20 # The optional thumbnail argument will output the thumbnail's filename.
@@ -19,29 +22,56 @@ module Technoweenie # :nodoc: @@ -19,29 +22,56 @@ module Technoweenie # :nodoc:
19 file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s 22 file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s
20 File.join(Rails.root, file_system_path, *partitioned_path(thumbnail_name_for(thumbnail))) 23 File.join(Rails.root, file_system_path, *partitioned_path(thumbnail_name_for(thumbnail)))
21 end 24 end
22 - 25 +
23 # Used as the base path that #public_filename strips off full_filename to create the public path 26 # Used as the base path that #public_filename strips off full_filename to create the public path
24 def base_path 27 def base_path
25 @base_path ||= File.join(Rails.root, 'public') 28 @base_path ||= File.join(Rails.root, 'public')
26 end 29 end
27 - 30 +
28 # The attachment ID used in the full path of a file 31 # The attachment ID used in the full path of a file
29 def attachment_path_id 32 def attachment_path_id
30 - (is_thumbnail? && respond_to?(:parent_id)) ? parent_id : id 33 + ((respond_to?(:parent_id) && parent_id) || id) || 0
31 end 34 end
32 -  
33 - # overrwrite this to do your own app-specific partitioning.  
34 - # you can thank Jamis Buck for this: http://www.37signals.com/svn/archives2/id_partitioning.php 35 +
  36 + # Partitions the given path into an array of path components.
  37 + #
  38 + # For example, given an <tt>*args</tt> of ["foo", "bar"], it will return
  39 + # <tt>["0000", "0001", "foo", "bar"]</tt> (assuming that that id returns 1).
  40 + #
  41 + # If the id is not an integer, then path partitioning will be performed by
  42 + # hashing the string value of the id with SHA-512, and splitting the result
  43 + # into 4 components. If the id a 128-bit UUID (as set by :uuid_primary_key => true)
  44 + # then it will be split into 2 components.
  45 + #
  46 + # To turn this off entirely, set :partition => false.
35 def partitioned_path(*args) 47 def partitioned_path(*args)
36 - ("%08d" % attachment_path_id).scan(/..../) + args 48 + if respond_to?(:attachment_options) && attachment_options[:partition] == false
  49 + args
  50 + elsif attachment_options[:uuid_primary_key]
  51 + # Primary key is a 128-bit UUID in hex format. Split it into 2 components.
  52 + path_id = attachment_path_id.to_s
  53 + component1 = path_id[0..15] || "-"
  54 + component2 = path_id[16..-1] || "-"
  55 + [component1, component2] + args
  56 + else
  57 + path_id = attachment_path_id
  58 + if path_id.is_a?(Integer)
  59 + # Primary key is an integer. Split it after padding it with 0.
  60 + ("%08d" % path_id).scan(/..../) + args
  61 + else
  62 + # Primary key is a String. Hash it, then split it into 4 components.
  63 + hash = Digest::SHA512.hexdigest(path_id.to_s)
  64 + [hash[0..31], hash[32..63], hash[64..95], hash[96..127]] + args
  65 + end
  66 + end
37 end 67 end
38 - 68 +
39 # Gets the public path to the file 69 # Gets the public path to the file
40 # The optional thumbnail argument will output the thumbnail's filename. 70 # The optional thumbnail argument will output the thumbnail's filename.
41 def public_filename(thumbnail = nil) 71 def public_filename(thumbnail = nil)
42 full_filename(thumbnail).gsub %r(^#{Regexp.escape(base_path)}), '' 72 full_filename(thumbnail).gsub %r(^#{Regexp.escape(base_path)}), ''
43 end 73 end
44 - 74 +
45 def filename=(value) 75 def filename=(value)
46 @old_filename = full_filename unless filename.nil? || @old_filename 76 @old_filename = full_filename unless filename.nil? || @old_filename
47 write_attribute :filename, sanitize_filename(value) 77 write_attribute :filename, sanitize_filename(value)
@@ -74,19 +104,19 @@ module Technoweenie # :nodoc: @@ -74,19 +104,19 @@ module Technoweenie # :nodoc:
74 @old_filename = nil 104 @old_filename = nil
75 true 105 true
76 end 106 end
77 - 107 +
78 # Saves the file to the file system 108 # Saves the file to the file system
79 def save_to_storage 109 def save_to_storage
80 if save_attachment? 110 if save_attachment?
81 # TODO: This overwrites the file if it exists, maybe have an allow_overwrite option? 111 # TODO: This overwrites the file if it exists, maybe have an allow_overwrite option?
82 FileUtils.mkdir_p(File.dirname(full_filename)) 112 FileUtils.mkdir_p(File.dirname(full_filename))
83 - File.cp(temp_path, full_filename)  
84 - File.chmod(attachment_options[:chmod] || 0644, full_filename) 113 + FileUtils.cp(temp_path, full_filename)
  114 + FileUtils.chmod(attachment_options[:chmod] || 0644, full_filename)
85 end 115 end
86 @old_filename = nil 116 @old_filename = nil
87 true 117 true
88 end 118 end
89 - 119 +
90 def current_data 120 def current_data
91 File.file?(full_filename) ? File.read(full_filename) : nil 121 File.file?(full_filename) ? File.read(full_filename) : nil
92 end 122 end
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/s3_backend.rb
@@ -12,31 +12,37 @@ module Technoweenie # :nodoc: @@ -12,31 +12,37 @@ module Technoweenie # :nodoc:
12 # 12 #
13 # == Configuration 13 # == Configuration
14 # 14 #
15 - # Configuration is done via <tt>Rails.root/config/amazon_s3.yml</tt> and is loaded according to the <tt>RAILS_ENV</tt>. 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. 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. 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. 18 # You can sign up for S3 and get access keys by visiting http://aws.amazon.com/s3.
19 # 19 #
20 - # Example configuration (Rails.root/config/amazon_s3.yml)  
21 - # 20 + # If you wish to use Amazon CloudFront to serve the files, you can also specify a distibution domain for the bucket.
  21 + # To read more about CloudFront, visit http://aws.amazon.com/cloudfront
  22 + #
  23 + # Example configuration (#{Rails.root}/config/amazon_s3.yml)
  24 + #
22 # development: 25 # development:
23 # bucket_name: appname_development 26 # bucket_name: appname_development
24 # access_key_id: <your key> 27 # access_key_id: <your key>
25 # secret_access_key: <your key> 28 # secret_access_key: <your key>
26 - # 29 + # distribution_domain: XXXX.cloudfront.net
  30 + #
27 # test: 31 # test:
28 # bucket_name: appname_test 32 # bucket_name: appname_test
29 # access_key_id: <your key> 33 # access_key_id: <your key>
30 # secret_access_key: <your key> 34 # secret_access_key: <your key>
31 - # 35 + # distribution_domain: XXXX.cloudfront.net
  36 + #
32 # production: 37 # production:
33 # bucket_name: appname 38 # bucket_name: appname
34 # access_key_id: <your key> 39 # access_key_id: <your key>
35 # secret_access_key: <your key> 40 # secret_access_key: <your key>
  41 + # distribution_domain: XXXX.cloudfront.net
36 # 42 #
37 # You can change the location of the config path by passing a full path to the :s3_config_path option. 43 # You can change the location of the config path by passing a full path to the :s3_config_path option.
38 # 44 #
39 - # has_attachment :storage => :s3, :s3_config_path => (Rails.root + '/config/s3.yml') 45 + # has_attachment :storage => :s3, :s3_config_path => (#{Rails.root} + '/config/s3.yml')
40 # 46 #
41 # === Required configuration parameters 47 # === Required configuration parameters
42 # 48 #
@@ -59,6 +65,8 @@ module Technoweenie # :nodoc: @@ -59,6 +65,8 @@ module Technoweenie # :nodoc:
59 # * <tt>:server</tt> - The server to make requests to. Defaults to <tt>s3.amazonaws.com</tt>. 65 # * <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. 66 # * <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. 67 # * <tt>:use_ssl</tt> - If set to true, <tt>:port</tt> will be implicitly set to 443, unless specified otherwise. Defaults to false.
  68 + # * <tt>:distribution_domain</tt> - The CloudFront distribution domain for the bucket. This can either be the assigned
  69 + # distribution domain (ie. XXX.cloudfront.net) or a chosen domain using a CNAME. See CloudFront for more details.
62 # 70 #
63 # == Usage 71 # == Usage
64 # 72 #
@@ -81,10 +89,43 @@ module Technoweenie # :nodoc: @@ -81,10 +89,43 @@ module Technoweenie # :nodoc:
81 # 89 #
82 # Which would result in URLs like <tt>http(s)://:server/:bucket_name/my/custom/path/:id/:filename.</tt> 90 # Which would result in URLs like <tt>http(s)://:server/:bucket_name/my/custom/path/:id/:filename.</tt>
83 # 91 #
  92 + # === Using different bucket names on different models
  93 + #
  94 + # By default the bucket name that the file will be stored to is the one specified by the
  95 + # <tt>:bucket_name</tt> key in the amazon_s3.yml file. You can use the <tt>:bucket_key</tt> option
  96 + # to overide this behavior on a per model basis. For instance if you want a bucket that will hold
  97 + # only Photos you can do this:
  98 + #
  99 + # class Photo < ActiveRecord::Base
  100 + # has_attachment :storage => :s3, :bucket_key => :photo_bucket_name
  101 + # end
  102 + #
  103 + # And then your amazon_s3.yml file needs to look like this.
  104 + #
  105 + # development:
  106 + # bucket_name: appname_development
  107 + # access_key_id: <your key>
  108 + # secret_access_key: <your key>
  109 + #
  110 + # test:
  111 + # bucket_name: appname_test
  112 + # access_key_id: <your key>
  113 + # secret_access_key: <your key>
  114 + #
  115 + # production:
  116 + # bucket_name: appname
  117 + # photo_bucket_name: appname_photos
  118 + # access_key_id: <your key>
  119 + # secret_access_key: <your key>
  120 + #
  121 + # If the bucket_key you specify is not there in a certain environment then attachment_fu will
  122 + # default to the <tt>bucket_name</tt> key. This way you only have to create special buckets
  123 + # this can be helpful if you only need special buckets in certain environments.
  124 + #
84 # === Permissions 125 # === Permissions
85 # 126 #
86 # By default, files are stored on S3 with public access permissions. You can customize this using 127 # 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 128 + # 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>. 129 # <tt>:private</tt>, <tt>:public_read_write</tt>, and <tt>:authenticated_read</tt>.
89 # 130 #
90 # === Other options 131 # === Other options
@@ -117,13 +158,23 @@ module Technoweenie # :nodoc: @@ -117,13 +158,23 @@ module Technoweenie # :nodoc:
117 # 158 #
118 # Niether <tt>base_path</tt> or <tt>full_filename</tt> include the bucket name as part of the path. 159 # 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. 160 # You can retrieve the bucket name using the <tt>bucket_name</tt> method.
  161 + #
  162 + # === Accessing CloudFront URLs
  163 + #
  164 + # You can get an object's CloudFront URL using the cloudfront_url accessor. Using the example from above:
  165 + # @postcard.cloudfront_url # => http://XXXX.cloudfront.net/photos/1/mexico.jpg
  166 + #
  167 + # The resulting url is in the form: http://:distribution_domain/:table_name/:id/:file
  168 + #
  169 + # If you set :cloudfront to true in your model, the public_filename will be the CloudFront
  170 + # URL, not the S3 URL.
120 module S3Backend 171 module S3Backend
121 class RequiredLibraryNotFoundError < StandardError; end 172 class RequiredLibraryNotFoundError < StandardError; end
122 class ConfigFileNotFoundError < StandardError; end 173 class ConfigFileNotFoundError < StandardError; end
123 174
124 def self.included(base) #:nodoc: 175 def self.included(base) #:nodoc:
125 mattr_reader :bucket_name, :s3_config 176 mattr_reader :bucket_name, :s3_config
126 - 177 +
127 begin 178 begin
128 require 'aws/s3' 179 require 'aws/s3'
129 include AWS::S3 180 include AWS::S3
@@ -132,13 +183,20 @@ module Technoweenie # :nodoc: @@ -132,13 +183,20 @@ module Technoweenie # :nodoc:
132 end 183 end
133 184
134 begin 185 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 186 + @@s3_config_path = base.attachment_options[:s3_config_path] || File.join(Rails.root, 'config', 'amazon_s3.yml')
  187 + @@s3_config = @@s3_config = YAML.load(ERB.new(File.read(@@s3_config_path)).result)[Rails.env].symbolize_keys
137 #rescue 188 #rescue
138 # raise ConfigFileNotFoundError.new('File %s not found' % @@s3_config_path) 189 # raise ConfigFileNotFoundError.new('File %s not found' % @@s3_config_path)
139 end 190 end
140 191
141 - @@bucket_name = s3_config[:bucket_name] 192 + bucket_key = base.attachment_options[:bucket_key]
  193 +
  194 + if bucket_key and s3_config[bucket_key.to_sym]
  195 + eval_string = "def bucket_name()\n \"#{s3_config[bucket_key.to_sym]}\"\nend"
  196 + else
  197 + eval_string = "def bucket_name()\n \"#{s3_config[:bucket_name]}\"\nend"
  198 + end
  199 + base.class_eval(eval_string, __FILE__, __LINE__)
142 200
143 Base.establish_connection!(s3_config.slice(:access_key_id, :secret_access_key, :server, :port, :use_ssl, :persistent, :proxy)) 201 Base.establish_connection!(s3_config.slice(:access_key_id, :secret_access_key, :server, :port, :use_ssl, :persistent, :proxy))
144 202
@@ -150,27 +208,35 @@ module Technoweenie # :nodoc: @@ -150,27 +208,35 @@ module Technoweenie # :nodoc:
150 def self.protocol 208 def self.protocol
151 @protocol ||= s3_config[:use_ssl] ? 'https://' : 'http://' 209 @protocol ||= s3_config[:use_ssl] ? 'https://' : 'http://'
152 end 210 end
153 - 211 +
154 def self.hostname 212 def self.hostname
155 @hostname ||= s3_config[:server] || AWS::S3::DEFAULT_HOST 213 @hostname ||= s3_config[:server] || AWS::S3::DEFAULT_HOST
156 end 214 end
157 - 215 +
158 def self.port_string 216 def self.port_string
159 @port_string ||= (s3_config[:port].nil? || s3_config[:port] == (s3_config[:use_ssl] ? 443 : 80)) ? '' : ":#{s3_config[:port]}" 217 @port_string ||= (s3_config[:port].nil? || s3_config[:port] == (s3_config[:use_ssl] ? 443 : 80)) ? '' : ":#{s3_config[:port]}"
160 end 218 end
161 219
  220 + def self.distribution_domain
  221 + @distribution_domain = s3_config[:distribution_domain]
  222 + end
  223 +
162 module ClassMethods 224 module ClassMethods
163 def s3_protocol 225 def s3_protocol
164 Technoweenie::AttachmentFu::Backends::S3Backend.protocol 226 Technoweenie::AttachmentFu::Backends::S3Backend.protocol
165 end 227 end
166 - 228 +
167 def s3_hostname 229 def s3_hostname
168 Technoweenie::AttachmentFu::Backends::S3Backend.hostname 230 Technoweenie::AttachmentFu::Backends::S3Backend.hostname
169 end 231 end
170 - 232 +
171 def s3_port_string 233 def s3_port_string
172 Technoweenie::AttachmentFu::Backends::S3Backend.port_string 234 Technoweenie::AttachmentFu::Backends::S3Backend.port_string
173 end 235 end
  236 +
  237 + def cloudfront_distribution_domain
  238 + Technoweenie::AttachmentFu::Backends::S3Backend.distribution_domain
  239 + end
174 end 240 end
175 241
176 # Overwrites the base filename writer in order to store the old filename 242 # Overwrites the base filename writer in order to store the old filename
@@ -196,22 +262,42 @@ module Technoweenie # :nodoc: @@ -196,22 +262,42 @@ module Technoweenie # :nodoc:
196 File.join(base_path, thumbnail_name_for(thumbnail)) 262 File.join(base_path, thumbnail_name_for(thumbnail))
197 end 263 end
198 264
199 - # All public objects are accessible via a GET request to the S3 servers. You can generate a 265 + # 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. 266 # url for an object using the s3_url method.
201 # 267 #
202 # @photo.s3_url 268 # @photo.s3_url
203 # 269 #
204 # The resulting url is in the form: <tt>http(s)://:server/:bucket_name/:table_name/:id/:file</tt> where 270 # 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 271 # 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>. 272 + # set using the configuration parameters in <tt>#{Rails.root}/config/amazon_s3.yml</tt>.
207 # 273 #
208 # The optional thumbnail argument will output the thumbnail's filename (if any). 274 # The optional thumbnail argument will output the thumbnail's filename (if any).
209 def s3_url(thumbnail = nil) 275 def s3_url(thumbnail = nil)
210 File.join(s3_protocol + s3_hostname + s3_port_string, bucket_name, full_filename(thumbnail)) 276 File.join(s3_protocol + s3_hostname + s3_port_string, bucket_name, full_filename(thumbnail))
211 end 277 end
212 - alias :public_filename :s3_url  
213 278
214 - # All private objects are accessible via an authenticated GET request to the S3 servers. You can generate an 279 + # All public objects are accessible via a GET request to CloudFront. You can generate a
  280 + # url for an object using the cloudfront_url method.
  281 + #
  282 + # @photo.cloudfront_url
  283 + #
  284 + # The resulting url is in the form: <tt>http://:distribution_domain/:table_name/:id/:file</tt> using
  285 + # the <tt>:distribution_domain</tt> variable set in the configuration parameters in <tt>#{Rails.root}/config/amazon_s3.yml</tt>.
  286 + #
  287 + # The optional thumbnail argument will output the thumbnail's filename (if any).
  288 + def cloudfront_url(thumbnail = nil)
  289 + s3_protocol + cloudfront_distribution_domain + "/" + full_filename(thumbnail)
  290 + end
  291 +
  292 + def public_filename(*args)
  293 + if attachment_options[:cloudfront]
  294 + cloudfront_url(*args)
  295 + else
  296 + s3_url(*args)
  297 + end
  298 + end
  299 +
  300 + # 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: 301 # authenticated url for an object like this:
216 # 302 #
217 # @photo.authenticated_s3_url 303 # @photo.authenticated_s3_url
@@ -223,7 +309,7 @@ module Technoweenie # :nodoc: @@ -223,7 +309,7 @@ module Technoweenie # :nodoc:
223 # 309 #
224 # # Absolute expiration date (October 13th, 2025) 310 # # Absolute expiration date (October 13th, 2025)
225 # @photo.authenticated_s3_url(:expires => Time.mktime(2025,10,13).to_i) 311 # @photo.authenticated_s3_url(:expires => Time.mktime(2025,10,13).to_i)
226 - # 312 + #
227 # # Expiration in five hours from now 313 # # Expiration in five hours from now
228 # @photo.authenticated_s3_url(:expires_in => 5.hours) 314 # @photo.authenticated_s3_url(:expires_in => 5.hours)
229 # 315 #
@@ -236,8 +322,9 @@ module Technoweenie # :nodoc: @@ -236,8 +322,9 @@ module Technoweenie # :nodoc:
236 # 322 #
237 # @photo.authenticated_s3_url('thumbnail', :expires_in => 5.hours, :use_ssl => true) 323 # @photo.authenticated_s3_url('thumbnail', :expires_in => 5.hours, :use_ssl => true)
238 def authenticated_s3_url(*args) 324 def authenticated_s3_url(*args)
239 - thumbnail = args.first.is_a?(String) ? args.first : nil  
240 - options = args.last.is_a?(Hash) ? args.last : {} 325 + options = args.extract_options!
  326 + options[:expires_in] = options[:expires_in].to_i if options[:expires_in]
  327 + thumbnail = args.shift
241 S3Object.url_for(full_filename(thumbnail), bucket_name, options) 328 S3Object.url_for(full_filename(thumbnail), bucket_name, options)
242 end 329 end
243 330
@@ -246,21 +333,29 @@ module Technoweenie # :nodoc: @@ -246,21 +333,29 @@ module Technoweenie # :nodoc:
246 end 333 end
247 334
248 def current_data 335 def current_data
249 - S3Object.value full_filename, bucket_name 336 + if attachment_options[:encrypted_storage] && self.respond_to?(:encryption_key) && self.encryption_key != nil
  337 + EncryptedData.decrypt_data(S3Object.value(full_filename, bucket_name), self.encryption_key)
  338 + else
  339 + S3Object.value full_filename, bucket_name
  340 + end
250 end 341 end
251 342
252 def s3_protocol 343 def s3_protocol
253 Technoweenie::AttachmentFu::Backends::S3Backend.protocol 344 Technoweenie::AttachmentFu::Backends::S3Backend.protocol
254 end 345 end
255 - 346 +
256 def s3_hostname 347 def s3_hostname
257 Technoweenie::AttachmentFu::Backends::S3Backend.hostname 348 Technoweenie::AttachmentFu::Backends::S3Backend.hostname
258 end 349 end
259 - 350 +
260 def s3_port_string 351 def s3_port_string
261 Technoweenie::AttachmentFu::Backends::S3Backend.port_string 352 Technoweenie::AttachmentFu::Backends::S3Backend.port_string
262 end 353 end
263 354
  355 + def cloudfront_distribution_domain
  356 + Technoweenie::AttachmentFu::Backends::S3Backend.distribution_domain
  357 + end
  358 +
264 protected 359 protected
265 # Called in the after_destroy callback 360 # Called in the after_destroy callback
266 def destroy_file 361 def destroy_file
@@ -269,7 +364,7 @@ module Technoweenie # :nodoc: @@ -269,7 +364,7 @@ module Technoweenie # :nodoc:
269 364
270 def rename_file 365 def rename_file
271 return unless @old_filename && @old_filename != filename 366 return unless @old_filename && @old_filename != filename
272 - 367 +
273 old_full_filename = File.join(base_path, @old_filename) 368 old_full_filename = File.join(base_path, @old_filename)
274 369
275 S3Object.rename( 370 S3Object.rename(
@@ -285,13 +380,27 @@ module Technoweenie # :nodoc: @@ -285,13 +380,27 @@ module Technoweenie # :nodoc:
285 380
286 def save_to_storage 381 def save_to_storage
287 if save_attachment? 382 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 - ) 383 + if attachment_options[:encrypted_storage]
  384 + S3Object.store(
  385 + full_filename,
  386 + (temp_path ? File.open(temp_path) : temp_data),
  387 + bucket_name,
  388 + :content_type => content_type,
  389 + :cache_control => attachment_options[:cache_control],
  390 + :access => attachment_options[:s3_access],
  391 + 'x-amz-server-side-encryption' => 'AES256',
  392 + 'Content-Disposition' => "attachment; filename=\"#{filename}\""
  393 + )
  394 + else
  395 + S3Object.store(
  396 + full_filename,
  397 + (temp_path ? File.open(temp_path) : temp_data),
  398 + bucket_name,
  399 + :content_type => content_type,
  400 + :cache_control => attachment_options[:cache_control],
  401 + :access => attachment_options[:s3_access]
  402 + )
  403 + end
295 end 404 end
296 405
297 @old_filename = nil 406 @old_filename = nil
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/core_image_processor.rb
@@ -44,7 +44,17 @@ module Technoweenie # :nodoc: @@ -44,7 +44,17 @@ module Technoweenie # :nodoc:
44 processor.render do |result| 44 processor.render do |result|
45 self.width = result.extent.size.width if respond_to?(:width) 45 self.width = result.extent.size.width if respond_to?(:width)
46 self.height = result.extent.size.height if respond_to?(:height) 46 self.height = result.extent.size.height if respond_to?(:height)
47 - result.save self.temp_path, OSX::NSJPEGFileType 47 + out_file = random_tempfile_filename
  48 + temp_paths.unshift Tempfile.new(out_file, Technoweenie::AttachmentFu.tempfile_path).path
  49 + properties = nil
  50 + # We don't check the source image since we're forcing the output to JPEG, apparently…
  51 + # Beware: apparently CoreImage only takes the percentage as a HINT, using a different actual quality…
  52 + quality = get_jpeg_quality
  53 + properties = { OSX::NSImageCompressionFactor => quality / 100.0 } if quality
  54 + result.save(self.temp_path, OSX::NSJPEGFileType, properties)
  55 + #
  56 + # puts "#{self.temp_path} @ #{quality.inspect} -> #{%x(identify -format '%Q' "#{self.temp_path}")}"
  57 + #
48 self.size = File.size(self.temp_path) 58 self.size = File.size(self.temp_path)
49 end 59 end
50 end 60 end
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/gd2_processor.rb
@@ -44,8 +44,13 @@ module Technoweenie # :nodoc: @@ -44,8 +44,13 @@ module Technoweenie # :nodoc:
44 w, h = [img.width, img.height] / size.to_s 44 w, h = [img.width, img.height] / size.to_s
45 img.resize!(w, h, false) 45 img.resize!(w, h, false)
46 end 46 end
47 - self.temp_path = random_tempfile_filename  
48 - self.size = img.export(self.temp_path) 47 + self.width = img.width if respond_to?(:width)
  48 + self.height = img.height if respond_to?(:height)
  49 + out_file = random_tempfile_filename
  50 + temp_paths.unshift out_file
  51 + jpeg = out_file =~ /\.jpe?g\z/i
  52 + quality = jpeg && get_jpeg_quality
  53 + self.size = img.export(self.temp_path, quality ? { :quality => quality } : {})
49 end 54 end
50 55
51 end 56 end
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/image_science_processor.rb
@@ -32,13 +32,21 @@ module Technoweenie # :nodoc: @@ -32,13 +32,21 @@ module Technoweenie # :nodoc:
32 # pngs for thumbnails. It has something to do with trying to save gifs 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 33 # with a larger palette than 256 colors, which is all the gif format
34 # supports. 34 # supports.
35 - filename.sub! /gif$/, 'png' 35 + filename.sub! /gif$/i, 'png'
36 content_type.sub!(/gif$/, 'png') 36 content_type.sub!(/gif$/, 'png')
37 - self.temp_path = write_to_temp_file(filename) 37 + temp_paths.unshift write_to_temp_file(filename)
38 grab_dimensions = lambda do |img| 38 grab_dimensions = lambda do |img|
39 self.width = img.width if respond_to?(:width) 39 self.width = img.width if respond_to?(:width)
40 self.height = img.height if respond_to?(:height) 40 self.height = img.height if respond_to?(:height)
41 - img.save self.temp_path 41 +
  42 + # We don't check for quality being a 0-100 value as we also allow FreeImage JPEG_xxx constants.
  43 + quality = content_type[/jpe?g/i] && get_jpeg_quality(false)
  44 + # Traditional ImageScience has a 1-arg save method, tdd-image_science has 1 mandatory + 1 optional
  45 + if quality && img.method(:save).arity == -2
  46 + img.save self.temp_path, quality
  47 + else
  48 + img.save self.temp_path
  49 + end
42 self.size = File.size(self.temp_path) 50 self.size = File.size(self.temp_path)
43 callback_with_args :after_resize, img 51 callback_with_args :after_resize, img
44 end 52 end
@@ -52,7 +60,18 @@ module Technoweenie # :nodoc: @@ -52,7 +60,18 @@ module Technoweenie # :nodoc:
52 end 60 end
53 else 61 else
54 new_size = [img.width, img.height] / size.to_s 62 new_size = [img.width, img.height] / size.to_s
55 - img.resize(new_size[0], new_size[1], &grab_dimensions) 63 + if size.ends_with?('!')
  64 + aspect = new_size[0].to_f / new_size[1].to_f
  65 + ih, iw = img.height, img.width
  66 + w, h = (ih * aspect), (iw / aspect)
  67 + w = [iw, w].min.to_i
  68 + h = [ih, h].min.to_i
  69 + img.with_crop((iw-w)/2, (ih-h)/2, (iw+w)/2, (ih+h)/2) { |crop|
  70 + crop.resize(new_size[0], new_size[1], &grab_dimensions)
  71 + }
  72 + else
  73 + img.resize(new_size[0], new_size[1], &grab_dimensions)
  74 + end
56 end 75 end
57 end 76 end
58 end 77 end
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb
@@ -7,12 +7,12 @@ module Technoweenie # :nodoc: @@ -7,12 +7,12 @@ module Technoweenie # :nodoc:
7 base.send :extend, ClassMethods 7 base.send :extend, ClassMethods
8 base.alias_method_chain :process_attachment, :processing 8 base.alias_method_chain :process_attachment, :processing
9 end 9 end
10 - 10 +
11 module ClassMethods 11 module ClassMethods
12 # Yields a block containing an MiniMagick Image for the given binary data. 12 # Yields a block containing an MiniMagick Image for the given binary data.
13 def with_image(file, &block) 13 def with_image(file, &block)
14 begin 14 begin
15 - binary_data = file.is_a?(MiniMagick::Image) ? file : MiniMagick::Image.from_file(file) unless !Object.const_defined?(:MiniMagick) 15 + binary_data = file.is_a?(MiniMagick::Image) ? file : MiniMagick::Image.open(file) unless !Object.const_defined?(:MiniMagick)
16 rescue 16 rescue
17 # Log the failure to load the image. 17 # Log the failure to load the image.
18 logger.debug("Exception working with image: #{$!}") 18 logger.debug("Exception working with image: #{$!}")
@@ -23,23 +23,30 @@ module Technoweenie # :nodoc: @@ -23,23 +23,30 @@ module Technoweenie # :nodoc:
23 !binary_data.nil? 23 !binary_data.nil?
24 end 24 end
25 end 25 end
26 - 26 +
27 protected 27 protected
28 def process_attachment_with_processing 28 def process_attachment_with_processing
29 return unless process_attachment_without_processing 29 return unless process_attachment_without_processing
30 with_image do |img| 30 with_image do |img|
31 resize_image_or_thumbnail! 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) 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 34 callback_with_args :after_resize, img
35 end if image? 35 end if image?
36 end 36 end
37 - 37 +
38 # Performs the actual resizing operation for a thumbnail 38 # Performs the actual resizing operation for a thumbnail
39 def resize_image(img, size) 39 def resize_image(img, size)
40 size = size.first if size.is_a?(Array) && size.length == 1 40 size = size.first if size.is_a?(Array) && size.length == 1
  41 + format = img[:format]
41 img.combine_options do |commands| 42 img.combine_options do |commands|
42 commands.strip unless attachment_options[:keep_profile] 43 commands.strip unless attachment_options[:keep_profile]
  44 +
  45 + # GIF is not handled correctly, so we move to PNG, as in other processors…
  46 + if format == 'GIF'
  47 + img.format('PNG')
  48 + end
  49 +
43 if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum)) 50 if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
44 if size.is_a?(Fixnum) 51 if size.is_a?(Fixnum)
45 size = [size, size] 52 size = [size, size]
@@ -47,13 +54,89 @@ module Technoweenie # :nodoc: @@ -47,13 +54,89 @@ module Technoweenie # :nodoc:
47 else 54 else
48 commands.resize(size.join('x') + '!') 55 commands.resize(size.join('x') + '!')
49 end 56 end
  57 + # extend to thumbnail size
  58 + elsif size.is_a?(String) and size =~ /e$/
  59 + size = size.gsub(/e/, '')
  60 + commands.resize(size.to_s + '>')
  61 + commands.background('#ffffff')
  62 + commands.gravity('center')
  63 + commands.extent(size)
  64 + # crop thumbnail, the smart way
  65 + elsif size.is_a?(String) and size =~ /c$/
  66 + size = size.gsub(/c/, '')
  67 +
  68 + # calculate sizes and aspect ratio
  69 + thumb_width, thumb_height = size.split("x")
  70 + thumb_width = thumb_width.to_f
  71 + thumb_height = thumb_height.to_f
  72 +
  73 + thumb_aspect = thumb_width.to_f / thumb_height.to_f
  74 + image_width, image_height = img[:width].to_f, img[:height].to_f
  75 + image_aspect = image_width / image_height
  76 +
  77 + # only crop if image is not smaller in both dimensions
  78 + unless image_width < thumb_width and image_height < thumb_height
  79 + command = calculate_offset(image_width,image_height,image_aspect,thumb_width,thumb_height,thumb_aspect)
  80 +
  81 + # crop image
  82 + commands.extract(command)
  83 + end
  84 +
  85 + # don not resize if image is not as height or width then thumbnail
  86 + if image_width < thumb_width or image_height < thumb_height
  87 + commands.background('#ffffff')
  88 + commands.gravity('center')
  89 + commands.extent(size)
  90 + # resize image
  91 + else
  92 + commands.resize("#{size.to_s}")
  93 + end
  94 + # crop end
50 else 95 else
51 commands.resize(size.to_s) 96 commands.resize(size.to_s)
52 end 97 end
53 end 98 end
54 - self.temp_path = img 99 + dims = img[:dimensions]
  100 + self.width = dims[0] if respond_to?(:width)
  101 + self.height = dims[1] if respond_to?(:height)
  102 + # Has to be done this far so we get proper dimensions
  103 + if format == 'JPEG'
  104 + quality = get_jpeg_quality
  105 + img.quality(quality) if quality
  106 + end
  107 + temp_paths.unshift img
  108 + self.size = File.size(self.temp_path)
55 end 109 end
  110 +
  111 + def calculate_offset(image_width,image_height,image_aspect,thumb_width,thumb_height,thumb_aspect)
  112 + # only crop if image is not smaller in both dimensions
  113 +
  114 + # special cases, image smaller in one dimension then thumbsize
  115 + if image_width < thumb_width
  116 + offset = (image_height / 2) - (thumb_height / 2)
  117 + command = "#{image_width}x#{thumb_height}+0+#{offset}"
  118 + elsif image_height < thumb_height
  119 + offset = (image_width / 2) - (thumb_width / 2)
  120 + command = "#{thumb_width}x#{image_height}+#{offset}+0"
  121 +
  122 + # normal thumbnail generation
  123 + # calculate height and offset y, width is fixed
  124 + elsif (image_aspect <= thumb_aspect or image_width < thumb_width) and image_height > thumb_height
  125 + height = image_width / thumb_aspect
  126 + offset = (image_height / 2) - (height / 2)
  127 + command = "#{image_width}x#{height}+0+#{offset}"
  128 + # calculate width and offset x, height is fixed
  129 + else
  130 + width = image_height * thumb_aspect
  131 + offset = (image_width / 2) - (width / 2)
  132 + command = "#{width}x#{image_height}+#{offset}+0"
  133 + end
  134 + # crop image
  135 + command
  136 + end
  137 +
  138 +
56 end 139 end
57 end 140 end
58 end 141 end
59 -end 142 -end
  143 +end
60 \ No newline at end of file 144 \ No newline at end of file
vendor/plugins/attachment_fu/lib/technoweenie/attachment_fu/processors/rmagick_processor.rb
@@ -13,6 +13,7 @@ module Technoweenie # :nodoc: @@ -13,6 +13,7 @@ module Technoweenie # :nodoc:
13 def with_image(file, &block) 13 def with_image(file, &block)
14 begin 14 begin
15 binary_data = file.is_a?(Magick::Image) ? file : Magick::Image.read(file).first unless !Object.const_defined?(:Magick) 15 binary_data = file.is_a?(Magick::Image) ? file : Magick::Image.read(file).first unless !Object.const_defined?(:Magick)
  16 + binary_data && binary_data.auto_orient!
16 rescue 17 rescue
17 # Log the failure to load the image. This should match ::Magick::ImageMagickError 18 # Log the failure to load the image. This should match ::Magick::ImageMagickError
18 # but that would cause acts_as_attachment to require rmagick. 19 # but that would cause acts_as_attachment to require rmagick.
@@ -42,11 +43,22 @@ module Technoweenie # :nodoc: @@ -42,11 +43,22 @@ module Technoweenie # :nodoc:
42 if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum)) 43 if size.is_a?(Fixnum) || (size.is_a?(Array) && size.first.is_a?(Fixnum))
43 size = [size, size] if size.is_a?(Fixnum) 44 size = [size, size] if size.is_a?(Fixnum)
44 img.thumbnail!(*size) 45 img.thumbnail!(*size)
  46 + elsif size.is_a?(String) && size =~ /^c.*$/ # Image cropping - example geometry string: c75x75
  47 + dimensions = size[1..size.size].split("x")
  48 + img.crop_resized!(dimensions[0].to_i, dimensions[1].to_i)
45 else 49 else
46 - img.change_geometry(size.to_s) { |cols, rows, image| image.resize!(cols<1 ? 1 : cols, rows<1 ? 1 : rows) } 50 + img.change_geometry(size.to_s) { |cols, rows, image|
  51 + image.resize!(cols<1 ? 1 : cols, rows<1 ? 1 : rows)
  52 + }
47 end 53 end
  54 + self.width = img.columns if respond_to?(:width)
  55 + self.height = img.rows if respond_to?(:height)
  56 + img = img.sharpen if attachment_options[:sharpen_on_resize] && img.changed?
48 img.strip! unless attachment_options[:keep_profile] 57 img.strip! unless attachment_options[:keep_profile]
49 - self.temp_path = write_to_temp_file(img.to_blob) 58 + quality = img.format.to_s[/JPEG/] && get_jpeg_quality
  59 + out_file = write_to_temp_file(img.to_blob { self.quality = quality if quality })
  60 + temp_paths.unshift out_file
  61 + self.size = File.size(self.temp_path)
50 end 62 end
51 end 63 end
52 end 64 end
vendor/plugins/attachment_fu/rackspace_cloudfiles.yml.tpl 0 → 100644
@@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
  1 +development:
  2 + container_name: appname_development
  3 + username:
  4 + api_key:
  5 +
  6 +test:
  7 + container_name: appname_test
  8 + username:
  9 + api_key:
  10 +
  11 +production:
  12 + container_name: appname_production
  13 + username:
  14 + api_key:
vendor/plugins/attachment_fu/test/backends/file_system_test.rb
1 require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper')) 1 require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
  2 +require 'digest/sha2'
2 3
3 class FileSystemTest < Test::Unit::TestCase 4 class FileSystemTest < Test::Unit::TestCase
4 include BaseAttachmentTests 5 include BaseAttachmentTests
@@ -11,7 +12,7 @@ class FileSystemTest &lt; Test::Unit::TestCase @@ -11,7 +12,7 @@ class FileSystemTest &lt; Test::Unit::TestCase
11 assert_equal attachment.size, File.open(attachment.full_filename).stat.size 12 assert_equal attachment.size, File.open(attachment.full_filename).stat.size
12 end 13 end
13 end 14 end
14 - 15 +
15 test_against_subclass :test_filesystem_size_for_file_attachment, FileAttachment 16 test_against_subclass :test_filesystem_size_for_file_attachment, FileAttachment
16 17
17 def test_should_not_overwrite_file_attachment(klass = FileAttachment) 18 def test_should_not_overwrite_file_attachment(klass = FileAttachment)
@@ -21,15 +22,15 @@ class FileSystemTest &lt; Test::Unit::TestCase @@ -21,15 +22,15 @@ class FileSystemTest &lt; Test::Unit::TestCase
21 assert_valid real 22 assert_valid real
22 assert !real.new_record?, real.errors.full_messages.join("\n") 23 assert !real.new_record?, real.errors.full_messages.join("\n")
23 assert !real.size.zero? 24 assert !real.size.zero?
24 - 25 +
25 fake = upload_file :filename => '/files/fake/rails.png' 26 fake = upload_file :filename => '/files/fake/rails.png'
26 assert_valid fake 27 assert_valid fake
27 assert !fake.size.zero? 28 assert !fake.size.zero?
28 - 29 +
29 assert_not_equal File.open(real.full_filename).stat.size, File.open(fake.full_filename).stat.size 30 assert_not_equal File.open(real.full_filename).stat.size, File.open(fake.full_filename).stat.size
30 end 31 end
31 end 32 end
32 - 33 +
33 test_against_subclass :test_should_not_overwrite_file_attachment, FileAttachment 34 test_against_subclass :test_should_not_overwrite_file_attachment, FileAttachment
34 35
35 def test_should_store_file_attachment_in_filesystem(klass = FileAttachment) 36 def test_should_store_file_attachment_in_filesystem(klass = FileAttachment)
@@ -38,13 +39,13 @@ class FileSystemTest &lt; Test::Unit::TestCase @@ -38,13 +39,13 @@ class FileSystemTest &lt; Test::Unit::TestCase
38 assert_created do 39 assert_created do
39 attachment = upload_file :filename => '/files/rails.png' 40 attachment = upload_file :filename => '/files/rails.png'
40 assert_valid attachment 41 assert_valid attachment
41 - assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist" 42 + assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
42 end 43 end
43 attachment 44 attachment
44 end 45 end
45 - 46 +
46 test_against_subclass :test_should_store_file_attachment_in_filesystem, FileAttachment 47 test_against_subclass :test_should_store_file_attachment_in_filesystem, FileAttachment
47 - 48 +
48 def test_should_delete_old_file_when_updating(klass = FileAttachment) 49 def test_should_delete_old_file_when_updating(klass = FileAttachment)
49 attachment_model klass 50 attachment_model klass
50 attachment = upload_file :filename => '/files/rails.png' 51 attachment = upload_file :filename => '/files/rails.png'
@@ -52,16 +53,16 @@ class FileSystemTest &lt; Test::Unit::TestCase @@ -52,16 +53,16 @@ class FileSystemTest &lt; Test::Unit::TestCase
52 assert_not_created do 53 assert_not_created do
53 use_temp_file 'files/rails.png' do |file| 54 use_temp_file 'files/rails.png' do |file|
54 attachment.filename = 'rails2.png' 55 attachment.filename = 'rails2.png'
55 - attachment.temp_path = File.join(fixture_path, file) 56 + attachment.temp_paths.unshift File.join(FIXTURE_PATH, file)
56 attachment.save! 57 attachment.save!
57 - assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist" 58 + assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
58 assert !File.exists?(old_filename), "#{old_filename} still exists" 59 assert !File.exists?(old_filename), "#{old_filename} still exists"
59 end 60 end
60 end 61 end
61 end 62 end
62 - 63 +
63 test_against_subclass :test_should_delete_old_file_when_updating, FileAttachment 64 test_against_subclass :test_should_delete_old_file_when_updating, FileAttachment
64 - 65 +
65 def test_should_delete_old_file_when_renaming(klass = FileAttachment) 66 def test_should_delete_old_file_when_renaming(klass = FileAttachment)
66 attachment_model klass 67 attachment_model klass
67 attachment = upload_file :filename => '/files/rails.png' 68 attachment = upload_file :filename => '/files/rails.png'
@@ -69,12 +70,74 @@ class FileSystemTest &lt; Test::Unit::TestCase @@ -69,12 +70,74 @@ class FileSystemTest &lt; Test::Unit::TestCase
69 assert_not_created do 70 assert_not_created do
70 attachment.filename = 'rails2.png' 71 attachment.filename = 'rails2.png'
71 attachment.save 72 attachment.save
72 - assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist" 73 + assert File.exists?(attachment.full_filename), "#{attachment.full_filename} does not exist"
73 assert !File.exists?(old_filename), "#{old_filename} still exists" 74 assert !File.exists?(old_filename), "#{old_filename} still exists"
74 assert !attachment.reload.size.zero? 75 assert !attachment.reload.size.zero?
75 assert_equal 'rails2.png', attachment.filename 76 assert_equal 'rails2.png', attachment.filename
76 end 77 end
77 end 78 end
78 - 79 +
79 test_against_subclass :test_should_delete_old_file_when_renaming, FileAttachment 80 test_against_subclass :test_should_delete_old_file_when_renaming, FileAttachment
80 -end  
81 \ No newline at end of file 81 \ No newline at end of file
  82 +
  83 + def test_path_partitioning_works_on_integer_id(klass = FileAttachment)
  84 + attachment_model klass
  85 +
  86 + # Create a random attachment object, doesn't matter what.
  87 + attachment = upload_file :filename => '/files/rails.png'
  88 + old_id = attachment.id
  89 + attachment.id = 1
  90 +
  91 + begin
  92 + assert_equal ["0000", "0001", "bar.txt"], attachment.send(:partitioned_path, "bar.txt")
  93 + ensure
  94 + attachment.id = old_id
  95 + end
  96 + end
  97 +
  98 + test_against_subclass :test_path_partitioning_works_on_integer_id, FileAttachment
  99 +
  100 + def test_path_partitioning_with_string_id_works_by_generating_hash(klass = FileAttachmentWithStringId)
  101 + attachment_model klass
  102 +
  103 + # Create a random attachment object, doesn't matter what.
  104 + attachment = upload_file :filename => '/files/rails.png'
  105 + old_id = attachment.id
  106 + attachment.id = "hello world some long string"
  107 + hash = Digest::SHA512.hexdigest("hello world some long string")
  108 +
  109 + begin
  110 + assert_equal [
  111 + hash[0..31],
  112 + hash[32..63],
  113 + hash[64..95],
  114 + hash[96..127],
  115 + "bar.txt"
  116 + ], attachment.send(:partitioned_path, "bar.txt")
  117 + ensure
  118 + attachment.id = old_id
  119 + end
  120 + end
  121 +
  122 + test_against_subclass :test_path_partitioning_with_string_id_works_by_generating_hash, FileAttachmentWithStringId
  123 +
  124 + def test_path_partition_string_id_hashing_is_turned_off_if_id_is_uuid(klass = FileAttachmentWithUuid)
  125 + attachment_model klass
  126 +
  127 + # Create a random attachment object, doesn't matter what.
  128 + attachment = upload_file :filename => '/files/rails.png'
  129 + old_id = attachment.id
  130 + attachment.id = "0c0743b698483569dc65909a8cdb3bf9"
  131 +
  132 + begin
  133 + assert_equal [
  134 + "0c0743b698483569",
  135 + "dc65909a8cdb3bf9",
  136 + "bar.txt"
  137 + ], attachment.send(:partitioned_path, "bar.txt")
  138 + ensure
  139 + attachment.id = old_id
  140 + end
  141 + end
  142 +
  143 + test_against_subclass :test_path_partition_string_id_hashing_is_turned_off_if_id_is_uuid, FileAttachmentWithUuid
  144 +end
vendor/plugins/attachment_fu/test/backends/remote/cloudfiles_test.rb 0 → 100644
@@ -0,0 +1,102 @@ @@ -0,0 +1,102 @@
  1 +require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
  2 +require 'net/http'
  3 +
  4 +class CloudfilesTest < Test::Unit::TestCase
  5 + def self.test_CloudFiles?
  6 + true unless ENV["TEST_CLOUDFILES"] == "false"
  7 + end
  8 +
  9 + if test_CloudFiles? && File.exist?(File.join(File.dirname(__FILE__), '../../rackspace_cloudfiles.yml'))
  10 + include BaseAttachmentTests
  11 + attachment_model CloudFilesAttachment
  12 +
  13 + def test_should_create_correct_container_name(klass = CloudFilesAttachment)
  14 + attachment_model klass
  15 + attachment = upload_file :filename => '/files/rails.png'
  16 + assert_equal attachment.cloudfiles_config[:container_name], attachment.container_name
  17 + end
  18 +
  19 + test_against_subclass :test_should_create_correct_container_name, CloudFilesAttachment
  20 +
  21 + def test_should_create_default_path_prefix(klass = CloudFilesAttachment)
  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, CloudFilesAttachment
  28 +
  29 + def test_should_create_custom_path_prefix(klass = CloudFilesWithPathPrefixAttachment)
  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, CloudFilesWithPathPrefixAttachment
  36 +
  37 +
  38 + def test_should_create_valid_url(klass = CloudFilesAttachment)
  39 + attachment_model klass
  40 + attachment = upload_file :filename => '/files/rails.png'
  41 + assert_match(%r!http://cdn.cloudfiles.mosso.com/(.*?)/cloud_files_attachments/1/rails.png!, attachment.cloudfiles_url)
  42 + end
  43 +
  44 + test_against_subclass :test_should_create_valid_url, CloudFilesAttachment
  45 +
  46 + def test_should_save_attachment(klass = CloudFilesAttachment)
  47 + attachment_model klass
  48 + assert_created do
  49 + attachment = upload_file :filename => '/files/rails.png'
  50 + assert_valid attachment
  51 + assert attachment.image?
  52 + assert !attachment.size.zero?
  53 + assert_kind_of Net::HTTPOK, http_response_for(attachment.cloudfiles_url)
  54 + end
  55 + end
  56 +
  57 + test_against_subclass :test_should_save_attachment, CloudFilesAttachment
  58 +
  59 + def test_should_delete_attachment_from_cloud_files_when_attachment_record_destroyed(klass = CloudFilesAttachment)
  60 + attachment_model klass
  61 + attachment = upload_file :filename => '/files/rails.png'
  62 +
  63 + urls = [attachment.cloudfiles_url] + attachment.thumbnails.collect(&:cloudfiles_url)
  64 +
  65 + urls.each {|url| assert_kind_of Net::HTTPOK, http_response_for(url) }
  66 + attachment.destroy
  67 + urls.each do |url|
  68 + begin
  69 + http_response_for(url)
  70 + rescue Net::HTTPForbidden, Net::HTTPNotFound
  71 + nil
  72 + end
  73 + end
  74 + end
  75 +
  76 + test_against_subclass :test_should_delete_attachment_from_cloud_files_when_attachment_record_destroyed, CloudFilesAttachment
  77 +
  78 +
  79 +
  80 + protected
  81 + def http_response_for(url)
  82 + url = URI.parse(url)
  83 + Net::HTTP.start(url.host, url.port) {|http| http.request_head(url.path) }
  84 + end
  85 +
  86 + def s3_protocol
  87 + Technoweenie::AttachmentFu::Backends::S3Backend.protocol
  88 + end
  89 +
  90 + def s3_hostname
  91 + Technoweenie::AttachmentFu::Backends::S3Backend.hostname
  92 + end
  93 +
  94 + def s3_port_string
  95 + Technoweenie::AttachmentFu::Backends::S3Backend.port_string
  96 + end
  97 + else
  98 + def test_flunk_s3
  99 + puts "s3 config file not loaded, tests not running"
  100 + end
  101 + end
  102 +end
0 \ No newline at end of file 103 \ No newline at end of file
vendor/plugins/attachment_fu/test/backends/remote/s3_test.rb
@@ -49,6 +49,18 @@ class S3Test &lt; Test::Unit::TestCase @@ -49,6 +49,18 @@ class S3Test &lt; Test::Unit::TestCase
49 end 49 end
50 50
51 test_against_subclass :test_should_create_authenticated_url, S3Attachment 51 test_against_subclass :test_should_create_authenticated_url, S3Attachment
  52 +
  53 + def test_should_create_authenticated_url_for_thumbnail(klass = S3Attachment)
  54 + attachment_model klass
  55 + attachment = upload_file :filename => '/files/rails.png'
  56 + ['large', :large].each do |thumbnail|
  57 + assert_match(
  58 + /^http.+rails_large\.png.+AWSAccessKeyId.+Expires.+Signature/,
  59 + attachment.authenticated_s3_url(thumbnail),
  60 + "authenticated_s3_url failed with #{thumbnail.class} parameter"
  61 + )
  62 + end
  63 + end
52 64
53 def test_should_save_attachment(klass = S3Attachment) 65 def test_should_save_attachment(klass = S3Attachment)
54 attachment_model klass 66 attachment_model klass
vendor/plugins/attachment_fu/test/base_attachment_tests.rb
1 module BaseAttachmentTests 1 module BaseAttachmentTests
2 def test_should_create_file_from_uploaded_file 2 def test_should_create_file_from_uploaded_file
3 assert_created do 3 assert_created do
4 - attachment = upload_file :filename => '/files/foo.txt' 4 + attachment = upload_file :filename => '/files/foo.txt', :content_type => 'text/plain'
5 assert_valid attachment 5 assert_valid attachment
6 assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file) 6 assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file)
7 - assert attachment.image? 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_should_create_file_from_merb_temp_file
  16 + assert_created do
  17 + attachment = upload_merb_file :filename => '/files/foo.txt', :content_type => 'text/plain'
  18 + assert_valid attachment
  19 + assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file)
  20 + assert !attachment.image?
8 assert !attachment.size.zero? 21 assert !attachment.size.zero?
9 #assert_equal 3, attachment.size 22 #assert_equal 3, attachment.size
10 assert_nil attachment.width 23 assert_nil attachment.width
@@ -18,7 +31,7 @@ module BaseAttachmentTests @@ -18,7 +31,7 @@ module BaseAttachmentTests
18 assert_valid attachment 31 assert_valid attachment
19 assert attachment.size > 0, "no data was set" 32 assert attachment.size > 0, "no data was set"
20 33
21 - attachment.temp_data = 'wtf' 34 + attachment.set_temp_data 'wtf'
22 assert attachment.save_attachment? 35 assert attachment.save_attachment?
23 attachment.save! 36 attachment.save!
24 37
@@ -32,7 +45,7 @@ module BaseAttachmentTests @@ -32,7 +45,7 @@ module BaseAttachmentTests
32 assert_valid attachment 45 assert_valid attachment
33 assert attachment.size > 0, "no data was set" 46 assert attachment.size > 0, "no data was set"
34 47
35 - attachment.temp_data = nil 48 + attachment.set_temp_data nil
36 assert !attachment.save_attachment? 49 assert !attachment.save_attachment?
37 end 50 end
38 end 51 end
@@ -42,7 +55,7 @@ module BaseAttachmentTests @@ -42,7 +55,7 @@ module BaseAttachmentTests
42 assert_not_created do # no new db_file records 55 assert_not_created do # no new db_file records
43 use_temp_file 'files/rails.png' do |file| 56 use_temp_file 'files/rails.png' do |file|
44 attachment.filename = 'rails2.png' 57 attachment.filename = 'rails2.png'
45 - attachment.temp_path = File.join(fixture_path, file) 58 + attachment.temp_paths.unshift File.join(FIXTURE_PATH, file)
46 attachment.save! 59 attachment.save!
47 end 60 end
48 end 61 end
@@ -54,4 +67,11 @@ module BaseAttachmentTests @@ -54,4 +67,11 @@ module BaseAttachmentTests
54 assert !attachment.save_attachment? 67 assert !attachment.save_attachment?
55 assert_nothing_raised { attachment.save! } 68 assert_nothing_raised { attachment.save! }
56 end 69 end
  70 +
  71 + def test_should_handle_nil_file_upload
  72 + attachment = attachment_model.create :uploaded_data => ''
  73 + assert_raise ActiveRecord::RecordInvalid do
  74 + attachment.save!
  75 + end
  76 + end
57 end 77 end
58 \ No newline at end of file 78 \ No newline at end of file
vendor/plugins/attachment_fu/test/basic_test.rb
  1 +# -*- coding: utf-8 -*-
1 require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper')) 2 require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
2 3
3 class BasicTest < Test::Unit::TestCase 4 class BasicTest < Test::Unit::TestCase
4 def test_should_set_default_min_size 5 def test_should_set_default_min_size
5 assert_equal 1, Attachment.attachment_options[:min_size] 6 assert_equal 1, Attachment.attachment_options[:min_size]
6 end 7 end
7 - 8 +
8 def test_should_set_default_max_size 9 def test_should_set_default_max_size
9 assert_equal 1.megabyte, Attachment.attachment_options[:max_size] 10 assert_equal 1.megabyte, Attachment.attachment_options[:max_size]
10 end 11 end
11 - 12 +
12 def test_should_set_default_size 13 def test_should_set_default_size
13 assert_equal (1..1.megabyte), Attachment.attachment_options[:size] 14 assert_equal (1..1.megabyte), Attachment.attachment_options[:size]
14 end 15 end
15 - 16 +
16 def test_should_set_default_thumbnails_option 17 def test_should_set_default_thumbnails_option
17 assert_equal Hash.new, Attachment.attachment_options[:thumbnails] 18 assert_equal Hash.new, Attachment.attachment_options[:thumbnails]
18 end 19 end
@@ -20,19 +21,19 @@ class BasicTest &lt; Test::Unit::TestCase @@ -20,19 +21,19 @@ class BasicTest &lt; Test::Unit::TestCase
20 def test_should_set_default_thumbnail_class 21 def test_should_set_default_thumbnail_class
21 assert_equal Attachment, Attachment.attachment_options[:thumbnail_class] 22 assert_equal Attachment, Attachment.attachment_options[:thumbnail_class]
22 end 23 end
23 - 24 +
24 def test_should_normalize_content_types_to_array 25 def test_should_normalize_content_types_to_array
25 assert_equal %w(pdf), PdfAttachment.attachment_options[:content_type] 26 assert_equal %w(pdf), PdfAttachment.attachment_options[:content_type]
26 assert_equal %w(pdf doc txt), DocAttachment.attachment_options[:content_type] 27 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] 28 + assert_equal Technoweenie::AttachmentFu.content_types, ImageAttachment.attachment_options[:content_type]
  29 + assert_equal ['pdf'] + Technoweenie::AttachmentFu.content_types, ImageOrPdfAttachment.attachment_options[:content_type]
29 end 30 end
30 - 31 +
31 def test_should_sanitize_content_type 32 def test_should_sanitize_content_type
32 @attachment = Attachment.new :content_type => ' foo ' 33 @attachment = Attachment.new :content_type => ' foo '
33 assert_equal 'foo', @attachment.content_type 34 assert_equal 'foo', @attachment.content_type
34 end 35 end
35 - 36 +
36 def test_should_sanitize_filenames 37 def test_should_sanitize_filenames
37 @attachment = Attachment.new :filename => 'blah/foo.bar' 38 @attachment = Attachment.new :filename => 'blah/foo.bar'
38 assert_equal 'foo.bar', @attachment.filename 39 assert_equal 'foo.bar', @attachment.filename
@@ -42,23 +43,79 @@ class BasicTest &lt; Test::Unit::TestCase @@ -42,23 +43,79 @@ class BasicTest &lt; Test::Unit::TestCase
42 43
43 @attachment.filename = 'f o!O-.bar' 44 @attachment.filename = 'f o!O-.bar'
44 assert_equal 'f_o_O-.bar', @attachment.filename 45 assert_equal 'f_o_O-.bar', @attachment.filename
  46 +
  47 +# @attachment.filename = 'sheeps_says_bææ'
  48 +# assert_equal 'sheeps_says_b__', @attachment.filename
  49 +
  50 + @attachment.filename = nil
  51 + assert_nil @attachment.filename
45 end 52 end
46 - 53 +
47 def test_should_convert_thumbnail_name 54 def test_should_convert_thumbnail_name
48 @attachment = FileAttachment.new :filename => 'foo.bar' 55 @attachment = FileAttachment.new :filename => 'foo.bar'
49 assert_equal 'foo.bar', @attachment.thumbnail_name_for(nil) 56 assert_equal 'foo.bar', @attachment.thumbnail_name_for(nil)
50 assert_equal 'foo.bar', @attachment.thumbnail_name_for('') 57 assert_equal 'foo.bar', @attachment.thumbnail_name_for('')
51 assert_equal 'foo_blah.bar', @attachment.thumbnail_name_for(:blah) 58 assert_equal 'foo_blah.bar', @attachment.thumbnail_name_for(:blah)
52 assert_equal 'foo_blah.blah.bar', @attachment.thumbnail_name_for('blah.blah') 59 assert_equal 'foo_blah.blah.bar', @attachment.thumbnail_name_for('blah.blah')
53 - 60 +
54 @attachment.filename = 'foo.bar.baz' 61 @attachment.filename = 'foo.bar.baz'
55 assert_equal 'foo.bar_blah.baz', @attachment.thumbnail_name_for(:blah) 62 assert_equal 'foo.bar_blah.baz', @attachment.thumbnail_name_for(:blah)
56 end 63 end
57 - 64 +
58 def test_should_require_valid_thumbnails_option 65 def test_should_require_valid_thumbnails_option
59 klass = Class.new(ActiveRecord::Base) 66 klass = Class.new(ActiveRecord::Base)
60 assert_raise ArgumentError do 67 assert_raise ArgumentError do
61 klass.has_attachment :thumbnails => [] 68 klass.has_attachment :thumbnails => []
62 end 69 end
63 end 70 end
64 -end  
65 \ No newline at end of file 71 \ No newline at end of file
  72 +
  73 + class ::ImageWithPolymorphicThumbsAttachment
  74 + cattr_accessor :thumbnail_creations
  75 +
  76 + def create_or_update_thumbnail(path, thumb, *size)
  77 + @@thumbnail_creations[thumb] = size.size == 1 ? size.first : size
  78 + end
  79 +
  80 + def self.reset_creations
  81 + @@thumbnail_creations = {}
  82 + end
  83 + end
  84 +
  85 + def test_should_handle_polymorphic_thumbnails_option
  86 + assert_polymorphic_thumb_creation nil,
  87 + :thumb => [50, 50], :geometry => 'x50'
  88 + assert_polymorphic_thumb_creation 'Product',
  89 + :thumb => [50, 50], :geometry => 'x50', :large_thumb => '169x169!', :zoomed => '500x500>'
  90 + assert_polymorphic_thumb_creation 'Editorial',
  91 + :thumb => [50, 50], :geometry => 'x50', :fullsize => '150x100>'
  92 + assert_polymorphic_thumb_creation 'User',
  93 + :thumb => [50, 50], :geometry => 'x50', :avatar => '64x64!'
  94 + end
  95 +
  96 + def test_should_compute_per_thumbnail_jpeg_quality
  97 + assert_jpeg_quality :thumb, 90
  98 + assert_jpeg_quality :avatar, 85
  99 + assert_jpeg_quality :large, 75
  100 + assert_jpeg_quality :large, 0x200 | 75, false
  101 + assert_jpeg_quality nil, 75
  102 + end
  103 +
  104 +private
  105 + def assert_jpeg_quality(thumbnail, quality, require_0_to_100 = true)
  106 + klass = ImageWithPerThumbJpegAttachment
  107 + w, h = if thumbnail
  108 + klass.attachment_options[:thumbnails][thumbnail].scan(/\d+/)
  109 + else
  110 + klass.attachment_options[:resize_to].scan(/\d+/)
  111 + end
  112 + attachment = klass.new(:thumbnail => thumbnail, :width => w, :height => h)
  113 + assert_equal quality, attachment.send(:get_jpeg_quality, require_0_to_100)
  114 + end
  115 +
  116 + def assert_polymorphic_thumb_creation(parent, defs)
  117 + attachment_model ImageWithPolymorphicThumbsAttachment
  118 + attachment_model.reset_creations
  119 + attachment = upload_file :filename => '/files/rails.png', :imageable_type => parent.to_s.classify, :imageable_id => nil
  120 + assert_equal defs, attachment_model.thumbnail_creations
  121 + end
  122 +end
vendor/plugins/attachment_fu/test/database.yml
1 sqlite: 1 sqlite:
2 :adapter: sqlite 2 :adapter: sqlite
3 - :dbfile: attachment_fu_plugin.sqlite.db 3 + :database: attachment_fu_plugin.sqlite.db
4 sqlite3: 4 sqlite3:
5 :adapter: sqlite3 5 :adapter: sqlite3
6 - :dbfile: attachment_fu_plugin.sqlite3.db 6 + :database: attachment_fu_plugin.sqlite3.db
7 postgresql: 7 postgresql:
8 :adapter: postgresql 8 :adapter: postgresql
9 :username: postgres 9 :username: postgres
vendor/plugins/attachment_fu/test/extra_attachment_test.rb
@@ -24,6 +24,16 @@ class OrphanAttachmentTest &lt; Test::Unit::TestCase @@ -24,6 +24,16 @@ class OrphanAttachmentTest &lt; Test::Unit::TestCase
24 end 24 end
25 end 25 end
26 26
  27 + def test_should_create_file_from_merb_temp_file
  28 + assert_created do
  29 + attachment = upload_merb_file :filename => '/files/foo.txt'
  30 + assert_valid attachment
  31 + assert !attachment.db_file.new_record? if attachment.respond_to?(:db_file)
  32 + assert attachment.image?
  33 + assert !attachment.size.zero?
  34 + end
  35 + end
  36 +
27 def test_should_create_image_from_uploaded_file_with_custom_content_type 37 def test_should_create_image_from_uploaded_file_with_custom_content_type
28 assert_created do 38 assert_created do
29 attachment = upload_file :content_type => 'foo/bar', :filename => '/files/rails.png' 39 attachment = upload_file :content_type => 'foo/bar', :filename => '/files/rails.png'
vendor/plugins/attachment_fu/test/fixtures/attachment.rb
@@ -3,11 +3,16 @@ class Attachment &lt; ActiveRecord::Base @@ -3,11 +3,16 @@ class Attachment &lt; ActiveRecord::Base
3 cattr_accessor :saves 3 cattr_accessor :saves
4 has_attachment :processor => :rmagick 4 has_attachment :processor => :rmagick
5 validates_as_attachment 5 validates_as_attachment
6 - after_attachment_saved do |record| 6 + after_save do |record|
7 self.saves += 1 7 self.saves += 1
8 end 8 end
9 end 9 end
10 10
  11 +class LowerQualityAttachment < Attachment
  12 + self.table_name = 'attachments'
  13 + has_attachment :resize_to => [55,55], :jpeg_quality => 50
  14 +end
  15 +
11 class SmallAttachment < Attachment 16 class SmallAttachment < Attachment
12 has_attachment :max_size => 1.kilobyte 17 has_attachment :max_size => 1.kilobyte
13 end 18 end
@@ -34,9 +39,26 @@ end @@ -34,9 +39,26 @@ end
34 39
35 class ImageWithThumbsAttachment < Attachment 40 class ImageWithThumbsAttachment < Attachment
36 has_attachment :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }, :resize_to => [55,55] 41 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 42 + # after_resize do |record, img|
  43 + # record.aspect_ratio = img.columns.to_f / img.rows.to_f
  44 + # end
  45 +end
  46 +
  47 +class ImageWithPerThumbJpegAttachment < Attachment
  48 + has_attachment :resize_to => '500x500!',
  49 + :thumbnails => { :thumb => '50x50!', :large => '300x300!', :avatar => '64x64!' },
  50 + :jpeg_quality => { :thumb => 90, '<5000' => 85, '>=5000' => 75, :large => 0x200 | 75 }
  51 +end
  52 +
  53 +class ImageWithPolymorphicThumbsAttachment < Attachment
  54 + belongs_to :imageable, :polymorphic => true
  55 + has_attachment :thumbnails => {
  56 + :thumb => [50, 50],
  57 + :geometry => 'x50',
  58 + :products => { :large_thumb => '169x169!', :zoomed => '500x500>' },
  59 + :editorials => { :fullsize => '150x100>' },
  60 + 'User' => { :avatar => '64x64!' }
  61 + }
40 end 62 end
41 63
42 class FileAttachment < ActiveRecord::Base 64 class FileAttachment < ActiveRecord::Base
@@ -44,6 +66,38 @@ class FileAttachment &lt; ActiveRecord::Base @@ -44,6 +66,38 @@ class FileAttachment &lt; ActiveRecord::Base
44 validates_as_attachment 66 validates_as_attachment
45 end 67 end
46 68
  69 +class FileAttachmentWithStringId < ActiveRecord::Base
  70 + self.table_name = 'file_attachments_with_string_id'
  71 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', :processor => :rmagick
  72 + validates_as_attachment
  73 +
  74 + before_validation :auto_generate_id
  75 + before_save :auto_generate_id
  76 + @@last_id = 0
  77 +
  78 + private
  79 + def auto_generate_id
  80 + @@last_id += 1
  81 + self.id = "id_#{@@last_id}"
  82 + end
  83 +end
  84 +
  85 +class FileAttachmentWithUuid < ActiveRecord::Base
  86 + self.table_name = 'file_attachments_with_string_id'
  87 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', :processor => :rmagick, :uuid_primary_key => true
  88 + validates_as_attachment
  89 +
  90 + before_validation :auto_generate_id
  91 + before_save :auto_generate_id
  92 + @@last_id = 0
  93 +
  94 + private
  95 + def auto_generate_id
  96 + @@last_id += 1
  97 + self.id = "%0127dx" % @@last_id
  98 + end
  99 +end
  100 +
47 class ImageFileAttachment < FileAttachment 101 class ImageFileAttachment < FileAttachment
48 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', 102 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
49 :content_type => :image, :resize_to => [50,50] 103 :content_type => :image, :resize_to => [50,50]
@@ -52,9 +106,9 @@ end @@ -52,9 +106,9 @@ end
52 class ImageWithThumbsFileAttachment < FileAttachment 106 class ImageWithThumbsFileAttachment < FileAttachment
53 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', 107 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
54 :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }, :resize_to => [55,55] 108 :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 109 + # after_resize do |record, img|
  110 + # record.aspect_ratio = img.columns.to_f / img.rows.to_f
  111 + # end
58 end 112 end
59 113
60 class ImageWithThumbsClassFileAttachment < FileAttachment 114 class ImageWithThumbsClassFileAttachment < FileAttachment
@@ -78,7 +132,7 @@ end @@ -78,7 +132,7 @@ end
78 class MinimalAttachment < ActiveRecord::Base 132 class MinimalAttachment < ActiveRecord::Base
79 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', :processor => :rmagick 133 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', :processor => :rmagick
80 validates_as_attachment 134 validates_as_attachment
81 - 135 +
82 def filename 136 def filename
83 "#{id}.file" 137 "#{id}.file"
84 end 138 end
@@ -87,7 +141,22 @@ end @@ -87,7 +141,22 @@ end
87 begin 141 begin
88 class ImageScienceAttachment < ActiveRecord::Base 142 class ImageScienceAttachment < ActiveRecord::Base
89 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', 143 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
90 - :processor => :image_science, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55 144 + :processor => :image_science, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55
  145 + end
  146 +
  147 + class ImageScienceLowerQualityAttachment < ActiveRecord::Base
  148 + self.table_name = 'image_science_attachments'
  149 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  150 + :processor => :image_science, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55,
  151 + :jpeg_quality => 75
  152 + end
  153 +
  154 + class ImageScienceWithPerThumbJpegAttachment < ImageScienceAttachment
  155 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  156 + :processor => :image_science,
  157 + :resize_to => '100x100',
  158 + :thumbnails => { :thumb => [50, 50], :editorial => '300x120', :avatar => '64x64!' },
  159 + :jpeg_quality => { :thumb => 90, '<5000' => 80, '>=5000' => 75 }
91 end 160 end
92 rescue MissingSourceFile 161 rescue MissingSourceFile
93 puts $!.message 162 puts $!.message
@@ -97,7 +166,21 @@ end @@ -97,7 +166,21 @@ end
97 begin 166 begin
98 class CoreImageAttachment < ActiveRecord::Base 167 class CoreImageAttachment < ActiveRecord::Base
99 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', 168 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
100 - :processor => :core_image, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55 169 + :processor => :core_image, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55
  170 + end
  171 +
  172 + class LowerQualityCoreImageAttachment < CoreImageAttachment
  173 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  174 + :processor => :core_image, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55,
  175 + :jpeg_quality => 50
  176 + end
  177 +
  178 + class CoreImageWithPerThumbJpegAttachment < CoreImageAttachment
  179 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  180 + :processor => :core_image,
  181 + :resize_to => '100x100',
  182 + :thumbnails => { :thumb => [50, 50], :editorial => '300x120', :avatar => '64x64!' },
  183 + :jpeg_quality => { :thumb => 90, '<5000' => 80, '>=5000' => 75 }
101 end 184 end
102 rescue MissingSourceFile 185 rescue MissingSourceFile
103 puts $!.message 186 puts $!.message
@@ -107,8 +190,57 @@ end @@ -107,8 +190,57 @@ end
107 begin 190 begin
108 class MiniMagickAttachment < ActiveRecord::Base 191 class MiniMagickAttachment < ActiveRecord::Base
109 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', 192 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
110 - :processor => :mini_magick, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55 193 + :processor => :mini_magick, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55
111 end 194 end
  195 +
  196 + class ImageThumbnailCrop < MiniMagickAttachment
  197 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  198 + :thumbnails => { :square => "50x50c", :vertical => "30x60c", :horizontal => "60x30c"}
  199 +
  200 + # TODO this is a bad duplication, this method is in the MiniMagick Processor
  201 + def self.calculate_offset(image_width,image_height,image_aspect,thumb_width,thumb_height,thumb_aspect)
  202 + # only crop if image is not smaller in both dimensions
  203 +
  204 + # special cases, image smaller in one dimension then thumbsize
  205 + if image_width < thumb_width
  206 + offset = (image_height / 2) - (thumb_height / 2)
  207 + command = "#{image_width}x#{thumb_height}+0+#{offset}"
  208 + elsif image_height < thumb_height
  209 + offset = (image_width / 2) - (thumb_width / 2)
  210 + command = "#{thumb_width}x#{image_height}+#{offset}+0"
  211 +
  212 + # normal thumbnail generation
  213 + # calculate height and offset y, width is fixed
  214 + elsif (image_aspect <= thumb_aspect or image_width < thumb_width) and image_height > thumb_height
  215 + height = image_width / thumb_aspect
  216 + offset = (image_height / 2) - (height / 2)
  217 + command = "#{image_width}x#{height}+0+#{offset}"
  218 + # calculate width and offset x, height is fixed
  219 + else
  220 + width = image_height * thumb_aspect
  221 + offset = (image_width / 2) - (width / 2)
  222 + command = "#{width}x#{image_height}+#{offset}+0"
  223 + end
  224 + # crop image
  225 + command
  226 + end
  227 + end
  228 +
  229 + class LowerQualityMiniMagickAttachment < ActiveRecord::Base
  230 + self.table_name = 'mini_magick_attachments'
  231 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  232 + :processor => :mini_magick, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55,
  233 + :jpeg_quality => 50
  234 + end
  235 +
  236 + class MiniMagickWithPerThumbJpegAttachment < MiniMagickAttachment
  237 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  238 + :processor => :mini_magick,
  239 + :resize_to => '100x100',
  240 + :thumbnails => { :thumb => [50, 50], :editorial => '300x120', :avatar => '64x64!' },
  241 + :jpeg_quality => { :thumb => 90, '<5000' => 80, '>=5000' => 75 }
  242 + end
  243 +
112 rescue MissingSourceFile 244 rescue MissingSourceFile
113 puts $!.message 245 puts $!.message
114 puts "no Mini Magick" 246 puts "no Mini Magick"
@@ -117,32 +249,49 @@ end @@ -117,32 +249,49 @@ end
117 begin 249 begin
118 class GD2Attachment < ActiveRecord::Base 250 class GD2Attachment < ActiveRecord::Base
119 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', 251 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
120 - :processor => :gd2, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55 252 + :processor => :gd2, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55
121 end 253 end
122 -rescue MissingSourceFile  
123 - puts $!.message  
124 - puts "no GD2"  
125 -end  
126 254
  255 + class LowerQualityGD2Attachment < GD2Attachment
  256 + has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
  257 + :processor => :gd2, :thumbnails => { :thumb => [50, 51], :geometry => '31>', :aspect => '25x25!' }, :resize_to => 55,
  258 + :jpeg_quality => 50
  259 + end
127 260
128 -begin  
129 - class MiniMagickAttachment < ActiveRecord::Base 261 + class GD2WithPerThumbJpegAttachment < GD2Attachment
130 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files', 262 has_attachment :path_prefix => 'vendor/plugins/attachment_fu/test/files',
131 - :processor => :mini_magick, :thumbnails => { :thumb => [50, 51], :geometry => '31>' }, :resize_to => 55 263 + :processor => :gd2,
  264 + :resize_to => '100x100',
  265 + :thumbnails => { :thumb => [50, 50], :editorial => '300x120', :avatar => '64x64!' },
  266 + :jpeg_quality => { :thumb => 90, '<5000' => 80, '>=5000' => 75 }
132 end 267 end
133 rescue MissingSourceFile 268 rescue MissingSourceFile
  269 + puts $!.message
  270 + puts "no GD2"
134 end 271 end
135 272
  273 +
136 begin 274 begin
137 class S3Attachment < ActiveRecord::Base 275 class S3Attachment < ActiveRecord::Base
138 has_attachment :storage => :s3, :processor => :rmagick, :s3_config_path => File.join(File.dirname(__FILE__), '../amazon_s3.yml') 276 has_attachment :storage => :s3, :processor => :rmagick, :s3_config_path => File.join(File.dirname(__FILE__), '../amazon_s3.yml')
139 validates_as_attachment 277 validates_as_attachment
140 end 278 end
141 279
  280 + class CloudFilesAttachment < ActiveRecord::Base
  281 + has_attachment :storage => :cloud_files, :processor => :rmagick, :cloudfiles_config_path => File.join(File.dirname(__FILE__), '../rackspace_cloudfiles.yml')
  282 + validates_as_attachment
  283 + end
  284 +
142 class S3WithPathPrefixAttachment < S3Attachment 285 class S3WithPathPrefixAttachment < S3Attachment
143 has_attachment :storage => :s3, :path_prefix => 'some/custom/path/prefix', :processor => :rmagick 286 has_attachment :storage => :s3, :path_prefix => 'some/custom/path/prefix', :processor => :rmagick
144 validates_as_attachment 287 validates_as_attachment
145 end 288 end
  289 +
  290 + class CloudFilesWithPathPrefixAttachment < CloudFilesAttachment
  291 + has_attachment :storage => :cloud_files, :path_prefix => 'some/custom/path/prefix', :processor => :rmagick
  292 + validates_as_attachment
  293 + end
  294 +
146 rescue 295 rescue
147 puts "S3 error: #{$!}" 296 puts "S3 error: #{$!}"
148 end 297 end
vendor/plugins/attachment_fu/test/fixtures/files/rails.jpg 0 → 100644

2.43 KB

vendor/plugins/attachment_fu/test/geometry_test.rb
@@ -23,6 +23,13 @@ class GeometryTest &lt; Test::Unit::TestCase @@ -23,6 +23,13 @@ class GeometryTest &lt; Test::Unit::TestCase
23 "100" => [100, 128] 23 "100" => [100, 128]
24 end 24 end
25 25
  26 + def test_should_resize_no_height_with_x
  27 + assert_geometry 50, 64,
  28 + "50x" => [50, 64],
  29 + "60x" => [60, 77],
  30 + "100x" => [100, 128]
  31 + end
  32 +
26 def test_should_resize_with_percent 33 def test_should_resize_with_percent
27 assert_geometry 50, 64, 34 assert_geometry 50, 64,
28 "50x50%" => [25, 32], 35 "50x50%" => [25, 32],
@@ -90,6 +97,12 @@ class GeometryTest &lt; Test::Unit::TestCase @@ -90,6 +97,12 @@ class GeometryTest &lt; Test::Unit::TestCase
90 "100>" => [50, 64] 97 "100>" => [50, 64]
91 end 98 end
92 99
  100 + def test_should_resize_with_aspect
  101 + assert_geometry 50, 64,
  102 + "35x35!" => [35, 35],
  103 + "70x70!" => [70, 70]
  104 + end
  105 +
93 protected 106 protected
94 def assert_geometry(width, height, values) 107 def assert_geometry(width, height, values)
95 values.each do |geo, result| 108 values.each do |geo, result|
vendor/plugins/attachment_fu/test/processors/core_image_test.rb
@@ -14,14 +14,41 @@ class CoreImageTest &lt; Test::Unit::TestCase @@ -14,14 +14,41 @@ class CoreImageTest &lt; Test::Unit::TestCase
14 14
15 thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ } 15 thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
16 geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ } 16 geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  17 + aspect = attachment.thumbnails.detect { |t| t.filename =~ /_aspect/ }
17 18
18 # test exact resize dimensions 19 # test exact resize dimensions
19 assert_equal 50, thumb.width 20 assert_equal 50, thumb.width
20 assert_equal 51, thumb.height 21 assert_equal 51, thumb.height
21 22
22 - # test geometry string 23 + # test geometry strings
23 assert_equal 31, geo.width 24 assert_equal 31, geo.width
24 assert_equal 41, geo.height 25 assert_equal 41, geo.height
  26 + assert_equal 25, aspect.width
  27 + assert_equal 25, aspect.height
  28 +
  29 + # This makes sure that we didn't overwrite the original file
  30 + # and will end up with a thumbnail instead of the original
  31 + assert_equal 42, attachment.width
  32 + assert_equal 55, attachment.height
  33 +
  34 + end
  35 +
  36 + def test_should_handle_jpeg_quality
  37 + attachment_model CoreImageAttachment
  38 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  39 + full_size = attachment.size
  40 + attachment_model LowerQualityCoreImageAttachment
  41 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  42 + lq_size = attachment.size
  43 + assert lq_size <= full_size * 0.9, 'Lower-quality JPEG filesize should be congruently smaller'
  44 +
  45 + # FIXME: wait for Marcus' reply to determine whether I can get exact-quality output or need to adjust for CoreImage.
  46 + # attachment_model CoreImageWithPerThumbJpegAttachment
  47 + # attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  48 + # assert_file_jpeg_quality attachment, :thumb, 90
  49 + # assert_file_jpeg_quality attachment, :avatar, 80
  50 + # assert_file_jpeg_quality attachment, :editorial, 75
  51 + # assert_file_jpeg_quality attachment, nil, 75
25 end 52 end
26 else 53 else
27 def test_flunk 54 def test_flunk
vendor/plugins/attachment_fu/test/processors/gd2_test.rb
@@ -12,16 +12,36 @@ class GD2Test &lt; Test::Unit::TestCase @@ -12,16 +12,36 @@ class GD2Test &lt; Test::Unit::TestCase
12 assert_equal 43, attachment.width 12 assert_equal 43, attachment.width
13 assert_equal 55, attachment.height 13 assert_equal 55, attachment.height
14 14
15 - thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }  
16 - geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ } 15 + thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
  16 + geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  17 + aspect = attachment.thumbnails.detect { |t| t.filename =~ /_aspect/ }
17 18
18 # test exact resize dimensions 19 # test exact resize dimensions
19 assert_equal 50, thumb.width 20 assert_equal 50, thumb.width
20 assert_equal 51, thumb.height 21 assert_equal 51, thumb.height
21 22
22 - # test geometry string 23 + # test geometry strings
23 assert_equal 31, geo.width 24 assert_equal 31, geo.width
24 assert_equal 40, geo.height 25 assert_equal 40, geo.height
  26 + assert_equal 25, aspect.width
  27 + assert_equal 25, aspect.height
  28 + end
  29 +
  30 + def test_should_handle_jpeg_quality
  31 + attachment_model GD2Attachment
  32 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  33 + full_size = attachment.size
  34 + attachment_model LowerQualityGD2Attachment
  35 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  36 + lq_size = attachment.size
  37 + assert lq_size <= full_size * 0.9, 'Lower-quality JPEG filesize should be congruently smaller'
  38 +
  39 + attachment_model GD2WithPerThumbJpegAttachment
  40 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  41 + assert_file_jpeg_quality attachment, :thumb, 90
  42 + assert_file_jpeg_quality attachment, :avatar, 80
  43 + assert_file_jpeg_quality attachment, :editorial, 75
  44 + assert_file_jpeg_quality attachment, nil, 75
25 end 45 end
26 else 46 else
27 def test_flunk 47 def test_flunk
vendor/plugins/attachment_fu/test/processors/image_science_test.rb
@@ -14,14 +14,37 @@ class ImageScienceTest &lt; Test::Unit::TestCase @@ -14,14 +14,37 @@ class ImageScienceTest &lt; Test::Unit::TestCase
14 14
15 thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ } 15 thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
16 geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ } 16 geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  17 + aspect = attachment.thumbnails.detect { |t| t.filename =~ /_aspect/ }
17 18
18 # test exact resize dimensions 19 # test exact resize dimensions
19 assert_equal 50, thumb.width 20 assert_equal 50, thumb.width
20 assert_equal 51, thumb.height 21 assert_equal 51, thumb.height
21 22
22 - # test geometry string 23 + # test geometry strings
23 assert_equal 31, geo.width 24 assert_equal 31, geo.width
24 assert_equal 41, geo.height 25 assert_equal 41, geo.height
  26 + assert_equal 25, aspect.width
  27 + assert_equal 25, aspect.height
  28 + end
  29 +
  30 + def test_should_handle_jpeg_quality
  31 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  32 + full_size = attachment.size
  33 + attachment_model ImageScienceLowerQualityAttachment
  34 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  35 + lq_size = attachment.size
  36 + if ImageScience.instance_method(:save).arity == -2 # tdd-image_science: JPEG quality processing
  37 + assert lq_size <= full_size * 0.75, 'Lower-quality JPEG filesize should be congruently smaller'
  38 + else
  39 + assert_equal full_size, lq_size, 'Unsupported lower-quality JPEG should yield exact same file size'
  40 + end
  41 +
  42 + attachment_model ImageScienceWithPerThumbJpegAttachment
  43 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  44 + assert_file_jpeg_quality attachment, :thumb, 90
  45 + assert_file_jpeg_quality attachment, :avatar, 80
  46 + assert_file_jpeg_quality attachment, :editorial, 75
  47 + assert_file_jpeg_quality attachment, nil, 75
25 end 48 end
26 else 49 else
27 def test_flunk 50 def test_flunk
vendor/plugins/attachment_fu/test/processors/mini_magick_test.rb
@@ -14,18 +14,109 @@ class MiniMagickTest &lt; Test::Unit::TestCase @@ -14,18 +14,109 @@ class MiniMagickTest &lt; Test::Unit::TestCase
14 14
15 thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ } 15 thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
16 geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ } 16 geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
  17 + aspect = attachment.thumbnails.detect { |t| t.filename =~ /_aspect/ }
17 18
18 # test exact resize dimensions 19 # test exact resize dimensions
19 assert_equal 50, thumb.width 20 assert_equal 50, thumb.width
20 assert_equal 51, thumb.height 21 assert_equal 51, thumb.height
21 22
22 - # test geometry string 23 + # test geometry strings
23 assert_equal 31, geo.width 24 assert_equal 31, geo.width
24 assert_equal 40, geo.height 25 assert_equal 40, geo.height
  26 + assert_equal 25, aspect.width
  27 + assert_equal 25, aspect.height
  28 + end
  29 +
  30 + def test_should_crop_image(klass = ImageThumbnailCrop)
  31 + attachment_model klass
  32 + attachment = upload_file :filename => '/files/rails.png'
  33 + assert_valid attachment
  34 + assert attachment.image?
  35 + # has_attachment :thumbnails => { :square => "50x50c", :vertical => "30x60c", :horizontal => "60x30c"}
  36 +
  37 + square = attachment.thumbnails.detect { |t| t.filename =~ /_square/ }
  38 + vertical = attachment.thumbnails.detect { |t| t.filename =~ /_vertical/ }
  39 + horizontal = attachment.thumbnails.detect { |t| t.filename =~ /_horizontal/ }
  40 +
  41 + # test excat resize
  42 + assert_equal 50, square.width
  43 + assert_equal 50, square.height
  44 +
  45 + assert_equal 30, vertical.width
  46 + assert_equal 60, vertical.height
  47 +
  48 + assert_equal 60, horizontal.width
  49 + assert_equal 30, horizontal.height
  50 + end
  51 +
  52 + # tests the first step in resize, crop the image in original size to right format
  53 + def test_should_crop_image_right(klass = ImageThumbnailCrop)
  54 + @@testcases.collect do |testcase|
  55 + image_width, image_height, thumb_width, thumb_height = testcase[:data]
  56 + image_aspect, thumb_aspect = image_width/image_height, thumb_width/thumb_height
  57 + crop_comand = klass.calculate_offset(image_width, image_height, image_aspect, thumb_width, thumb_height,thumb_aspect)
  58 + # pattern matching on crop command
  59 + if testcase.has_key?(:height)
  60 + assert crop_comand.match(/^#{image_width}x#{testcase[:height]}\+0\+#{testcase[:yoffset]}$/)
  61 + else
  62 + assert crop_comand.match(/^#{testcase[:width]}x#{image_height}\+#{testcase[:xoffset]}\+0$/)
  63 + end
  64 + end
  65 + end
  66 +
  67 + def test_should_handle_jpeg_quality
  68 + attachment_model MiniMagickAttachment
  69 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  70 + full_size = attachment.size
  71 + attachment_model LowerQualityMiniMagickAttachment
  72 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  73 + lq_size = attachment.size
  74 + assert lq_size <= full_size * 0.9, 'Lower-quality JPEG filesize should be congruently smaller'
  75 +
  76 + attachment_model MiniMagickWithPerThumbJpegAttachment
  77 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  78 + assert_file_jpeg_quality attachment, :thumb, 90
  79 + assert_file_jpeg_quality attachment, :avatar, 80
  80 + assert_file_jpeg_quality attachment, :editorial, 75
  81 + assert_file_jpeg_quality attachment, nil, 75
25 end 82 end
26 else 83 else
27 def test_flunk 84 def test_flunk
28 puts "MiniMagick not loaded, tests not running" 85 puts "MiniMagick not loaded, tests not running"
29 end 86 end
30 end 87 end
  88 +
  89 + @@testcases = [
  90 + # image_aspect <= 1 && thumb_aspect >= 1
  91 + {:data => [10.0,40.0,2.0,1.0], :height => 5.0, :yoffset => 17.5}, # 1b
  92 + {:data => [10.0,40.0,1.0,1.0], :height => 10.0, :yoffset => 15.0}, # 1b
  93 +
  94 + # image_aspect < 1 && thumb_aspect < 1
  95 + {:data => [10.0,40.0,1.0,2.0], :height => 20.0, :yoffset => 10.0}, # 1a
  96 + {:data => [2.0,3.0,1.0,2.0], :width => 1.5, :xoffset => 0.25}, # 1a
  97 +
  98 + # image_aspect = thumb_aspect
  99 + {:data => [10.0,10.0,1.0,1.0], :height => 10.0, :yoffset => 0.0}, # QUADRAT 1c
  100 +
  101 + # image_aspect >= 1 && thumb_aspect > 1 && image_aspect < thumb_aspect
  102 + {:data => [6.0,3.0,4.0,1.0], :height => 1.5, :yoffset => 0.75}, # 2b
  103 + {:data => [6.0,6.0,4.0,1.0], :height => 1.5, :yoffset => 2.25}, # 2b
  104 +
  105 + # image_aspect > 1 && thumb_aspect > 1 && image_aspect > thumb_aspect
  106 + {:data => [9.0,3.0,2.0,1.0], :width => 6.0, :xoffset => 1.5}, # 2a
  107 +
  108 + # image_aspect > 1 && thumb_aspect < 1 && image_aspect < thumb_aspect
  109 + {:data => [10.0,5.0,0.1,2.0], :width => 0.25, :xoffset => 4.875}, # 4
  110 + {:data => [10.0,5.0,1.0,2.0], :width => 2.5, :xoffset => 3.75}, # 4
  111 +
  112 + # image_aspect > 1 && thumb_aspect > 1 && image_aspect > thumb_aspect
  113 + {:data => [9.0,3.0,2.0,1.0], :width => 6.0, :xoffset => 1.5}, # 3a
  114 + # image_aspect > 1 && thumb_aspect > 1 && image_aspect < thumb_aspect
  115 + {:data => [9.0,3.0,5.0,1.0], :height => 1.8, :yoffset => 0.6} # 3a
  116 + ]
  117 +
  118 +
  119 +
  120 +
  121 +
31 end 122 end
vendor/plugins/attachment_fu/test/processors/rmagick_test.rb
1 require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper')) 1 require File.expand_path(File.join(File.dirname(__FILE__), '..', 'test_helper'))
2 -  
3 class RmagickTest < Test::Unit::TestCase 2 class RmagickTest < Test::Unit::TestCase
4 attachment_model Attachment 3 attachment_model Attachment
5 4
@@ -49,20 +48,21 @@ class RmagickTest &lt; Test::Unit::TestCase @@ -49,20 +48,21 @@ class RmagickTest &lt; Test::Unit::TestCase
49 end 48 end
50 end 49 end
51 50
52 - def test_should_create_thumbnail_with_geometry_string 51 + def test_should_create_thumbnail_with_geometry_strings
53 attachment = upload_file :filename => '/files/rails.png' 52 attachment = upload_file :filename => '/files/rails.png'
54 53
55 assert_created do 54 assert_created do
56 basename, ext = attachment.filename.split '.' 55 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 56 + { 'x50' => [39, 50], '25x25!' => [25, 25] }.each do |geo, (w, h)|
  57 + thumbnail = attachment.create_or_update_thumbnail(attachment.create_temp_file, 'thumb', geo)
  58 + assert_valid thumbnail
  59 + assert !thumbnail.size.zero?
  60 + assert_equal w, thumbnail.width
  61 + assert_equal h, thumbnail.height
  62 + assert_equal [thumbnail], attachment.thumbnails
  63 + assert_equal attachment.id, thumbnail.parent_id if thumbnail.respond_to?(:parent_id)
  64 + assert_equal "#{basename}_thumb.#{ext}", thumbnail.filename
  65 + end
66 end 66 end
67 end 67 end
68 68
@@ -123,7 +123,7 @@ class RmagickTest &lt; Test::Unit::TestCase @@ -123,7 +123,7 @@ class RmagickTest &lt; Test::Unit::TestCase
123 assert_equal 55, attachment.width 123 assert_equal 55, attachment.width
124 assert_equal 55, attachment.height 124 assert_equal 55, attachment.height
125 assert_equal 2, attachment.thumbnails.length 125 assert_equal 2, attachment.thumbnails.length
126 - assert_equal 1.0, attachment.aspect_ratio 126 + # assert_equal 1.0, attachment.aspect_ratio
127 127
128 thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ } 128 thumb = attachment.thumbnails.detect { |t| t.filename =~ /_thumb/ }
129 assert !thumb.new_record?, thumb.errors.full_messages.join("\n") 129 assert !thumb.new_record?, thumb.errors.full_messages.join("\n")
@@ -131,7 +131,7 @@ class RmagickTest &lt; Test::Unit::TestCase @@ -131,7 +131,7 @@ class RmagickTest &lt; Test::Unit::TestCase
131 #assert_in_delta 4673, thumb.size, 2 131 #assert_in_delta 4673, thumb.size, 2
132 assert_equal 50, thumb.width 132 assert_equal 50, thumb.width
133 assert_equal 50, thumb.height 133 assert_equal 50, thumb.height
134 - assert_equal 1.0, thumb.aspect_ratio 134 + # assert_equal 1.0, thumb.aspect_ratio
135 135
136 geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ } 136 geo = attachment.thumbnails.detect { |t| t.filename =~ /_geometry/ }
137 assert !geo.new_record?, geo.errors.full_messages.join("\n") 137 assert !geo.new_record?, geo.errors.full_messages.join("\n")
@@ -139,7 +139,7 @@ class RmagickTest &lt; Test::Unit::TestCase @@ -139,7 +139,7 @@ class RmagickTest &lt; Test::Unit::TestCase
139 #assert_equal 3915, geo.size 139 #assert_equal 3915, geo.size
140 assert_equal 50, geo.width 140 assert_equal 50, geo.width
141 assert_equal 50, geo.height 141 assert_equal 50, geo.height
142 - assert_equal 1.0, geo.aspect_ratio 142 + # assert_equal 1.0, geo.aspect_ratio
143 end 143 end
144 end 144 end
145 145
@@ -181,7 +181,7 @@ class RmagickTest &lt; Test::Unit::TestCase @@ -181,7 +181,7 @@ class RmagickTest &lt; Test::Unit::TestCase
181 assert_not_created do 181 assert_not_created do
182 use_temp_file "files/rails.png" do |file| 182 use_temp_file "files/rails.png" do |file|
183 attachment.filename = 'rails2.png' 183 attachment.filename = 'rails2.png'
184 - attachment.temp_path = File.join(fixture_path, file) 184 + attachment.temp_paths.unshift File.join(FIXTURE_PATH, file)
185 attachment.save 185 attachment.save
186 new_filenames = [attachment.reload.full_filename] + attachment.thumbnails.collect { |t| t.reload.full_filename } 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" } 187 new_filenames.each { |f| assert File.exists?(f), "#{f} does not exist" }
@@ -224,7 +224,7 @@ class RmagickTest &lt; Test::Unit::TestCase @@ -224,7 +224,7 @@ class RmagickTest &lt; Test::Unit::TestCase
224 # #temp_path calls #full_filename, which is not getting mixed into the attachment. Maybe we don't need to 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? 225 # set temp_path at all?
226 # 226 #
227 - # attachment.temp_path = File.join(fixture_path, file) 227 + # attachment.temp_paths.unshift File.join(FIXTURE_PATH, file)
228 attachment.save! 228 attachment.save!
229 end 229 end
230 end 230 end
@@ -247,6 +247,23 @@ class RmagickTest &lt; Test::Unit::TestCase @@ -247,6 +247,23 @@ class RmagickTest &lt; Test::Unit::TestCase
247 end 247 end
248 248
249 test_against_subclass :test_should_overwrite_old_thumbnail_records_when_renaming, ImageWithThumbsAttachment 249 test_against_subclass :test_should_overwrite_old_thumbnail_records_when_renaming, ImageWithThumbsAttachment
  250 +
  251 + def test_should_handle_jpeg_quality
  252 + attachment_model ImageWithThumbsAttachment
  253 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  254 + full_size = attachment.size
  255 + attachment_model LowerQualityAttachment
  256 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  257 + lq_size = attachment.size
  258 + assert lq_size <= full_size * 0.9, 'Lower-quality JPEG filesize should be congruently smaller'
  259 +
  260 + attachment_model ImageWithPerThumbJpegAttachment
  261 + attachment = upload_file :filename => '/files/rails.jpg', :content_type => 'image/jpeg'
  262 + assert_file_jpeg_quality attachment, :thumb, 90
  263 + assert_file_jpeg_quality attachment, :avatar, 85
  264 + assert_file_jpeg_quality attachment, :large, 75
  265 + assert_file_jpeg_quality attachment, nil, 75
  266 + end
250 else 267 else
251 def test_flunk 268 def test_flunk
252 puts "RMagick not installed, no tests running" 269 puts "RMagick not installed, no tests running"
vendor/plugins/attachment_fu/test/schema.rb
@@ -2,6 +2,8 @@ ActiveRecord::Schema.define(:version =&gt; 0) do @@ -2,6 +2,8 @@ ActiveRecord::Schema.define(:version =&gt; 0) do
2 create_table :attachments, :force => true do |t| 2 create_table :attachments, :force => true do |t|
3 t.column :db_file_id, :integer 3 t.column :db_file_id, :integer
4 t.column :parent_id, :integer 4 t.column :parent_id, :integer
  5 + t.column :imageable_id, :integer
  6 + t.column :imageable_type, :string, :limit => 255
5 t.column :thumbnail, :string 7 t.column :thumbnail, :string
6 t.column :filename, :string, :limit => 255 8 t.column :filename, :string, :limit => 255
7 t.column :content_type, :string, :limit => 255 9 t.column :content_type, :string, :limit => 255
@@ -22,6 +24,19 @@ ActiveRecord::Schema.define(:version =&gt; 0) do @@ -22,6 +24,19 @@ ActiveRecord::Schema.define(:version =&gt; 0) do
22 t.column :type, :string 24 t.column :type, :string
23 t.column :aspect_ratio, :float 25 t.column :aspect_ratio, :float
24 end 26 end
  27 +
  28 + create_table :file_attachments_with_string_id, :id => false, :force => true do |t|
  29 + t.column :id, :string
  30 + t.column :parent_id, :string
  31 + t.column :thumbnail, :string
  32 + t.column :filename, :string, :limit => 255
  33 + t.column :content_type, :string, :limit => 255
  34 + t.column :size, :integer
  35 + t.column :width, :integer
  36 + t.column :height, :integer
  37 + t.column :type, :string
  38 + t.column :aspect_ratio, :float
  39 + end
25 40
26 create_table :gd2_attachments, :force => true do |t| 41 create_table :gd2_attachments, :force => true do |t|
27 t.column :parent_id, :integer 42 t.column :parent_id, :integer
@@ -105,4 +120,17 @@ ActiveRecord::Schema.define(:version =&gt; 0) do @@ -105,4 +120,17 @@ ActiveRecord::Schema.define(:version =&gt; 0) do
105 t.column :type, :string 120 t.column :type, :string
106 t.column :aspect_ratio, :float 121 t.column :aspect_ratio, :float
107 end 122 end
108 -end  
109 \ No newline at end of file 123 \ No newline at end of file
  124 +
  125 + create_table :cloud_files_attachments, :force => true do |t|
  126 + t.column :parent_id, :integer
  127 + t.column :thumbnail, :string
  128 + t.column :filename, :string, :limit => 255
  129 + t.column :content_type, :string, :limit => 255
  130 + t.column :size, :integer
  131 + t.column :width, :integer
  132 + t.column :height, :integer
  133 + t.column :type, :string
  134 + t.column :aspect_ratio, :float
  135 + end
  136 +
  137 +end
vendor/plugins/attachment_fu/test/test_helper.rb
1 -$:.unshift(File.dirname(__FILE__) + '/../lib') 1 +$LOAD_PATH.unshift(File.dirname(__FILE__))
  2 +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2 3
3 ENV['RAILS_ENV'] = 'test' 4 ENV['RAILS_ENV'] = 'test'
4 -ENV['Rails.root'] ||= File.dirname(__FILE__) + '/../../../..'  
5 5
  6 +require 'rails/all'
6 require 'test/unit' 7 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 8 +require 'pothoven-attachment_fu'
  9 +include ActionDispatch::TestProcess
  10 +
  11 +# Define the application and configuration
  12 +module RbConfig
  13 + class Application < ::Rails::Application
  14 + # configuration here if needed
  15 + config.active_support.deprecation = :stderr
28 end 16 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 17 end
33 18
34 -ActiveRecord::Base.establish_connection(config[db_adapter]) 19 +# Initialize the application
  20 +RbConfig::Application.initialize!
35 21
  22 +# Setup database
36 load(File.dirname(__FILE__) + "/schema.rb") 23 load(File.dirname(__FILE__) + "/schema.rb")
37 24
38 -Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures"  
39 -$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path) 25 +FIXTURE_PATH = File.dirname(__FILE__) + "/fixtures"
  26 +$LOAD_PATH.unshift(FIXTURE_PATH)
40 27
41 class Test::Unit::TestCase #:nodoc: 28 class Test::Unit::TestCase #:nodoc:
42 - include ActionController::TestProcess 29 + # include ActionDispatch::TestProcess
43 def create_fixtures(*table_names) 30 def create_fixtures(*table_names)
44 if block_given? 31 if block_given?
45 - Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield } 32 + Fixtures.create_fixtures(FIXTURE_PATH, table_names) { yield }
46 else 33 else
47 - Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) 34 + Fixtures.create_fixtures(FIXTURE_PATH, table_names)
48 end 35 end
49 end 36 end
50 37
@@ -53,16 +40,18 @@ class Test::Unit::TestCase #:nodoc: @@ -53,16 +40,18 @@ class Test::Unit::TestCase #:nodoc:
53 DbFile.transaction { [Attachment, FileAttachment, OrphanAttachment, MinimalAttachment, DbFile].each { |klass| klass.delete_all } } 40 DbFile.transaction { [Attachment, FileAttachment, OrphanAttachment, MinimalAttachment, DbFile].each { |klass| klass.delete_all } }
54 attachment_model self.class.attachment_model 41 attachment_model self.class.attachment_model
55 end 42 end
56 - 43 +
57 def teardown 44 def teardown
58 FileUtils.rm_rf File.join(File.dirname(__FILE__), 'files') 45 FileUtils.rm_rf File.join(File.dirname(__FILE__), 'files')
  46 + # Files generated by random_tempfile_filename
  47 + FileUtils.rm_rf Dir['[0-9]*.{png,jpg}']
59 end 48 end
60 49
61 - self.use_transactional_fixtures = true  
62 - self.use_instantiated_fixtures = false 50 + #self.use_transactional_fixtures = true
  51 + #self.use_instantiated_fixtures = false
63 52
64 def self.attachment_model(klass = nil) 53 def self.attachment_model(klass = nil)
65 - @attachment_model = klass if klass 54 + @attachment_model = klass if klass
66 @attachment_model 55 @attachment_model
67 end 56 end
68 57
@@ -81,7 +70,18 @@ class Test::Unit::TestCase #:nodoc: @@ -81,7 +70,18 @@ class Test::Unit::TestCase #:nodoc:
81 protected 70 protected
82 def upload_file(options = {}) 71 def upload_file(options = {})
83 use_temp_file options[:filename] do |file| 72 use_temp_file options[:filename] do |file|
84 - att = attachment_model.create :uploaded_data => fixture_file_upload(file, options[:content_type] || 'image/png') 73 +puts options
  74 + opts = { :uploaded_data => fixture_file_upload(file, options[:content_type] || 'image/png') }
  75 + opts.update(options.reject { |k, v| ![:imageable_type, :imageable_id].include?(k) })
  76 + att = attachment_model.create opts
  77 + att.reload unless att.new_record?
  78 + return att
  79 + end
  80 + end
  81 +
  82 + def upload_merb_file(options = {})
  83 + use_temp_file options[:filename] do |file|
  84 + att = attachment_model.create :uploaded_data => {"size" => file.size, "content_type" => options[:content_type] || 'image/png', "filename" => file, 'tempfile' => fixture_file_upload(file, options[:content_type] || 'image/png')}
85 att.reload unless att.new_record? 85 att.reload unless att.new_record?
86 return att 86 return att
87 end 87 end
@@ -89,11 +89,13 @@ class Test::Unit::TestCase #:nodoc: @@ -89,11 +89,13 @@ class Test::Unit::TestCase #:nodoc:
89 89
90 def use_temp_file(fixture_filename) 90 def use_temp_file(fixture_filename)
91 temp_path = File.join('/tmp', File.basename(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 92 + temp_dir = File.join(FIXTURE_PATH, 'tmp')
  93 + use_file = File.join(FIXTURE_PATH, temp_path)
  94 + FileUtils.mkdir_p temp_dir
  95 + FileUtils.cp File.join(FIXTURE_PATH, fixture_filename), use_file
  96 + yield use_file
95 ensure 97 ensure
96 - FileUtils.rm_rf File.join(fixture_path, 'tmp') 98 + FileUtils.rm_rf temp_dir
97 end 99 end
98 100
99 def assert_created(num = 1) 101 def assert_created(num = 1)
@@ -107,11 +109,36 @@ class Test::Unit::TestCase #:nodoc: @@ -107,11 +109,36 @@ class Test::Unit::TestCase #:nodoc:
107 end 109 end
108 end 110 end
109 end 111 end
110 - 112 +
  113 + def assert_valid(record)
  114 + assert record.valid?, record.errors.full_messages.join("\n")
  115 + end
  116 +
  117 + def assert_file_jpeg_quality(model, thumbnail, expected)
  118 + filename = if model.respond_to?(:full_filename)
  119 + model.full_filename(thumbnail)
  120 + else
  121 + thumb = thumbnail ? model.thumbnails.find(:first, :conditions => { :thumbnail => thumbnail.to_s }, :include => :db_file) : model
  122 + unless thumb && thumb.db_file && thumb.db_file.data && thumb.db_file.data.size > 0
  123 + STDERR.puts "Cannot find DB file data for thumbnail #{thumbnail.inspect} -> Aborting JPEG quality check."
  124 + return
  125 + end
  126 + result = Tempfile.new('dbfile_dump').path
  127 + File.open(result, 'wb') { |f| f.write(thumb.db_file.data) }
  128 + result
  129 + end
  130 + quality = %x(identify -format '%Q' "#{filename}" 2> /dev/null)
  131 + if $?.success?
  132 + assert_equal expected, quality.to_i, "Produced JPEG quality (thumbnail: #{thumbnail.inspect}) is incorrect."
  133 + else
  134 + STDERR.puts "ImageMagick's identify not found / not in PATH: can't quickly check produced image quality."
  135 + end
  136 + end
  137 +
111 def assert_not_created 138 def assert_not_created
112 assert_created(0) { yield } 139 assert_created(0) { yield }
113 end 140 end
114 - 141 +
115 def should_reject_by_size_with(klass) 142 def should_reject_by_size_with(klass)
116 attachment_model klass 143 attachment_model klass
117 assert_not_created do 144 assert_not_created do
@@ -121,22 +148,22 @@ class Test::Unit::TestCase #:nodoc: @@ -121,22 +148,22 @@ class Test::Unit::TestCase #:nodoc:
121 assert_nil attachment.db_file if attachment.respond_to?(:db_file) 148 assert_nil attachment.db_file if attachment.respond_to?(:db_file)
122 end 149 end
123 end 150 end
124 - 151 +
125 def assert_difference(object, method = nil, difference = 1) 152 def assert_difference(object, method = nil, difference = 1)
126 initial_value = object.send(method) 153 initial_value = object.send(method)
127 yield 154 yield
128 assert_equal initial_value + difference, object.send(method) 155 assert_equal initial_value + difference, object.send(method)
129 end 156 end
130 - 157 +
131 def assert_no_difference(object, method, &block) 158 def assert_no_difference(object, method, &block)
132 assert_difference object, method, 0, &block 159 assert_difference object, method, 0, &block
133 end 160 end
134 - 161 +
135 def attachment_model(klass = nil) 162 def attachment_model(klass = nil)
136 - @attachment_model = klass if klass 163 + @attachment_model = klass if klass
137 @attachment_model 164 @attachment_model
138 end 165 end
139 end 166 end
140 167
141 require File.join(File.dirname(__FILE__), 'fixtures/attachment') 168 require File.join(File.dirname(__FILE__), 'fixtures/attachment')
142 -require File.join(File.dirname(__FILE__), 'base_attachment_tests')  
143 \ No newline at end of file 169 \ No newline at end of file
  170 +require File.join(File.dirname(__FILE__), 'base_attachment_tests')
vendor/plugins/attachment_fu/test/validation_test.rb
@@ -4,52 +4,52 @@ class ValidationTest &lt; Test::Unit::TestCase @@ -4,52 +4,52 @@ class ValidationTest &lt; Test::Unit::TestCase
4 def test_should_invalidate_big_files 4 def test_should_invalidate_big_files
5 @attachment = SmallAttachment.new 5 @attachment = SmallAttachment.new
6 assert !@attachment.valid? 6 assert !@attachment.valid?
7 - assert @attachment.errors.on(:size) 7 + assert @attachment.errors[:size]
8 8
9 @attachment.size = 2000 9 @attachment.size = 2000
10 assert !@attachment.valid? 10 assert !@attachment.valid?
11 - assert @attachment.errors.on(:size), @attachment.errors.full_messages.to_sentence 11 + assert @attachment.errors[:size], @attachment.errors.full_messages.to_sentence
12 12
13 @attachment.size = 1000 13 @attachment.size = 1000
14 assert !@attachment.valid? 14 assert !@attachment.valid?
15 - assert_nil @attachment.errors.on(:size) 15 + assert @attachment.errors[:size].empty?
16 end 16 end
17 17
18 def test_should_invalidate_small_files 18 def test_should_invalidate_small_files
19 @attachment = BigAttachment.new 19 @attachment = BigAttachment.new
20 assert !@attachment.valid? 20 assert !@attachment.valid?
21 - assert @attachment.errors.on(:size) 21 + assert @attachment.errors[:size]
22 22
23 @attachment.size = 2000 23 @attachment.size = 2000
24 assert !@attachment.valid? 24 assert !@attachment.valid?
25 - assert @attachment.errors.on(:size), @attachment.errors.full_messages.to_sentence 25 + assert @attachment.errors[:size], @attachment.errors.full_messages.to_sentence
26 26
27 @attachment.size = 1.megabyte 27 @attachment.size = 1.megabyte
28 assert !@attachment.valid? 28 assert !@attachment.valid?
29 - assert_nil @attachment.errors.on(:size) 29 + assert @attachment.errors[:size].empty?
30 end 30 end
31 31
32 def test_should_validate_content_type 32 def test_should_validate_content_type
33 @attachment = PdfAttachment.new 33 @attachment = PdfAttachment.new
34 assert !@attachment.valid? 34 assert !@attachment.valid?
35 - assert @attachment.errors.on(:content_type) 35 + assert @attachment.errors[:content_type]
36 36
37 @attachment.content_type = 'foo' 37 @attachment.content_type = 'foo'
38 assert !@attachment.valid? 38 assert !@attachment.valid?
39 - assert @attachment.errors.on(:content_type) 39 + assert @attachment.errors[:content_type]
40 40
41 @attachment.content_type = 'pdf' 41 @attachment.content_type = 'pdf'
42 assert !@attachment.valid? 42 assert !@attachment.valid?
43 - assert_nil @attachment.errors.on(:content_type) 43 + assert @attachment.errors[:content_type].empty?
44 end 44 end
45 45
46 def test_should_require_filename 46 def test_should_require_filename
47 @attachment = Attachment.new 47 @attachment = Attachment.new
48 assert !@attachment.valid? 48 assert !@attachment.valid?
49 - assert @attachment.errors.on(:filename) 49 + assert @attachment.errors[:filename]
50 50
51 @attachment.filename = 'foo' 51 @attachment.filename = 'foo'
52 assert !@attachment.valid? 52 assert !@attachment.valid?
53 - assert_nil @attachment.errors.on(:filename) 53 + assert @attachment.errors[:filename].empty?
54 end 54 end
55 end 55 end
56 \ No newline at end of file 56 \ No newline at end of file