emojidex/emojidex

View on GitHub
lib/emojidex/data/collection/cache.rb

Summary

Maintainability
B
4 hrs
Test Coverage
require 'json'
require 'fileutils'
require_relative 'asset_information'
require_relative '../../service/transactor'
require_relative '../../defaults'

module Emojidex
  module Data
    # local caching functionality for collections
    module CollectionCache
      include Emojidex::Data::CollectionAssetInformation
      attr_reader :cache_path, :download_queue
      attr_accessor :download_threads, :formats, :sizes

      def setup_cache(path = nil)
        @formats = Emojidex::Defaults.selected_formats unless @formats
        @sizes = Emojidex::Defaults.selected_sizes unless @sizes
        @download_queue = []
        @download_threads = 4
        # check if cache dir is already set
        return @cache_path if @cache_path && path.nil?
        # setup cache
        @cache_path =
          File.expand_path((path || Emojidex::Defaults.system_cache_path) + '/emoji')
        # ENV['EMOJI_CACHE'] = @cache_path
        FileUtils.mkdir_p(@cache_path)
        Emojidex::Defaults.sizes.keys.each do |size|
          FileUtils.mkdir_p(@cache_path + "/#{size}")
        end
        # load will expect emoji.json even if it contains no emoji
        unless File.exist? "#{cache_path}/emoji.json"
          File.open("#{@cache_path}/emoji.json", 'w') { |f| f.write '[]' }
        end
        @cache_path
      end

      def load_cache(path = nil)
        setup_cache(path)
        load_local_collection(@cache_path)
      end

      # Caches emoji to local emoji storage cache
      # Options:
      #   cache_path: manually specify cache location
      #   formats: formats to cache (default is SVG only)
      #   sizes: sizes to cache (default is px32, but this is irrelivant for SVG)
      def cache(options = {})
        _cache(options)
        cache_index
      end

      # Caches emoji to local emoji storage cache
      # +regenerates checksums and paths
      # Options:
      #   cache_path: manually specify cache location
      #   formats: formats to cache (default is SVG only)
      #   sizes: sizes to cache (default is px32, but this is irrelivant for SVG)
      def cache!(options = {})
        _cache(options)
        generate_paths
        generate_checksums
        cache_index
      end

      # Updates an index in the specified destination (or the cache path if not specified).
      # This method reads the existing index, combines the contents with this collection, and
      # writes the results.
      def cache_index(destination = nil)
        destination ||= @cache_path
        idx = Emojidex::Data::Collection.new
        idx.r18 = @r18
        idx.load_local_collection(destination) if FileTest.exist? "#{destination}/emoji.json"
        idx.add_emoji @emoji.values
        idx.write_index(destination)
      end

      # [over]writes a sanitized index to the specified destination.
      # WARNING: This method destroys any index files in the destination.
      def write_index(destination)
        idx = @emoji.values.to_json
        idx = JSON.parse idx
        idx.each do |moji|
          moji['paths'] = nil
          moji['remote_checksums'] = nil
          moji.delete_if { |_k, v| v.nil? }
        end
        File.open("#{destination}/emoji.json", 'w') { |f| f.write idx.to_json }
      end

      private

      def _svg_check_copy(moji)
        if @vector_source_path != nil
          src_d = "#{@vector_source_path}/#{moji.code}"
          src = "#{src_d}.svg"

          FileUtils.cp(src, @cache_path) if File.exist?(src) && @vector_source_path != @cache_path
          FileUtils.cp_r(src_d, @cache_path) if File.directory?(src_d) && @vector_source_path != @cache_path # Copies source frames and data files if unpacked
          @download_queue << { moji: moji, formats: :svg, sizes: [] } unless File.exist?("#{@cache_path}/#{moji.code}.svg") # nothing was copied, get from net
          return
        end

        @download_queue << { moji: moji, formats: :svg, sizes: [] }
      end

      def _raster_check_copy(moji, format, sizes)
        sizes.each do |size|
          if @raster_source_path != nil
            src_d = "#{@raster_source_path}/#{size}/#{moji.code}"
            src = "#{src_d}.#{format}"

            FileUtils.cp("#{src}", ("#{@cache_path}/#{size}")) if File.exist?(src) && @raster_source_path != @cache_path
            FileUtils.cp_r(src, @cache_path) if File.directory?(src_d) && @raster_source_path != @cache_path # Copies source frames and data files if unpacked
            @download_queue << { moji: moji, formats: [format], sizes: [size] } unless File.exist?("#{@cache_path}/#{size}/#{moji.code}.#{format}") # nothing was copied, get from net
          end
        end
      end

      def _process_download_queue
        thr = []
        @download_queue.each do |dl|
          thr << Thread.new { _cache_from_net(dl[:moji], dl[:formats], dl[:sizes]) }
          thr.each(&:join) if thr.length >= (@download_threads - 1)
        end
        thr.each(&:join) # grab end of queue
      end

      def _cache_from_net(moji, formats, sizes)
        formats = *formats unless formats.class == Array
        dls = []
        dls << Thread.new { moji.cache(:svg) } if formats.include? :svg
        dls << Thread.new { moji.cache(:png, sizes) } if formats.include? :png
        dls.each(&:join)
      end

      def _cache(options)
        setup_cache options[:cache_path]
        formats = options[:formats] || @formats
        sizes = options[:sizes] || @sizes

        @emoji.values.each do |moji|
          _svg_check_copy(moji) if formats.include? :svg
          _raster_check_copy(moji, :png, sizes) if formats.include? :png
        end
        _process_download_queue

        @vector_source_path = @cache_path if formats.include? :svg
        @raster_source_path = @cache_path if formats.include? :png
      end
    end
  end
end