require 'trailblazer/operation' require 'action_dispatch/http/upload' require 'tempfile' module Trailblazer # TODO: document: # to_hash # from_hash # initialize/tmp_dir class Operation::UploadedFile def initialize(uploaded, options={}) @uploaded = uploaded @options = options @tmp_dir = options[:tmp_dir] end def to_hash path = persist! hash = { filename: @uploaded.original_filename, type: @uploaded.content_type, tempfile_path: path } cleanup! hash end # Returns a ActionDispatch::Http::UploadedFile as if the upload was in the same request. def self.from_hash(hash) suffix = File.extname(hash[:filename]) # we need to create a Tempfile to make Http::UploadedFile work. tmp = Tempfile.new(["bla", suffix]) # always force file suffix to avoid problems with imagemagick etc. file = File.open(hash[:tempfile_path])# doesn't close automatically :( # fixme: introduce strategy (Tempfile:=>slow, File:=> hopefully less memory footprint) tmp.write(file.read) # DISCUSS: We need Tempfile.new() to avoid this slow and memory-consuming mechanics. file.close # TODO: can we test that? File.unlink(file) ActionDispatch::Http::UploadedFile.new(hash.merge(tempfile: tmp)) end private attr_reader :tmp_dir # convert Tempfile from Rails upload into persistent "temp" file so it is available in workers. def persist! path = @uploaded.path # original Tempfile path (from Rails). path = path_with_tmp_dir(path) path = path + "_trailblazer_upload" FileUtils.mv(@uploaded.path, path) # move Rails upload file into persistent `path`. path end def path_with_tmp_dir(path) return path unless tmp_dir # if tmp_dir set, create path in it. @with_tmp_dir = Tempfile.new(File.basename(path), tmp_dir) @with_tmp_dir.path # use Tempfile to create nested dirs (os-dependent.) end def delete!(file) file.close file.unlink # the Rails uploaded file is already unlinked since moved. end def cleanup! delete!(@uploaded.tempfile) if @uploaded.respond_to?(:tempfile) # this is Rails' uploaded file, not sure if we need to do that. in 3.2, we don't have UploadedFile#close, yet. delete!(@with_tmp_dir) if @with_tmp_dir # we used that file to create a tmp file path below tmp_dir. end end end