You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@labs.apache.org by fa...@apache.org on 2009/03/27 11:33:07 UTC

svn commit: r759091 [7/8] - in /labs/consite: ./ trunk/ trunk/conferences/ trunk/conferences/app/ trunk/conferences/app/controllers/ trunk/conferences/app/controllers/admin/ trunk/conferences/app/controllers/admin/con/ trunk/conferences/app/helpers/ tr...

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_column.rb
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_column.rb?rev=759091&view=auto
==============================================================================
--- labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_column.rb (added)
+++ labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_column.rb Fri Mar 27 10:32:54 2009
@@ -0,0 +1,720 @@
+require 'fileutils'
+require 'tempfile'
+require 'magick_file_column'
+
+module FileColumn # :nodoc:
+  def self.append_features(base)
+    super
+    base.extend(ClassMethods)
+  end
+
+  def self.create_state(instance,attr)
+    filename = instance[attr]
+    if filename.nil? or filename.empty?
+      NoUploadedFile.new(instance,attr)
+    else
+      PermanentUploadedFile.new(instance,attr)
+    end
+  end
+
+  def self.init_options(defaults, model, attr)
+    options = defaults.dup
+    options[:store_dir] ||= File.join(options[:root_path], model, attr)
+    unless options[:store_dir].is_a?(Symbol)
+      options[:tmp_base_dir] ||= File.join(options[:store_dir], "tmp")
+    end
+    options[:base_url] ||= options[:web_root] + File.join(model, attr)
+
+    [:store_dir, :tmp_base_dir].each do |dir_sym|
+      if options[dir_sym].is_a?(String) and !File.exists?(options[dir_sym])
+        FileUtils.mkpath(options[dir_sym])
+      end
+    end
+
+    options
+  end
+
+  class BaseUploadedFile # :nodoc:
+
+    def initialize(instance,attr)
+      @instance, @attr = instance, attr
+      @options_method = "#{attr}_options".to_sym
+    end
+
+
+    def assign(file)
+      if file.is_a? File
+        # this did not come in via a CGI request. However,
+        # assigning files directly may be useful, so we
+        # make just this file object similar enough to an uploaded
+        # file that we can handle it. 
+        file.extend FileColumn::FileCompat
+      end
+
+      if file.nil?
+        delete
+      else
+        if file.size == 0
+          # user did not submit a file, so we
+          # can simply ignore this
+          self
+        else
+          if file.is_a?(String)
+            # if file is a non-empty string it is most probably
+            # the filename and the user forgot to set the encoding
+            # to multipart/form-data. Since we would raise an exception
+            # because of the missing "original_filename" method anyways,
+            # we raise a more meaningful exception rightaway.
+            raise TypeError.new("Do not know how to handle a string with value '#{file}' that was passed to a file_column. Check if the form's encoding has been set to 'multipart/form-data'.")
+          end
+          upload(file)
+        end
+      end
+    end
+
+    def just_uploaded?
+      @just_uploaded
+    end
+
+    def on_save(&blk)
+      @on_save ||= []
+      @on_save << Proc.new
+    end
+    
+    # the following methods are overriden by sub-classes if needed
+
+    def temp_path
+      nil
+    end
+
+    def absolute_dir
+      if absolute_path then File.dirname(absolute_path) else nil end
+    end
+
+    def relative_dir
+      if relative_path then File.dirname(relative_path) else nil end
+    end
+
+    def after_save
+      @on_save.each { |blk| blk.call } if @on_save
+      self
+    end
+
+    def after_destroy
+    end
+
+    def options
+      @instance.send(@options_method)
+    end
+
+    private
+    
+    def store_dir
+      if options[:store_dir].is_a? Symbol
+        raise ArgumentError.new("'#{options[:store_dir]}' is not an instance method of class #{@instance.class.name}") unless @instance.respond_to?(options[:store_dir])
+
+        dir = File.join(options[:root_path], @instance.send(options[:store_dir]))
+        FileUtils.mkpath(dir) unless File.exists?(dir)
+        dir
+      else 
+        options[:store_dir]
+      end
+    end
+
+    def tmp_base_dir
+      if options[:tmp_base_dir]
+        options[:tmp_base_dir] 
+      else
+        dir = File.join(store_dir, "tmp")
+        FileUtils.mkpath(dir) unless File.exists?(dir)
+        dir
+      end
+    end
+
+    def clone_as(klass)
+      klass.new(@instance, @attr)
+    end
+
+  end
+    
+
+  class NoUploadedFile < BaseUploadedFile # :nodoc:
+    def delete
+      # we do not have a file so deleting is easy
+      self
+    end
+
+    def upload(file)
+      # replace ourselves with a TempUploadedFile
+      temp = clone_as TempUploadedFile
+      temp.store_upload(file)
+      temp
+    end
+
+    def absolute_path(subdir=nil)
+      nil
+    end
+
+
+    def relative_path(subdir=nil)
+      nil
+    end
+
+    def assign_temp(temp_path)
+      return self if temp_path.nil? or temp_path.empty?
+      temp = clone_as TempUploadedFile
+      temp.parse_temp_path temp_path
+      temp
+    end
+  end
+
+  class RealUploadedFile < BaseUploadedFile # :nodoc:
+    def absolute_path(subdir=nil)
+      if subdir
+        File.join(@dir, subdir, @filename)
+      else
+        File.join(@dir, @filename)
+      end
+    end
+
+    def relative_path(subdir=nil)
+      if subdir
+        File.join(relative_path_prefix, subdir, @filename)
+      else
+        File.join(relative_path_prefix, @filename)
+      end
+    end
+
+    private
+
+    # regular expressions to try for identifying extensions
+    EXT_REGEXPS = [ 
+      /^(.+)\.([^.]+\.[^.]+)$/, # matches "something.tar.gz"
+      /^(.+)\.([^.]+)$/ # matches "something.jpg"
+    ]
+
+    def split_extension(filename,fallback=nil)
+      EXT_REGEXPS.each do |regexp|
+        if filename =~ regexp
+          base,ext = $1, $2
+          return [base, ext] if options[:extensions].include?(ext.downcase)
+        end
+      end
+      if fallback and filename =~ EXT_REGEXPS.last
+        return [$1, $2]
+      end
+      [filename, ""]
+    end
+    
+  end
+
+  class TempUploadedFile < RealUploadedFile # :nodoc:
+
+    def store_upload(file)
+      @tmp_dir = FileColumn.generate_temp_name
+      @dir = File.join(tmp_base_dir, @tmp_dir)      
+      FileUtils.mkdir(@dir)
+      
+      @filename = FileColumn::sanitize_filename(file.original_filename)
+      local_file_path = File.join(tmp_base_dir,@tmp_dir,@filename)
+      
+      # stored uploaded file into local_file_path
+      # If it was a Tempfile object, the temporary file will be
+      # cleaned up automatically, so we do not have to care for this
+      if file.respond_to?(:local_path) and file.local_path and File.exists?(file.local_path)
+        FileUtils.copy_file(file.local_path, local_file_path)
+      elsif file.respond_to?(:read)
+        File.open(local_file_path, "wb") { |f| f.write(file.read) }
+      else
+        raise ArgumentError.new("Do not know how to handle #{file.inspect}")
+      end
+      File.chmod(options[:permissions], local_file_path)
+      
+      if options[:fix_file_extensions]
+        # try to determine correct file extension and fix
+        # if necessary
+        content_type = get_content_type((file.content_type.chomp if file.content_type))
+        if content_type and options[:mime_extensions][content_type]
+          @filename = correct_extension(@filename,options[:mime_extensions][content_type])
+        end
+
+        new_local_file_path = File.join(tmp_base_dir,@tmp_dir,@filename)
+        File.rename(local_file_path, new_local_file_path) unless new_local_file_path == local_file_path
+        local_file_path = new_local_file_path
+      end
+      
+      @instance[@attr] = @filename
+      @just_uploaded = true
+    end
+
+
+    # tries to identify and strip the extension of filename
+    # if an regular expresion from EXT_REGEXPS matches and the
+    # downcased extension is a known extension (in options[:extensions])
+    # we'll strip this extension
+    def strip_extension(filename)
+      split_extension(filename).first
+    end
+
+    def correct_extension(filename, ext)
+      strip_extension(filename) << ".#{ext}"
+    end
+    
+    def parse_temp_path(temp_path, instance_options=nil)
+      raise ArgumentError.new("invalid format of '#{temp_path}'") unless temp_path =~ %r{^((\d+\.)+\d+)/([^/].+)$}
+      @tmp_dir, @filename = $1, FileColumn.sanitize_filename($3)
+      @dir = File.join(tmp_base_dir, @tmp_dir)
+
+      @instance[@attr] = @filename unless instance_options == :ignore_instance
+    end
+    
+    def upload(file)
+      # store new file
+      temp = clone_as TempUploadedFile
+      temp.store_upload(file)
+      
+      # delete old copy
+      delete_files
+
+      # and return new TempUploadedFile object
+      temp
+    end
+
+    def delete
+      delete_files
+      @instance[@attr] = ""
+      clone_as NoUploadedFile
+    end
+
+    def assign_temp(temp_path)
+      return self if temp_path.nil? or temp_path.empty?
+      # we can ignore this since we've already received a newly uploaded file
+
+      # however, we delete the old temporary files
+      temp = clone_as TempUploadedFile
+      temp.parse_temp_path(temp_path, :ignore_instance)
+      temp.delete_files
+
+      self
+    end
+
+    def temp_path
+      File.join(@tmp_dir, @filename)
+    end
+
+    def after_save
+      super
+
+      # we have a newly uploaded image, move it to the correct location
+      file = clone_as PermanentUploadedFile
+      file.move_from(File.join(tmp_base_dir, @tmp_dir), @just_uploaded)
+
+      # delete temporary files
+      delete_files
+
+      # replace with the new PermanentUploadedFile object
+      file
+    end
+
+    def delete_files
+      FileUtils.rm_rf(File.join(tmp_base_dir, @tmp_dir))
+    end
+
+    def get_content_type(fallback=nil)
+      if options[:file_exec]
+        begin
+          content_type = `#{options[:file_exec]} -bi "#{File.join(@dir,@filename)}"`.chomp
+          content_type = fallback unless $?.success?
+          content_type.gsub!(/;.+$/,"") if content_type
+          content_type
+        rescue
+          fallback
+        end
+      else
+        fallback
+      end
+    end
+
+    private
+
+    def relative_path_prefix
+      File.join("tmp", @tmp_dir)
+    end
+  end
+
+  
+  class PermanentUploadedFile < RealUploadedFile # :nodoc:
+    def initialize(*args)
+      super *args
+      @dir = File.join(store_dir, relative_path_prefix)
+      @filename = @instance[@attr]
+      @filename = nil if @filename.empty?
+    end
+
+    def move_from(local_dir, just_uploaded)
+      # remove old permament dir first
+      # this creates a short moment, where neither the old nor
+      # the new files exist but we can't do much about this as
+      # filesystems aren't transactional.
+      FileUtils.rm_rf @dir
+
+      FileUtils.mv local_dir, @dir
+
+      @just_uploaded = just_uploaded
+    end
+
+    def upload(file)
+      temp = clone_as TempUploadedFile
+      temp.store_upload(file)
+      temp
+    end
+
+    def delete
+      file = clone_as NoUploadedFile
+      @instance[@attr] = ""
+      file.on_save { delete_files }
+      file
+    end
+
+    def assign_temp(temp_path)
+      return nil if temp_path.nil? or temp_path.empty?
+
+      temp = clone_as TempUploadedFile
+      temp.parse_temp_path(temp_path)
+      temp
+    end
+
+    def after_destroy
+      delete_files
+    end
+
+    def delete_files
+      FileUtils.rm_rf @dir
+    end
+
+    private
+    
+    def relative_path_prefix
+      raise RuntimeError.new("Trying to access file_column, but primary key got lost.") if @instance.id.to_s.empty?
+      @instance.id.to_s
+    end
+  end
+    
+  # The FileColumn module allows you to easily handle file uploads. You can designate
+  # one or more columns of your model's table as "file columns" like this:
+  #
+  #   class Entry < ActiveRecord::Base
+  #
+  #     file_column :image
+  #   end
+  #
+  # Now, by default, an uploaded file "test.png" for an entry object with primary key 42 will
+  # be stored in in "public/entry/image/42/test.png". The filename "test.png" will be stored
+  # in the record's "image" column. The "entries" table should have a +VARCHAR+ column
+  # named "image".
+  #
+  # The methods of this module are automatically included into <tt>ActiveRecord::Base</tt>
+  # as class methods, so that you can use them in your models.
+  #
+  # == Generated Methods
+  #
+  # After calling "<tt>file_column :image</tt>" as in the example above, a number of instance methods
+  # will automatically be generated, all prefixed by "image":
+  #
+  # * <tt>Entry#image=(uploaded_file)</tt>: this will handle a newly uploaded file
+  #   (see below). Note that
+  #   you can simply call your upload field "entry[image]" in your view (or use the
+  #   helper).
+  # * <tt>Entry#image(subdir=nil)</tt>: This will return an absolute path (as a
+  #   string) to the currently uploaded file
+  #   or nil if no file has been uploaded
+  # * <tt>Entry#image_relative_path(subdir=nil)</tt>: This will return a path relative to
+  #   this file column's base directory
+  #   as a string or nil if no file has been uploaded. This would be "42/test.png" in the example.
+  # * <tt>Entry#image_just_uploaded?</tt>: Returns true if a new file has been uploaded to this instance.
+  #   You can use this in your code to perform certain actions (e. g., validation,
+  #   custom post-processing) only on newly uploaded files.
+  #
+  # You can access the raw value of the "image" column (which will contain the filename) via the
+  # <tt>ActiveRecord::Base#attributes</tt> or <tt>ActiveRecord::Base#[]</tt> methods like this:
+  #
+  #   entry['image']    # e.g."test.png"
+  #
+  # == Storage of uploaded files
+  #
+  # For a model class +Entry+ and a column +image+, all files will be stored under
+  # "public/entry/image". A sub-directory named after the primary key of the object will
+  # be created, so that files can be stored using their real filename. For example, a file
+  # "test.png" stored in an Entry object with id 42 will be stored in
+  #
+  #   public/entry/image/42/test.png
+  #
+  # Files will be moved to this location in an +after_save+ callback. They will be stored in
+  # a temporary location previously as explained in the next section.
+  #
+  # By default, files will be created with unix permissions of <tt>0644</tt> (i. e., owner has
+  # read/write access, group and others only have read access). You can customize
+  # this by passing the desired mode as a <tt>:permissions</tt> options. The value
+  # you give here is passed directly to <tt>File::chmod</tt>, so on Unix you should
+  # give some octal value like 0644, for example.
+  #
+  # == Handling of form redisplay
+  #
+  # Suppose you have a form for creating a new object where the user can upload an image. The form may
+  # have to be re-displayed because of validation errors. The uploaded file has to be stored somewhere so
+  # that the user does not have to upload it again. FileColumn will store these in a temporary directory
+  # (called "tmp" and located under the column's base directory by default) so that it can be moved to
+  # the final location if the object is successfully created. If the form is never completed, though, you
+  # can easily remove all the images in this "tmp" directory once per day or so.
+  #
+  # So in the example above, the image "test.png" would first be stored in 
+  # "public/entry/image/tmp/<some_random_key>/test.png" and be moved to
+  # "public/entry/image/<primary_key>/test.png".
+  #
+  # This temporary location of newly uploaded files has another advantage when updating objects. If the
+  # update fails for some reasons (e.g. due to validations), the existing image will not be overwritten, so
+  # it has a kind of "transactional behaviour".
+  #
+  # == Additional Files and Directories
+  #
+  # FileColumn allows you to keep more than one file in a directory and will move/delete
+  # all the files and directories it finds in a model object's directory when necessary.
+  #
+  # As a convenience you can access files stored in sub-directories via the +subdir+
+  # parameter if they have the same filename.
+  #
+  # Suppose your uploaded file is named "vancouver.jpg" and you want to create a
+  # thumb-nail and store it in the "thumb" directory. If you call
+  # <tt>image("thumb")</tt>, you
+  # will receive an absolute path for the file "thumb/vancouver.jpg" in the same
+  # directory "vancouver.jpg" is stored. Look at the documentation of FileColumn::Magick
+  # for more examples and how to create these thumb-nails automatically.
+  #
+  # == File Extensions
+  #
+  # FileColumn will try to fix the file extension of uploaded files, so that
+  # the files are served with the correct mime-type by your web-server. Most
+  # web-servers are setting the mime-type based on the file's extension. You
+  # can disable this behaviour by passing the <tt>:fix_file_extensions</tt> option
+  # with a value of +nil+ to +file_column+.
+  #
+  # In order to set the correct extension, FileColumn tries to determine
+  # the files mime-type first. It then uses the +MIME_EXTENSIONS+ hash to
+  # choose the corresponding file extension. You can override this hash
+  # by passing in a <tt>:mime_extensions</tt> option to +file_column+.
+  #
+  # The mime-type of the uploaded file is determined with the following steps:
+  #
+  # 1. Run the external "file" utility. You can specify the full path to
+  #    the executable in the <tt>:file_exec</tt> option or set this option
+  #    to +nil+ to disable this step
+  #
+  # 2. If the file utility couldn't determine the mime-type or the utility was not
+  #    present, the content-type provided by the user's browser is used
+  #    as a fallback.
+  #
+  # == Custom Storage Directories
+  #
+  # FileColumn's storage location is determined in the following way. All
+  # files are saved below the so-called "root_path" directory, which defaults to
+  # "RAILS_ROOT/public". For every file_column, you can set a separte "store_dir"
+  # option. It defaults to "model_name/attribute_name".
+  # 
+  # Files will always be stored in sub-directories of the store_dir path. The
+  # subdirectory is named after the instance's +id+ attribute for a saved model,
+  # or "tmp/<randomkey>" for unsaved models.
+  #
+  # You can specify a custom root_path by setting the <tt>:root_path</tt> option.
+  # 
+  # You can specify a custom storage_dir by setting the <tt>:storage_dir</tt> option.
+  #
+  # For setting a static storage_dir that doesn't change with respect to a particular
+  # instance, you assign <tt>:storage_dir</tt> a String representing a directory
+  # as an absolute path.
+  #
+  # If you need more fine-grained control over the storage directory, you
+  # can use the name of a callback-method as a symbol for the
+  # <tt>:store_dir</tt> option. This method has to be defined as an
+  # instance method in your model. It will be called without any arguments
+  # whenever the storage directory for an uploaded file is needed. It should return
+  # a String representing a directory relativeo to root_path.
+  #
+  # Uploaded files for unsaved models objects will be stored in a temporary
+  # directory. By default this directory will be a "tmp" directory in
+  # your <tt>:store_dir</tt>. You can override this via the
+  # <tt>:tmp_base_dir</tt> option.
+  module ClassMethods
+
+    # default mapping of mime-types to file extensions. FileColumn will try to
+    # rename a file to the correct extension if it detects a known mime-type
+    MIME_EXTENSIONS = {
+      "image/gif" => "gif",
+      "image/jpeg" => "jpg",
+      "image/pjpeg" => "jpg",
+      "image/x-png" => "png",
+      "image/jpg" => "jpg",
+      "image/png" => "png",
+      "application/x-shockwave-flash" => "swf",
+      "application/pdf" => "pdf",
+      "application/pgp-signature" => "sig",
+      "application/futuresplash" => "spl",
+      "application/msword" => "doc",
+      "application/postscript" => "ps",
+      "application/x-bittorrent" => "torrent",
+      "application/x-dvi" => "dvi",
+      "application/x-gzip" => "gz",
+      "application/x-ns-proxy-autoconfig" => "pac",
+      "application/x-shockwave-flash" => "swf",
+      "application/x-tgz" => "tar.gz",
+      "application/x-tar" => "tar",
+      "application/zip" => "zip",
+      "audio/mpeg" => "mp3",
+      "audio/x-mpegurl" => "m3u",
+      "audio/x-ms-wma" => "wma",
+      "audio/x-ms-wax" => "wax",
+      "audio/x-wav" => "wav",
+      "image/x-xbitmap" => "xbm",             
+      "image/x-xpixmap" => "xpm",             
+      "image/x-xwindowdump" => "xwd",             
+      "text/css" => "css",             
+      "text/html" => "html",                          
+      "text/javascript" => "js",
+      "text/plain" => "txt",
+      "text/xml" => "xml",
+      "video/mpeg" => "mpeg",
+      "video/quicktime" => "mov",
+      "video/x-msvideo" => "avi",
+      "video/x-ms-asf" => "asf",
+      "video/x-ms-wmv" => "wmv"
+    }
+
+    EXTENSIONS = Set.new MIME_EXTENSIONS.values
+    EXTENSIONS.merge %w(jpeg)
+
+    # default options. You can override these with +file_column+'s +options+ parameter
+    DEFAULT_OPTIONS = {
+      :root_path => File.join(RAILS_ROOT, "public"),
+      :web_root => "",
+      :mime_extensions => MIME_EXTENSIONS,
+      :extensions => EXTENSIONS,
+      :fix_file_extensions => true,
+      :permissions => 0644,
+
+      # path to the unix "file" executbale for
+      # guessing the content-type of files
+      :file_exec => "file" 
+    }
+    
+    # handle the +attr+ attribute as a "file-upload" column, generating additional methods as explained
+    # above. You should pass the attribute's name as a symbol, like this:
+    #
+    #   file_column :image
+    #
+    # You can pass in an options hash that overrides the options
+    # in +DEFAULT_OPTIONS+.
+    def file_column(attr, options={})
+      options = DEFAULT_OPTIONS.merge(options) if options
+      
+      my_options = FileColumn::init_options(options, 
+                                            Inflector.underscore(self.name).to_s,
+                                            attr.to_s)
+      
+      state_attr = "@#{attr}_state".to_sym
+      state_method = "#{attr}_state".to_sym
+      
+      define_method state_method do
+        result = instance_variable_get state_attr
+        if result.nil?
+          result = FileColumn::create_state(self, attr.to_s)
+          instance_variable_set state_attr, result
+        end
+        result
+      end
+      
+      private state_method
+      
+      define_method attr do |*args|
+        send(state_method).absolute_path *args
+      end
+      
+      define_method "#{attr}_relative_path" do |*args|
+        send(state_method).relative_path *args
+      end
+
+      define_method "#{attr}_dir" do
+        send(state_method).absolute_dir
+      end
+
+      define_method "#{attr}_relative_dir" do
+        send(state_method).relative_dir
+      end
+
+      define_method "#{attr}=" do |file|
+        state = send(state_method).assign(file)
+        instance_variable_set state_attr, state
+        if state.options[:after_upload] and state.just_uploaded?
+          state.options[:after_upload].each do |sym|
+            self.send sym
+          end
+        end
+      end
+      
+      define_method "#{attr}_temp" do
+        send(state_method).temp_path
+      end
+      
+      define_method "#{attr}_temp=" do |temp_path|
+        instance_variable_set state_attr, send(state_method).assign_temp(temp_path)
+      end
+      
+      after_save_method = "#{attr}_after_save".to_sym
+      
+      define_method after_save_method do
+        instance_variable_set state_attr, send(state_method).after_save
+      end
+      
+      after_save after_save_method
+      
+      after_destroy_method = "#{attr}_after_destroy".to_sym
+      
+      define_method after_destroy_method do
+        send(state_method).after_destroy
+      end
+      after_destroy after_destroy_method
+      
+      define_method "#{attr}_just_uploaded?" do
+        send(state_method).just_uploaded?
+      end
+
+      # this creates a closure keeping a reference to my_options
+      # right now that's the only way we store the options. We
+      # might use a class attribute as well
+      define_method "#{attr}_options" do
+        my_options
+      end
+
+      private after_save_method, after_destroy_method
+
+      FileColumn::MagickExtension::file_column(self, attr, my_options) if options[:magick]
+    end
+    
+  end
+  
+  private
+  
+  def self.generate_temp_name
+    now = Time.now
+    "#{now.to_i}.#{now.usec}.#{Process.pid}"
+  end
+  
+  def self.sanitize_filename(filename)
+    filename = File.basename(filename.gsub("\\", "/")) # work-around for IE
+    filename.gsub!(/[^a-zA-Z0-9\.\-\+_]/,"_")
+    filename = "_#{filename}" if filename =~ /^\.+$/
+    filename = "unnamed" if filename.size == 0
+    filename
+  end
+  
+end
+
+

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_column.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_column_helper.rb
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_column_helper.rb?rev=759091&view=auto
==============================================================================
--- labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_column_helper.rb (added)
+++ labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_column_helper.rb Fri Mar 27 10:32:54 2009
@@ -0,0 +1,150 @@
+# This module contains helper methods for displaying and uploading files
+# for attributes created by +FileColumn+'s +file_column+ method. It will be
+# automatically included into ActionView::Base, thereby making this module's
+# methods available in all your views.
+module FileColumnHelper
+  
+  # Use this helper to create an upload field for a file_column attribute. This will generate
+  # an additional hidden field to keep uploaded files during form-redisplays. For example,
+  # when called with
+  #
+  #   <%= file_column_field("entry", "image") %>
+  #
+  # the following HTML will be generated (assuming the form is redisplayed and something has
+  # already been uploaded):
+  #
+  #   <input type="hidden" name="entry[image_temp]" value="..." />
+  #   <input type="file" name="entry[image]" />
+  #
+  # You can use the +option+ argument to pass additional options to the file-field tag.
+  #
+  # Be sure to set the enclosing form's encoding to 'multipart/form-data', by
+  # using something like this:
+  #
+  #    <%= form_tag {:action => "create", ...}, :multipart => true %>
+  def file_column_field(object, method, options={})
+    result = ActionView::Helpers::InstanceTag.new(object.dup, method.to_s+"_temp", self).to_input_field_tag("hidden", {})
+    result << ActionView::Helpers::InstanceTag.new(object.dup, method, self).to_input_field_tag("file", options)
+  end
+  
+  # Creates an URL where an uploaded file can be accessed. When called for an Entry object with
+  # id 42 (stored in <tt>@entry</tt>) like this
+  #
+  #   <%= url_for_file_column(@entry, "image")
+  #
+  # the following URL will be produced, assuming the file "test.png" has been stored in
+  # the "image"-column of an Entry object stored in <tt>@entry</tt>:
+  #
+  #  /entry/image/42/test.png
+  #
+  # This will produce a valid URL even for temporary uploaded files, e.g. files where the object
+  # they are belonging to has not been saved in the database yet.
+  #
+  # The URL produces, although starting with a slash, will be relative
+  # to your app's root. If you pass it to one rails' +image_tag+
+  # helper, rails will properly convert it to an absolute
+  # URL. However, this will not be the case, if you create a link with
+  # the +link_to+ helper. In this case, you can pass <tt>:absolute =>
+  # true</tt> to +options+, which will make sure, the generated URL is
+  # absolute on your server.  Examples:
+  #
+  #    <%= image_tag url_for_file_column(@entry, "image") %>
+  #    <%= link_to "Download", url_for_file_column(@entry, "image", :absolute => true) %>
+  #
+  # If there is currently no uploaded file stored in the object's column this method will
+  # return +nil+.
+  def url_for_file_column(object, method, options=nil)
+    case object
+    when String, Symbol
+      object = instance_variable_get("@#{object.to_s}")
+    end
+
+    # parse options
+    subdir = nil
+    absolute = false
+    if options
+      case options
+      when Hash
+        subdir = options[:subdir]
+        absolute = options[:absolute]
+      when String, Symbol
+        subdir = options
+      end
+    end
+    
+    relative_path = object.send("#{method}_relative_path", subdir)
+    return nil unless relative_path
+
+    url = ""
+    url << request.relative_url_root.to_s if absolute
+    url << "/"
+    url << object.send("#{method}_options")[:base_url] << "/"
+    url << relative_path
+  end
+
+  # Same as +url_for_file_colum+ but allows you to access different versions
+  # of the image that have been processed by RMagick.
+  #
+  # If your +options+ parameter is non-nil this will
+  # access a different version of an image that will be produced by
+  # RMagick. You can use the following types for +options+:
+  #
+  # * a <tt>:symbol</tt> will select a version defined in the model
+  #   via FileColumn::Magick's <tt>:versions</tt> feature.
+  # * a <tt>geometry_string</tt> will dynamically create an
+  #   image resized as specified by <tt>geometry_string</tt>. The image will
+  #   be stored so that it does not have to be recomputed the next time the
+  #   same version string is used.
+  # * <tt>some_hash</tt> will dynamically create an image
+  #   that is created according to the options in <tt>some_hash</tt>. This
+  #   accepts exactly the same options as Magick's version feature.
+  #
+  # The version produced by RMagick will be stored in a special sub-directory.
+  # The directory's name will be derived from the options you specified
+  # (via a hash function) but if you want
+  # to set it yourself, you can use the <tt>:name => name</tt> option.
+  #
+  # Examples:
+  #
+  #    <%= url_for_image_column @entry, "image", "640x480" %>
+  #
+  # will produce an URL like this
+  #
+  #    /entry/image/42/bdn19n/filename.jpg
+  #    # "640x480".hash.abs.to_s(36) == "bdn19n"
+  #
+  # and
+  #
+  #    <%= url_for_image_column @entry, "image", 
+  #       :size => "50x50", :crop => "1:1", :name => "thumb" %>
+  #
+  # will produce something like this:
+  #
+  #    /entry/image/42/thumb/filename.jpg
+  #
+  # Hint: If you are using the same geometry string / options hash multiple times, you should
+  # define it in a helper to stay with DRY. Another option is to define it in the model via
+  # FileColumn::Magick's <tt>:versions</tt> feature and then refer to it via a symbol.
+  #
+  # The URL produced by this method is relative to your application's root URL,
+  # although it will start with a slash.
+  # If you pass this URL to rails' +image_tag+ helper, it will be converted to an
+  # absolute URL automatically.
+  # If there is currently no image uploaded, or there is a problem while loading
+  # the image this method will return +nil+.
+  def url_for_image_column(object, method, options=nil)
+    case object
+    when String, Symbol
+      object = instance_variable_get("@#{object.to_s}")
+    end
+    subdir = nil
+    if options
+      subdir = object.send("#{method}_state").create_magick_version_if_needed(options)
+    end
+    if subdir.nil?
+      nil
+    else
+      url_for_file_column(object, method, subdir)
+    end
+  end
+end

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_column_helper.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_compat.rb
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_compat.rb?rev=759091&view=auto
==============================================================================
--- labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_compat.rb (added)
+++ labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_compat.rb Fri Mar 27 10:32:54 2009
@@ -0,0 +1,28 @@
+module FileColumn
+
+  # This bit of code allows you to pass regular old files to
+  # file_column.  file_column depends on a few extra methods that the
+  # CGI uploaded file class adds.  We will add the equivalent methods
+  # to file objects if necessary by extending them with this module. This
+  # avoids opening up the standard File class which might result in
+  # naming conflicts.
+
+  module FileCompat # :nodoc:
+    def original_filename
+      File.basename(path)
+    end
+    
+    def size
+      File.size(path)
+    end
+    
+    def local_path
+      path
+    end
+    
+    def content_type
+      nil
+    end
+  end
+end
+

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/lib/file_compat.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/lib/magick_file_column.rb
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/lib/magick_file_column.rb?rev=759091&view=auto
==============================================================================
--- labs/consite/trunk/conferences/vendor/plugins/file_column/lib/magick_file_column.rb (added)
+++ labs/consite/trunk/conferences/vendor/plugins/file_column/lib/magick_file_column.rb Fri Mar 27 10:32:54 2009
@@ -0,0 +1,260 @@
+module FileColumn # :nodoc:
+  
+  class BaseUploadedFile # :nodoc:
+    def transform_with_magick
+      if needs_transform?
+        begin
+          img = ::Magick::Image::read(absolute_path).first
+        rescue ::Magick::ImageMagickError
+          if options[:magick][:image_required]
+            @magick_errors ||= []
+            @magick_errors << "invalid image"
+          end
+          return
+        end
+        
+        if options[:magick][:versions]
+          options[:magick][:versions].each_pair do |version, version_options|
+            next if version_options[:lazy]
+            dirname = version_options[:name]
+            FileUtils.mkdir File.join(@dir, dirname)
+            transform_image(img, version_options, absolute_path(dirname))
+          end
+        end
+        if options[:magick][:size] or options[:magick][:crop] or options[:magick][:transformation] or options[:magick][:attributes]
+          transform_image(img, options[:magick], absolute_path)
+        end
+
+        GC.start
+      end
+    end
+
+    def create_magick_version_if_needed(version)
+      # RMagick might not have been loaded so far.
+      # We do not want to require it on every call of this method
+      # as this might be fairly expensive, so we just try if ::Magick
+      # exists and require it if not.
+      begin 
+        ::Magick 
+      rescue NameError
+        require 'RMagick'
+      end
+
+      if version.is_a?(Symbol)
+        version_options = options[:magick][:versions][version]
+      else
+        version_options = MagickExtension::process_options(version)
+      end
+
+      unless File.exists?(absolute_path(version_options[:name]))
+        begin
+          img = ::Magick::Image::read(absolute_path).first
+        rescue ::Magick::ImageMagickError
+          # we might be called directly from the view here
+          # so we just return nil if we cannot load the image
+          return nil
+        end
+        dirname = version_options[:name]
+        FileUtils.mkdir File.join(@dir, dirname)
+        transform_image(img, version_options, absolute_path(dirname))
+      end
+
+      version_options[:name]
+    end
+
+    attr_reader :magick_errors
+    
+    def has_magick_errors?
+      @magick_errors and !@magick_errors.empty?
+    end
+
+    private
+    
+    def needs_transform?
+      options[:magick] and just_uploaded? and 
+        (options[:magick][:size] or options[:magick][:versions] or options[:magick][:transformation] or options[:magick][:attributes])
+    end
+
+    def transform_image(img, img_options, dest_path)
+      begin
+        if img_options[:transformation]
+          if img_options[:transformation].is_a?(Symbol)
+            img = @instance.send(img_options[:transformation], img)
+          else
+            img = img_options[:transformation].call(img)
+          end
+        end
+        if img_options[:crop]
+          dx, dy = img_options[:crop].split(':').map { |x| x.to_f }
+          w, h = (img.rows * dx / dy), (img.columns * dy / dx)
+          img = img.crop(::Magick::CenterGravity, [img.columns, w].min, 
+                         [img.rows, h].min, true)
+        end
+
+        if img_options[:size]
+          img = img.change_geometry(img_options[:size]) do |c, r, i|
+            i.resize(c, r)
+          end
+        end
+      ensure
+        img.write(dest_path) do
+          if img_options[:attributes]
+            img_options[:attributes].each_pair do |property, value| 
+              self.send "#{property}=", value
+            end
+          end
+        end
+        File.chmod options[:permissions], dest_path
+      end
+    end
+  end
+
+  # If you are using file_column to upload images, you can
+  # directly process the images with RMagick,
+  # a ruby extension
+  # for accessing the popular imagemagick libraries. You can find
+  # more information about RMagick at http://rmagick.rubyforge.org.
+  #
+  # You can control what to do by adding a <tt>:magick</tt> option
+  # to your options hash. All operations are performed immediately
+  # after a new file is assigned to the file_column attribute (i.e.,
+  # when a new file has been uploaded).
+  #
+  # == Resizing images
+  #
+  # To resize the uploaded image according to an imagemagick geometry
+  # string, just use the <tt>:size</tt> option:
+  #
+  #    file_column :image, :magick => {:size => "800x600>"}
+  #
+  # If the uploaded file cannot be loaded by RMagick, file_column will
+  # signal a validation error for the corresponding attribute. If you
+  # want to allow non-image files to be uploaded in a column that uses
+  # the <tt>:magick</tt> option, you can set the <tt>:image_required</tt>
+  # attribute to +false+:
+  #
+  #    file_column :image, :magick => {:size => "800x600>",
+  #                                    :image_required => false }
+  #
+  # == Multiple versions
+  #
+  # You can also create additional versions of your image, for example
+  # thumb-nails, like this:
+  #    file_column :image, :magick => {:versions => {
+  #         :thumb => {:size => "50x50"},
+  #         :medium => {:size => "640x480>"}
+  #       }
+  #
+  # These versions will be stored in separate sub-directories, named like the
+  # symbol you used to identify the version. So in the previous example, the
+  # image versions will be stored in "thumb", "screen" and "widescreen"
+  # directories, resp. 
+  # A name different from the symbol can be set via the <tt>:name</tt> option.
+  #
+  # These versions can be accessed via FileColumnHelper's +url_for_image_column+
+  # method like this:
+  #
+  #    <%= url_for_image_column "entry", "image", :thumb %>
+  #
+  # == Cropping images
+  #
+  # If you wish to crop your images with a size ratio before scaling
+  # them according to your version geometry, you can use the :crop directive.
+  #    file_column :image, :magick => {:versions => {
+  #         :square => {:crop => "1:1", :size => "50x50", :name => "thumb"},
+  #         :screen => {:crop => "4:3", :size => "640x480>"},
+  #         :widescreen => {:crop => "16:9", :size => "640x360!"},
+  #       }
+  #    }
+  #
+  # == Custom attributes
+  #
+  # To change some of the image properties like compression level before they
+  # are saved you can set the <tt>:attributes</tt> option.
+  # For a list of available attributes go to http://www.simplesystems.org/RMagick/doc/info.html
+  # 
+  #     file_column :image, :magick => { :attributes => { :quality => 30 } }
+  # 
+  # == Custom transformations
+  #
+  # To perform custom transformations on uploaded images, you can pass a
+  # callback to file_column:
+  #    file_column :image, :magick => 
+  #       Proc.new { |image| image.quantize(256, Magick::GRAYColorspace) }
+  #
+  # The callback you give, receives one argument, which is an instance
+  # of Magick::Image, the RMagick image class. It should return a transformed
+  # image. Instead of passing a <tt>Proc</tt> object, you can also give a
+  # <tt>Symbol</tt>, the name of an instance method of your model.
+  #
+  # Custom transformations can be combined via the standard :size and :crop
+  # features, by using the :transformation option:
+  #   file_column :image, :magick => {
+  #      :transformation => Proc.new { |image| ... },
+  #      :size => "640x480"
+  #    }
+  #
+  # In this case, the standard resizing operations will be performed after the
+  # custom transformation.
+  #
+  # Of course, custom transformations can be used in versions, as well.
+  #
+  # <b>Note:</b> You'll need the
+  # RMagick extension being installed  in order to use file_column's
+  # imagemagick integration.
+  module MagickExtension
+
+    def self.file_column(klass, attr, options) # :nodoc:
+      require 'RMagick'
+      options[:magick] = process_options(options[:magick],false) if options[:magick]
+      if options[:magick][:versions]
+        options[:magick][:versions].each_pair do |name, value|
+          options[:magick][:versions][name] = process_options(value, name.to_s)
+        end
+      end
+      state_method = "#{attr}_state".to_sym
+      after_assign_method = "#{attr}_magick_after_assign".to_sym
+      
+      klass.send(:define_method, after_assign_method) do
+        self.send(state_method).transform_with_magick
+      end
+      
+      options[:after_upload] ||= []
+      options[:after_upload] << after_assign_method
+      
+      klass.validate do |record|
+        state = record.send(state_method)
+        if state.has_magick_errors?
+          state.magick_errors.each do |error|
+            record.errors.add attr, error
+          end
+        end
+      end
+    end
+
+    
+    def self.process_options(options,create_name=true)
+      case options
+      when String then options = {:size => options}
+      when Proc, Symbol then options = {:transformation => options }
+      end
+      if options[:geometry]
+        options[:size] = options.delete(:geometry)
+      end
+      options[:image_required] = true unless options.key?(:image_required)
+      if options[:name].nil? and create_name
+        if create_name == true
+          hash = 0
+          for key in [:size, :crop]
+            hash = hash ^ options[key].hash if options[key]
+          end
+          options[:name] = hash.abs.to_s(36)
+        else
+          options[:name] = create_name
+        end
+      end
+      options
+    end
+
+  end
+end

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/lib/magick_file_column.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/lib/rails_file_column.rb
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/lib/rails_file_column.rb?rev=759091&view=auto
==============================================================================
--- labs/consite/trunk/conferences/vendor/plugins/file_column/lib/rails_file_column.rb (added)
+++ labs/consite/trunk/conferences/vendor/plugins/file_column/lib/rails_file_column.rb Fri Mar 27 10:32:54 2009
@@ -0,0 +1,19 @@
+# require this file from your "config/environment.rb" (after rails has been loaded)
+# to integrate the file_column extension into rails.
+
+require 'file_column'
+require 'file_column_helper'
+
+
+module ActiveRecord # :nodoc:
+  class Base # :nodoc:
+    # make file_column method available in all active record decendants
+    include FileColumn
+  end
+end
+
+module ActionView # :nodoc:
+  class Base # :nodoc:
+    include FileColumnHelper
+  end
+end

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/lib/rails_file_column.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/lib/test_case.rb
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/lib/test_case.rb?rev=759091&view=auto
==============================================================================
--- labs/consite/trunk/conferences/vendor/plugins/file_column/lib/test_case.rb (added)
+++ labs/consite/trunk/conferences/vendor/plugins/file_column/lib/test_case.rb Fri Mar 27 10:32:54 2009
@@ -0,0 +1,124 @@
+require 'test/unit'
+
+# Add the methods +upload+, the <tt>setup_file_fixtures</tt> and
+# <tt>teardown_file_fixtures</tt> to the class Test::Unit::TestCase.
+class Test::Unit::TestCase
+  # Returns a +Tempfile+ object as it would have been generated on file upload.
+  # Use this method to create the parameters when emulating form posts with 
+  # file fields.
+  #
+  # === Example:
+  #
+  #    def test_file_column_post
+  #      entry = { :title => 'foo', :file => upload('/tmp/foo.txt')}
+  #      post :upload, :entry => entry
+  #  
+  #      # ...
+  #    end
+  #
+  # === Parameters
+  #
+  # * <tt>path</tt> The path to the file to upload.
+  # * <tt>content_type</tt> The MIME type of the file. If it is <tt>:guess</tt>,
+  #   the method will try to guess it.
+  def upload(path, content_type=:guess, type=:tempfile)
+    if content_type == :guess
+      case path
+      when /\.jpg$/ then content_type = "image/jpeg"
+      when /\.png$/ then content_type = "image/png"
+      else content_type = nil
+      end
+    end
+    uploaded_file(path, content_type, File.basename(path), type)
+  end
+  
+  # Copies the fixture files from "RAILS_ROOT/test/fixtures/file_column" into
+  # the temporary storage directory used for testing
+  # ("RAILS_ROOT/test/tmp/file_column"). Call this method in your
+  # <tt>setup</tt> methods to get the file fixtures (images, for example) into
+  # the directory used by file_column in testing.
+  #
+  # Note that the files and directories in the "fixtures/file_column" directory 
+  # must have the same structure as you would expect in your "/public" directory
+  # after uploading with FileColumn.
+  #
+  # For example, the directory structure could look like this:
+  #
+  #   test/fixtures/file_column/
+  #   `-- container
+  #       |-- first_image
+  #       |   |-- 1
+  #       |   |   `-- image1.jpg
+  #       |   `-- tmp
+  #       `-- second_image
+  #           |-- 1
+  #           |   `-- image2.jpg
+  #           `-- tmp
+  #
+  # Your fixture file for this one "container" class fixture could look like this:
+  #
+  #   first:
+  #     id:           1
+  #     first_image:  image1.jpg
+  #     second_image: image1.jpg
+  #
+  # A usage example:
+  #
+  #  def setup
+  #    setup_fixture_files
+  #
+  #    # ...
+  #  end
+  def setup_fixture_files
+    tmp_path = File.join(RAILS_ROOT, "test", "tmp", "file_column")
+    file_fixtures = Dir.glob File.join(RAILS_ROOT, "test", "fixtures", "file_column", "*")
+    
+    FileUtils.mkdir_p tmp_path unless File.exists?(tmp_path)
+    FileUtils.cp_r file_fixtures, tmp_path
+  end
+  
+  # Removes the directory "RAILS_ROOT/test/tmp/file_column/" so the files
+  # copied on test startup are removed. Call this in your unit test's +teardown+
+  # method.
+  #
+  # A usage example:
+  #
+  #  def teardown
+  #    teardown_fixture_files
+  #
+  #    # ...
+  #  end
+  def teardown_fixture_files
+    FileUtils.rm_rf File.join(RAILS_ROOT, "test", "tmp", "file_column")
+  end
+  
+  private
+  
+  def uploaded_file(path, content_type, filename, type=:tempfile) # :nodoc:
+    if type == :tempfile
+      t = Tempfile.new(File.basename(filename))
+      FileUtils.copy_file(path, t.path)
+    else
+      if path
+        t = StringIO.new(IO.read(path))
+      else
+        t = StringIO.new
+      end
+    end
+    (class << t; self; end).class_eval do
+      alias local_path path if type == :tempfile
+      define_method(:local_path) { "" } if type == :stringio
+      define_method(:original_filename) {filename}
+      define_method(:content_type) {content_type}
+    end
+    return t
+  end
+end
+
+# If we are running in the "test" environment, we overwrite the default 
+# settings for FileColumn so that files are not uploaded into "/public/"
+# in tests but rather into the directory "/test/tmp/file_column".
+if RAILS_ENV == "test"
+  FileColumn::ClassMethods::DEFAULT_OPTIONS[:root_path] =
+    File.join(RAILS_ROOT, "test", "tmp", "file_column")
+end

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/lib/test_case.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/lib/validations.rb
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/lib/validations.rb?rev=759091&view=auto
==============================================================================
--- labs/consite/trunk/conferences/vendor/plugins/file_column/lib/validations.rb (added)
+++ labs/consite/trunk/conferences/vendor/plugins/file_column/lib/validations.rb Fri Mar 27 10:32:54 2009
@@ -0,0 +1,112 @@
+module FileColumn
+  module Validations #:nodoc:
+    
+    def self.append_features(base)
+      super
+      base.extend(ClassMethods)
+    end
+
+    # This module contains methods to create validations of uploaded files. All methods
+    # in this module will be included as class methods into <tt>ActiveRecord::Base</tt>
+    # so that you can use them in your models like this:
+    #
+    #    class Entry < ActiveRecord::Base
+    #      file_column :image
+    #      validates_filesize_of :image, :in => 0..1.megabyte
+    #    end
+    module ClassMethods
+      EXT_REGEXP = /\.([A-z0-9]+)$/
+    
+      # This validates the file type of one or more file_columns.  A list of file columns
+      # should be given followed by an options hash.
+      #
+      # Required options:
+      # * <tt>:in</tt> => list of extensions or mime types. If mime types are used they
+      #   will be mapped into an extension via FileColumn::ClassMethods::MIME_EXTENSIONS.
+      #
+      # Examples:
+      #     validates_file_format_of :field, :in => ["gif", "png", "jpg"]
+      #     validates_file_format_of :field, :in => ["image/jpeg"]
+      def validates_file_format_of(*attrs)
+      
+        options = attrs.pop if attrs.last.is_a?Hash
+        raise ArgumentError, "Please include the :in option." if !options || !options[:in]
+        options[:in] = [options[:in]] if options[:in].is_a?String
+        raise ArgumentError, "Invalid value for option :in" unless options[:in].is_a?Array
+      
+        validates_each(attrs, options) do |record, attr, value|
+          unless value.blank?
+            mime_extensions = record.send("#{attr}_options")[:mime_extensions]
+            extensions = options[:in].map{|o| mime_extensions[o] || o }
+            record.errors.add attr, "is not a valid format." unless extensions.include?(value.scan(EXT_REGEXP).flatten.first)
+          end
+        end
+      
+      end
+    
+      # This validates the file size of one or more file_columns.  A list of file columns
+      # should be given followed by an options hash.
+      #
+      # Required options:
+      # * <tt>:in</tt> => A size range.  Note that you can use ActiveSupport's
+      #   numeric extensions for kilobytes, etc.
+      #
+      # Examples:
+      #    validates_filesize_of :field, :in => 0..100.megabytes
+      #    validates_filesize_of :field, :in => 15.kilobytes..1.megabyte
+      def validates_filesize_of(*attrs)  
+      
+        options = attrs.pop if attrs.last.is_a?Hash
+        raise ArgumentError, "Please include the :in option." if !options || !options[:in]
+        raise ArgumentError, "Invalid value for option :in" unless options[:in].is_a?Range
+      
+        validates_each(attrs, options) do |record, attr, value|
+          unless value.blank?
+            size = File.size(value)
+            record.errors.add attr, "is smaller than the allowed size range." if size < options[:in].first
+            record.errors.add attr, "is larger than the allowed size range." if size > options[:in].last
+          end
+        end
+      
+      end 
+
+      IMAGE_SIZE_REGEXP = /^(\d+)x(\d+)$/
+
+      # Validates the image size of one or more file_columns.  A list of file columns
+      # should be given followed by an options hash. The validation will pass
+      # if both image dimensions (rows and columns) are at least as big as
+      # given in the <tt>:min</tt> option.
+      #
+      # Required options:
+      # * <tt>:min</tt> => minimum image dimension string, in the format NNxNN
+      #   (columns x rows).
+      #
+      # Example:
+      #    validates_image_size :field, :min => "1200x1800"
+      #
+      # This validation requires RMagick to be installed on your system
+      # to check the image's size.
+      def validates_image_size(*attrs)      
+        options = attrs.pop if attrs.last.is_a?Hash
+        raise ArgumentError, "Please include a :min option." if !options || !options[:min]
+        minimums = options[:min].scan(IMAGE_SIZE_REGEXP).first.collect{|n| n.to_i} rescue []
+        raise ArgumentError, "Invalid value for option :min (should be 'XXxYY')" unless minimums.size == 2
+
+        require 'RMagick'
+
+        validates_each(attrs, options) do |record, attr, value|
+          unless value.blank?
+            begin
+              img = ::Magick::Image::read(value).first
+              record.errors.add('image', "is too small, must be at least #{minimums[0]}x#{minimums[1]}") if ( img.rows < minimums[1] || img.columns < minimums[0] )
+            rescue ::Magick::ImageMagickError
+              record.errors.add('image', "invalid image")
+            end
+            img = nil
+            GC.start
+          end
+        end
+      end
+    end
+  end
+end

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/lib/validations.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/test/abstract_unit.rb
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/test/abstract_unit.rb?rev=759091&view=auto
==============================================================================
--- labs/consite/trunk/conferences/vendor/plugins/file_column/test/abstract_unit.rb (added)
+++ labs/consite/trunk/conferences/vendor/plugins/file_column/test/abstract_unit.rb Fri Mar 27 10:32:54 2009
@@ -0,0 +1,63 @@
+require 'test/unit'
+require 'rubygems'
+require 'active_support'
+require 'active_record'
+require 'action_view'
+require File.dirname(__FILE__) + '/connection'
+require 'stringio'
+
+RAILS_ROOT = File.dirname(__FILE__)
+RAILS_ENV = ""
+
+$: << "../lib"
+
+require 'file_column'
+require 'file_compat'
+require 'validations'
+require 'test_case'
+
+# do not use the file executable normally in our tests as
+# it may not be present on the machine we are running on
+FileColumn::ClassMethods::DEFAULT_OPTIONS = 
+  FileColumn::ClassMethods::DEFAULT_OPTIONS.merge({:file_exec => nil})
+
+class ActiveRecord::Base
+    include FileColumn
+    include FileColumn::Validations
+end
+
+
+class RequestMock
+  attr_accessor :relative_url_root
+
+  def initialize
+    @relative_url_root = ""
+  end
+end
+
+class Test::Unit::TestCase
+
+  def assert_equal_paths(expected_path, path)
+    assert_equal normalize_path(expected_path), normalize_path(path)
+  end
+
+
+  private
+  
+  def normalize_path(path)
+    Pathname.new(path).realpath
+  end
+
+  def clear_validations
+    [:validate, :validate_on_create, :validate_on_update].each do |attr|
+        Entry.write_inheritable_attribute attr, []
+        Movie.write_inheritable_attribute attr, []
+      end
+  end
+
+  def file_path(filename)
+    File.expand_path("#{File.dirname(__FILE__)}/fixtures/#{filename}")
+  end
+
+  alias_method :f, :file_path
+end

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/test/abstract_unit.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/test/connection.rb
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/test/connection.rb?rev=759091&view=auto
==============================================================================
--- labs/consite/trunk/conferences/vendor/plugins/file_column/test/connection.rb (added)
+++ labs/consite/trunk/conferences/vendor/plugins/file_column/test/connection.rb Fri Mar 27 10:32:54 2009
@@ -0,0 +1,17 @@
+print "Using native MySQL\n"
+require 'logger'
+
+ActiveRecord::Base.logger = Logger.new("debug.log")
+
+db = 'file_column_test'
+
+ActiveRecord::Base.establish_connection(
+  :adapter  => "mysql",
+  :host     => "localhost",
+  :username => "rails",
+  :password => "",
+  :database => db,
+  :socket => "/var/run/mysqld/mysqld.sock"
+)
+
+load File.dirname(__FILE__) + "/fixtures/schema.rb"

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/test/connection.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/test/file_column_helper_test.rb
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/test/file_column_helper_test.rb?rev=759091&view=auto
==============================================================================
--- labs/consite/trunk/conferences/vendor/plugins/file_column/test/file_column_helper_test.rb (added)
+++ labs/consite/trunk/conferences/vendor/plugins/file_column/test/file_column_helper_test.rb Fri Mar 27 10:32:54 2009
@@ -0,0 +1,97 @@
+require File.dirname(__FILE__) + '/abstract_unit'
+require File.dirname(__FILE__) + '/fixtures/entry'
+
+class UrlForFileColumnTest < Test::Unit::TestCase
+  include FileColumnHelper
+
+  def setup
+    Entry.file_column :image
+    @request = RequestMock.new
+  end
+
+  def test_url_for_file_column_with_temp_entry
+    @e = Entry.new(:image => upload(f("skanthak.png")))
+    url = url_for_file_column("e", "image")
+    assert_match %r{^/entry/image/tmp/\d+(\.\d+)+/skanthak.png$}, url
+  end
+
+  def test_url_for_file_column_with_saved_entry
+    @e = Entry.new(:image => upload(f("skanthak.png")))
+    assert @e.save
+
+    url = url_for_file_column("e", "image")
+    assert_equal "/entry/image/#{@e.id}/skanthak.png", url
+  end
+
+  def test_url_for_file_column_works_with_symbol
+    @e = Entry.new(:image => upload(f("skanthak.png")))
+    assert @e.save
+
+    url = url_for_file_column(:e, :image)
+    assert_equal "/entry/image/#{@e.id}/skanthak.png", url
+  end
+  
+  def test_url_for_file_column_works_with_object
+    e = Entry.new(:image => upload(f("skanthak.png")))
+    assert e.save
+
+    url = url_for_file_column(e, "image")
+    assert_equal "/entry/image/#{e.id}/skanthak.png", url
+  end
+
+  def test_url_for_file_column_should_return_nil_on_no_uploaded_file
+    e = Entry.new
+    assert_nil url_for_file_column(e, "image")
+  end
+
+  def test_url_for_file_column_without_extension
+    e = Entry.new
+    e.image = uploaded_file(file_path("kerb.jpg"), "something/unknown", "local_filename")
+    assert e.save
+    assert_equal "/entry/image/#{e.id}/local_filename", url_for_file_column(e, "image")
+  end
+end
+
+class UrlForFileColumnTest < Test::Unit::TestCase
+  include FileColumnHelper
+  include ActionView::Helpers::AssetTagHelper
+  include ActionView::Helpers::TagHelper
+  include ActionView::Helpers::UrlHelper
+
+  def setup
+    Entry.file_column :image
+
+    # mock up some request data structures for AssetTagHelper
+    @request = RequestMock.new
+    @request.relative_url_root = "/foo/bar"
+    @controller = self
+  end
+
+  def request
+    @request
+  end
+
+  IMAGE_URL = %r{^/foo/bar/entry/image/.+/skanthak.png$}
+  def test_with_image_tag
+    e = Entry.new(:image => upload(f("skanthak.png")))
+    html = image_tag url_for_file_column(e, "image")
+    url = html.scan(/src=\"(.+)\"/).first.first
+
+    assert_match IMAGE_URL, url
+  end
+
+  def test_with_link_to_tag
+    e = Entry.new(:image => upload(f("skanthak.png")))
+    html = link_to "Download", url_for_file_column(e, "image", :absolute => true)
+    url = html.scan(/href=\"(.+)\"/).first.first
+    
+    assert_match IMAGE_URL, url
+  end
+
+  def test_relative_url_root_not_modified
+    e = Entry.new(:image => upload(f("skanthak.png")))
+    url_for_file_column(e, "image", :absolute => true)
+    
+    assert_equal "/foo/bar", @request.relative_url_root
+  end
+end

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/test/file_column_helper_test.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/test/file_column_test.rb
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/test/file_column_test.rb?rev=759091&view=auto
==============================================================================
--- labs/consite/trunk/conferences/vendor/plugins/file_column/test/file_column_test.rb (added)
+++ labs/consite/trunk/conferences/vendor/plugins/file_column/test/file_column_test.rb Fri Mar 27 10:32:54 2009
@@ -0,0 +1,650 @@
+require File.dirname(__FILE__) + '/abstract_unit'
+
+require File.dirname(__FILE__) + '/fixtures/entry'
+
+class Movie < ActiveRecord::Base
+end
+
+
+class FileColumnTest < Test::Unit::TestCase
+  
+  def setup
+    # we define the file_columns here so that we can change
+    # settings easily in a single test
+
+    Entry.file_column :image
+    Entry.file_column :file
+    Movie.file_column :movie
+
+    clear_validations
+  end
+  
+  def teardown
+    FileUtils.rm_rf File.dirname(__FILE__)+"/public/entry/"
+    FileUtils.rm_rf File.dirname(__FILE__)+"/public/movie/"
+    FileUtils.rm_rf File.dirname(__FILE__)+"/public/my_store_dir/"
+  end
+  
+  def test_column_write_method
+    assert Entry.new.respond_to?("image=")
+  end
+  
+  def test_column_read_method
+    assert Entry.new.respond_to?("image")
+  end
+  
+  def test_sanitize_filename
+    assert_equal "test.jpg", FileColumn::sanitize_filename("test.jpg")
+    assert FileColumn::sanitize_filename("../../very_tricky/foo.bar") !~ /[\\\/]/, "slashes not removed"
+    assert_equal "__foo", FileColumn::sanitize_filename('`*foo')
+    assert_equal "foo.txt", FileColumn::sanitize_filename('c:\temp\foo.txt')
+    assert_equal "_.", FileColumn::sanitize_filename(".")
+  end
+  
+  def test_default_options
+    e = Entry.new
+    assert_match %r{/public/entry/image}, e.image_options[:store_dir]
+    assert_match %r{/public/entry/image/tmp}, e.image_options[:tmp_base_dir]
+  end
+  
+  def test_assign_without_save_with_tempfile
+    do_test_assign_without_save(:tempfile)
+  end
+  
+  def test_assign_without_save_with_stringio
+    do_test_assign_without_save(:stringio)
+  end
+  
+  def do_test_assign_without_save(upload_type)
+    e = Entry.new
+    e.image = uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png", upload_type)
+    assert e.image.is_a?(String), "#{e.image.inspect} is not a String"
+    assert File.exists?(e.image)
+    assert FileUtils.identical?(e.image, file_path("skanthak.png"))
+  end
+  
+  def test_filename_preserved
+    e = Entry.new
+    e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "local_filename.jpg")
+    assert_equal "local_filename.jpg", File.basename(e.image)
+  end
+  
+  def test_filename_stored_in_attribute
+    e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg"))
+    assert_equal "kerb.jpg", e["image"]
+  end
+  
+  def test_extension_added
+    e = Entry.new
+    e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "local_filename")
+    assert_equal "local_filename.jpg", File.basename(e.image)
+    assert_equal "local_filename.jpg", e["image"]
+  end
+
+  def test_no_extension_without_content_type
+    e = Entry.new
+    e.image = uploaded_file(file_path("kerb.jpg"), "something/unknown", "local_filename")
+    assert_equal "local_filename", File.basename(e.image)
+    assert_equal "local_filename", e["image"]
+  end
+
+  def test_extension_unknown_type
+    e = Entry.new
+    e.image = uploaded_file(file_path("kerb.jpg"), "not/known", "local_filename")
+    assert_equal "local_filename", File.basename(e.image)
+    assert_equal "local_filename", e["image"]
+  end
+
+  def test_extension_unknown_type_with_extension
+    e = Entry.new
+    e.image = uploaded_file(file_path("kerb.jpg"), "not/known", "local_filename.abc")
+    assert_equal "local_filename.abc", File.basename(e.image)
+    assert_equal "local_filename.abc", e["image"]
+  end
+
+  def test_extension_corrected
+    e = Entry.new
+    e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "local_filename.jpeg")
+    assert_equal "local_filename.jpg", File.basename(e.image)
+    assert_equal "local_filename.jpg", e["image"]
+  end
+
+  def test_double_extension
+    e = Entry.new
+    e.image = uploaded_file(file_path("kerb.jpg"), "application/x-tgz", "local_filename.tar.gz")
+    assert_equal "local_filename.tar.gz", File.basename(e.image)
+    assert_equal "local_filename.tar.gz", e["image"]
+  end
+
+  FILE_UTILITY = "/usr/bin/file"
+
+  def test_get_content_type_with_file
+    Entry.file_column :image, :file_exec => FILE_UTILITY
+
+    # run this test only if the machine we are running on
+    # has the file utility installed
+    if File.executable?(FILE_UTILITY)
+      e = Entry.new
+      file = FileColumn::TempUploadedFile.new(e, "image")
+      file.instance_variable_set :@dir, File.dirname(file_path("kerb.jpg"))
+      file.instance_variable_set :@filename, File.basename(file_path("kerb.jpg"))
+      
+      assert_equal "image/jpeg", file.get_content_type
+    else
+      puts "Warning: Skipping test_get_content_type_with_file test as '#{options[:file_exec]}' does not exist"
+    end
+  end
+
+  def test_fix_extension_with_file
+    Entry.file_column :image, :file_exec => FILE_UTILITY
+
+    # run this test only if the machine we are running on
+    # has the file utility installed
+    if File.executable?(FILE_UTILITY)
+      e = Entry.new(:image => uploaded_file(file_path("skanthak.png"), "", "skanthak.jpg"))
+      
+      assert_equal "skanthak.png", File.basename(e.image)
+    else
+      puts "Warning: Skipping test_fix_extension_with_file test as '#{options[:file_exec]}' does not exist"
+    end
+  end
+
+  def test_do_not_fix_file_extensions
+    Entry.file_column :image, :fix_file_extensions => false
+
+    e = Entry.new(:image => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb"))
+
+    assert_equal "kerb", File.basename(e.image)
+  end
+
+  def test_correct_extension
+    e = Entry.new
+    file = FileColumn::TempUploadedFile.new(e, "image")
+    
+    assert_equal "filename.jpg", file.correct_extension("filename.jpeg","jpg")
+    assert_equal "filename.tar.gz", file.correct_extension("filename.jpg","tar.gz")
+    assert_equal "filename.jpg", file.correct_extension("filename.tar.gz","jpg")
+    assert_equal "Protokoll_01.09.2005.doc", file.correct_extension("Protokoll_01.09.2005","doc")
+    assert_equal "strange.filenames.exist.jpg", file.correct_extension("strange.filenames.exist","jpg")
+    assert_equal "another.strange.one.jpg", file.correct_extension("another.strange.one.png","jpg")
+  end
+
+  def test_assign_with_save
+    e = Entry.new
+    e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")
+    tmp_file_path = e.image
+    assert e.save
+    assert File.exists?(e.image)
+    assert FileUtils.identical?(e.image, file_path("kerb.jpg"))
+    assert_equal "#{e.id}/kerb.jpg", e.image_relative_path
+    assert !File.exists?(tmp_file_path), "temporary file '#{tmp_file_path}' not removed"
+    assert !File.exists?(File.dirname(tmp_file_path)), "temporary directory '#{File.dirname(tmp_file_path)}' not removed"
+    
+    local_path = e.image
+    e = Entry.find(e.id)
+    assert_equal local_path, e.image
+  end
+
+  def test_dir_methods
+    e = Entry.new
+    e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")
+    e.save
+    
+    assert_equal_paths File.join(RAILS_ROOT, "public", "entry", "image", e.id.to_s), e.image_dir
+    assert_equal File.join(e.id.to_s), e.image_relative_dir
+  end
+
+  def test_store_dir_callback
+    Entry.file_column :image, {:store_dir => :my_store_dir}
+    e = Entry.new
+
+    e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")    
+    assert e.save
+    
+    assert_equal_paths File.join(RAILS_ROOT, "public", "my_store_dir", e.id), e.image_dir   
+  end
+
+  def test_tmp_dir_with_store_dir_callback
+    Entry.file_column :image, {:store_dir => :my_store_dir}
+    e = Entry.new
+    e.image = upload(f("kerb.jpg"))
+    
+    assert_equal File.expand_path(File.join(RAILS_ROOT, "public", "my_store_dir", "tmp")), File.expand_path(File.join(e.image_dir,".."))
+  end
+
+  def test_invalid_store_dir_callback
+    Entry.file_column :image, {:store_dir => :my_store_dir_doesnt_exit}    
+    e = Entry.new
+    assert_raise(ArgumentError) {
+      e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")
+      e.save
+    }
+  end
+
+  def test_subdir_parameter
+    e = Entry.new
+    assert_nil e.image("thumb")
+    assert_nil e.image_relative_path("thumb")
+    assert_nil e.image(nil)
+
+    e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")
+    
+    assert_equal "kerb.jpg", File.basename(e.image("thumb"))
+    assert_equal "kerb.jpg", File.basename(e.image_relative_path("thumb"))
+
+    assert_equal File.join(e.image_dir,"thumb","kerb.jpg"), e.image("thumb")
+    assert_match %r{/thumb/kerb\.jpg$}, e.image_relative_path("thumb") 
+
+    assert_equal e.image, e.image(nil)
+    assert_equal e.image_relative_path, e.image_relative_path(nil)
+  end
+
+  def test_cleanup_after_destroy
+    e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg"))
+    assert e.save
+    local_path = e.image
+    assert File.exists?(local_path)
+    assert e.destroy
+    assert !File.exists?(local_path), "'#{local_path}' still exists although entry was destroyed"
+    assert !File.exists?(File.dirname(local_path))
+  end
+  
+  def test_keep_tmp_image
+    e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg"))
+    e.validation_should_fail = true
+    assert !e.save, "e should not save due to validation errors"
+    assert File.exists?(local_path = e.image)
+    image_temp = e.image_temp
+    e = Entry.new("image_temp" => image_temp)
+    assert_equal local_path, e.image
+    assert e.save
+    assert FileUtils.identical?(e.image, file_path("kerb.jpg"))
+  end
+  
+  def test_keep_tmp_image_with_existing_image
+    e = Entry.new("image" =>uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg"))
+    assert e.save
+    assert File.exists?(local_path = e.image)
+    e = Entry.find(e.id)
+    e.image = uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png")
+    e.validation_should_fail = true
+    assert !e.save
+    temp_path = e.image_temp
+    e = Entry.find(e.id)
+    e.image_temp = temp_path
+    assert e.save
+    
+    assert FileUtils.identical?(e.image, file_path("skanthak.png"))
+    assert !File.exists?(local_path), "old image has not been deleted"
+  end
+  
+  def test_replace_tmp_image_temp_first
+    do_test_replace_tmp_image([:image_temp, :image])
+  end
+  
+  def test_replace_tmp_image_temp_last
+    do_test_replace_tmp_image([:image, :image_temp])
+  end
+  
+  def do_test_replace_tmp_image(order)
+    e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg"))
+    e.validation_should_fail = true
+    assert !e.save
+    image_temp = e.image_temp
+    temp_path = e.image
+    new_img = uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png")
+    e = Entry.new
+    for method in order
+      case method
+      when :image_temp then e.image_temp = image_temp
+      when :image then e.image = new_img
+      end
+    end
+    assert e.save
+    assert FileUtils.identical?(e.image, file_path("skanthak.png")), "'#{e.image}' is not the expected 'skanthak.png'"
+    assert !File.exists?(temp_path), "temporary file '#{temp_path}' is not cleaned up"
+    assert !File.exists?(File.dirname(temp_path)), "temporary directory not cleaned up"
+    assert e.image_just_uploaded?
+  end
+  
+  def test_replace_image_on_saved_object
+    e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg"))
+    assert e.save
+    old_file = e.image
+    e = Entry.find(e.id)
+    e.image = uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png")
+    assert e.save
+    assert FileUtils.identical?(file_path("skanthak.png"), e.image)
+    assert old_file != e.image
+    assert !File.exists?(old_file), "'#{old_file}' has not been cleaned up"
+  end
+  
+  def test_edit_without_touching_image
+    e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg"))
+    assert e.save
+    e = Entry.find(e.id)
+    assert e.save
+    assert FileUtils.identical?(file_path("kerb.jpg"), e.image)
+  end
+  
+  def test_save_without_image
+    e = Entry.new
+    assert e.save
+    e.reload
+    assert_nil e.image
+  end
+  
+  def test_delete_saved_image
+    e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg"))
+    assert e.save
+    local_path = e.image
+    e.image = nil
+    assert_nil e.image
+    assert File.exists?(local_path), "file '#{local_path}' should not be deleted until transaction is saved"
+    assert e.save
+    assert_nil e.image
+    assert !File.exists?(local_path)
+    e.reload
+    assert e["image"].blank?
+    e = Entry.find(e.id)
+    assert_nil e.image
+  end
+  
+  def test_delete_tmp_image
+    e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg"))
+    local_path = e.image
+    e.image = nil
+    assert_nil e.image
+    assert e["image"].blank?
+    assert !File.exists?(local_path)
+  end
+  
+  def test_delete_nonexistant_image
+    e = Entry.new
+    e.image = nil
+    assert e.save
+    assert_nil e.image
+  end
+
+  def test_delete_image_on_non_null_column
+    e = Entry.new("file" => upload(f("skanthak.png")))
+    assert e.save
+
+    local_path = e.file
+    assert File.exists?(local_path)
+    e.file = nil
+    assert e.save
+    assert !File.exists?(local_path)
+  end
+
+  def test_ie_filename
+    e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", 'c:\images\kerb.jpg'))
+    assert e.image_relative_path =~ /^tmp\/[\d\.]+\/kerb\.jpg$/, "relative path '#{e.image_relative_path}' was not as expected"
+    assert File.exists?(e.image)
+  end
+  
+  def test_just_uploaded?
+    e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", 'c:\images\kerb.jpg'))
+    assert e.image_just_uploaded?
+    assert e.save
+    assert e.image_just_uploaded?
+    
+    e = Entry.new("image" => uploaded_file(file_path("kerb.jpg"), "image/jpeg", 'kerb.jpg'))
+    temp_path = e.image_temp
+    e = Entry.new("image_temp" => temp_path)
+    assert !e.image_just_uploaded?
+    assert e.save
+    assert !e.image_just_uploaded?
+  end
+  
+  def test_empty_tmp
+    e = Entry.new
+    e.image_temp = ""
+    assert_nil e.image
+  end
+  
+  def test_empty_tmp_with_image
+    e = Entry.new
+    e.image_temp = ""
+    e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", 'c:\images\kerb.jpg')
+    local_path = e.image
+    assert File.exists?(local_path)
+    e.image_temp = ""
+    assert local_path, e.image
+  end
+  
+  def test_empty_filename
+    e = Entry.new
+    assert_equal "", e["file"]
+    assert_nil e.file
+    assert_nil e["image"]
+    assert_nil e.image
+  end
+  
+  def test_with_two_file_columns
+    e = Entry.new
+    e.image = uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg")
+    e.file = uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png")
+    assert e.save
+    assert_match %{/entry/image/}, e.image
+    assert_match %{/entry/file/}, e.file
+    assert FileUtils.identical?(e.image, file_path("kerb.jpg"))
+    assert FileUtils.identical?(e.file, file_path("skanthak.png"))
+  end
+  
+  def test_with_two_models
+    e = Entry.new(:image => uploaded_file(file_path("kerb.jpg"), "image/jpeg", "kerb.jpg"))
+    m = Movie.new(:movie => uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png"))
+    assert e.save
+    assert m.save
+    assert_match %{/entry/image/}, e.image
+    assert_match %{/movie/movie/}, m.movie
+    assert FileUtils.identical?(e.image, file_path("kerb.jpg"))
+    assert FileUtils.identical?(m.movie, file_path("skanthak.png"))
+  end
+
+  def test_no_file_uploaded
+    e = Entry.new
+    assert_nothing_raised { e.image =
+        uploaded_file(nil, "application/octet-stream", "", :stringio) }
+    assert_equal nil, e.image
+  end
+
+  # when safari submits a form where no file has been
+  # selected, it does not transmit a content-type and
+  # the result is an empty string ""
+  def test_no_file_uploaded_with_safari
+    e = Entry.new
+    assert_nothing_raised { e.image = "" }
+    assert_equal nil, e.image
+  end
+
+  def test_detect_wrong_encoding
+    e = Entry.new
+    assert_raise(TypeError) { e.image ="img42.jpg" }
+  end
+
+  def test_serializable_before_save
+    e = Entry.new
+    e.image = uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png")
+    assert_nothing_raised { 
+      flash = Marshal.dump(e) 
+      e = Marshal.load(flash)
+    }
+    assert File.exists?(e.image)
+  end
+
+  def test_should_call_after_upload_on_new_upload
+    Entry.file_column :image, :after_upload => [:after_assign]
+    e = Entry.new
+    e.image = upload(f("skanthak.png"))
+    assert e.after_assign_called?
+  end
+
+  def test_should_call_user_after_save_on_save
+    e = Entry.new(:image => upload(f("skanthak.png")))
+    assert e.save
+    
+    assert_kind_of FileColumn::PermanentUploadedFile, e.send(:image_state)
+    assert e.after_save_called?
+  end
+
+
+  def test_assign_standard_files
+    e = Entry.new
+    e.image = File.new(file_path('skanthak.png'))
+    
+    assert_equal 'skanthak.png', File.basename(e.image)
+    assert FileUtils.identical?(file_path('skanthak.png'), e.image)
+    
+    assert e.save
+  end
+
+
+  def test_validates_filesize
+    Entry.validates_filesize_of :image, :in => 50.kilobytes..100.kilobytes
+
+    e = Entry.new(:image => upload(f("kerb.jpg")))
+    assert e.save
+
+    e.image = upload(f("skanthak.png"))
+    assert !e.save
+    assert e.errors.invalid?("image")
+  end
+
+  def test_validates_file_format_simple
+    e = Entry.new(:image => upload(f("skanthak.png")))
+    assert e.save
+    
+    Entry.validates_file_format_of :image, :in => ["jpg"]
+
+    e.image = upload(f("kerb.jpg"))
+    assert e.save
+
+    e.image = upload(f("mysql.sql"))
+    assert !e.save
+    assert e.errors.invalid?("image")
+    
+  end
+
+  def test_validates_image_size
+    Entry.validates_image_size :image, :min => "640x480"
+    
+    e = Entry.new(:image => upload(f("kerb.jpg")))
+    assert e.save
+
+    e = Entry.new(:image => upload(f("skanthak.png")))
+    assert !e.save
+    assert e.errors.invalid?("image")
+  end
+
+  def do_permission_test(uploaded_file, permissions=0641)
+    Entry.file_column :image, :permissions => permissions
+    
+    e = Entry.new(:image => uploaded_file)
+    assert e.save
+
+    assert_equal permissions, (File.stat(e.image).mode & 0777)
+  end
+
+  def test_permissions_with_small_file
+    do_permission_test upload(f("skanthak.png"), :guess, :stringio)
+  end
+
+  def test_permission_with_big_file
+    do_permission_test upload(f("kerb.jpg"))
+  end
+
+  def test_permission_that_overrides_umask
+    do_permission_test upload(f("skanthak.png"), :guess, :stringio), 0666
+    do_permission_test upload(f("kerb.jpg")), 0666
+  end
+
+  def test_access_with_empty_id
+    # an empty id might happen after a clone or through some other
+    # strange event. Since we would create a path that contains nothing
+    # where the id would have been, we should fail fast with an exception
+    # in this case
+    
+    e = Entry.new(:image => upload(f("skanthak.png")))
+    assert e.save
+    id = e.id
+
+    e = Entry.find(id)
+    
+    e["id"] = ""
+    assert_raise(RuntimeError) { e.image }
+    
+    e = Entry.find(id)
+    e["id"] = nil
+    assert_raise(RuntimeError) { e.image }
+  end
+end
+
+# Tests for moving temp dir to permanent dir
+class FileColumnMoveTest < Test::Unit::TestCase
+  
+  def setup
+    # we define the file_columns here so that we can change
+    # settings easily in a single test
+
+    Entry.file_column :image
+    
+  end
+  
+  def teardown
+    FileUtils.rm_rf File.dirname(__FILE__)+"/public/entry/"
+  end
+
+  def test_should_move_additional_files_from_tmp
+    e = Entry.new
+    e.image = uploaded_file(file_path("skanthak.png"), "image/png", "skanthak.png")
+    FileUtils.cp file_path("kerb.jpg"), File.dirname(e.image)
+    assert e.save
+    dir = File.dirname(e.image)
+    assert File.exists?(File.join(dir, "skanthak.png"))
+    assert File.exists?(File.join(dir, "kerb.jpg"))
+  end
+
+  def test_should_move_direcotries_on_save
+    e = Entry.new(:image => upload(f("skanthak.png")))
+    
+    FileUtils.mkdir( e.image_dir+"/foo" )
+    FileUtils.cp file_path("kerb.jpg"), e.image_dir+"/foo/kerb.jpg"
+    
+    assert e.save
+
+    assert File.exists?(e.image)
+    assert File.exists?(File.dirname(e.image)+"/foo/kerb.jpg")
+  end
+
+  def test_should_overwrite_dirs_with_files_on_reupload
+    e = Entry.new(:image => upload(f("skanthak.png")))
+
+    FileUtils.mkdir( e.image_dir+"/kerb.jpg")
+    FileUtils.cp file_path("kerb.jpg"), e.image_dir+"/kerb.jpg/"
+    assert e.save
+
+    e.image = upload(f("kerb.jpg"))
+    assert e.save
+
+    assert File.file?(e.image_dir+"/kerb.jpg")
+  end
+
+  def test_should_overwrite_files_with_dirs_on_reupload
+    e = Entry.new(:image => upload(f("skanthak.png")))
+
+    assert e.save
+    assert File.file?(e.image_dir+"/skanthak.png")
+
+    e.image = upload(f("kerb.jpg"))
+    FileUtils.mkdir(e.image_dir+"/skanthak.png")
+    
+    assert e.save
+    assert File.file?(e.image_dir+"/kerb.jpg")
+    assert !File.file?(e.image_dir+"/skanthak.png")
+    assert File.directory?(e.image_dir+"/skanthak.png")
+  end
+
+end
+

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/test/file_column_test.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/test/file_column_test.rb
------------------------------------------------------------------------------
    svn:executable = *

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/entry.rb
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/entry.rb?rev=759091&view=auto
==============================================================================
--- labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/entry.rb (added)
+++ labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/entry.rb Fri Mar 27 10:32:54 2009
@@ -0,0 +1,32 @@
+class Entry < ActiveRecord::Base
+  attr_accessor :validation_should_fail
+
+  def validate
+    errors.add("image","some stupid error") if @validation_should_fail
+  end
+  
+  def after_assign
+    @after_assign_called = true
+  end
+  
+  def after_assign_called?
+    @after_assign_called
+  end
+  
+  def after_save
+    @after_save_called = true
+  end
+
+  def after_save_called?
+    @after_save_called
+  end
+
+  def my_store_dir
+    # not really dynamic but at least it could be...
+    "my_store_dir"
+  end
+
+  def load_image_with_rmagick(path)
+    Magick::Image::read(path).first
+  end
+end

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/entry.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/invalid-image.jpg
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/invalid-image.jpg?rev=759091&view=auto
==============================================================================
Binary file - no diff available.

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/invalid-image.jpg
------------------------------------------------------------------------------
    svn:mime-type = image/jpeg

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/kerb.jpg
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/kerb.jpg?rev=759091&view=auto
==============================================================================
Binary file - no diff available.

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/kerb.jpg
------------------------------------------------------------------------------
    svn:mime-type = image/jpeg

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/mysql.sql
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/mysql.sql?rev=759091&view=auto
==============================================================================
--- labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/mysql.sql (added)
+++ labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/mysql.sql Fri Mar 27 10:32:54 2009
@@ -0,0 +1,25 @@
+-- MySQL dump 9.11
+--
+-- Host: localhost    Database: file_column_test
+-- ------------------------------------------------------
+-- Server version	4.0.24
+
+--
+-- Table structure for table `entries`
+--
+
+DROP TABLE IF EXISTS entries;
+CREATE TABLE entries (
+  id int(11) NOT NULL auto_increment,
+  image varchar(200) default NULL,
+  file varchar(200) NOT NULL,
+  PRIMARY KEY  (id)
+) TYPE=MyISAM;
+
+DROP TABLE IF EXISTS movies;
+CREATE TABLE movies (
+  id int(11) NOT NULL auto_increment,
+  movie varchar(200) default NULL,
+  PRIMARY KEY  (id)
+) TYPE=MyISAM;
+

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/mysql.sql
------------------------------------------------------------------------------
    svn:eol-style = native

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/schema.rb
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/schema.rb?rev=759091&view=auto
==============================================================================
--- labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/schema.rb (added)
+++ labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/schema.rb Fri Mar 27 10:32:54 2009
@@ -0,0 +1,10 @@
+ActiveRecord::Schema.define do
+  create_table :entries, :force => true do |t|
+    t.column :image, :string, :null => true
+    t.column :file, :string, :null => false
+  end
+  
+  create_table :movies, :force => true do |t|
+    t.column :movie, :string
+  end
+end

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/schema.rb
------------------------------------------------------------------------------
    svn:eol-style = native

Added: labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/skanthak.png
URL: http://svn.apache.org/viewvc/labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/skanthak.png?rev=759091&view=auto
==============================================================================
Binary file - no diff available.

Propchange: labs/consite/trunk/conferences/vendor/plugins/file_column/test/fixtures/skanthak.png
------------------------------------------------------------------------------
    svn:mime-type = image/png



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@labs.apache.org
For additional commands, e-mail: commits-help@labs.apache.org