KatanaCode/carrierwave-blitline

View on GitHub
lib/carrierwave/blitline.rb

Summary

Maintainability
A
0 mins
Test Coverage
module CarrierWave
  # Extend the behaviour of CarrierWave to support Blitline services
  module Blitline
    # frozen_string_literal: true

    # From the Blitline gem
    require "blitline"
    require "active_support/core_ext/module/attribute_accessors"
    require "active_support/core_ext/module/delegation"
    require "active_support/concern"
    require "carrierwave/blitline/version"
    require "carrierwave/blitline/image_version"
    require "carrierwave/blitline/function"
    require "carrierwave/blitline/image_version_function_presenter"

    extend ActiveSupport::Concern

    # Does the version name come at the start (carrierwave default) or at the
    # end of the filename
    RIP_VERSION_NAMES_AT_START = true

    # Blitline API version
    BLITLINE_VERSION = 1.21

    ##
    #
    UNIQUE_IDENTIFIER_TEMPLATE = "%{app_name}_%{rails_env}_%{token}".freeze

    # Extends the including class with ClassMethods, add an after_store callback
    # and includes ImageMagick if required.
    included do
      after :store, :rip_process_images
    end

    mattr_accessor :s3_bucket_name

    mattr_accessor :s3_bucket_region

    mattr_accessor :blitline_application_id


    # =============
    # = Delegates =
    # =============

    delegate :blitline_image_versions, to: :class

    delegate :process_via_blitline?,   to: :class

    # Send a request to Blitline to optimize the original file and create any
    # required versions.
    #
    #   This is called by an after_store macro and because Carrier creates virtual
    #   instancies for each version would be called 4 times for an image with three
    #   versions.
    #
    #   Because we only want to do this on completion we check all the versions
    #   have been called by testing it is OK to begin processing
    #
    #   A hash is created (job_hash) with Blitline's required commands and sent using the
    #   Blitline gem.
    #
    #  file - not used within the method, but required for the callback to function
    def rip_process_images(_file)
      return unless rip_can_begin_processing?
      Rails.logger.tagged("Blitline") { |l| l.debug(job_hash.to_json) }
      blitline_service.add_job_via_hash(job_hash)
      begin
        blitline_service.post_jobs
      rescue StandardError => e
        Rails.logger.tagged("Blitline") do |logger|
          logger.error format("ERROR: Blitline processing error for %<class>\n%<message>",
                              class: model.class.name, message: e.message)
        end
      end
    end

    # Returns a Hash of params posted off to Blitline API
    def job_hash
      {
        "application_id": CarrierWave::Blitline.blitline_application_id,
        "src": url,
        "v": BLITLINE_VERSION,
        "functions": functions
      }.with_indifferent_access
    end

    # Returns a Hash for each function included in the Blitline API post
    def functions
      blitline_image_versions.map do |version|
        ImageVersionFunctionPresenter.new(version, self).to_hash
      end
    end

    # sends a request to Blitline to re-process themain image and all versions
    def optimize!
      rip_process_images(true) if process_via_blitline?
    end

    # Can we post the images to Blitline for processing?
    #   CarrierWave creates virtual Uploaders for each version of an image. These
    #   versions are processed before the original, so the only way to tell if the
    #   versions are all complete is to check the classname for the current call
    #   and if there is no '::' it is the original class.
    #
    # Returns a boolean
    def rip_can_begin_processing?
      process_via_blitline? && (!self.class.name.include? "::")
    end

    def filename
      "#{model.class.to_s.underscore}.#{file.extension}" if file
    end

    def unique_identifier
      @unique_identifier ||= begin
        UNIQUE_IDENTIFIER_TEMPLATE % { app_name: Rails.application.class.name,
                                       rails_env: Rails.env,
                                       token: SecureRandom.base64(10) }
      end
    end

    def file_name_for_version(version)
      file_name, file_type  = filename.split('.')
      name_components       = [version.name, file_name].compact
      name_components.reverse! unless RIP_VERSION_NAMES_AT_START
      file_namewith_version = name_components.join("_") + ".#{file_type}"
      File.join(store_dir, file_namewith_version).to_s
    end

    def params_for_function(function_name, *args)
      send("params_for_#{function_name}", *args)
    end

    def params_for_no_op(*_args)
      {}
    end

    def params_for_resize_to_fill(*args)
      args.flatten!
      { width: args.first, height: args.last }
    end

    def params_for_resize_to_fit(*args)
      args.flatten!
      { width: args.first, height: args.last }
    end


    private


    def blitline_service
      @blitline_service ||= ::Blitline.new
    end

    # Class methods to extend your Uploader classes
    module ClassMethods
      def version(name, &block)
        blitline_image_versions << ImageVersion.new(name, &block)
        # If process_via_blitline? is true, we still want to register the version with
        #  the Uploader, but we don't want to define the conversions.
        if process_via_blitline?
          super(name) {}
        else
          super(name, &block)
        end
      end

      def blitline_image_versions
        @blitline_image_versions ||= [ImageVersion.new(nil)]
      end

      def process_via_blitline(value = true)
        @process_via_blitline = value
      end

      def process_via_blitline?
        defined?(@process_via_blitline) && @process_via_blitline == true
      end
    end
  end
end