/vendor/plugins/community_engine/engine_plugins/attachment_fu/lib/technoweenie/attachment_fu/backends/s3_backend.rb

https://github.com/huwy/givechange_dev · Ruby · 310 lines · 119 code · 31 blank · 160 comment · 10 complexity · 5d96888c56c1aa5388a77fbf82a216dd MD5 · raw file

  1. module Technoweenie # :nodoc:
  2. module AttachmentFu # :nodoc:
  3. module Backends
  4. # = AWS::S3 Storage Backend
  5. #
  6. # Enables use of {Amazon's Simple Storage Service}[http://aws.amazon.com/s3] as a storage mechanism
  7. #
  8. # == Requirements
  9. #
  10. # Requires the {AWS::S3 Library}[http://amazon.rubyforge.org] for S3 by Marcel Molina Jr. installed either
  11. # as a gem or a as a Rails plugin.
  12. #
  13. # == Configuration
  14. #
  15. # Configuration is done via <tt>RAILS_ROOT/config/amazon_s3.yml</tt> and is loaded according to the <tt>RAILS_ENV</tt>.
  16. # The minimum connection options that you must specify are a bucket name, your access key id and your secret access key.
  17. # If you don't already have your access keys, all you need to sign up for the S3 service is an account at Amazon.
  18. # You can sign up for S3 and get access keys by visiting http://aws.amazon.com/s3.
  19. #
  20. # Example configuration (RAILS_ROOT/config/amazon_s3.yml)
  21. #
  22. # development:
  23. # bucket_name: appname_development
  24. # access_key_id: <your key>
  25. # secret_access_key: <your key>
  26. #
  27. # test:
  28. # bucket_name: appname_test
  29. # access_key_id: <your key>
  30. # secret_access_key: <your key>
  31. #
  32. # production:
  33. # bucket_name: appname
  34. # access_key_id: <your key>
  35. # secret_access_key: <your key>
  36. #
  37. # You can change the location of the config path by passing a full path to the :s3_config_path option.
  38. #
  39. # has_attachment :storage => :s3, :s3_config_path => (RAILS_ROOT + '/config/s3.yml')
  40. #
  41. # === Required configuration parameters
  42. #
  43. # * <tt>:access_key_id</tt> - The access key id for your S3 account. Provided by Amazon.
  44. # * <tt>:secret_access_key</tt> - The secret access key for your S3 account. Provided by Amazon.
  45. # * <tt>:bucket_name</tt> - A unique bucket name (think of the bucket_name as being like a database name).
  46. #
  47. # If any of these required arguments is missing, a MissingAccessKey exception will be raised from AWS::S3.
  48. #
  49. # == About bucket names
  50. #
  51. # Bucket names have to be globaly unique across the S3 system. And you can only have up to 100 of them,
  52. # so it's a good idea to think of a bucket as being like a database, hence the correspondance in this
  53. # implementation to the development, test, and production environments.
  54. #
  55. # The number of objects you can store in a bucket is, for all intents and purposes, unlimited.
  56. #
  57. # === Optional configuration parameters
  58. #
  59. # * <tt>:server</tt> - The server to make requests to. Defaults to <tt>s3.amazonaws.com</tt>.
  60. # * <tt>:port</tt> - The port to the requests should be made on. Defaults to 80 or 443 if <tt>:use_ssl</tt> is set.
  61. # * <tt>:use_ssl</tt> - If set to true, <tt>:port</tt> will be implicitly set to 443, unless specified otherwise. Defaults to false.
  62. #
  63. # == Usage
  64. #
  65. # To specify S3 as the storage mechanism for a model, set the acts_as_attachment <tt>:storage</tt> option to <tt>:s3</tt>.
  66. #
  67. # class Photo < ActiveRecord::Base
  68. # has_attachment :storage => :s3
  69. # end
  70. #
  71. # === Customizing the path
  72. #
  73. # By default, files are prefixed using a pseudo hierarchy in the form of <tt>:table_name/:id</tt>, which results
  74. # in S3 urls that look like: http(s)://:server/:bucket_name/:table_name/:id/:filename with :table_name
  75. # representing the customizable portion of the path. You can customize this prefix using the <tt>:path_prefix</tt>
  76. # option:
  77. #
  78. # class Photo < ActiveRecord::Base
  79. # has_attachment :storage => :s3, :path_prefix => 'my/custom/path'
  80. # end
  81. #
  82. # Which would result in URLs like <tt>http(s)://:server/:bucket_name/my/custom/path/:id/:filename.</tt>
  83. #
  84. # === Permissions
  85. #
  86. # By default, files are stored on S3 with public access permissions. You can customize this using
  87. # the <tt>:s3_access</tt> option to <tt>has_attachment</tt>. Available values are
  88. # <tt>:private</tt>, <tt>:public_read_write</tt>, and <tt>:authenticated_read</tt>.
  89. #
  90. # === Other options
  91. #
  92. # Of course, all the usual configuration options apply, such as content_type and thumbnails:
  93. #
  94. # class Photo < ActiveRecord::Base
  95. # has_attachment :storage => :s3, :content_type => ['application/pdf', :image], :resize_to => 'x50'
  96. # has_attachment :storage => :s3, :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
  97. # end
  98. #
  99. # === Accessing S3 URLs
  100. #
  101. # You can get an object's URL using the s3_url accessor. For example, assuming that for your postcard app
  102. # you had a bucket name like 'postcard_world_development', and an attachment model called Photo:
  103. #
  104. # @postcard.s3_url # => http(s)://s3.amazonaws.com/postcard_world_development/photos/1/mexico.jpg
  105. #
  106. # The resulting url is in the form: http(s)://:server/:bucket_name/:table_name/:id/:file.
  107. # The optional thumbnail argument will output the thumbnail's filename (if any).
  108. #
  109. # Additionally, you can get an object's base path relative to the bucket root using
  110. # <tt>base_path</tt>:
  111. #
  112. # @photo.file_base_path # => photos/1
  113. #
  114. # And the full path (including the filename) using <tt>full_filename</tt>:
  115. #
  116. # @photo.full_filename # => photos/
  117. #
  118. # Niether <tt>base_path</tt> or <tt>full_filename</tt> include the bucket name as part of the path.
  119. # You can retrieve the bucket name using the <tt>bucket_name</tt> method.
  120. module S3Backend
  121. class RequiredLibraryNotFoundError < StandardError; end
  122. class ConfigFileNotFoundError < StandardError; end
  123. def self.included(base) #:nodoc:
  124. mattr_reader :bucket_name, :s3_config
  125. begin
  126. require 'aws/s3'
  127. include AWS::S3
  128. rescue LoadError
  129. raise RequiredLibraryNotFoundError.new('AWS::S3 could not be loaded')
  130. end
  131. begin
  132. @@s3_config_path = base.attachment_options[:s3_config_path] || (RAILS_ROOT + '/config/amazon_s3.yml')
  133. @@s3_config = YAML.load_file(@@s3_config_path)[ENV['RAILS_ENV'] || RAILS_ENV ].symbolize_keys
  134. #rescue
  135. # raise ConfigFileNotFoundError.new('File %s not found' % @@s3_config_path)
  136. end
  137. @@bucket_name = s3_config[:bucket_name]
  138. Base.establish_connection!(
  139. :access_key_id => s3_config[:access_key_id],
  140. :secret_access_key => s3_config[:secret_access_key],
  141. :server => s3_config[:server],
  142. :port => s3_config[:port],
  143. :use_ssl => s3_config[:use_ssl],
  144. :persistent => s3_config[:use_persistent]
  145. )
  146. # Bucket.create(@@bucket_name)
  147. base.before_update :rename_file
  148. end
  149. def self.protocol
  150. @protocol ||= s3_config[:use_ssl] ? 'https://' : 'http://'
  151. end
  152. def self.hostname
  153. @hostname ||= s3_config[:server] || AWS::S3::DEFAULT_HOST
  154. end
  155. def self.port_string
  156. @port_string ||= s3_config[:port] == (s3_config[:use_ssl] ? 443 : 80) ? '' : ":#{s3_config[:port]}"
  157. end
  158. module ClassMethods
  159. def s3_protocol
  160. Technoweenie::AttachmentFu::Backends::S3Backend.protocol
  161. end
  162. def s3_hostname
  163. Technoweenie::AttachmentFu::Backends::S3Backend.hostname
  164. end
  165. def s3_port_string
  166. Technoweenie::AttachmentFu::Backends::S3Backend.port_string
  167. end
  168. end
  169. # Overwrites the base filename writer in order to store the old filename
  170. def filename=(value)
  171. @old_filename = filename unless filename.nil? || @old_filename
  172. write_attribute :filename, sanitize_filename(value)
  173. end
  174. # The attachment ID used in the full path of a file
  175. def attachment_path_id
  176. ((respond_to?(:parent_id) && parent_id) || id).to_s
  177. end
  178. # The pseudo hierarchy containing the file relative to the bucket name
  179. # Example: <tt>:table_name/:id</tt>
  180. def base_path
  181. File.join(attachment_options[:path_prefix], attachment_path_id)
  182. end
  183. # The full path to the file relative to the bucket name
  184. # Example: <tt>:table_name/:id/:filename</tt>
  185. def full_filename(thumbnail = nil)
  186. File.join(base_path, thumbnail_name_for(thumbnail))
  187. end
  188. # All public objects are accessible via a GET request to the S3 servers. You can generate a
  189. # url for an object using the s3_url method.
  190. #
  191. # @photo.s3_url
  192. #
  193. # The resulting url is in the form: <tt>http(s)://:server/:bucket_name/:table_name/:id/:file</tt> where
  194. # the <tt>:server</tt> variable defaults to <tt>AWS::S3 URL::DEFAULT_HOST</tt> (s3.amazonaws.com) and can be
  195. # set using the configuration parameters in <tt>RAILS_ROOT/config/amazon_s3.yml</tt>.
  196. #
  197. # The optional thumbnail argument will output the thumbnail's filename (if any).
  198. def s3_url(thumbnail = nil)
  199. File.join(s3_protocol + s3_hostname + s3_port_string, bucket_name, full_filename(thumbnail))
  200. end
  201. alias :public_filename :s3_url
  202. # All private objects are accessible via an authenticated GET request to the S3 servers. You can generate an
  203. # authenticated url for an object like this:
  204. #
  205. # @photo.authenticated_s3_url
  206. #
  207. # By default authenticated urls expire 5 minutes after they were generated.
  208. #
  209. # Expiration options can be specified either with an absolute time using the <tt>:expires</tt> option,
  210. # or with a number of seconds relative to now with the <tt>:expires_in</tt> option:
  211. #
  212. # # Absolute expiration date (October 13th, 2025)
  213. # @photo.authenticated_s3_url(:expires => Time.mktime(2025,10,13).to_i)
  214. #
  215. # # Expiration in five hours from now
  216. # @photo.authenticated_s3_url(:expires_in => 5.hours)
  217. #
  218. # You can specify whether the url should go over SSL with the <tt>:use_ssl</tt> option.
  219. # By default, the ssl settings for the current connection will be used:
  220. #
  221. # @photo.authenticated_s3_url(:use_ssl => true)
  222. #
  223. # Finally, the optional thumbnail argument will output the thumbnail's filename (if any):
  224. #
  225. # @photo.authenticated_s3_url('thumbnail', :expires_in => 5.hours, :use_ssl => true)
  226. def authenticated_s3_url(*args)
  227. thumbnail = args.first.is_a?(String) ? args.first : nil
  228. options = args.last.is_a?(Hash) ? args.last : {}
  229. S3Object.url_for(full_filename(thumbnail), bucket_name, options)
  230. end
  231. def create_temp_file
  232. write_to_temp_file current_data
  233. end
  234. def current_data
  235. S3Object.value full_filename, bucket_name
  236. end
  237. def s3_protocol
  238. Technoweenie::AttachmentFu::Backends::S3Backend.protocol
  239. end
  240. def s3_hostname
  241. Technoweenie::AttachmentFu::Backends::S3Backend.hostname
  242. end
  243. def s3_port_string
  244. Technoweenie::AttachmentFu::Backends::S3Backend.port_string
  245. end
  246. protected
  247. # Called in the after_destroy callback
  248. def destroy_file
  249. S3Object.delete full_filename, bucket_name
  250. end
  251. def rename_file
  252. return unless @old_filename && @old_filename != filename
  253. old_full_filename = File.join(base_path, @old_filename)
  254. S3Object.rename(
  255. old_full_filename,
  256. full_filename,
  257. bucket_name,
  258. :access => attachment_options[:s3_access]
  259. )
  260. @old_filename = nil
  261. true
  262. end
  263. def save_to_storage
  264. if save_attachment?
  265. S3Object.store(
  266. full_filename,
  267. temp_data,
  268. bucket_name,
  269. :content_type => content_type,
  270. :access => attachment_options[:s3_access]
  271. )
  272. end
  273. @old_filename = nil
  274. true
  275. end
  276. end
  277. end
  278. end
  279. end