simplybusiness/kitcat

View on GitHub
lib/kitcat/framework.rb

Summary

Maintainability
A
35 mins
Test Coverage
require 'ruby-progressbar'
require 'active_model'
require 'active_support/core_ext'
require 'kitcat/logging'

module Kitcat
  class Framework
    attr_reader :last_item_processed,
                :number_of_items_processed,
                :migration_strategy,
                :logging

    delegate :log_file_path,
             :start_logging, :end_logging,
             :log_success, :log_failure,
             :log_interrupt_callback_start,
             :log_interrupt_callback_finish, to: :logging

    # @params migration_strategy {Object}
    #           Instance implementing the methods of +Kitcat::Callbacks+
    #
    #         migration_name {String} Optional.
    #           The name of the migration. Used as tag in log file name. If not given, a random/unique one is used.
    #
    #         number_of_items_to_process {Integer} Optional.
    #           If given, the processing will stop after processing that many number of items.
    #
    #         progress_bar {Boolean} Optional.
    #           When +True+. it will instantiate a use a progress bar, incrementing that by 1 every time
    #           the +migration_strategy+ finishes processing an item. The total load will be calculated based on the
    #           result of +migration_strategy#criteria#count+.
    #           When +False+, progress bar will not be used
    #
    #         progress_bar_output Optional. Defaults to STDOUT. Anything that responds to
    #           #print, #flush, #tty? and #puts.
    #           It is taken into account only if progress bar is enabled.
    #
    def initialize(migration_strategy,
                   migration_name: nil,
                   number_of_items_to_process: nil,
                   progress_bar: true,
                   progress_bar_output: STDOUT)
      @migration_strategy         = migration_strategy
      @number_of_items_to_process = number_of_items_to_process
      @last_item_processed        = nil
      @progress_bar               = initialize_progress_bar(progress_bar, progress_bar_output)
      @logging                    = Kitcat::Logging.new(migration_strategy, migration_name)
    end

    def execute
      trap_signals

      start_logging

      @number_of_items_processed = 0

      items.each do |item|
        break unless execute_for(item)
      end
    ensure
      end_logging
    end

    def number_of_items_to_process
      @number_of_items_to_process ||= migration_strategy.criteria.count
    end

    def progress_bar?
      !@progress_bar.nil?
    end

    def progress
      return -1 unless progress_bar?
      @progress_bar.progress
    end

    private

    def execute_for(item)
      begin
        if migration_strategy.process(item)

          commit_success(item)

          return false unless process_more?
        else
          commit_failure(item)

          return false
        end
        if @interrupted
          handle_user_interrupt
          return false
        end
      rescue StandardError
        commit_failure(item)
        raise
      end

      true
    end

    def commit_success(item)
      log_success(item)

      @number_of_items_processed += 1

      increment_progress_bar

      @last_item_processed = item
    end

    def commit_failure(item)
      log_failure(item)
    end

    def trap_signals
      @interrupted = false
      Signal.trap('TERM') { @interrupted = true }
      Signal.trap('INT') { @interrupted = true }
    end

    def items
      return enum_for(:items) unless block_given?

      enum = migration_strategy.criteria.each

      loop do
        yield enum.next
      end
    end

    def initialize_progress_bar(progress_bar_flag, output)
      create_progress_bar(output) if progress_bar_flag || progress_bar_flag.nil?
    end

    def create_progress_bar(output)
      @progress_bar = ProgressBar.create(total:  migration_strategy.criteria.count,
                                         output: output,
                                         progress_mark: ' ',
                                         remainder_mark: '-',
                                         length: terminal_width,
                                         format: '%a %bá—§%i %p%% %e')
    end

    def process_more?
      @number_of_items_to_process.nil? || @number_of_items_processed < @number_of_items_to_process
    end

    def increment_progress_bar
      return unless progress_bar?
      @progress_bar.increment
    end

    def handle_user_interrupt
      log_interrupt_callback_start
      migration_strategy.interrupt_callback if migration_strategy.respond_to?(:interrupt_callback)
      log_interrupt_callback_finish
      true
    end

    # The following is to correctly calculate the width of the terminal
    # so that the progress bar occupies the whole width
    #
    def terminal_width
      TerminalWidthCalculator.calculate
    end
  end
end