Предварительно рассчитанная контрольная сумма md5 файла в качестве имени файла - PullRequest
4 голосов
/ 24 октября 2011

Используя средство загрузки для несущей на носителе, пытаясь обеспечить уникальность загруженных изображений, используя контрольную сумму md5 в качестве имени файла

похоже, что я делаю что-то не так

модель определяется как:

class Image < ActiveRecord::Base
  attr_accessible :description, :img
  mount_uploader :img, ImageUploader

Мой код загрузчика выглядит следующим образом:

class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file

def store_dir
  "images/#{filename[0,2]}"
end

def md5
  @md5 ||= ::Digest::MD5.file(current_path).hexdigest
end

def filename
  @name ||= "#{md5}#{::File.extname(current_path)}" if super
end

во-первых, я подозреваю, что этот подход вызывает вычисление контрольной суммы каждый раз, когда запись изображения запрашивается для отображения

, во-вторых, послезапись изображения сохранена, каждый из img.original_filename img.filename img.path img.current_path кажется неопределенным со следующей ошибкой:

You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
app/uploaders/image_uploader.rb:17:in `store_dir'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:43:in `store_path'
carrierwave (0.5.7) lib/carrierwave/storage/file.rb:41:in `retrieve!'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:95:in `block in retrieve_from_store!'
carrierwave (0.5.7) lib/carrierwave/uploader/callbacks.rb:17:in `with_callbacks'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:94:in `retrieve_from_store!'
carrierwave (0.5.7) lib/carrierwave/mount.rb:311:in `uploader'

приветствуется любая помощь или подсказка

UPD:

изменил загрузчик следующим образом:

def store_dir
  "images/#{model.img_identifier[0,2]}"
end

def filename
  @name ||= "#{md5}#{::File.extname(current_path)}"
end

protected
def md5
  var = :"@#{mounted_as}_md5"
  model.instance_variable_get(var) or model.instance_variable_set(var, ::Digest::MD5.file(current_path).hexdigest)
end

current_path, кажется, ссылается на полный путь переданного в форму временного файла, таким образом, действительный для извлечения расширенияи дайджест вычисления img_identifier означает сохранение результирующего имени файла и, таким образом, становится действительным для извлечения префикса для нашего store_dir, все еще не уверенного, вызвано ли какое-либо предупреждение при таком подходе

, также все еще не убедительногоо том, как должна выполняться проверка уникальности файла

UPD:

Я добавил этот before_validation обратный вызов в моем классе модели:

validates_uniqueness_of :checksum
before_validation :assign_checksum

def assign_checksum
  self.checksum = img.md5 if img.present? and img_changed?
end

где checksum - это отдельное строковое поле в таблице базы данных моей модели
оно является довольно избыточным, поскольку оно в целом дублирует поле img, но я все еще не могу понятьспособ проверки уникальности самого img.

UPD:

Таким образом отошли от избыточности дБ.В моей модели:

validate :img_uniqueness

def img_uniqueness
  errors.add :img, "Image already exists in database" if Image.where(:img => self.img.filename).first
end

теперь нет необходимости в checksum поле

Ответы [ 4 ]

3 голосов
/ 23 октября 2013

Это может помочь: Как использовать файл MD5 в качестве имени файла https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Use-file%60s-MD5-as-filename

А может быть

Как: использовать дайджест файла (например, MD5, SHA-1) в качестве пути к файлу https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Use-file's-digest-(e.g.-MD5,-SHA-1)-as-file-path

0 голосов
/ 24 октября 2011

Я бы рассмотрел вики способ уникальности имени файла,

https://github.com/jnicklas/carrierwave/wiki/How-to%3A-Create-random-and-unique-filenames-for-all-versioned-files

0 голосов
/ 14 ноября 2012

Добавьте поле md5hash в вашу модель, затем добавьте следующий код в вашу модель:

  before_validation :compute_hash
  validates_uniqueness_of :md5hash, :on => :create

  def compute_hash
    self.md5hash = Digest::MD5.hexdigest(self.file.read)
  end

Это должно сработать.

0 голосов
/ 24 октября 2011

1.Когда вы определяете store_dir .. имя файла, кажется, NIL !!

Это кажется немедленной ошибкой - попробуйте оператор puts, чтобы распечатать, какое имя файла установлено в..

Если имя файла NIL, вы увидите ошибку, которую видите:

You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
app/uploaders/image_uploader.rb:17:in `store_dir'

