module CarrierWave # this is an internal class, used by CarrierWave::Mount so that # we don't pollute the model with a lot of methods. class Mounter # :nodoc: class Single < Mounter # :nodoc def identifier uploaders.first&.identifier end def temporary_identifier temporary_identifiers.first end end class Multiple < Mounter # :nodoc def identifier uploaders.map(&:identifier).presence end def temporary_identifier temporary_identifiers.presence end end def self.build(record, column) if record.class.uploader_options[column][:multiple] Multiple.new(record, column) else Single.new(record, column) end end attr_reader :column, :record, :remote_urls, :remove, :integrity_errors, :processing_errors, :download_errors attr_accessor :remote_request_headers, :uploader_options def initialize(record, column) @record = record @column = column @options = record.class.uploader_options[column] @download_errors = [] @processing_errors = [] @integrity_errors = [] @removed_uploaders = [] @added_uploaders = [] end def uploader_class record.class.uploaders[column] end def blank_uploader uploader_class.new(record, column) end def identifiers uploaders.map(&:identifier) end def read_identifiers [record.read_uploader(serialization_column)].flatten.reject(&:blank?) end def uploaders @uploaders ||= read_identifiers.map do |identifier| uploader = blank_uploader uploader.retrieve_from_store!(identifier) uploader end end def cache(new_files) return if !new_files.is_a?(Array) && new_files.blank? old_uploaders = uploaders @uploaders = new_files.map do |new_file| handle_error do if new_file.is_a?(String) if (uploader = old_uploaders.detect { |old_uploader| old_uploader.identifier == new_file }) uploader.staged = true uploader else begin uploader = blank_uploader uploader.retrieve_from_cache!(new_file) uploader rescue CarrierWave::InvalidParameter nil end end else uploader = blank_uploader uploader.cache!(new_file) uploader end end end.reject(&:blank?) @removed_uploaders += (old_uploaders - @uploaders) write_temporary_identifier end def cache_names uploaders.map(&:cache_name).compact end def cache_names=(cache_names) cache_names = cache_names.reject(&:blank?) return if cache_names.blank? clear_unstaged cache_names.each do |cache_name| uploader = blank_uploader uploader.retrieve_from_cache!(cache_name) @uploaders << uploader rescue CarrierWave::InvalidParameter # ignore end write_temporary_identifier end def remote_urls=(urls) if urls.nil? urls = [] else urls = Array.wrap(urls).reject(&:blank?) return if urls.blank? end @download_errors.clear @remote_urls = urls clear_unstaged @remote_urls.zip(remote_request_headers || []) do |url, header| handle_error do uploader = blank_uploader uploader.download!(url, header || {}) @uploaders << uploader end end write_temporary_identifier end def store! uploaders.each(&:store!) end def write_identifier return if record.frozen? clear! if remove? additions, remains = uploaders.partition(&:cached?) existing_identifiers = (@removed_uploaders + remains).map(&:identifier) additions.each do |uploader| uploader.deduplicate(existing_identifiers) existing_identifiers << uploader.identifier end @added_uploaders += additions record.write_uploader(serialization_column, identifier) end def urls(*args) uploaders.map { |u| u.url(*args) } end def blank? uploaders.none?(&:present?) end def remove=(value) @remove = value write_temporary_identifier end def remove? remove.present? && (remove.to_s !~ /\A0|false$\z/) end def remove! uploaders.each(&:remove!) clear! end def clear! @removed_uploaders += uploaders @remove = nil @uploaders = [] end def reset_changes! @removed_uploaders = [] @added_uploaders = [] end def serialization_column option(:mount_on) || column end def remove_previous current_paths = uploaders.map(&:path) @removed_uploaders .reject {|uploader| current_paths.include?(uploader.path) } .each { |uploader| uploader.remove! if uploader.remove_previously_stored_files_after_update } reset_changes! end def remove_added current_paths = (@removed_uploaders + uploaders.select(&:staged)).map(&:path) @added_uploaders .reject {|uploader| current_paths.include?(uploader.path) } .each { |uploader| uploader.remove! } reset_changes! end private def option(name) self.uploader_options ||= {} self.uploader_options[name] ||= record.class.uploader_option(column, name) end def clear_unstaged @uploaders ||= [] staged, unstaged = @uploaders.partition(&:staged) @uploaders = staged @removed_uploaders += unstaged end def handle_error yield rescue CarrierWave::DownloadError => e @download_errors << e raise e unless option(:ignore_download_errors) rescue CarrierWave::ProcessingError => e @processing_errors << e raise e unless option(:ignore_processing_errors) rescue CarrierWave::IntegrityError => e @integrity_errors << e raise e unless option(:ignore_integrity_errors) end def write_temporary_identifier return if record.frozen? record.write_uploader(serialization_column, temporary_identifier) end def temporary_identifiers if remove? [] else uploaders.map { |uploader| uploader.temporary_identifier } end end end # Mounter end # CarrierWave