carrierwaveuploader--carrie.../lib/carrierwave/uploader/cache.rb

208 lines
6.3 KiB
Ruby

module CarrierWave
class FormNotMultipart < UploadError
def message
"You tried to assign a String or a Pathname to an uploader, for security reasons, this is not allowed.\n\n If this is a file upload, please check that your upload form is multipart encoded."
end
end
class CacheCounter
@@counter = 0
def self.increment
@@counter += 1
end
end
##
# Generates a unique cache id for use in the caching system
#
# === Returns
#
# [String] a cache id in the format TIMEINT-PID-COUNTER-RND
#
def self.generate_cache_id
[Time.now.utc.to_i,
Process.pid,
'%04d' % (CarrierWave::CacheCounter.increment % 1000),
'%04d' % rand(9999)
].map(&:to_s).join('-')
end
module Uploader
module Cache
extend ActiveSupport::Concern
include CarrierWave::Uploader::Callbacks
include CarrierWave::Uploader::Configuration
module ClassMethods
##
# Removes cached files which are older than one day. You could call this method
# from a rake task to clean out old cached files.
#
# You can call this method directly on the module like this:
#
# CarrierWave.clean_cached_files!
#
# === Note
#
# This only works as long as you haven't done anything funky with your cache_dir.
# It's recommended that you keep cache files in one place only.
#
def clean_cached_files!(seconds=60*60*24)
cache_storage.new(CarrierWave::Uploader::Base.new).clean_cache!(seconds)
end
end
##
# Returns true if the uploader has been cached
#
# === Returns
#
# [Bool] whether the current file is cached
#
def cached?
@cache_id
end
##
# Caches the remotely stored file
#
# This is useful when about to process images. Most processing solutions
# require the file to be stored on the local filesystem.
#
def cache_stored_file!
cache!
end
def sanitized_file
_content = file.read
if _content.is_a?(File) # could be if storage is Fog
sanitized = CarrierWave::Storage::Fog.new(self).retrieve!(File.basename(_content.path))
else
sanitized = SanitizedFile.new :tempfile => StringIO.new(_content),
:filename => File.basename(path), :content_type => file.content_type
end
sanitized
end
##
# Returns a String which uniquely identifies the currently cached file for later retrieval
#
# === Returns
#
# [String] a cache name, in the format TIMEINT-PID-COUNTER-RND/filename.txt
#
def cache_name
File.join(cache_id, full_original_filename) if cache_id and original_filename
end
##
# Caches the given file. Calls process! to trigger any process callbacks.
#
# By default, cache!() uses copy_to(), which operates by copying the file
# to the cache, then deleting the original file. If move_to_cache() is
# overriden to return true, then cache!() uses move_to(), which simply
# moves the file to the cache. Useful for large files.
#
# === Parameters
#
# [new_file (File, IOString, Tempfile)] any kind of file object
#
# === Raises
#
# [CarrierWave::FormNotMultipart] if the assigned parameter is a string
#
def cache!(new_file = sanitized_file)
new_file = CarrierWave::SanitizedFile.new(new_file)
return if new_file.empty?
raise CarrierWave::FormNotMultipart if new_file.is_path? && ensure_multipart_form
self.cache_id = CarrierWave.generate_cache_id unless cache_id
@filename = new_file.filename
self.original_filename = new_file.filename
begin
# first, create a workfile on which we perform processings
if move_to_cache
@file = new_file.move_to(File.expand_path(workfile_path, root), permissions, directory_permissions)
else
@file = new_file.copy_to(File.expand_path(workfile_path, root), permissions, directory_permissions)
end
with_callbacks(:cache, @file) do
@file = cache_storage.cache!(@file)
end
ensure
FileUtils.rm_rf(workfile_path(''))
end
end
##
# Retrieves the file with the given cache_name from the cache.
#
# === Parameters
#
# [cache_name (String)] uniquely identifies a cache file
#
# === Raises
#
# [CarrierWave::InvalidParameter] if the cache_name is incorrectly formatted.
#
def retrieve_from_cache!(cache_name)
with_callbacks(:retrieve_from_cache, cache_name) do
self.cache_id, self.original_filename = cache_name.to_s.split('/', 2)
@filename = original_filename
@file = cache_storage.retrieve_from_cache!(full_filename(original_filename))
end
end
##
# Calculates the path where the cache file should be stored.
#
# === Parameters
#
# [for_file (String)] name of the file <optional>
#
# === Returns
#
# [String] the cache path
#
def cache_path(for_file=full_filename(original_filename))
File.join(*[cache_dir, @cache_id, for_file].compact)
end
private
def workfile_path(for_file=original_filename)
File.join(CarrierWave.tmp_path, @cache_id, version_name.to_s, for_file)
end
attr_reader :cache_id, :original_filename
# We can override the full_original_filename method in other modules
alias_method :full_original_filename, :original_filename
def cache_id=(cache_id)
# Earlier version used 3 part cache_id. Thus we should allow for
# the cache_id to have both 3 part and 4 part formats.
raise CarrierWave::InvalidParameter, "invalid cache id" unless cache_id =~ /\A[\d]+\-[\d]+(\-[\d]{4})?\-[\d]{4}\z/
@cache_id = cache_id
end
def original_filename=(filename)
raise CarrierWave::InvalidParameter, "invalid filename" if filename =~ CarrierWave::SanitizedFile.sanitize_regexp
@original_filename = filename
end
def cache_storage
@cache_storage ||= self.class.cache_storage.new(self)
end
end # Cache
end # Uploader
end # CarrierWave