diff options
| author | hukl <contact@smyck.org> | 2009-04-24 11:43:08 +0200 |
|---|---|---|
| committer | hukl <contact@smyck.org> | 2009-04-25 14:55:27 +0200 |
| commit | cf1b60e0cfa7d1a8f4a80d686649cc12e73a634e (patch) | |
| tree | b68bd845d290ce968892c4532bcff52083925834 /vendor/plugins/paperclip/lib | |
| parent | b2b78c06074046bd73cc3408a29386a976f0469c (diff) | |
Integrated basic Asset upload functionality. You can upload files now and use their url in pages.
Diffstat (limited to 'vendor/plugins/paperclip/lib')
14 files changed, 1579 insertions, 0 deletions
diff --git a/vendor/plugins/paperclip/lib/paperclip.rb b/vendor/plugins/paperclip/lib/paperclip.rb new file mode 100644 index 0000000..74c3d79 --- /dev/null +++ b/vendor/plugins/paperclip/lib/paperclip.rb | |||
| @@ -0,0 +1,318 @@ | |||
| 1 | # Paperclip allows file attachments that are stored in the filesystem. All graphical | ||
| 2 | # transformations are done using the Graphics/ImageMagick command line utilities and | ||
| 3 | # are stored in Tempfiles until the record is saved. Paperclip does not require a | ||
| 4 | # separate model for storing the attachment's information, instead adding a few simple | ||
| 5 | # columns to your table. | ||
| 6 | # | ||
| 7 | # Author:: Jon Yurek | ||
| 8 | # Copyright:: Copyright (c) 2008 thoughtbot, inc. | ||
| 9 | # License:: MIT License (http://www.opensource.org/licenses/mit-license.php) | ||
| 10 | # | ||
| 11 | # Paperclip defines an attachment as any file, though it makes special considerations | ||
| 12 | # for image files. You can declare that a model has an attached file with the | ||
| 13 | # +has_attached_file+ method: | ||
| 14 | # | ||
| 15 | # class User < ActiveRecord::Base | ||
| 16 | # has_attached_file :avatar, :styles => { :thumb => "100x100" } | ||
| 17 | # end | ||
| 18 | # | ||
| 19 | # user = User.new | ||
| 20 | # user.avatar = params[:user][:avatar] | ||
| 21 | # user.avatar.url | ||
| 22 | # # => "/users/avatars/4/original_me.jpg" | ||
| 23 | # user.avatar.url(:thumb) | ||
| 24 | # # => "/users/avatars/4/thumb_me.jpg" | ||
| 25 | # | ||
| 26 | # See the +has_attached_file+ documentation for more details. | ||
| 27 | |||
| 28 | require 'tempfile' | ||
| 29 | require 'paperclip/upfile' | ||
| 30 | require 'paperclip/iostream' | ||
| 31 | require 'paperclip/geometry' | ||
| 32 | require 'paperclip/processor' | ||
| 33 | require 'paperclip/thumbnail' | ||
| 34 | require 'paperclip/storage' | ||
| 35 | require 'paperclip/attachment' | ||
| 36 | if defined? RAILS_ROOT | ||
| 37 | Dir.glob(File.join(File.expand_path(RAILS_ROOT), "lib", "paperclip_processors", "*.rb")).each do |processor| | ||
| 38 | require processor | ||
| 39 | end | ||
| 40 | end | ||
| 41 | |||
| 42 | # The base module that gets included in ActiveRecord::Base. See the | ||
| 43 | # documentation for Paperclip::ClassMethods for more useful information. | ||
| 44 | module Paperclip | ||
| 45 | |||
| 46 | VERSION = "2.2.8" | ||
| 47 | |||
| 48 | class << self | ||
| 49 | # Provides configurability to Paperclip. There are a number of options available, such as: | ||
| 50 | # * whiny_thumbnails: Will raise an error if Paperclip cannot process thumbnails of | ||
| 51 | # an uploaded image. Defaults to true. | ||
| 52 | # * log: Logs progress to the Rails log. Uses ActiveRecord's logger, so honors | ||
| 53 | # log levels, etc. Defaults to true. | ||
| 54 | # * command_path: Defines the path at which to find the command line | ||
| 55 | # programs if they are not visible to Rails the system's search path. Defaults to | ||
| 56 | # nil, which uses the first executable found in the user's search path. | ||
| 57 | # * image_magick_path: Deprecated alias of command_path. | ||
| 58 | def options | ||
| 59 | @options ||= { | ||
| 60 | :whiny_thumbnails => true, | ||
| 61 | :image_magick_path => nil, | ||
| 62 | :command_path => nil, | ||
| 63 | :log => true, | ||
| 64 | :swallow_stderr => true | ||
| 65 | } | ||
| 66 | end | ||
| 67 | |||
| 68 | def path_for_command command #:nodoc: | ||
| 69 | if options[:image_magick_path] | ||
| 70 | warn("[DEPRECATION] :image_magick_path is deprecated and will be removed. Use :command_path instead") | ||
| 71 | end | ||
| 72 | path = [options[:command_path] || options[:image_magick_path], command].compact | ||
| 73 | File.join(*path) | ||
| 74 | end | ||
| 75 | |||
| 76 | def interpolates key, &block | ||
| 77 | Paperclip::Attachment.interpolations[key] = block | ||
| 78 | end | ||
| 79 | |||
| 80 | # The run method takes a command to execute and a string of parameters | ||
| 81 | # that get passed to it. The command is prefixed with the :command_path | ||
| 82 | # option from Paperclip.options. If you have many commands to run and | ||
| 83 | # they are in different paths, the suggested course of action is to | ||
| 84 | # symlink them so they are all in the same directory. | ||
| 85 | # | ||
| 86 | # If the command returns with a result code that is not one of the | ||
| 87 | # expected_outcodes, a PaperclipCommandLineError will be raised. Generally | ||
| 88 | # a code of 0 is expected, but a list of codes may be passed if necessary. | ||
| 89 | def run cmd, params = "", expected_outcodes = 0 | ||
| 90 | command = %Q<#{%Q[#{path_for_command(cmd)} #{params}].gsub(/\s+/, " ")}> | ||
| 91 | command = "#{command} 2>#{bit_bucket}" if Paperclip.options[:swallow_stderr] | ||
| 92 | output = `#{command}` | ||
| 93 | unless [expected_outcodes].flatten.include?($?.exitstatus) | ||
| 94 | raise PaperclipCommandLineError, "Error while running #{cmd}" | ||
| 95 | end | ||
| 96 | output | ||
| 97 | end | ||
| 98 | |||
| 99 | def bit_bucket #:nodoc: | ||
| 100 | File.exists?("/dev/null") ? "/dev/null" : "NUL" | ||
| 101 | end | ||
| 102 | |||
| 103 | def included base #:nodoc: | ||
| 104 | base.extend ClassMethods | ||
| 105 | unless base.respond_to?(:define_callbacks) | ||
| 106 | base.send(:include, Paperclip::CallbackCompatability) | ||
| 107 | end | ||
| 108 | end | ||
| 109 | |||
| 110 | def processor name #:nodoc: | ||
| 111 | name = name.to_s.camelize | ||
| 112 | processor = Paperclip.const_get(name) | ||
| 113 | unless processor.ancestors.include?(Paperclip::Processor) | ||
| 114 | raise PaperclipError.new("Processor #{name} was not found") | ||
| 115 | end | ||
| 116 | processor | ||
| 117 | end | ||
| 118 | end | ||
| 119 | |||
| 120 | class PaperclipError < StandardError #:nodoc: | ||
| 121 | end | ||
| 122 | |||
| 123 | class PaperclipCommandLineError < StandardError #:nodoc: | ||
| 124 | end | ||
| 125 | |||
| 126 | class NotIdentifiedByImageMagickError < PaperclipError #:nodoc: | ||
| 127 | end | ||
| 128 | |||
| 129 | module ClassMethods | ||
| 130 | # +has_attached_file+ gives the class it is called on an attribute that maps to a file. This | ||
| 131 | # is typically a file stored somewhere on the filesystem and has been uploaded by a user. | ||
| 132 | # The attribute returns a Paperclip::Attachment object which handles the management of | ||
| 133 | # that file. The intent is to make the attachment as much like a normal attribute. The | ||
| 134 | # thumbnails will be created when the new file is assigned, but they will *not* be saved | ||
| 135 | # until +save+ is called on the record. Likewise, if the attribute is set to +nil+ is | ||
| 136 | # called on it, the attachment will *not* be deleted until +save+ is called. See the | ||
| 137 | # Paperclip::Attachment documentation for more specifics. There are a number of options | ||
| 138 | # you can set to change the behavior of a Paperclip attachment: | ||
| 139 | # * +url+: The full URL of where the attachment is publically accessible. This can just | ||
| 140 | # as easily point to a directory served directly through Apache as it can to an action | ||
| 141 | # that can control permissions. You can specify the full domain and path, but usually | ||
| 142 | # just an absolute path is sufficient. The leading slash *must* be included manually for | ||
| 143 | # absolute paths. The default value is | ||
| 144 | # "/system/:attachment/:id/:style/:basename.:extension". See | ||
| 145 | # Paperclip::Attachment#interpolate for more information on variable interpolaton. | ||
| 146 | # :url => "/:class/:attachment/:id/:style_:basename.:extension" | ||
| 147 | # :url => "http://some.other.host/stuff/:class/:id_:extension" | ||
| 148 | # * +default_url+: The URL that will be returned if there is no attachment assigned. | ||
| 149 | # This field is interpolated just as the url is. The default value is | ||
| 150 | # "/:attachment/:style/missing.png" | ||
| 151 | # has_attached_file :avatar, :default_url => "/images/default_:style_avatar.png" | ||
| 152 | # User.new.avatar_url(:small) # => "/images/default_small_avatar.png" | ||
| 153 | # * +styles+: A hash of thumbnail styles and their geometries. You can find more about | ||
| 154 | # geometry strings at the ImageMagick website | ||
| 155 | # (http://www.imagemagick.org/script/command-line-options.php#resize). Paperclip | ||
| 156 | # also adds the "#" option (e.g. "50x50#"), which will resize the image to fit maximally | ||
| 157 | # inside the dimensions and then crop the rest off (weighted at the center). The | ||
| 158 | # default value is to generate no thumbnails. | ||
| 159 | # * +default_style+: The thumbnail style that will be used by default URLs. | ||
| 160 | # Defaults to +original+. | ||
| 161 | # has_attached_file :avatar, :styles => { :normal => "100x100#" }, | ||
| 162 | # :default_style => :normal | ||
| 163 | # user.avatar.url # => "/avatars/23/normal_me.png" | ||
| 164 | # * +whiny_thumbnails+: Will raise an error if Paperclip cannot post_process an uploaded file due | ||
| 165 | # to a command line error. This will override the global setting for this attachment. | ||
| 166 | # Defaults to true. | ||
| 167 | # * +convert_options+: When creating thumbnails, use this free-form options | ||
| 168 | # field to pass in various convert command options. Typical options are "-strip" to | ||
| 169 | # remove all Exif data from the image (save space for thumbnails and avatars) or | ||
| 170 | # "-depth 8" to specify the bit depth of the resulting conversion. See ImageMagick | ||
| 171 | # convert documentation for more options: (http://www.imagemagick.org/script/convert.php) | ||
| 172 | # Note that this option takes a hash of options, each of which correspond to the style | ||
| 173 | # of thumbnail being generated. You can also specify :all as a key, which will apply | ||
| 174 | # to all of the thumbnails being generated. If you specify options for the :original, | ||
| 175 | # it would be best if you did not specify destructive options, as the intent of keeping | ||
| 176 | # the original around is to regenerate all the thumbnails when requirements change. | ||
| 177 | # has_attached_file :avatar, :styles => { :large => "300x300", :negative => "100x100" } | ||
| 178 | # :convert_options => { | ||
| 179 | # :all => "-strip", | ||
| 180 | # :negative => "-negate" | ||
| 181 | # } | ||
| 182 | # * +storage+: Chooses the storage backend where the files will be stored. The current | ||
| 183 | # choices are :filesystem and :s3. The default is :filesystem. Make sure you read the | ||
| 184 | # documentation for Paperclip::Storage::Filesystem and Paperclip::Storage::S3 | ||
| 185 | # for backend-specific options. | ||
| 186 | def has_attached_file name, options = {} | ||
| 187 | include InstanceMethods | ||
| 188 | |||
| 189 | write_inheritable_attribute(:attachment_definitions, {}) if attachment_definitions.nil? | ||
| 190 | attachment_definitions[name] = {:validations => {}}.merge(options) | ||
| 191 | |||
| 192 | after_save :save_attached_files | ||
| 193 | before_destroy :destroy_attached_files | ||
| 194 | |||
| 195 | define_callbacks :before_post_process, :after_post_process | ||
| 196 | define_callbacks :"before_#{name}_post_process", :"after_#{name}_post_process" | ||
| 197 | |||
| 198 | define_method name do |*args| | ||
| 199 | a = attachment_for(name) | ||
| 200 | (args.length > 0) ? a.to_s(args.first) : a | ||
| 201 | end | ||
| 202 | |||
| 203 | define_method "#{name}=" do |file| | ||
| 204 | attachment_for(name).assign(file) | ||
| 205 | end | ||
| 206 | |||
| 207 | define_method "#{name}?" do | ||
| 208 | attachment_for(name).file? | ||
| 209 | end | ||
| 210 | |||
| 211 | validates_each(name) do |record, attr, value| | ||
| 212 | attachment = record.attachment_for(name) | ||
| 213 | attachment.send(:flush_errors) unless attachment.valid? | ||
| 214 | end | ||
| 215 | end | ||
| 216 | |||
| 217 | # Places ActiveRecord-style validations on the size of the file assigned. The | ||
| 218 | # possible options are: | ||
| 219 | # * +in+: a Range of bytes (i.e. +1..1.megabyte+), | ||
| 220 | # * +less_than+: equivalent to :in => 0..options[:less_than] | ||
| 221 | # * +greater_than+: equivalent to :in => options[:greater_than]..Infinity | ||
| 222 | # * +message+: error message to display, use :min and :max as replacements | ||
| 223 | def validates_attachment_size name, options = {} | ||
| 224 | min = options[:greater_than] || (options[:in] && options[:in].first) || 0 | ||
| 225 | max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0) | ||
| 226 | range = (min..max) | ||
| 227 | message = options[:message] || "file size must be between :min and :max bytes." | ||
| 228 | |||
| 229 | attachment_definitions[name][:validations][:size] = lambda do |attachment, instance| | ||
| 230 | if attachment.file? && !range.include?(attachment.size.to_i) | ||
| 231 | message.gsub(/:min/, min.to_s).gsub(/:max/, max.to_s) | ||
| 232 | end | ||
| 233 | end | ||
| 234 | end | ||
| 235 | |||
| 236 | # Adds errors if thumbnail creation fails. The same as specifying :whiny_thumbnails => true. | ||
| 237 | def validates_attachment_thumbnails name, options = {} | ||
| 238 | attachment_definitions[name][:whiny_thumbnails] = true | ||
| 239 | end | ||
| 240 | |||
| 241 | # Places ActiveRecord-style validations on the presence of a file. | ||
| 242 | def validates_attachment_presence name, options = {} | ||
| 243 | message = options[:message] || "must be set." | ||
| 244 | attachment_definitions[name][:validations][:presence] = lambda do |attachment, instance| | ||
| 245 | message unless attachment.file? | ||
| 246 | end | ||
| 247 | end | ||
| 248 | |||
| 249 | # Places ActiveRecord-style validations on the content type of the file | ||
| 250 | # assigned. The possible options are: | ||
| 251 | # * +content_type+: Allowed content types. Can be a single content type | ||
| 252 | # or an array. Each type can be a String or a Regexp. It should be | ||
| 253 | # noted that Internet Explorer upload files with content_types that you | ||
| 254 | # may not expect. For example, JPEG images are given image/pjpeg and | ||
| 255 | # PNGs are image/x-png, so keep that in mind when determining how you | ||
| 256 | # match. Allows all by default. | ||
| 257 | # * +message+: The message to display when the uploaded file has an invalid | ||
| 258 | # content type. | ||
| 259 | # NOTE: If you do not specify an [attachment]_content_type field on your | ||
| 260 | # model, content_type validation will work _ONLY upon assignment_ and | ||
| 261 | # re-validation after the instance has been reloaded will always succeed. | ||
| 262 | def validates_attachment_content_type name, options = {} | ||
| 263 | attachment_definitions[name][:validations][:content_type] = lambda do |attachment, instance| | ||
| 264 | valid_types = [options[:content_type]].flatten | ||
| 265 | |||
| 266 | unless attachment.original_filename.blank? | ||
| 267 | unless valid_types.blank? | ||
| 268 | content_type = attachment.instance_read(:content_type) | ||
| 269 | unless valid_types.any?{|t| content_type.nil? || t === content_type } | ||
| 270 | options[:message] || "is not one of the allowed file types." | ||
| 271 | end | ||
| 272 | end | ||
| 273 | end | ||
| 274 | end | ||
| 275 | end | ||
| 276 | |||
| 277 | # Returns the attachment definitions defined by each call to | ||
| 278 | # has_attached_file. | ||
| 279 | def attachment_definitions | ||
| 280 | read_inheritable_attribute(:attachment_definitions) | ||
| 281 | end | ||
| 282 | end | ||
| 283 | |||
| 284 | module InstanceMethods #:nodoc: | ||
| 285 | def attachment_for name | ||
| 286 | @_paperclip_attachments ||= {} | ||
| 287 | @_paperclip_attachments[name] ||= Attachment.new(name, self, self.class.attachment_definitions[name]) | ||
| 288 | end | ||
| 289 | |||
| 290 | def each_attachment | ||
| 291 | self.class.attachment_definitions.each do |name, definition| | ||
| 292 | yield(name, attachment_for(name)) | ||
| 293 | end | ||
| 294 | end | ||
| 295 | |||
| 296 | def save_attached_files | ||
| 297 | logger.info("[paperclip] Saving attachments.") | ||
| 298 | each_attachment do |name, attachment| | ||
| 299 | attachment.send(:save) | ||
| 300 | end | ||
| 301 | end | ||
| 302 | |||
| 303 | def destroy_attached_files | ||
| 304 | logger.info("[paperclip] Deleting attachments.") | ||
| 305 | each_attachment do |name, attachment| | ||
| 306 | attachment.send(:queue_existing_for_delete) | ||
| 307 | attachment.send(:flush_deletes) | ||
| 308 | end | ||
| 309 | end | ||
| 310 | end | ||
| 311 | |||
| 312 | end | ||
| 313 | |||
| 314 | # Set it all up. | ||
| 315 | if Object.const_defined?("ActiveRecord") | ||
| 316 | ActiveRecord::Base.send(:include, Paperclip) | ||
| 317 | File.send(:include, Paperclip::Upfile) | ||
| 318 | end | ||
diff --git a/vendor/plugins/paperclip/lib/paperclip/attachment.rb b/vendor/plugins/paperclip/lib/paperclip/attachment.rb new file mode 100644 index 0000000..897c67e --- /dev/null +++ b/vendor/plugins/paperclip/lib/paperclip/attachment.rb | |||
| @@ -0,0 +1,403 @@ | |||
| 1 | module Paperclip | ||
| 2 | # The Attachment class manages the files for a given attachment. It saves | ||
| 3 | # when the model saves, deletes when the model is destroyed, and processes | ||
| 4 | # the file upon assignment. | ||
| 5 | class Attachment | ||
| 6 | |||
| 7 | def self.default_options | ||
| 8 | @default_options ||= { | ||
| 9 | :url => "/system/:attachment/:id/:style/:basename.:extension", | ||
| 10 | :path => ":rails_root/public/system/:attachment/:id/:style/:basename.:extension", | ||
| 11 | :styles => {}, | ||
| 12 | :default_url => "/:attachment/:style/missing.png", | ||
| 13 | :default_style => :original, | ||
| 14 | :validations => {}, | ||
| 15 | :storage => :filesystem | ||
| 16 | } | ||
| 17 | end | ||
| 18 | |||
| 19 | attr_reader :name, :instance, :styles, :default_style, :convert_options, :queued_for_write | ||
| 20 | |||
| 21 | # Creates an Attachment object. +name+ is the name of the attachment, | ||
| 22 | # +instance+ is the ActiveRecord object instance it's attached to, and | ||
| 23 | # +options+ is the same as the hash passed to +has_attached_file+. | ||
| 24 | def initialize name, instance, options = {} | ||
| 25 | @name = name | ||
| 26 | @instance = instance | ||
| 27 | |||
| 28 | options = self.class.default_options.merge(options) | ||
| 29 | |||
| 30 | @url = options[:url] | ||
| 31 | @url = @url.call(self) if @url.is_a?(Proc) | ||
| 32 | @path = options[:path] | ||
| 33 | @path = @path.call(self) if @path.is_a?(Proc) | ||
| 34 | @styles = options[:styles] | ||
| 35 | @styles = @styles.call(self) if @styles.is_a?(Proc) | ||
| 36 | @default_url = options[:default_url] | ||
| 37 | @validations = options[:validations] | ||
| 38 | @default_style = options[:default_style] | ||
| 39 | @storage = options[:storage] | ||
| 40 | @whiny = options[:whiny_thumbnails] | ||
| 41 | @convert_options = options[:convert_options] || {} | ||
| 42 | @processors = options[:processors] || [:thumbnail] | ||
| 43 | @options = options | ||
| 44 | @queued_for_delete = [] | ||
| 45 | @queued_for_write = {} | ||
| 46 | @errors = {} | ||
| 47 | @validation_errors = nil | ||
| 48 | @dirty = false | ||
| 49 | |||
| 50 | normalize_style_definition | ||
| 51 | initialize_storage | ||
| 52 | end | ||
| 53 | |||
| 54 | # What gets called when you call instance.attachment = File. It clears | ||
| 55 | # errors, assigns attributes, processes the file, and runs validations. It | ||
| 56 | # also queues up the previous file for deletion, to be flushed away on | ||
| 57 | # #save of its host. In addition to form uploads, you can also assign | ||
| 58 | # another Paperclip attachment: | ||
| 59 | # new_user.avatar = old_user.avatar | ||
| 60 | # If the file that is assigned is not valid, the processing (i.e. | ||
| 61 | # thumbnailing, etc) will NOT be run. | ||
| 62 | def assign uploaded_file | ||
| 63 | %w(file_name).each do |field| | ||
| 64 | unless @instance.class.column_names.include?("#{name}_#{field}") | ||
| 65 | raise PaperclipError.new("#{@instance.class} model does not have required column '#{name}_#{field}'") | ||
| 66 | end | ||
| 67 | end | ||
| 68 | |||
| 69 | if uploaded_file.is_a?(Paperclip::Attachment) | ||
| 70 | uploaded_file = uploaded_file.to_file(:original) | ||
| 71 | close_uploaded_file = uploaded_file.respond_to?(:close) | ||
| 72 | end | ||
| 73 | |||
| 74 | return nil unless valid_assignment?(uploaded_file) | ||
| 75 | |||
| 76 | uploaded_file.binmode if uploaded_file.respond_to? :binmode | ||
| 77 | self.clear | ||
| 78 | |||
| 79 | return nil if uploaded_file.nil? | ||
| 80 | |||
| 81 | @queued_for_write[:original] = uploaded_file.to_tempfile | ||
| 82 | instance_write(:file_name, uploaded_file.original_filename.strip.gsub(/[^\w\d\.\-]+/, '_')) | ||
| 83 | instance_write(:content_type, uploaded_file.content_type.to_s.strip) | ||
| 84 | instance_write(:file_size, uploaded_file.size.to_i) | ||
| 85 | instance_write(:updated_at, Time.now) | ||
| 86 | |||
| 87 | @dirty = true | ||
| 88 | |||
| 89 | post_process if valid? | ||
| 90 | |||
| 91 | # Reset the file size if the original file was reprocessed. | ||
| 92 | instance_write(:file_size, @queued_for_write[:original].size.to_i) | ||
| 93 | ensure | ||
| 94 | uploaded_file.close if close_uploaded_file | ||
| 95 | validate | ||
| 96 | end | ||
| 97 | |||
| 98 | # Returns the public URL of the attachment, with a given style. Note that | ||
| 99 | # this does not necessarily need to point to a file that your web server | ||
| 100 | # can access and can point to an action in your app, if you need fine | ||
| 101 | # grained security. This is not recommended if you don't need the | ||
| 102 | # security, however, for performance reasons. set | ||
| 103 | # include_updated_timestamp to false if you want to stop the attachment | ||
| 104 | # update time appended to the url | ||
| 105 | def url style = default_style, include_updated_timestamp = true | ||
| 106 | url = original_filename.nil? ? interpolate(@default_url, style) : interpolate(@url, style) | ||
| 107 | include_updated_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url | ||
| 108 | end | ||
| 109 | |||
| 110 | # Returns the path of the attachment as defined by the :path option. If the | ||
| 111 | # file is stored in the filesystem the path refers to the path of the file | ||
| 112 | # on disk. If the file is stored in S3, the path is the "key" part of the | ||
| 113 | # URL, and the :bucket option refers to the S3 bucket. | ||
| 114 | def path style = nil #:nodoc: | ||
| 115 | original_filename.nil? ? nil : interpolate(@path, style) | ||
| 116 | end | ||
| 117 | |||
| 118 | # Alias to +url+ | ||
| 119 | def to_s style = nil | ||
| 120 | url(style) | ||
| 121 | end | ||
| 122 | |||
| 123 | # Returns true if there are no errors on this attachment. | ||
| 124 | def valid? | ||
| 125 | validate | ||
| 126 | errors.empty? | ||
| 127 | end | ||
| 128 | |||
| 129 | # Returns an array containing the errors on this attachment. | ||
| 130 | def errors | ||
| 131 | @errors | ||
| 132 | end | ||
| 133 | |||
| 134 | # Returns true if there are changes that need to be saved. | ||
| 135 | def dirty? | ||
| 136 | @dirty | ||
| 137 | end | ||
| 138 | |||
| 139 | # Saves the file, if there are no errors. If there are, it flushes them to | ||
| 140 | # the instance's errors and returns false, cancelling the save. | ||
| 141 | def save | ||
| 142 | if valid? | ||
| 143 | flush_deletes | ||
| 144 | flush_writes | ||
| 145 | @dirty = false | ||
| 146 | true | ||
| 147 | else | ||
| 148 | flush_errors | ||
| 149 | false | ||
| 150 | end | ||
| 151 | end | ||
| 152 | |||
| 153 | # Clears out the attachment. Has the same effect as previously assigning | ||
| 154 | # nil to the attachment. Does NOT save. If you wish to clear AND save, | ||
| 155 | # use #destroy. | ||
| 156 | def clear | ||
| 157 | queue_existing_for_delete | ||
| 158 | @errors = {} | ||
| 159 | @validation_errors = nil | ||
| 160 | end | ||
| 161 | |||
| 162 | # Destroys the attachment. Has the same effect as previously assigning | ||
| 163 | # nil to the attachment *and saving*. This is permanent. If you wish to | ||
| 164 | # wipe out the existing attachment but not save, use #clear. | ||
| 165 | def destroy | ||
| 166 | clear | ||
| 167 | save | ||
| 168 | end | ||
| 169 | |||
| 170 | # Returns the name of the file as originally assigned, and lives in the | ||
| 171 | # <attachment>_file_name attribute of the model. | ||
| 172 | def original_filename | ||
| 173 | instance_read(:file_name) | ||
| 174 | end | ||
| 175 | |||
| 176 | # Returns the size of the file as originally assigned, and lives in the | ||
| 177 | # <attachment>_file_size attribute of the model. | ||
| 178 | def size | ||
| 179 | instance_read(:file_size) || (@queued_for_write[:original] && @queued_for_write[:original].size) | ||
| 180 | end | ||
| 181 | |||
| 182 | # Returns the content_type of the file as originally assigned, and lives | ||
| 183 | # in the <attachment>_content_type attribute of the model. | ||
| 184 | def content_type | ||
| 185 | instance_read(:content_type) | ||
| 186 | end | ||
| 187 | |||
| 188 | # Returns the last modified time of the file as originally assigned, and | ||
| 189 | # lives in the <attachment>_updated_at attribute of the model. | ||
| 190 | def updated_at | ||
| 191 | time = instance_read(:updated_at) | ||
| 192 | time && time.to_i | ||
| 193 | end | ||
| 194 | |||
| 195 | # A hash of procs that are run during the interpolation of a path or url. | ||
| 196 | # A variable of the format :name will be replaced with the return value of | ||
| 197 | # the proc named ":name". Each lambda takes the attachment and the current | ||
| 198 | # style as arguments. This hash can be added to with your own proc if | ||
| 199 | # necessary. | ||
| 200 | def self.interpolations | ||
| 201 | @interpolations ||= { | ||
| 202 | :rails_root => lambda{|attachment,style| RAILS_ROOT }, | ||
| 203 | :rails_env => lambda{|attachment,style| RAILS_ENV }, | ||
| 204 | :class => lambda do |attachment,style| | ||
| 205 | attachment.instance.class.name.underscore.pluralize | ||
| 206 | end, | ||
| 207 | :basename => lambda do |attachment,style| | ||
| 208 | attachment.original_filename.gsub(/#{File.extname(attachment.original_filename)}$/, "") | ||
| 209 | end, | ||
| 210 | :extension => lambda do |attachment,style| | ||
| 211 | ((style = attachment.styles[style]) && style[:format]) || | ||
| 212 | File.extname(attachment.original_filename).gsub(/^\.+/, "") | ||
| 213 | end, | ||
| 214 | :id => lambda{|attachment,style| attachment.instance.id }, | ||
| 215 | :id_partition => lambda do |attachment, style| | ||
| 216 | ("%09d" % attachment.instance.id).scan(/\d{3}/).join("/") | ||
| 217 | end, | ||
| 218 | :attachment => lambda{|attachment,style| attachment.name.to_s.downcase.pluralize }, | ||
| 219 | :style => lambda{|attachment,style| style || attachment.default_style }, | ||
| 220 | } | ||
| 221 | end | ||
| 222 | |||
| 223 | # This method really shouldn't be called that often. It's expected use is | ||
| 224 | # in the paperclip:refresh rake task and that's it. It will regenerate all | ||
| 225 | # thumbnails forcefully, by reobtaining the original file and going through | ||
| 226 | # the post-process again. | ||
| 227 | def reprocess! | ||
| 228 | new_original = Tempfile.new("paperclip-reprocess") | ||
| 229 | new_original.binmode | ||
| 230 | if old_original = to_file(:original) | ||
| 231 | new_original.write( old_original.read ) | ||
| 232 | new_original.rewind | ||
| 233 | |||
| 234 | @queued_for_write = { :original => new_original } | ||
| 235 | post_process | ||
| 236 | |||
| 237 | old_original.close if old_original.respond_to?(:close) | ||
| 238 | |||
| 239 | save | ||
| 240 | else | ||
| 241 | true | ||
| 242 | end | ||
| 243 | end | ||
| 244 | |||
| 245 | # Returns true if a file has been assigned. | ||
| 246 | def file? | ||
| 247 | !original_filename.blank? | ||
| 248 | end | ||
| 249 | |||
| 250 | # Writes the attachment-specific attribute on the instance. For example, | ||
| 251 | # instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's | ||
| 252 | # "avatar_file_name" field (assuming the attachment is called avatar). | ||
| 253 | def instance_write(attr, value) | ||
| 254 | setter = :"#{name}_#{attr}=" | ||
| 255 | responds = instance.respond_to?(setter) | ||
| 256 | self.instance_variable_set("@_#{setter.to_s.chop}", value) | ||
| 257 | instance.send(setter, value) if responds || attr.to_s == "file_name" | ||
| 258 | end | ||
| 259 | |||
| 260 | # Reads the attachment-specific attribute on the instance. See instance_write | ||
| 261 | # for more details. | ||
| 262 | def instance_read(attr) | ||
| 263 | getter = :"#{name}_#{attr}" | ||
| 264 | responds = instance.respond_to?(getter) | ||
| 265 | cached = self.instance_variable_get("@_#{getter}") | ||
| 266 | return cached if cached | ||
| 267 | instance.send(getter) if responds || attr.to_s == "file_name" | ||
| 268 | end | ||
| 269 | |||
| 270 | private | ||
| 271 | |||
| 272 | def logger #:nodoc: | ||
| 273 | instance.logger | ||
| 274 | end | ||
| 275 | |||
| 276 | def log message #:nodoc: | ||
| 277 | logger.info("[paperclip] #{message}") if logging? | ||
| 278 | end | ||
| 279 | |||
| 280 | def logging? #:nodoc: | ||
| 281 | Paperclip.options[:log] | ||
| 282 | end | ||
| 283 | |||
| 284 | def valid_assignment? file #:nodoc: | ||
| 285 | file.nil? || (file.respond_to?(:original_filename) && file.respond_to?(:content_type)) | ||
| 286 | end | ||
| 287 | |||
| 288 | def validate #:nodoc: | ||
| 289 | unless @validation_errors | ||
| 290 | @validation_errors = @validations.inject({}) do |errors, validation| | ||
| 291 | name, block = validation | ||
| 292 | errors[name] = block.call(self, instance) if block | ||
| 293 | errors | ||
| 294 | end | ||
| 295 | @validation_errors.reject!{|k,v| v == nil } | ||
| 296 | @errors.merge!(@validation_errors) | ||
| 297 | end | ||
| 298 | @validation_errors | ||
| 299 | end | ||
| 300 | |||
| 301 | def normalize_style_definition #:nodoc: | ||
| 302 | @styles.each do |name, args| | ||
| 303 | unless args.is_a? Hash | ||
| 304 | dimensions, format = *args | ||
| 305 | @styles[name] = { | ||
| 306 | :processors => @processors, | ||
| 307 | :geometry => dimensions, | ||
| 308 | :format => format, | ||
| 309 | :whiny => @whiny, | ||
| 310 | :convert_options => extra_options_for(name) | ||
| 311 | } | ||
| 312 | else | ||
| 313 | @styles[name] = { | ||
| 314 | :processors => @processors, | ||
| 315 | :whiny => @whiny, | ||
| 316 | :convert_options => extra_options_for(name) | ||
| 317 | }.merge(@styles[name]) | ||
| 318 | end | ||
| 319 | end | ||
| 320 | end | ||
| 321 | |||
| 322 | def solidify_style_definitions #:nodoc: | ||
| 323 | @styles.each do |name, args| | ||
| 324 | @styles[name][:geometry] = @styles[name][:geometry].call(instance) if @styles[name][:geometry].respond_to?(:call) | ||
| 325 | @styles[name][:processors] = @styles[name][:processors].call(instance) if @styles[name][:processors].respond_to?(:call) | ||
| 326 | end | ||
| 327 | end | ||
| 328 | |||
| 329 | def initialize_storage #:nodoc: | ||
| 330 | @storage_module = Paperclip::Storage.const_get(@storage.to_s.capitalize) | ||
| 331 | self.extend(@storage_module) | ||
| 332 | end | ||
| 333 | |||
| 334 | def extra_options_for(style) #:nodoc: | ||
| 335 | all_options = convert_options[:all] | ||
| 336 | all_options = all_options.call(instance) if all_options.respond_to?(:call) | ||
| 337 | style_options = convert_options[style] | ||
| 338 | style_options = style_options.call(instance) if style_options.respond_to?(:call) | ||
| 339 | |||
| 340 | [ style_options, all_options ].compact.join(" ") | ||
| 341 | end | ||
| 342 | |||
| 343 | def post_process #:nodoc: | ||
| 344 | return if @queued_for_write[:original].nil? | ||
| 345 | solidify_style_definitions | ||
| 346 | return if fire_events(:before) | ||
| 347 | post_process_styles | ||
| 348 | return if fire_events(:after) | ||
| 349 | end | ||
| 350 | |||
| 351 | def fire_events(which) | ||
| 352 | return true if callback(:"#{which}_post_process") == false | ||
| 353 | return true if callback(:"#{which}_#{name}_post_process") == false | ||
| 354 | end | ||
| 355 | |||
| 356 | def callback which #:nodoc: | ||
| 357 | instance.run_callbacks(which, @queued_for_write){|result, obj| result == false } | ||
| 358 | end | ||
| 359 | |||
| 360 | def post_process_styles | ||
| 361 | @styles.each do |name, args| | ||
| 362 | begin | ||
| 363 | raise RuntimeError.new("Style #{name} has no processors defined.") if args[:processors].blank? | ||
| 364 | @queued_for_write[name] = args[:processors].inject(@queued_for_write[:original]) do |file, processor| | ||
| 365 | Paperclip.processor(processor).make(file, args, self) | ||
| 366 | end | ||
| 367 | rescue PaperclipError => e | ||
| 368 | log("An error was received while processing: #{e.inspect}") | ||
| 369 | (@errors[:processing] ||= []) << e.message if @whiny | ||
| 370 | end | ||
| 371 | end | ||
| 372 | end | ||
| 373 | |||
| 374 | def interpolate pattern, style = default_style #:nodoc: | ||
| 375 | interpolations = self.class.interpolations.sort{|a,b| a.first.to_s <=> b.first.to_s } | ||
| 376 | interpolations.reverse.inject( pattern.dup ) do |result, interpolation| | ||
| 377 | tag, blk = interpolation | ||
| 378 | result.gsub(/:#{tag}/) do |match| | ||
| 379 | blk.call( self, style ) | ||
| 380 | end | ||
| 381 | end | ||
| 382 | end | ||
| 383 | |||
| 384 | def queue_existing_for_delete #:nodoc: | ||
| 385 | return unless file? | ||
| 386 | @queued_for_delete += [:original, *@styles.keys].uniq.map do |style| | ||
| 387 | path(style) if exists?(style) | ||
| 388 | end.compact | ||
| 389 | instance_write(:file_name, nil) | ||
| 390 | instance_write(:content_type, nil) | ||
| 391 | instance_write(:file_size, nil) | ||
| 392 | instance_write(:updated_at, nil) | ||
| 393 | end | ||
| 394 | |||
| 395 | def flush_errors #:nodoc: | ||
| 396 | @errors.each do |error, message| | ||
| 397 | [message].flatten.each {|m| instance.errors.add(name, m) } | ||
| 398 | end | ||
| 399 | end | ||
| 400 | |||
| 401 | end | ||
| 402 | end | ||
| 403 | |||
diff --git a/vendor/plugins/paperclip/lib/paperclip/callback_compatability.rb b/vendor/plugins/paperclip/lib/paperclip/callback_compatability.rb new file mode 100644 index 0000000..10b08fc --- /dev/null +++ b/vendor/plugins/paperclip/lib/paperclip/callback_compatability.rb | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | module Paperclip | ||
| 2 | # This module is intended as a compatability shim for the differences in | ||
| 3 | # callbacks between Rails 2.0 and Rails 2.1. | ||
| 4 | module CallbackCompatability | ||
| 5 | def self.included(base) | ||
| 6 | base.extend(ClassMethods) | ||
| 7 | base.send(:include, InstanceMethods) | ||
| 8 | end | ||
| 9 | |||
| 10 | module ClassMethods | ||
| 11 | # The implementation of this method is taken from the Rails 1.2.6 source, | ||
| 12 | # from rails/activerecord/lib/active_record/callbacks.rb, line 192. | ||
| 13 | def define_callbacks(*args) | ||
| 14 | args.each do |method| | ||
| 15 | self.class_eval <<-"end_eval" | ||
| 16 | def self.#{method}(*callbacks, &block) | ||
| 17 | callbacks << block if block_given? | ||
| 18 | write_inheritable_array(#{method.to_sym.inspect}, callbacks) | ||
| 19 | end | ||
| 20 | end_eval | ||
| 21 | end | ||
| 22 | end | ||
| 23 | end | ||
| 24 | |||
| 25 | module InstanceMethods | ||
| 26 | # The callbacks in < 2.1 don't worry about the extra options or the | ||
| 27 | # block, so just run what we have available. | ||
| 28 | def run_callbacks(meth, opts = nil, &blk) | ||
| 29 | callback(meth) | ||
| 30 | end | ||
| 31 | end | ||
| 32 | end | ||
| 33 | end | ||
diff --git a/vendor/plugins/paperclip/lib/paperclip/geometry.rb b/vendor/plugins/paperclip/lib/paperclip/geometry.rb new file mode 100644 index 0000000..7fbe038 --- /dev/null +++ b/vendor/plugins/paperclip/lib/paperclip/geometry.rb | |||
| @@ -0,0 +1,115 @@ | |||
| 1 | module Paperclip | ||
| 2 | |||
| 3 | # Defines the geometry of an image. | ||
| 4 | class Geometry | ||
| 5 | attr_accessor :height, :width, :modifier | ||
| 6 | |||
| 7 | # Gives a Geometry representing the given height and width | ||
| 8 | def initialize width = nil, height = nil, modifier = nil | ||
| 9 | @height = height.to_f | ||
| 10 | @width = width.to_f | ||
| 11 | @modifier = modifier | ||
| 12 | end | ||
| 13 | |||
| 14 | # Uses ImageMagick to determing the dimensions of a file, passed in as either a | ||
| 15 | # File or path. | ||
| 16 | def self.from_file file | ||
| 17 | file = file.path if file.respond_to? "path" | ||
| 18 | geometry = begin | ||
| 19 | Paperclip.run("identify", %Q[-format "%wx%h" "#{file}"[0]]) | ||
| 20 | rescue PaperclipCommandLineError | ||
| 21 | "" | ||
| 22 | end | ||
| 23 | parse(geometry) || | ||
| 24 | raise(NotIdentifiedByImageMagickError.new("#{file} is not recognized by the 'identify' command.")) | ||
| 25 | end | ||
| 26 | |||
| 27 | # Parses a "WxH" formatted string, where W is the width and H is the height. | ||
| 28 | def self.parse string | ||
| 29 | if match = (string && string.match(/\b(\d*)x?(\d*)\b([\>\<\#\@\%^!])?/)) | ||
| 30 | Geometry.new(*match[1,3]) | ||
| 31 | end | ||
| 32 | end | ||
| 33 | |||
| 34 | # True if the dimensions represent a square | ||
| 35 | def square? | ||
| 36 | height == width | ||
| 37 | end | ||
| 38 | |||
| 39 | # True if the dimensions represent a horizontal rectangle | ||
| 40 | def horizontal? | ||
| 41 | height < width | ||
| 42 | end | ||
| 43 | |||
| 44 | # True if the dimensions represent a vertical rectangle | ||
| 45 | def vertical? | ||
| 46 | height > width | ||
| 47 | end | ||
| 48 | |||
| 49 | # The aspect ratio of the dimensions. | ||
| 50 | def aspect | ||
| 51 | width / height | ||
| 52 | end | ||
| 53 | |||
| 54 | # Returns the larger of the two dimensions | ||
| 55 | def larger | ||
| 56 | [height, width].max | ||
| 57 | end | ||
| 58 | |||
| 59 | # Returns the smaller of the two dimensions | ||
| 60 | def smaller | ||
| 61 | [height, width].min | ||
| 62 | end | ||
| 63 | |||
| 64 | # Returns the width and height in a format suitable to be passed to Geometry.parse | ||
| 65 | def to_s | ||
| 66 | s = "" | ||
| 67 | s << width.to_i.to_s if width > 0 | ||
| 68 | s << "x#{height.to_i}" if height > 0 | ||
| 69 | s << modifier.to_s | ||
| 70 | s | ||
| 71 | end | ||
| 72 | |||
| 73 | # Same as to_s | ||
| 74 | def inspect | ||
| 75 | to_s | ||
| 76 | end | ||
| 77 | |||
| 78 | # Returns the scaling and cropping geometries (in string-based ImageMagick format) | ||
| 79 | # neccessary to transform this Geometry into the Geometry given. If crop is true, | ||
| 80 | # then it is assumed the destination Geometry will be the exact final resolution. | ||
| 81 | # In this case, the source Geometry is scaled so that an image containing the | ||
| 82 | # destination Geometry would be completely filled by the source image, and any | ||
| 83 | # overhanging image would be cropped. Useful for square thumbnail images. The cropping | ||
| 84 | # is weighted at the center of the Geometry. | ||
| 85 | def transformation_to dst, crop = false | ||
| 86 | if crop | ||
| 87 | ratio = Geometry.new( dst.width / self.width, dst.height / self.height ) | ||
| 88 | scale_geometry, scale = scaling(dst, ratio) | ||
| 89 | crop_geometry = cropping(dst, ratio, scale) | ||
| 90 | else | ||
| 91 | scale_geometry = dst.to_s | ||
| 92 | end | ||
| 93 | |||
| 94 | [ scale_geometry, crop_geometry ] | ||
| 95 | end | ||
| 96 | |||
| 97 | private | ||
| 98 | |||
| 99 | def scaling dst, ratio | ||
| 100 | if ratio.horizontal? || ratio.square? | ||
| 101 | [ "%dx" % dst.width, ratio.width ] | ||
| 102 | else | ||
| 103 | [ "x%d" % dst.height, ratio.height ] | ||
| 104 | end | ||
| 105 | end | ||
| 106 | |||
| 107 | def cropping dst, ratio, scale | ||
| 108 | if ratio.horizontal? || ratio.square? | ||
| 109 | "%dx%d+%d+%d" % [ dst.width, dst.height, 0, (self.height * scale - dst.height) / 2 ] | ||
| 110 | else | ||
| 111 | "%dx%d+%d+%d" % [ dst.width, dst.height, (self.width * scale - dst.width) / 2, 0 ] | ||
| 112 | end | ||
| 113 | end | ||
| 114 | end | ||
| 115 | end | ||
diff --git a/vendor/plugins/paperclip/lib/paperclip/iostream.rb b/vendor/plugins/paperclip/lib/paperclip/iostream.rb new file mode 100644 index 0000000..74e2014 --- /dev/null +++ b/vendor/plugins/paperclip/lib/paperclip/iostream.rb | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | # Provides method that can be included on File-type objects (IO, StringIO, Tempfile, etc) to allow stream copying | ||
| 2 | # and Tempfile conversion. | ||
| 3 | module IOStream | ||
| 4 | |||
| 5 | # Returns a Tempfile containing the contents of the readable object. | ||
| 6 | def to_tempfile | ||
| 7 | tempfile = Tempfile.new("stream") | ||
| 8 | tempfile.binmode | ||
| 9 | self.stream_to(tempfile) | ||
| 10 | end | ||
| 11 | |||
| 12 | # Copies one read-able object from one place to another in blocks, obviating the need to load | ||
| 13 | # the whole thing into memory. Defaults to 8k blocks. If this module is included in both | ||
| 14 | # StringIO and Tempfile, then either can have its data copied anywhere else without typing | ||
| 15 | # worries or memory overhead worries. Returns a File if a String is passed in as the destination | ||
| 16 | # and returns the IO or Tempfile as passed in if one is sent as the destination. | ||
| 17 | def stream_to path_or_file, in_blocks_of = 8192 | ||
| 18 | dstio = case path_or_file | ||
| 19 | when String then File.new(path_or_file, "wb+") | ||
| 20 | when IO then path_or_file | ||
| 21 | when Tempfile then path_or_file | ||
| 22 | end | ||
| 23 | buffer = "" | ||
| 24 | self.rewind | ||
| 25 | while self.read(in_blocks_of, buffer) do | ||
| 26 | dstio.write(buffer) | ||
| 27 | end | ||
| 28 | dstio.rewind | ||
| 29 | dstio | ||
| 30 | end | ||
| 31 | end | ||
| 32 | |||
| 33 | class IO #:nodoc: | ||
| 34 | include IOStream | ||
| 35 | end | ||
| 36 | |||
| 37 | %w( Tempfile StringIO ).each do |klass| | ||
| 38 | if Object.const_defined? klass | ||
| 39 | Object.const_get(klass).class_eval do | ||
| 40 | include IOStream | ||
| 41 | end | ||
| 42 | end | ||
| 43 | end | ||
| 44 | |||
| 45 | # Corrects a bug in Windows when asking for Tempfile size. | ||
| 46 | if defined? Tempfile | ||
| 47 | class Tempfile | ||
| 48 | def size | ||
| 49 | if @tmpfile | ||
| 50 | @tmpfile.fsync | ||
| 51 | @tmpfile.flush | ||
| 52 | @tmpfile.stat.size | ||
| 53 | else | ||
| 54 | 0 | ||
| 55 | end | ||
| 56 | end | ||
| 57 | end | ||
| 58 | end | ||
diff --git a/vendor/plugins/paperclip/lib/paperclip/matchers.rb b/vendor/plugins/paperclip/lib/paperclip/matchers.rb new file mode 100644 index 0000000..ca24b5e --- /dev/null +++ b/vendor/plugins/paperclip/lib/paperclip/matchers.rb | |||
| @@ -0,0 +1,4 @@ | |||
| 1 | require 'paperclip/matchers/have_attached_file_matcher' | ||
| 2 | require 'paperclip/matchers/validate_attachment_presence_matcher' | ||
| 3 | require 'paperclip/matchers/validate_attachment_content_type_matcher' | ||
| 4 | require 'paperclip/matchers/validate_attachment_size_matcher' | ||
diff --git a/vendor/plugins/paperclip/lib/paperclip/matchers/have_attached_file_matcher.rb b/vendor/plugins/paperclip/lib/paperclip/matchers/have_attached_file_matcher.rb new file mode 100644 index 0000000..da0dd8b --- /dev/null +++ b/vendor/plugins/paperclip/lib/paperclip/matchers/have_attached_file_matcher.rb | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | module Paperclip | ||
| 2 | module Shoulda | ||
| 3 | module Matchers | ||
| 4 | def have_attached_file name | ||
| 5 | HaveAttachedFileMatcher.new(name) | ||
| 6 | end | ||
| 7 | |||
| 8 | class HaveAttachedFileMatcher | ||
| 9 | def initialize attachment_name | ||
| 10 | @attachment_name = attachment_name | ||
| 11 | end | ||
| 12 | |||
| 13 | def matches? subject | ||
| 14 | @subject = subject | ||
| 15 | responds? && has_column? && included? | ||
| 16 | end | ||
| 17 | |||
| 18 | def failure_message | ||
| 19 | "Should have an attachment named #{@attachment_name}" | ||
| 20 | end | ||
| 21 | |||
| 22 | def negative_failure_message | ||
| 23 | "Should not have an attachment named #{@attachment_name}" | ||
| 24 | end | ||
| 25 | |||
| 26 | def description | ||
| 27 | "have an attachment named #{@attachment_name}" | ||
| 28 | end | ||
| 29 | |||
| 30 | protected | ||
| 31 | |||
| 32 | def responds? | ||
| 33 | methods = @subject.instance_methods | ||
| 34 | methods.include?("#{@attachment_name}") && | ||
| 35 | methods.include?("#{@attachment_name}=") && | ||
| 36 | methods.include?("#{@attachment_name}?") | ||
| 37 | end | ||
| 38 | |||
| 39 | def has_column? | ||
| 40 | @subject.column_names.include?("#{@attachment_name}_file_name") | ||
| 41 | end | ||
| 42 | |||
| 43 | def included? | ||
| 44 | @subject.ancestors.include?(Paperclip::InstanceMethods) | ||
| 45 | end | ||
| 46 | end | ||
| 47 | end | ||
| 48 | end | ||
| 49 | end | ||
diff --git a/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb b/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb new file mode 100644 index 0000000..b4e97fd --- /dev/null +++ b/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb | |||
| @@ -0,0 +1,66 @@ | |||
| 1 | module Paperclip | ||
| 2 | module Shoulda | ||
| 3 | module Matchers | ||
| 4 | def validate_attachment_content_type name | ||
| 5 | ValidateAttachmentContentTypeMatcher.new(name) | ||
| 6 | end | ||
| 7 | |||
| 8 | class ValidateAttachmentContentTypeMatcher | ||
| 9 | def initialize attachment_name | ||
| 10 | @attachment_name = attachment_name | ||
| 11 | end | ||
| 12 | |||
| 13 | def allowing *types | ||
| 14 | @allowed_types = types.flatten | ||
| 15 | self | ||
| 16 | end | ||
| 17 | |||
| 18 | def rejecting *types | ||
| 19 | @rejected_types = types.flatten | ||
| 20 | self | ||
| 21 | end | ||
| 22 | |||
| 23 | def matches? subject | ||
| 24 | @subject = subject | ||
| 25 | @allowed_types && @rejected_types && | ||
| 26 | allowed_types_allowed? && rejected_types_rejected? | ||
| 27 | end | ||
| 28 | |||
| 29 | def failure_message | ||
| 30 | "Content types #{@allowed_types.join(", ")} should be accepted" + | ||
| 31 | " and #{@rejected_types.join(", ")} rejected by #{@attachment_name}" | ||
| 32 | end | ||
| 33 | |||
| 34 | def negative_failure_message | ||
| 35 | "Content types #{@allowed_types.join(", ")} should be rejected" + | ||
| 36 | " and #{@rejected_types.join(", ")} accepted by #{@attachment_name}" | ||
| 37 | end | ||
| 38 | |||
| 39 | def description | ||
| 40 | "validate the content types allowed on attachment #{@attachment_name}" | ||
| 41 | end | ||
| 42 | |||
| 43 | protected | ||
| 44 | |||
| 45 | def allow_types?(types) | ||
| 46 | types.all? do |type| | ||
| 47 | file = StringIO.new(".") | ||
| 48 | file.content_type = type | ||
| 49 | attachment = @subject.new.attachment_for(@attachment_name) | ||
| 50 | attachment.assign(file) | ||
| 51 | attachment.errors[:content_type].nil? | ||
| 52 | end | ||
| 53 | end | ||
| 54 | |||
| 55 | def allowed_types_allowed? | ||
| 56 | allow_types?(@allowed_types) | ||
| 57 | end | ||
| 58 | |||
| 59 | def rejected_types_rejected? | ||
| 60 | not allow_types?(@rejected_types) | ||
| 61 | end | ||
| 62 | end | ||
| 63 | end | ||
| 64 | end | ||
| 65 | end | ||
| 66 | |||
diff --git a/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_presence_matcher.rb b/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_presence_matcher.rb new file mode 100644 index 0000000..61dc0ea --- /dev/null +++ b/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_presence_matcher.rb | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | module Paperclip | ||
| 2 | module Shoulda | ||
| 3 | module Matchers | ||
| 4 | def validate_attachment_presence name | ||
| 5 | ValidateAttachmentPresenceMatcher.new(name) | ||
| 6 | end | ||
| 7 | |||
| 8 | class ValidateAttachmentPresenceMatcher | ||
| 9 | def initialize attachment_name | ||
| 10 | @attachment_name = attachment_name | ||
| 11 | end | ||
| 12 | |||
| 13 | def matches? subject | ||
| 14 | @subject = subject | ||
| 15 | error_when_not_valid? && no_error_when_valid? | ||
| 16 | end | ||
| 17 | |||
| 18 | def failure_message | ||
| 19 | "Attachment #{@attachment_name} should be required" | ||
| 20 | end | ||
| 21 | |||
| 22 | def negative_failure_message | ||
| 23 | "Attachment #{@attachment_name} should not be required" | ||
| 24 | end | ||
| 25 | |||
| 26 | def description | ||
| 27 | "require presence of attachment #{@attachment_name}" | ||
| 28 | end | ||
| 29 | |||
| 30 | protected | ||
| 31 | |||
| 32 | def error_when_not_valid? | ||
| 33 | @attachment = @subject.new.send(@attachment_name) | ||
| 34 | @attachment.assign(nil) | ||
| 35 | not @attachment.errors[:presence].nil? | ||
| 36 | end | ||
| 37 | |||
| 38 | def no_error_when_valid? | ||
| 39 | @file = StringIO.new(".") | ||
| 40 | @attachment = @subject.new.send(@attachment_name) | ||
| 41 | @attachment.assign(@file) | ||
| 42 | @attachment.errors[:presence].nil? | ||
| 43 | end | ||
| 44 | end | ||
| 45 | end | ||
| 46 | end | ||
| 47 | end | ||
| 48 | |||
diff --git a/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_size_matcher.rb b/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_size_matcher.rb new file mode 100644 index 0000000..f84c479 --- /dev/null +++ b/vendor/plugins/paperclip/lib/paperclip/matchers/validate_attachment_size_matcher.rb | |||
| @@ -0,0 +1,83 @@ | |||
| 1 | module Paperclip | ||
| 2 | module Shoulda | ||
| 3 | module Matchers | ||
| 4 | def validate_attachment_size name | ||
| 5 | ValidateAttachmentSizeMatcher.new(name) | ||
| 6 | end | ||
| 7 | |||
| 8 | class ValidateAttachmentSizeMatcher | ||
| 9 | def initialize attachment_name | ||
| 10 | @attachment_name = attachment_name | ||
| 11 | @low, @high = 0, (1.0/0) | ||
| 12 | end | ||
| 13 | |||
| 14 | def less_than size | ||
| 15 | @high = size | ||
| 16 | self | ||
| 17 | end | ||
| 18 | |||
| 19 | def greater_than size | ||
| 20 | @low = size | ||
| 21 | self | ||
| 22 | end | ||
| 23 | |||
| 24 | def in range | ||
| 25 | @low, @high = range.first, range.last | ||
| 26 | self | ||
| 27 | end | ||
| 28 | |||
| 29 | def matches? subject | ||
| 30 | @subject = subject | ||
| 31 | lower_than_low? && higher_than_low? && lower_than_high? && higher_than_high? | ||
| 32 | end | ||
| 33 | |||
| 34 | def failure_message | ||
| 35 | "Attachment #{@attachment_name} must be between #{@low} and #{@high} bytes" | ||
| 36 | end | ||
| 37 | |||
| 38 | def negative_failure_message | ||
| 39 | "Attachment #{@attachment_name} cannot be between #{@low} and #{@high} bytes" | ||
| 40 | end | ||
| 41 | |||
| 42 | def description | ||
| 43 | "validate the size of attachment #{@attachment_name}" | ||
| 44 | end | ||
| 45 | |||
| 46 | protected | ||
| 47 | |||
| 48 | def override_method object, method, &replacement | ||
| 49 | (class << object; self; end).class_eval do | ||
| 50 | define_method(method, &replacement) | ||
| 51 | end | ||
| 52 | end | ||
| 53 | |||
| 54 | def passes_validation_with_size(new_size) | ||
| 55 | file = StringIO.new(".") | ||
| 56 | override_method(file, :size){ new_size } | ||
| 57 | attachment = @subject.new.attachment_for(@attachment_name) | ||
| 58 | attachment.assign(file) | ||
| 59 | attachment.errors[:size].nil? | ||
| 60 | end | ||
| 61 | |||
| 62 | def lower_than_low? | ||
| 63 | not passes_validation_with_size(@low - 1) | ||
| 64 | end | ||
| 65 | |||
| 66 | def higher_than_low? | ||
| 67 | passes_validation_with_size(@low + 1) | ||
| 68 | end | ||
| 69 | |||
| 70 | def lower_than_high? | ||
| 71 | return true if @high == (1.0/0) | ||
| 72 | passes_validation_with_size(@high - 1) | ||
| 73 | end | ||
| 74 | |||
| 75 | def higher_than_high? | ||
| 76 | return true if @high == (1.0/0) | ||
| 77 | not passes_validation_with_size(@high + 1) | ||
| 78 | end | ||
| 79 | end | ||
| 80 | end | ||
| 81 | end | ||
| 82 | end | ||
| 83 | |||
diff --git a/vendor/plugins/paperclip/lib/paperclip/processor.rb b/vendor/plugins/paperclip/lib/paperclip/processor.rb new file mode 100644 index 0000000..9082bfd --- /dev/null +++ b/vendor/plugins/paperclip/lib/paperclip/processor.rb | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | module Paperclip | ||
| 2 | # Paperclip processors allow you to modify attached files when they are | ||
| 3 | # attached in any way you are able. Paperclip itself uses command-line | ||
| 4 | # programs for its included Thumbnail processor, but custom processors | ||
| 5 | # are not required to follow suit. | ||
| 6 | # | ||
| 7 | # Processors are required to be defined inside the Paperclip module and | ||
| 8 | # are also required to be a subclass of Paperclip::Processor. There are | ||
| 9 | # only two methods you must implement to properly be a subclass: | ||
| 10 | # #initialize and #make. Initialize's arguments are the file that will | ||
| 11 | # be operated on (which is an instance of File), and a hash of options | ||
| 12 | # that were defined in has_attached_file's style hash. | ||
| 13 | # | ||
| 14 | # All #make needs to do is return an instance of File (Tempfile is | ||
| 15 | # acceptable) which contains the results of the processing. | ||
| 16 | # | ||
| 17 | # See Paperclip.run for more information about using command-line | ||
| 18 | # utilities from within Processors. | ||
| 19 | class Processor | ||
| 20 | attr_accessor :file, :options, :attachment | ||
| 21 | |||
| 22 | def initialize file, options = {}, attachment = nil | ||
| 23 | @file = file | ||
| 24 | @options = options | ||
| 25 | @attachment = attachment | ||
| 26 | end | ||
| 27 | |||
| 28 | def make | ||
| 29 | end | ||
| 30 | |||
| 31 | def self.make file, options = {}, attachment = nil | ||
| 32 | new(file, options, attachment).make | ||
| 33 | end | ||
| 34 | end | ||
| 35 | |||
| 36 | # Due to how ImageMagick handles its image format conversion and how Tempfile | ||
| 37 | # handles its naming scheme, it is necessary to override how Tempfile makes | ||
| 38 | # its names so as to allow for file extensions. Idea taken from the comments | ||
| 39 | # on this blog post: | ||
| 40 | # http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions | ||
| 41 | class Tempfile < ::Tempfile | ||
| 42 | # Replaces Tempfile's +make_tmpname+ with one that honors file extensions. | ||
| 43 | def make_tmpname(basename, n) | ||
| 44 | extension = File.extname(basename) | ||
| 45 | sprintf("%s,%d,%d%s", File.basename(basename, extension), $$, n, extension) | ||
| 46 | end | ||
| 47 | end | ||
| 48 | end | ||
diff --git a/vendor/plugins/paperclip/lib/paperclip/storage.rb b/vendor/plugins/paperclip/lib/paperclip/storage.rb new file mode 100644 index 0000000..58913ad --- /dev/null +++ b/vendor/plugins/paperclip/lib/paperclip/storage.rb | |||
| @@ -0,0 +1,236 @@ | |||
| 1 | module Paperclip | ||
| 2 | module Storage | ||
| 3 | |||
| 4 | # The default place to store attachments is in the filesystem. Files on the local | ||
| 5 | # filesystem can be very easily served by Apache without requiring a hit to your app. | ||
| 6 | # They also can be processed more easily after they've been saved, as they're just | ||
| 7 | # normal files. There is one Filesystem-specific option for has_attached_file. | ||
| 8 | # * +path+: The location of the repository of attachments on disk. This can (and, in | ||
| 9 | # almost all cases, should) be coordinated with the value of the +url+ option to | ||
| 10 | # allow files to be saved into a place where Apache can serve them without | ||
| 11 | # hitting your app. Defaults to | ||
| 12 | # ":rails_root/public/:attachment/:id/:style/:basename.:extension" | ||
| 13 | # By default this places the files in the app's public directory which can be served | ||
| 14 | # directly. If you are using capistrano for deployment, a good idea would be to | ||
| 15 | # make a symlink to the capistrano-created system directory from inside your app's | ||
| 16 | # public directory. | ||
| 17 | # See Paperclip::Attachment#interpolate for more information on variable interpolaton. | ||
| 18 | # :path => "/var/app/attachments/:class/:id/:style/:basename.:extension" | ||
| 19 | module Filesystem | ||
| 20 | def self.extended base | ||
| 21 | end | ||
| 22 | |||
| 23 | def exists?(style = default_style) | ||
| 24 | if original_filename | ||
| 25 | File.exist?(path(style)) | ||
| 26 | else | ||
| 27 | false | ||
| 28 | end | ||
| 29 | end | ||
| 30 | |||
| 31 | # Returns representation of the data of the file assigned to the given | ||
| 32 | # style, in the format most representative of the current storage. | ||
| 33 | def to_file style = default_style | ||
| 34 | @queued_for_write[style] || (File.new(path(style), 'rb') if exists?(style)) | ||
| 35 | end | ||
| 36 | alias_method :to_io, :to_file | ||
| 37 | |||
| 38 | def flush_writes #:nodoc: | ||
| 39 | @queued_for_write.each do |style, file| | ||
| 40 | file.close | ||
| 41 | FileUtils.mkdir_p(File.dirname(path(style))) | ||
| 42 | logger.info("[paperclip] saving #{path(style)}") | ||
| 43 | FileUtils.mv(file.path, path(style)) | ||
| 44 | FileUtils.chmod(0644, path(style)) | ||
| 45 | end | ||
| 46 | @queued_for_write = {} | ||
| 47 | end | ||
| 48 | |||
| 49 | def flush_deletes #:nodoc: | ||
| 50 | @queued_for_delete.each do |path| | ||
| 51 | begin | ||
| 52 | logger.info("[paperclip] deleting #{path}") | ||
| 53 | FileUtils.rm(path) if File.exist?(path) | ||
| 54 | rescue Errno::ENOENT => e | ||
| 55 | # ignore file-not-found, let everything else pass | ||
| 56 | end | ||
| 57 | begin | ||
| 58 | while(true) | ||
| 59 | path = File.dirname(path) | ||
| 60 | FileUtils.rmdir(path) | ||
| 61 | end | ||
| 62 | rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR | ||
| 63 | # Stop trying to remove parent directories | ||
| 64 | rescue SystemCallError => e | ||
| 65 | logger.info("[paperclip] There was an unexpected error while deleting directories: #{e.class}") | ||
| 66 | # Ignore it | ||
| 67 | end | ||
| 68 | end | ||
| 69 | @queued_for_delete = [] | ||
| 70 | end | ||
| 71 | end | ||
| 72 | |||
| 73 | # Amazon's S3 file hosting service is a scalable, easy place to store files for | ||
| 74 | # distribution. You can find out more about it at http://aws.amazon.com/s3 | ||
| 75 | # There are a few S3-specific options for has_attached_file: | ||
| 76 | # * +s3_credentials+: Takes a path, a File, or a Hash. The path (or File) must point | ||
| 77 | # to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon | ||
| 78 | # gives you. You can 'environment-space' this just like you do to your | ||
| 79 | # database.yml file, so different environments can use different accounts: | ||
| 80 | # development: | ||
| 81 | # access_key_id: 123... | ||
| 82 | # secret_access_key: 123... | ||
| 83 | # test: | ||
| 84 | # access_key_id: abc... | ||
| 85 | # secret_access_key: abc... | ||
| 86 | # production: | ||
| 87 | # access_key_id: 456... | ||
| 88 | # secret_access_key: 456... | ||
| 89 | # This is not required, however, and the file may simply look like this: | ||
| 90 | # access_key_id: 456... | ||
| 91 | # secret_access_key: 456... | ||
| 92 | # In which case, those access keys will be used in all environments. You can also | ||
| 93 | # put your bucket name in this file, instead of adding it to the code directly. | ||
| 94 | # This is useful when you want the same account but a different bucket for | ||
| 95 | # development versus production. | ||
| 96 | # * +s3_permissions+: This is a String that should be one of the "canned" access | ||
| 97 | # policies that S3 provides (more information can be found here: | ||
| 98 | # http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAccessPolicy.html#RESTCannedAccessPolicies) | ||
| 99 | # The default for Paperclip is "public-read". | ||
| 100 | # * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either | ||
| 101 | # 'http' or 'https'. Defaults to 'http' when your :s3_permissions are 'public-read' (the | ||
| 102 | # default), and 'https' when your :s3_permissions are anything else. | ||
| 103 | # * +s3_headers+: A hash of headers such as {'Expires' => 1.year.from_now.httpdate} | ||
| 104 | # * +bucket+: This is the name of the S3 bucket that will store your files. Remember | ||
| 105 | # that the bucket must be unique across all of Amazon S3. If the bucket does not exist | ||
| 106 | # Paperclip will attempt to create it. The bucket name will not be interpolated. | ||
| 107 | # You can define the bucket as a Proc if you want to determine it's name at runtime. | ||
| 108 | # Paperclip will call that Proc with attachment as the only argument. | ||
| 109 | # * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the | ||
| 110 | # S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the | ||
| 111 | # link in the +url+ entry for more information about S3 domains and buckets. | ||
| 112 | # * +url+: There are three options for the S3 url. You can choose to have the bucket's name | ||
| 113 | # placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket). | ||
| 114 | # Lastly, you can specify a CNAME (which requires the CNAME to be specified as | ||
| 115 | # :s3_alias_url. You can read more about CNAMEs and S3 at | ||
| 116 | # http://docs.amazonwebservices.com/AmazonS3/latest/index.html?VirtualHosting.html | ||
| 117 | # Normally, this won't matter in the slightest and you can leave the default (which is | ||
| 118 | # path-style, or :s3_path_url). But in some cases paths don't work and you need to use | ||
| 119 | # the domain-style (:s3_domain_url). Anything else here will be treated like path-style. | ||
| 120 | # NOTE: If you use a CNAME for use with CloudFront, you can NOT specify https as your | ||
| 121 | # :s3_protocol; This is *not supported* by S3/CloudFront. Finally, when using the host | ||
| 122 | # alias, the :bucket parameter is ignored, as the hostname is used as the bucket name | ||
| 123 | # by S3. | ||
| 124 | # * +path+: This is the key under the bucket in which the file will be stored. The | ||
| 125 | # URL will be constructed from the bucket and the path. This is what you will want | ||
| 126 | # to interpolate. Keys should be unique, like filenames, and despite the fact that | ||
| 127 | # S3 (strictly speaking) does not support directories, you can still use a / to | ||
| 128 | # separate parts of your file name. | ||
| 129 | module S3 | ||
| 130 | def self.extended base | ||
| 131 | require 'right_aws' | ||
| 132 | base.instance_eval do | ||
| 133 | @s3_credentials = parse_credentials(@options[:s3_credentials]) | ||
| 134 | @bucket = @options[:bucket] || @s3_credentials[:bucket] | ||
| 135 | @bucket = @bucket.call(self) if @bucket.is_a?(Proc) | ||
| 136 | @s3_options = @options[:s3_options] || {} | ||
| 137 | @s3_permissions = @options[:s3_permissions] || 'public-read' | ||
| 138 | @s3_protocol = @options[:s3_protocol] || (@s3_permissions == 'public-read' ? 'http' : 'https') | ||
| 139 | @s3_headers = @options[:s3_headers] || {} | ||
| 140 | @s3_host_alias = @options[:s3_host_alias] | ||
| 141 | @url = ":s3_path_url" unless @url.to_s.match(/^:s3.*url$/) | ||
| 142 | end | ||
| 143 | base.class.interpolations[:s3_alias_url] = lambda do |attachment, style| | ||
| 144 | "#{attachment.s3_protocol}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}" | ||
| 145 | end | ||
| 146 | base.class.interpolations[:s3_path_url] = lambda do |attachment, style| | ||
| 147 | "#{attachment.s3_protocol}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}" | ||
| 148 | end | ||
| 149 | base.class.interpolations[:s3_domain_url] = lambda do |attachment, style| | ||
| 150 | "#{attachment.s3_protocol}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}" | ||
| 151 | end | ||
| 152 | end | ||
| 153 | |||
| 154 | def s3 | ||
| 155 | @s3 ||= RightAws::S3.new(@s3_credentials[:access_key_id], | ||
| 156 | @s3_credentials[:secret_access_key], | ||
| 157 | @s3_options) | ||
| 158 | end | ||
| 159 | |||
| 160 | def s3_bucket | ||
| 161 | @s3_bucket ||= s3.bucket(@bucket, true, @s3_permissions) | ||
| 162 | end | ||
| 163 | |||
| 164 | def bucket_name | ||
| 165 | @bucket | ||
| 166 | end | ||
| 167 | |||
| 168 | def s3_host_alias | ||
| 169 | @s3_host_alias | ||
| 170 | end | ||
| 171 | |||
| 172 | def parse_credentials creds | ||
| 173 | creds = find_credentials(creds).stringify_keys | ||
| 174 | (creds[ENV['RAILS_ENV']] || creds).symbolize_keys | ||
| 175 | end | ||
| 176 | |||
| 177 | def exists?(style = default_style) | ||
| 178 | s3_bucket.key(path(style)) ? true : false | ||
| 179 | end | ||
| 180 | |||
| 181 | def s3_protocol | ||
| 182 | @s3_protocol | ||
| 183 | end | ||
| 184 | |||
| 185 | # Returns representation of the data of the file assigned to the given | ||
| 186 | # style, in the format most representative of the current storage. | ||
| 187 | def to_file style = default_style | ||
| 188 | @queued_for_write[style] || s3_bucket.key(path(style)) | ||
| 189 | end | ||
| 190 | alias_method :to_io, :to_file | ||
| 191 | |||
| 192 | def flush_writes #:nodoc: | ||
| 193 | @queued_for_write.each do |style, file| | ||
| 194 | begin | ||
| 195 | logger.info("[paperclip] saving #{path(style)}") | ||
| 196 | key = s3_bucket.key(path(style)) | ||
| 197 | key.data = file | ||
| 198 | key.put(nil, @s3_permissions, {'Content-type' => instance_read(:content_type)}.merge(@s3_headers)) | ||
| 199 | rescue RightAws::AwsError => e | ||
| 200 | raise | ||
| 201 | end | ||
| 202 | end | ||
| 203 | @queued_for_write = {} | ||
| 204 | end | ||
| 205 | |||
| 206 | def flush_deletes #:nodoc: | ||
| 207 | @queued_for_delete.each do |path| | ||
| 208 | begin | ||
| 209 | logger.info("[paperclip] deleting #{path}") | ||
| 210 | if file = s3_bucket.key(path) | ||
| 211 | file.delete | ||
| 212 | end | ||
| 213 | rescue RightAws::AwsError | ||
| 214 | # Ignore this. | ||
| 215 | end | ||
| 216 | end | ||
| 217 | @queued_for_delete = [] | ||
| 218 | end | ||
| 219 | |||
| 220 | def find_credentials creds | ||
| 221 | case creds | ||
| 222 | when File | ||
| 223 | YAML.load_file(creds.path) | ||
| 224 | when String | ||
| 225 | YAML.load_file(creds) | ||
| 226 | when Hash | ||
| 227 | creds | ||
| 228 | else | ||
| 229 | raise ArgumentError, "Credentials are not a path, file, or hash." | ||
| 230 | end | ||
| 231 | end | ||
| 232 | private :find_credentials | ||
| 233 | |||
| 234 | end | ||
| 235 | end | ||
| 236 | end | ||
diff --git a/vendor/plugins/paperclip/lib/paperclip/thumbnail.rb b/vendor/plugins/paperclip/lib/paperclip/thumbnail.rb new file mode 100644 index 0000000..2178a9c --- /dev/null +++ b/vendor/plugins/paperclip/lib/paperclip/thumbnail.rb | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | module Paperclip | ||
| 2 | # Handles thumbnailing images that are uploaded. | ||
| 3 | class Thumbnail < Processor | ||
| 4 | |||
| 5 | attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options | ||
| 6 | |||
| 7 | # Creates a Thumbnail object set to work on the +file+ given. It | ||
| 8 | # will attempt to transform the image into one defined by +target_geometry+ | ||
| 9 | # which is a "WxH"-style string. +format+ will be inferred from the +file+ | ||
| 10 | # unless specified. Thumbnail creation will raise no errors unless | ||
| 11 | # +whiny+ is true (which it is, by default. If +convert_options+ is | ||
| 12 | # set, the options will be appended to the convert command upon image conversion | ||
| 13 | def initialize file, options = {}, attachment = nil | ||
| 14 | super | ||
| 15 | geometry = options[:geometry] | ||
| 16 | @file = file | ||
| 17 | @crop = geometry[-1,1] == '#' | ||
| 18 | @target_geometry = Geometry.parse geometry | ||
| 19 | @current_geometry = Geometry.from_file @file | ||
| 20 | @convert_options = options[:convert_options] | ||
| 21 | @whiny = options[:whiny].nil? ? true : options[:whiny] | ||
| 22 | @format = options[:format] | ||
| 23 | |||
| 24 | @current_format = File.extname(@file.path) | ||
| 25 | @basename = File.basename(@file.path, @current_format) | ||
| 26 | end | ||
| 27 | |||
| 28 | # Returns true if the +target_geometry+ is meant to crop. | ||
| 29 | def crop? | ||
| 30 | @crop | ||
| 31 | end | ||
| 32 | |||
| 33 | # Returns true if the image is meant to make use of additional convert options. | ||
| 34 | def convert_options? | ||
| 35 | not @convert_options.blank? | ||
| 36 | end | ||
| 37 | |||
| 38 | # Performs the conversion of the +file+ into a thumbnail. Returns the Tempfile | ||
| 39 | # that contains the new image. | ||
| 40 | def make | ||
| 41 | src = @file | ||
| 42 | dst = Tempfile.new([@basename, @format].compact.join(".")) | ||
| 43 | dst.binmode | ||
| 44 | |||
| 45 | command = <<-end_command | ||
| 46 | "#{ File.expand_path(src.path) }[0]" | ||
| 47 | #{ transformation_command } | ||
| 48 | "#{ File.expand_path(dst.path) }" | ||
| 49 | end_command | ||
| 50 | |||
| 51 | begin | ||
| 52 | success = Paperclip.run("convert", command.gsub(/\s+/, " ")) | ||
| 53 | rescue PaperclipCommandLineError | ||
| 54 | raise PaperclipError, "There was an error processing the thumbnail for #{@basename}" if @whiny | ||
| 55 | end | ||
| 56 | |||
| 57 | dst | ||
| 58 | end | ||
| 59 | |||
| 60 | # Returns the command ImageMagick's +convert+ needs to transform the image | ||
| 61 | # into the thumbnail. | ||
| 62 | def transformation_command | ||
| 63 | scale, crop = @current_geometry.transformation_to(@target_geometry, crop?) | ||
| 64 | trans = "-resize \"#{scale}\"" | ||
| 65 | trans << " -crop \"#{crop}\" +repage" if crop | ||
| 66 | trans << " #{convert_options}" if convert_options? | ||
| 67 | trans | ||
| 68 | end | ||
| 69 | end | ||
| 70 | end | ||
diff --git a/vendor/plugins/paperclip/lib/paperclip/upfile.rb b/vendor/plugins/paperclip/lib/paperclip/upfile.rb new file mode 100644 index 0000000..f0c8a44 --- /dev/null +++ b/vendor/plugins/paperclip/lib/paperclip/upfile.rb | |||
| @@ -0,0 +1,48 @@ | |||
| 1 | module Paperclip | ||
| 2 | # The Upfile module is a convenience module for adding uploaded-file-type methods | ||
| 3 | # to the +File+ class. Useful for testing. | ||
| 4 | # user.avatar = File.new("test/test_avatar.jpg") | ||
| 5 | module Upfile | ||
| 6 | |||
| 7 | # Infer the MIME-type of the file from the extension. | ||
| 8 | def content_type | ||
| 9 | type = (self.path.match(/\.(\w+)$/)[1] rescue "octet-stream").downcase | ||
| 10 | case type | ||
| 11 | when %r"jpe?g" then "image/jpeg" | ||
| 12 | when %r"tiff?" then "image/tiff" | ||
| 13 | when %r"png", "gif", "bmp" then "image/#{type}" | ||
| 14 | when "txt" then "text/plain" | ||
| 15 | when %r"html?" then "text/html" | ||
| 16 | when "csv", "xml", "css", "js" then "text/#{type}" | ||
| 17 | else "application/x-#{type}" | ||
| 18 | end | ||
| 19 | end | ||
| 20 | |||
| 21 | # Returns the file's normal name. | ||
| 22 | def original_filename | ||
| 23 | File.basename(self.path) | ||
| 24 | end | ||
| 25 | |||
| 26 | # Returns the size of the file. | ||
| 27 | def size | ||
| 28 | File.size(self) | ||
| 29 | end | ||
| 30 | end | ||
| 31 | end | ||
| 32 | |||
| 33 | if defined? StringIO | ||
| 34 | class StringIO | ||
| 35 | attr_accessor :original_filename, :content_type | ||
| 36 | def original_filename | ||
| 37 | @original_filename ||= "stringio.txt" | ||
| 38 | end | ||
| 39 | def content_type | ||
| 40 | @content_type ||= "text/plain" | ||
| 41 | end | ||
| 42 | end | ||
| 43 | end | ||
| 44 | |||
| 45 | class File #:nodoc: | ||
| 46 | include Paperclip::Upfile | ||
| 47 | end | ||
| 48 | |||
