updated top the the new version of attachment_fu plugin to work out some
[selectricity] / vendor / plugins / attachment_fu / lib / technoweenie / attachment_fu / backends / cloud_file_backend.rb
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/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/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 + '/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 + '/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

Benjamin Mako Hill || Want to submit a patch?