# frozen_string_literal: true require 'digest/sha1' require 'fspath' require 'image_optim/cache_path' class ImageOptim # Handles image cache class Cache def initialize(image_optim, workers_by_format) return unless image_optim.cache_dir @cache_dir = FSPath.new(image_optim.cache_dir) @cache_worker_digests = image_optim.cache_worker_digests @options_by_format = Hash[workers_by_format.map do |format, workers| [format, workers.map(&:inspect).sort.join(', ')] end] @bins_by_format = Hash[workers_by_format.map do |format, workers| [format, workers.map(&:used_bins).flatten!.map! do |sym| bin = image_optim.resolve_bin!(sym) "#{bin.name}[#{bin.digest}]" end.sort!.uniq.join(', ')] end] @global_options = begin options = {} options[:timeout] = image_optim.timeout if image_optim.timeout options.empty? ? '' : options.inspect end end def fetch(original) return yield unless @cache_dir digest = digest(original, original.image_format) cached = @cache_dir / digest return cached.size? && CachePath.convert(cached) if cached.file? optimized = yield cached.dirname.mkpath if optimized tmp = FSPath.temp_file_path(digest, @cache_dir) FileUtils.mv(optimized, tmp) tmp.chmod(~File.umask & 0o666) tmp.rename(cached) cached_path = CachePath.convert(cached) # mark cached image as already optimized cached = @cache_dir / digest(cached, original.image_format) cached.dirname.mkpath FileUtils.touch(cached) cached_path else # mark image as already optimized FileUtils.touch(cached) nil end end private def options_by_format(format) @options_by_format[format] end def bins_by_format(format) @bins_by_format[format] end def digest(path, format) digest = Digest::SHA1.file(path) digest.update options_by_format(format) digest.update bins_by_format(format) if @cache_worker_digests digest.update @global_options s = digest.hexdigest "#{s[0..1]}/#{s[2..-1]}" end end end