module Ecoportal module API class V1 class Job class Awaiter TIMER_ARGS = %i[ total timeout start last ldelay status lstatus ].freeze Timer = Struct.new(*TIMER_ARGS) do self::MAX_START_DELAY = 60 attr_reader :timestamp def initialize(...) @timestamp = Time.now super yield(self) if block_given? end alias_method :original_start, :start # `start` time only counts from the moment that # it already started to progress def start return timestamp unless lstatus&.started? original_start || timestamp end alias_method :original_last, :last def last original_last || start end def new(**kargs, &block) self.class.new(**new_kargs.merge(kargs), &block) end def job_id status.id end def complete? status.complete?(total) end def started? status.started? end def pending status.pending(total) end def progress status.progress end def increased status.progress_increase(lstatus) end def waited timestamp - start end def lwaited timestamp - last end def net_waited last_delay = ldelay || 0 waited - (last_delay / 2) end def time_left (timeout - waited).round(2) end def time_left_to_start return max_start_delay if started? (max_start_delay - waited).round(2) end def timeout_in return time_left if started? time_left_to_start end # timeout library is evil. So we make poor-man timeout. # https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/ def timeout? !time_left.positive? end def start_timeout? return false if status.started? !time_left_to_start.positive? end def on_timeout!(&block) return start_timeout!(&block) if start_timeout? return timeout!(&block) if timeout? false end def timeout! return false unless timeout? yield(self) msg = "Job '#{job_id}' not complete (size: #{total}).\n" msg << " Timed out after #{timeout} seconds.\n" msg << " Current status: #{status}" raise API::Errors::TimeOut, msg end def start_timeout! return false unless start_timeout? yield(self) msg = "Job '#{job_id}' not started (size: #{total}).\n" msg << " Start timed out after #{max_start_delay} seconds.\n" msg << " Current status: #{status}" raise API::Errors::StartTimeOut, msg end protected def new_kargs %i[total timeout start].each_with_object({}) do |key, kargs| kargs[key] = send(key) end.tap do |kargs| kargs[:start] = timestamp kargs[:lstatus] = status end end private def max_start_delay self.class::MAX_START_DELAY end end end end end end end