ПРИМЕЧАНИЕ:

вы переопределяете и имя файла и store_dir ... и используете имя файла внутри определения store_dir ... Здесь может быть проблема типа "курица и яйцо" ... лучше проверить это

store_dir должен быть просто каталогом, например, /somewhere/on/your/disk/images

filename должен быть просто именем файла без пути, например 24371592d9ea16625854ed68ac4b5846 или 24371592d9ea16625854ed68ac4b5846.jpg

например, проверьте, как эти два используются в коде в store.rb (в конце ниже)

Вопрос:

с использованием filename[0,2] - вы используетекаталоги с двухбуквенным префиксом суммы MD5 для хранения изображений?

2.Примечание: Что такое current_path ?? Кажется неправильным.Должен быть путь + имя файла, а не просто путь

3.Проверьте второй фрагмент кода (ниже) из store.rb

Похоже, вы установили store_dir в относительную директорию - это очень хрупко и подвержено ошибкам .. может быть, лучше установитьэто абсолютный путь (начиная с '/')

4.Попробуйте установить имя файла и store_dir в константы для отладки

Так же, как проверка работоспособности, она работает, когда вы делаете это:

def store_dir
   '/tmp/'      # from the source code, it looks like this needs to start with '/' !!
end

def filename
   'my_uploaded_file'
end

, которая должна работать перед установкой имени файла на сумму MD5..


Из исходного кода: (0.5.7)

lib / carrierwave / storage / file.rb

lib /carrierwave / uploader / store.rb

Вы можете переопределить CarrierWave :: Uploader :: Store # filename , чтобы указать имя файла по вашему выбору (см. исходный код):

Переопределение в CarrierWave :: Uploader :: Base должно работать, так как «Store» включен в «Base»;это переопределяет имя файла по умолчанию.

С store.rb

  # Override this in your Uploader to change the filename.                                                                                                                                                                                                                   
  #                                                                                                                                                                                                                                                                          
  # Be careful using record ids as filenames. If the filename is stored in the database                                                                                                                                                                                      
  # the record id will be nil when the filename is set. Don't use record ids unless you                                                                                                                                                                                      
  # understand this limitation.                                                                                                                                                                                                                                              
  #                                                                                                                                                                                                                                                                          
  # Do not use the version_name in the filename, as it will prevent versions from being                                                                                                                                                                                      
  # loaded correctly.                                                                                                                                                                                                                                                        
  #                                                                                                                                                                                                                                                                          
  # === Returns                                                                                                                                                                                                                                                              
  #                                                                                                                                                                                                                                                                          
  # [String] a filename                                                                                                                                                                                                                                                      
  #                                                                                                                                                                                                                                                                          
  def filename
    @filename
  end

Вы также можете проверить кэшированное имя файла, вычислить сумму MD5 (или лучше сумму SHA1 ), а затем используйте результат для именования файла.


С store.rb

  # Calculates the path where the file should be stored. If +for_file+ is given, it will be                                                                                                                                                                                 
  # used as the filename, otherwise +CarrierWave::Uploader#filename+ is assumed.                                                                                                                                                                                            
  #                                                                                                                                                                                                                                                                         
  # === Parameters                                                                                                                                                                                                                                                          
  #                                                                                                                                                                                                                                                                         
  # [for_file (String)] name of the file <optional>                                                                                                                                                                                                                         
  #                                                                                                                                                                                                                                                                         
  # === Returns                                                                                                                                                                                                                                                             
  #                                                                                                                                                                                                                                                                         
  # [String] the store path                                                                                                                                                                                                                                                 
  #                                                                                                                                                                                                                                                                         
  def store_path(for_file=filename)   # DEFAULT argument is filename - you don't need to override both
    File.join([store_dir, full_filename(for_file)].compact)
  end

Равенство файла:

files_equal?( filename1, filename2 )

      return true if File.size(filename1) == File.size(filename2)

      # return MD5(filename1) == MD5(filename2)

      # as we assume that the filename == MD5 + some suffix :

      return File.basename(filename1) == File.basename(filename2)   # if names == MD5 are the same

end
...