#-- # Copyright (C) 2007 Dimitrij Denissenko # Please read LICENSE document for more information. #++ class Attachment < ActiveRecord::Base include ActionView::Helpers::NumberHelper belongs_to :attachable, :polymorphic => true validates_presence_of :file, :max_size, :on => :create validates_presence_of :original_filename, :content_type after_create :write_physical_file before_destroy :delete_physical_file attr_accessor :file, :max_size cattr_accessor :storage_path @@storage_path = File.join(RAILS_ROOT, ATTACHMENTS_DIR) def self.max_size RetroCM[:general][:attachments][:max_size] end def self.parse(stream, max_size = nil) return nil if stream.blank? object = new(stream, max_size) object ? object : nil end def initialize(stream, max_size = nil) super(nil) stream ||= '' self.file = stream self.max_size = (max_size || self.class.max_size).kilobytes if stream.size > 0 && stream.respond_to?(:original_filename) && stream.respond_to?(:content_type) self.original_filename = sanitize_filename(file.original_filename) self.content_type = (file.content_type || '').strip if content_type.blank? or content_type == Mime::Map.unknown self.content_type = Mime::Map.mime_type(original_filename) || content_type end @available = true else @available = false end end def available? @available == true end def validate_on_create # verify attachment size if file.size < 1 self.errors.add_to_base('size is invalid') elsif max_size && file.size > max_size self.errors.add_to_base("size of #{number_to_human_size(file.size)} exceeds the maximum limit of #{number_to_human_size(max_size)}") end # verify that path is writable if !File.directory?(self.class.storage_path) || !File.writable?(self.class.storage_path) self.errors.add_to_base('upload is not permitted') end end def physical_filename self.new_record? ? nil : path_to(self.id) end def hash Digest::SHA1.hexdigest("+++#{self.id}---#{self.original_filename}+++") end def content if physical_filename return File.open(physical_filename, 'rb').read elsif file file.rewind return file.read else return '' end end def inline? self.content_type.match(%r{^text/}i) || self.content_type.match(%r{image/(png|jpg|jpeg|gif)}i) end def self.delete_orphaned Dir.glob(File.join(storage_path, '*')).each do |file| File.unlink(file) unless find_by_id(File.basename(file)) end end private def path_to(file_name) File.expand_path(File.join(self.class.storage_path, file_name.to_s)) end def write_physical_file raise "Cannot write file. #{self.class.name} is not saved yet!" unless physical_filename file.rewind File.open(physical_filename, 'wb') do |f| f.write(file.read) end end def sanitize_filename(value) just_filename = value.gsub(/^.*(\\|\/)/, '') filename = just_filename.gsub(/[^\w\.\-]/,'_') end def delete_physical_file begin File.unlink(self.physical_filename) return true rescue return false end end end