Permalink
Browse files

A little reorg, plus added S3 support.

git-svn-id: https://svn.thoughtbot.com/plugins/paperclip/trunk@222 7bbfaf0e-4d1d-0410-9690-a8bb5f8ef2aa
  • Loading branch information...
jyurek
jyurek committed Sep 25, 2007
1 parent 16b60a5 commit 66b47339c6bd51ea307ff740b21548fe4838fe0a
Showing with 681 additions and 491 deletions.
  1. +4 −491 lib/paperclip.rb
  2. +359 −0 lib/paperclip/paperclip.rb
  3. +100 −0 lib/paperclip/storage.rb
  4. +80 −0 lib/paperclip/storage/filesystem.rb
  5. +100 −0 lib/paperclip/storage/s3.rb
  6. +24 −0 test/models.rb
  7. +14 −0 test/paperclip_s3_test.rb
View

Large diffs are not rendered by default.

Oops, something went wrong.
View

Large diffs are not rendered by default.

Oops, something went wrong.
View
@@ -0,0 +1,100 @@
+module Thoughtbot
+ module Paperclip
+ class Storage
+ def interpolate attachment, source, style
+ style ||= attachment[:default_style]
+ file_name = attachment[:instance]["#{attachment[:name]}_file_name"]
+ returning source.dup do |s|
+ s.gsub!(/:rails_root/, RAILS_ROOT)
+ s.gsub!(/:id/, attachment[:instance].id.to_s) if attachment[:instance].id
+ s.gsub!(/:class/, attachment[:instance].class.to_s.underscore.pluralize)
+ s.gsub!(/:style/, style.to_s )
+ s.gsub!(/:attachment/, attachment[:name].to_s.pluralize)
+ if file_name
+ file_bits = file_name.split(".")
+ s.gsub!(/:name/, file_name)
+ s.gsub!(/:base/, [file_bits[0], *file_bits[1..-2]].join("."))
+ s.gsub!(/:ext/, file_bits.last )
+ end
+ end
+ end
+
+ def make_thumbnails attachment
+ attachment[:files] ||= {}
+ attachment[:files][:original] ||= File.new( path_for(attachment, :original) )
+ attachment[:thumbnails].each do |style, geometry|
+ begin
+ attachment[:files][style] = make_thumbnail(attachment, attachment[:files][:original], geometry)
+ rescue PaperclipError => e
+ attachment[:errors] << "thumbnail '#{style}' could not be created."
+ end
+ end
+ end
+
+ def make_thumbnail attachment, orig_io, geometry
+ operator = geometry[-1,1]
+ begin
+ geometry, crop_geometry = geometry_for_crop(geometry, orig_io) if operator == '#'
+ command = "#{path_for_command "convert"} - -scale '#{geometry}' #{operator == '#' ? "-crop '#{crop_geometry}'" : ""} - 2>/dev/null"
+ thumb = IO.popen(command, "w+") do |io|
+ orig_io.rewind
+ io.write(orig_io.read)
+ io.close_write
+ StringIO.new(io.read)
+ end
+ rescue Errno::EPIPE => e
+ raise PaperclipError.new(attachment), "Could not create thumbnail. Is ImageMagick or GraphicsMagick installed and available?"
+ rescue SystemCallError => e
+ raise PaperclipError.new(attachment), "Could not create thumbnail."
+ end
+ if ::Thoughtbot::Paperclip.options[:whiny_thumbnails] && !$?.success?
+ raise PaperclipError.new(attachment), "Convert returned with result code #{$?.exitstatus}: #{thumb.read}"
+ end
+ thumb
+ end
+
+ def geometry_for_crop geometry, orig_io
+ IO.popen("#{path_for_command "identify"} - 2>/dev/null", "w+") do |io|
+ orig_io.rewind
+ io.write(orig_io.read)
+ io.close_write
+ if match = io.read.split[2].match(/(\d+)x(\d+)/)
+ src = match[1,2].map(&:to_f)
+ srch = src[0] > src[1]
+ dst = geometry.match(/(\d+)x(\d+)/)[1,2].map(&:to_f)
+ dsth = dst[0] > dst[1]
+ ar = src[0] / src[1]
+
+ scale_geometry, scale = if dst[0] == dst[1]
+ if srch
+ [ "x#{dst[1]}", src[1] / dst[1] ]
+ else
+ [ "#{dst[0]}x", src[0] / dst[0] ]
+ end
+ elsif dsth
+ [ "#{dst[0]}x", src[0] / dst[0] ]
+ else
+ [ "x#{dst[1]}", src[1] / dst[1] ]
+ end
+
+ crop_geometry = if dsth
+ "%dx%d+%d+%d" % [ dst[0], dst[1], 0, (src[1] / scale - dst[1]) / 2 ]
+ else
+ "%dx%d+%d+%d" % [ dst[0], dst[1], (src[0] / scale - dst[0]) / 2, 0 ]
+ end
+
+ [ scale_geometry, crop_geometry ]
+ end
+ end
+ end
+
+ def path_for_command command
+ File.join([::Thoughtbot::Paperclip.options[:image_magick_path], command].compact)
+ end
+
+ def to_s
+ self.class.name
+ end
+ end
+ end
+end
@@ -0,0 +1,80 @@
+module Thoughtbot
+ module Paperclip
+ module ClassMethods
+
+ def has_attached_file_with_fs *attachment_names
+ has_attached_file_without_fs *attachment_names
+ end
+ alias_method_chain :has_attached_file, :fs
+
+ end
+
+ class Storage
+ class Filesystem < Storage
+ def path_for attachment, style = nil
+ style ||= attachment[:default_style]
+ file = attachment[:instance]["#{attachment[:name]}_file_name"]
+ return nil unless file && attachment[:instance].id
+
+ prefix = interpolate attachment, "#{attachment[:path_prefix]}/#{attachment[:path]}", style
+ File.join( prefix.split("/") )
+ end
+
+ def url_for attachment, style = nil
+ style ||= attachment[:default_style]
+ file = attachment[:instance]["#{attachment[:name]}_file_name"]
+ return nil unless file && attachment[:instance].id
+
+ interpolate attachment, "#{attachment[:url_prefix]}/#{attachment[:path]}", style
+ end
+
+ def ensure_directories_for attachment
+ attachment[:files].each do |style, file|
+ dirname = File.dirname(path_for(attachment, style))
+ FileUtils.mkdir_p dirname
+ end
+ end
+
+ def write_attachment attachment
+ return if attachment[:files].blank?
+ ensure_directories_for attachment
+ attachment[:files].each do |style, atch|
+ atch.rewind
+ data = atch.read
+ File.open( path_for(attachment, style), "w" ) do |file|
+ file.rewind
+ file.write(data)
+ end
+ end
+ attachment[:files] = nil
+ attachment[:dirty] = false
+ end
+
+ def delete_attachment attachment, complain = false
+ (attachment[:thumbnails].keys + [:original]).each do |style|
+ file_path = path_for(attachment, style)
+ begin
+ FileUtils.rm file_path if file_path
+ rescue SystemCallError => e
+ raise PaperclipError.new(attachment), "Could not delete thumbnail." if ::Thoughtbot::Paperclip.options[:whiny_deletes] || complain
+ end
+ end
+ end
+
+ def attachment_valid? attachment
+ attachment[:thumbnails].merge(:original => nil).all? do |style, geometry|
+ if attachment[:instance]["#{attachment[:name]}_file_name"]
+ if attachment[:dirty]
+ !attachment[:files][style].blank? && attachment[:errors].empty?
+ else
+ File.file?( path_for(attachment, style) )
+ end
+ else
+ false
+ end
+ end
+ end
+ end
+ end
+ end
+end
View
@@ -0,0 +1,100 @@
+module Thoughtbot
+ module Paperclip
+ module ClassMethods
+ def has_attached_file_with_s3 *attachment_names
+ attachments, options = has_attached_file_without_s3 *attachment_names
+
+ access_key = secret_key = ""
+ if file_name = s3_credentials_file
+ creds = YAML.load_file(file_name)
+ creds = creds[RAILS_ENV] || creds if Object.const_defined?("RAILS_ENV")
+ access_key = creds['access_key_id']
+ secret_key = creds['secret_access_key']
+ else
+ access_key = Thoughtbot::Paperclip.options[:s3_access_key_id]
+ secret_key = Thoughtbot::Paperclip.options[:s3_secret_access_key]
+ end
+
+ if options[:storage].to_s.downcase == "s3"
+ require 'aws/s3'
+ AWS::S3::Base.establish_connection!(
+ :access_key_id => access_key,
+ :secret_access_key => secret_key,
+ :persistent => Thoughtbot::Paperclip.options[:s3_persistent] || true
+ )
+ end
+ end
+ alias_method_chain :has_attached_file, :s3
+
+ private
+ def s3_credentials_file
+ [ Thoughtbot::Paperclip.options[:s3_credentials_file], File.join(RAILS_ROOT, "config", "s3.yml") ].compact.each do |f|
+ return f if File.exists?(f)
+ end
+ nil
+ end
+ end
+
+ class Storage
+ class S3 < Storage
+ def path_for attachment, style = nil
+ style ||= attachment[:default_style]
+ file = attachment[:instance]["#{attachment[:name]}_file_name"]
+ return nil unless file && attachment[:instance].id
+
+ interpolate attachment, attachment[:path], style
+ end
+
+ def url_for attachment, style = nil
+ "http://s3.amazonaws.com/#{bucket_for(attachment)}/#{path_for(attachment, style)}"
+ end
+
+ def bucket_for attachment
+ bucket_name = interpolate attachment, attachment[:url_prefix], nil
+ end
+
+ def ensure_bucket_for attachment, style = nil
+ begin
+ bucket_name = bucket_for attachment
+ AWS::S3::Bucket.create(bucket_name)
+ bucket_name
+ rescue AWS::S3::S3Exception => e
+ raise Thoughtbot::Paperclip::PaperclipError.new(attachment), "You are not allowed access to the bucket '#{bucket_name}'."
+ end
+ end
+
+ def write_attachment attachment
+ return if attachment[:files].blank?
+ bucket = ensure_bucket_for attachment
+ attachment[:files].each do |style, atch|
+ atch.rewind
+ AWS::S3::S3Object.store( path_for(attachment, style), atch, bucket, :access => attachment[:access] || :public_read )
+ end
+ attachment[:files] = nil
+ attachment[:dirty] = false
+ end
+
+ def delete_attachment attachment, complain = false
+ (attachment[:thumbnails].keys + [:original]).each do |style|
+ file_path = path_for(attachment, style)
+ AWS::S3::S3Object.delete( file_path, bucket_for(attachment) )
+ end
+ end
+
+ def attachment_valid? attachment
+ attachment[:thumbnails].merge(:original => nil).all? do |style, geometry|
+ if attachment[:instance]["#{attachment[:name]}_file_name"]
+ if attachment[:dirty]
+ !attachment[:files][style].blank? && attachment[:errors].empty?
+ else
+ AWS::S3::S3Object.exists?( path_for(attachment, style), bucket_for(attachment) )
+ end
+ else
+ false
+ end
+ end
+ end
+ end
+ end
+ end
+end
View
@@ -13,6 +13,12 @@
table.column :avatar_file_name, :string
table.column :avatar_content_type, :string
end
+ ActiveRecord::Base.connection.create_table :ess_threes, :force => true do |table|
+ table.column :resume_file_name, :string
+ table.column :resume_content_type, :string
+ table.column :avatar_file_name, :string
+ table.column :avatar_content_type, :string
+ end
ActiveRecord::Base.connection.create_table :negatives, :force => true do |table|
table.column :this_is_the_wrong_name_file_name, :string
end
@@ -47,5 +53,23 @@ class NonStandard < ActiveRecord::Base
:missing_url => "/:class/:style/:attachment/404.png"
end
+# class EssThree < ActiveRecord::Base
+# has_attached_file :resume, :attachment_type => :document,
+# :path_prefix => "paperclip/test",
+# :path => ":attachment_:id_:name",
+# :missing_url => "/:class/:style/:attachment/404.txt",
+# :storage => :S3
+# has_attached_file :avatar, :attachment_type => :image,
+# :thumbnails => { :cropped => "200x10#",
+# :bigger => "1000x1000",
+# :smaller => "200x200>",
+# :square => "150x150#" },
+# :path_prefix => "paperclip/test/images",
+# :path => ":class/:attachment/:id/:style_:name",
+# :default_style => :square,
+# :missing_url => "/:class/:style/:attachment/404.png",
+# :storage => :S3
+# end
+
class Negative < ActiveRecord::Base
end
View
@@ -0,0 +1,14 @@
+require 'test/unit'
+require File.dirname(__FILE__) + "/test_helper.rb"
+require File.dirname(__FILE__) + "/../init.rb"
+require File.join(File.dirname(__FILE__), "models.rb")
+
+class PaperclipS3Test < Test::Unit::TestCase
+ def setup
+ end
+
+ def test_truth
+ assert true
+ end
+
+end

0 comments on commit 66b4733

Please sign in to comment.