Permalink
Browse files

Merge branch 'rewrite-s3' into rewrite

  • Loading branch information...
2 parents 022ec71 + 1b4a9b2 commit 52370cf40d8b6fe9287acde72190812f2ef26cd0 @technoweenie committed Dec 14, 2008
Showing with 436 additions and 6 deletions.
  1. +3 −0 .gitignore
  2. +13 −0 README
  3. +16 −1 lib/attachment_fu.rb
  4. +4 −4 lib/attachment_fu/geometry.rb
  5. +217 −0 lib/attachment_fu/tasks/s3.rb
  6. +28 −1 spec/attachment_fu_spec.rb
  7. +22 −0 spec/spec_helper.rb
  8. +133 −0 spec/tasks/s3_spec.rb
View
@@ -1,2 +1,5 @@
test/amazon_s3.yml
test/debug.log
+.DS_Store
+spec/s3.yml
+spec/config.rb
View
13 README
@@ -0,0 +1,13 @@
+attachment_fu
+=============
+
+Specs
+=====
+
+When running specs for the first time, a spec/config.rb file will be created for you. This file is ignored by git
+and can be used to set any personal settings (such as S3 connection information).
+
+S3 Specs
+========
+
+Modify the AttachmentFu::Tasks::S3.connect call in your spec/config.rb file.
View
@@ -112,6 +112,7 @@ def attachment_tasks(&block)
class_inheritable_accessor :attachment_path
before_create :set_new_attachment
after_save :save_attachment
+ after_update :rename_attachment
after_destroy :delete_attachment
end
end
@@ -135,7 +136,7 @@ def self.setup(klass)
# Sets default tasks
def self.reset
Tasks
- [:resize, :thumbnails].each do |task|
+ [:resize, :thumbnails, :s3].each do |task|
create_task task, "attachment_fu/tasks/#{task}"
end
create_task :get_image_size, "attachment_fu/tasks/resize"
@@ -261,6 +262,10 @@ def processed?
!self.class.attachment_tasks.any? { |s| process_task?(s) }
end
+ def renamed_filename
+ (new_record? || !filename_changed? || filename.nil? || filename_was.nil?) ? nil : filename_was
+ end
+
protected
def thumbnailed_filename(thumbnail)
if thumbnail
@@ -332,6 +337,16 @@ def delete_attachment
end
end
+ def rename_attachment
+ if old_name = renamed_filename
+ old_path = File.join(File.dirname(full_path), old_name)
+ return unless File.exist?(old_path)
+ FileUtils.rm(full_path) if File.exist?(full_path)
+ FileUtils.mv(old_path, full_path)
+ File.chmod(0644, full_path)
+ end
+ end
+
# Saves the attachment to the file system. It also processes
# or queues the attachment for processing.
def save_attachment
@@ -56,10 +56,10 @@ def new_dimensions_for(orig_width, orig_height)
case @flag
when :percent
- scale_x = @width.zero? ? 100 : @width
- scale_y = @height.zero? ? @width : @height
- new_width = scale_x.to_f * (orig_width.to_f / 100.0)
- new_height = scale_y.to_f * (orig_height.to_f / 100.0)
+ scale_x = @width.zero? ? 100 : @width
+ scale_y = @height.zero? ? @width : @height
+ new_width = scale_x.to_f * (orig_width.to_f / 100.0)
+ new_height = scale_y.to_f * (orig_height.to_f / 100.0)
when :<, :>, nil
scale_factor =
if new_width.zero? || new_height.zero?
@@ -0,0 +1,217 @@
+require 'aws/s3'
+module AttachmentFu
+ class Tasks
+ class S3
+ include AWS::S3
+
+ attr_reader :options
+
+ class << self
+ attr_reader :connection_options
+
+ # :access_key_id => REQUIRED
+ # :secret_access_key => REQUIRED
+ # :server => defaults to Amazon
+ # :use_ssl => Use SSL, defaults to false
+ # :port => Set this for custom ports. 80 or 443 is implied, depending on :use_ssl.
+ # :persistent => Whether the S3 lib should use persistent connections or not.
+ # :proxy => http proxy for accessing S3
+ def connect(options)
+ o = options.slice(:access_key_id, :secret_access_key, :server, :port, :use_ssl, :persistent, :proxy)
+ AWS::S3::Base.establish_connection!(o.dup) # establish_connection! modifies the hash
+ @connection_options = o
+ end
+
+ def connected?
+ !@connection_options.nil?
+ end
+ end
+
+ # Some valid options:
+ #
+ # # model-specific options
+ # :bucket_name => REQUIRED
+ # :access => defaults to :authenticated_read. Other valid choices include: :public_read (common), :public_read_write, and :private.
+ #
+ # # global connection options
+ # :access_key_id => REQUIRED
+ # :secret_access_key => REQUIRED
+ # :server => defaults to Amazon
+ # :use_ssl => Use SSL, defaults to false
+ # :port => Set this for custom ports. 80 or 443 is implied, depending on :use_ssl.
+ # :persistent => Whether the S3 lib should use persistent connections or not.
+ # :proxy => http proxy for accessing S3
+ #
+ # AWS::S3 only supports one connection (why would you want to connect to multiple S3 hosts anyway?). You can
+ # also send only the connection options to AttachmentFu::Tasks::S3.connect(...).
+ #
+ def initialize(klass, options)
+ klass.class_eval do
+ def s3
+ @s3 ||= S3TaskProxy.new(self)
+ end
+ end
+
+ @options = options
+ @options.update(self.class.connection_options) if self.class.connected?
+ @options[:access] ||= :authenticated_read
+ self.class.connect(@options) unless self.class.connected?
+ end
+
+ def call(attachment, options = {})
+ options = @options.merge(options)
+ attachment.s3.store
+ end
+
+ def exist?(attachment, thumbnail = nil, options = @options)
+ S3Object.exists?(attachment.s3.path(thumbnail), options[:bucket_name])
+ end
+
+ def store(attachment, options = @options)
+ S3Object.store \
+ attachment.s3.path,
+ File.open(attachment.full_path),
+ options[:bucket_name],
+ :content_type => attachment.content_type,
+ :access => options[:access]
+ end
+
+ def rename(attachment, old_path, options = @options)
+ S3Object.rename old_path, attachment.s3.path, options[:bucket_name]
+ end
+
+ def delete(attachment, options = @options)
+ S3Object.delete attachment.s3.path, options[:bucket_name]
+ rescue AWS::S3::NoSuchKey
+ end
+
+ def object_for(attachment, thumbnail = nil, options = @options)
+ S3Object.find(attachment.s3.path(thumbnail), options[:bucket_name])
+ end
+
+ def stream_for(attachment, thumbnail = nil, options = @options, &block)
+ S3Object.stream(attachment.s3.path(thumbnail), options[:bucket_name], &block)
+ end
+
+ # For authenticated URLs, pass one of these options:
+ #
+ # :expires_in => Defaults to 5 minutes.
+ # :auth or :authenticated => if true, create an authenticated URL.
+ #
+ def url_for(attachment, thumbnail = nil, options = nil)
+ options = options ? @options.merge(options) : @options
+ if options.key?(:expires_in) || options.key?(:auth) || options.key?(:authenticated)
+ S3Object.url_for(attachment.s3.path(thumbnail), options[:bucket_name], options.slice(:expires_in, :use_ssl))
+ else
+ File.join(protocol(options) + hostname(options) + port_string(options), bucket_name(options), attachment.s3.path(thumbnail))
+ end
+ end
+
+ def protocol(options = @options)
+ @protocol ||= options[:use_ssl] ? 'https://' : 'http://'
+ end
+
+ def hostname(options = @options)
+ @hostname ||= options[:server] || AWS::S3::DEFAULT_HOST
+ end
+
+ def port_string(options = @options)
+ @port_string ||= (options[:port].nil? || options[:port] == (options[:use_ssl] ? 443 : 80)) ? '' : ":#{options[:port]}"
+ end
+
+ def bucket_name(options = @options)
+ options[:bucket_name]
+ end
+
+ class S3TaskProxy
+ def initialize(asset)
+ @asset = asset
+ end
+
+ # For authenticated URLs, pass one of these options:
+ #
+ # :expires_in => Defaults to 5 minutes.
+ # :auth or :authenticated => if true, create an authenticated URL.
+ #
+ def url(thumbnail = nil, options = {})
+ if thumbnail.is_a?(Hash)
+ options = thumbnail
+ thumbnail = nil
+ end
+ task.url_for(@asset, thumbnail, options)
+ end
+
+ # Retrieve the S3 metadata for the stored object.
+ #
+ # @attachment = Attachment.find 1
+ # open(@attachment.filename, 'wb') do |f|
+ # f.write @attachment.s3_object.value
+ # end
+ #
+ def object(thumbnail = nil)
+ (@object ||= {})[thumbnail || :default] ||= task.object_for(@asset, thumbnail)
+ end
+
+ def object_exists?(thumbnail = nil)
+ task.exist?(@asset, thumbnail)
+ end
+
+ # Stream the S3 object data.
+ #
+ # @attachment = Attachment.find 1
+ # open(@attachment.filename, 'wb') do |f|
+ # @attachment.s3_stream(thumbnail) do |chunk|
+ # f.write chunk
+ # end
+ # end
+ #
+ def stream(thumbnail = nil, &block)
+ task.stream_for(@asset, thumbnail, &block)
+ end
+
+ def store
+ task.store(@asset)
+ @asset.send(:delete_attachment)
+ end
+
+ def rename(old_name = @asset.renamed_filename)
+ return if old_name.nil?
+ task.rename(@asset, File.join(File.dirname(path), old_name))
+ @object.clear if @object
+ end
+
+ def delete
+ task.delete(@asset)
+ @object.clear if @object
+ end
+
+ # The path used for working with S3. By default it is the public path without the leading /.
+ def path(thumbnail = nil)
+ @asset.public_path(thumbnail)[1..-1]
+ end
+
+ def connected?
+ !task.nil?
+ end
+
+ def options
+ task.options
+ end
+
+ def task
+ @asset.class.attachment_tasks[:s3]
+ rescue ArgumentError
+ end
+
+ def inspect
+ "<#{@asset.class} S3: #{path.inspect}, #{options.inspect}>"
+ end
+ end
+ end
+ end
+end
+
+# AttachmentFu::Tasks::S3.connect(:access_key_id => '...', :secret_key => '...', ...)
+# task :s3, :bucket_name => 'snarf', :access => :authenticated_read
+#
+AttachmentFu.create_task :s3, AttachmentFu::Tasks::S3
View
@@ -111,7 +111,34 @@ class QueuedAsset < ActiveRecord::Base
@asset.temp_path.should be_nil
end
end
-
+
+ describe "being renamed" do
+ before :all do
+ @file = File.join(File.dirname(__FILE__), 'guinea_pig.rb')
+ FileUtils.cp __FILE__, @file
+
+ @asset = BasicAsset.new(:content_type => 'application/x-ruby')
+ @asset.set_temp_path @file
+ @asset.save!
+ @old_path = @asset.full_path
+ @asset.filename = "hedgehog.rb"
+ @asset.save!
+ end
+
+ after :all do
+ @asset.destroy
+ end
+
+ it "removes traces of old filename" do
+ File.exist?(@old_path).should == false
+ end
+
+ it "moves file contents to new filename" do
+ File.exist?(@asset.full_path).should == true
+ IO.read(@asset.full_path).should == IO.read(__FILE__)
+ end
+ end
+
describe "being deleted" do
before do
@file = File.join(File.dirname(__FILE__), 'guinea_pig.rb')
View
@@ -1,5 +1,6 @@
require 'rubygems'
+RAILS_ENV = 'test'
dir = File.dirname(__FILE__)
rails_app = "#{dir}/../../../../config/environment.rb"
vendor_rspec = "#{dir}/../../rspec/lib"
@@ -68,4 +69,25 @@ def is_faux_attachment(*args, &block)
AttachmentFu.root_path = File.expand_path(File.join(File.dirname(__FILE__), 'assets'))
+config_rb = File.join(File.dirname(__FILE__), 'config.rb')
+if !File.exist?(config_rb)
+ open config_rb, 'w' do |f|
+ f.write <<-END_CONFIG
+# Replace this with your custom settings.
+# This file is ignored in .gitignore
+
+# I'd recommend using ParkPlace for local S3 testing.
+# http://code.whytheluckystiff.net/parkplace
+#
+#AttachmentFu::Tasks::S3.connect(
+# :server => "localhost",
+# :port => 3002,
+# :access_key_id => "ACCESS",
+# :secret_access_key => "SECRET")
+ END_CONFIG
+ end
+end
+
+require config_rb
+
Debugger.start
Oops, something went wrong.

0 comments on commit 52370cf

Please sign in to comment.