class Adhearsion::CallController::Dial::Dial

Attributes

status[RW]

Public Class Methods

new(to, options, call) click to toggle source
# File lib/adhearsion/call_controller/dial.rb, line 91
def initialize(to, options, call)
  raise Call::Hangup unless call.alive? && call.active?
  @id = SecureRandom.uuid
  @options, @call = options, call
  @targets = to.respond_to?(:has_key?) ? to : Array(to)
  @call_targets = {}
  set_defaults
end

Public Instance Methods

await_completion() click to toggle source

Block until the dial operation is completed by an appropriate quorum of the involved calls ending

# File lib/adhearsion/call_controller/dial.rb, line 301
def await_completion
  @latch.wait(@options[:timeout]) || status.timeout!
  return unless status.result == :answer
  logger.debug "Main calls were completed, waiting for any added calls: #{@waiters.inspect}"
  @waiters.each(&:wait)
  logger.debug "All calls were completed, unblocking."
end
cleanup_calls() click to toggle source

Hangup any remaining calls

# File lib/adhearsion/call_controller/dial.rb, line 317
def cleanup_calls
  calls_to_hangup = @calls.map do |call|
    ignoring_ended_calls do
      [call.id, call] if call.active?
    end
  end.compact
  if calls_to_hangup.size.zero?
    logger.info "#dial finished with no remaining outbound calls"
    return
  end
  if @skip_cleanup
    logger.info "#dial finished. Leaving #{calls_to_hangup.size} outbound calls going which are still active: #{calls_to_hangup.map(&:first).join ", "}."
  else
    logger.info "#dial finished. Hanging up #{calls_to_hangup.size} outbound calls which are still active: #{calls_to_hangup.map(&:first).join ", "}."
    calls_to_hangup.each do |id, outbound_call|
      ignoring_ended_calls do
        if @cleanup_controller
          logger.info "#dial running #{@cleanup_controller.class.name} on #{outbound_call.id}"
          outbound_call.execute_controller @cleanup_controller.new(outbound_call, @cleanup_metadata), ->(call) { call.hangup }
        else
          logger.info "#dial hanging up #{outbound_call.id}"
          outbound_call.hangup
        end
      end
    end
  end
end
delete_logger() click to toggle source
# File lib/adhearsion/call_controller/dial.rb, line 345
def delete_logger
  ::Logging::Repository.instance.delete logger_id
end
inspect() click to toggle source
# File lib/adhearsion/call_controller/dial.rb, line 100
def inspect
  "#<#{self.class}[#{@id}] to=#{@to.inspect} options=#{@options.inspect}>"
end
merge(other) click to toggle source

Merge another Dial into this one, joining all calls to a mixer @param [Dial] other the other dial operation to merge calls from

# File lib/adhearsion/call_controller/dial.rb, line 280
def merge(other)
  logger.info "Merging with #{other.inspect}"

  split
  other.split

  rejoin({mixer_name: @id}, {})
  other.rejoin({mixer_name: @id}, {})

  calls_to_merge = other.status.calls + [other.root_call]
  @calls.merge calls_to_merge

  latch = CountDownLatch.new calls_to_merge.size
  calls_to_merge.each do |call|
    call.on_end { |event| latch.countdown! }
  end
  @waiters << latch
end
place_calls() click to toggle source

Dials the set of outbound calls

# File lib/adhearsion/call_controller/dial.rb, line 214
def place_calls
  @calls.each do |call|
    target, specific_options = @call_targets[call]
    local_options = @options.dup.deep_merge specific_options if specific_options
    call.dial target, (local_options || @options)
  end
end
prep_calls() { |new_call| ... } click to toggle source

Prepares a set of OutboundCall actors to be dialed and links their lifecycles to the Dial operation

@yield Each call to the passed block for further setup operations

# File lib/adhearsion/call_controller/dial.rb, line 149
def prep_calls
  @calls = Set.new
  @targets.map do |target, specific_options|
    new_call = OutboundCall.new

    join_status = JoinStatus.new
    status.joins[new_call] = join_status

    new_call.on_end do |event|
      @latch.countdown! unless new_call["dial_countdown_#{@id}"]
      if event.reason == :error
        status.error!
        join_status.errored!
      end
    end

    new_call.on_answer do |event|
      pre_confirmation_tasks new_call

      new_call.on_joined @call do |joined|
        join_status.started joined.timestamp.to_time
      end

      new_call.on_unjoined @call do |unjoined|
        join_status.ended unjoined.timestamp.to_time
        unless @splitting
          new_call["dial_countdown_#{@id}"] = true
          @latch.countdown!
        end
      end

      if @confirmation_controller
        status.unconfirmed!
        join_status.unconfirmed!
        condition = Celluloid::Condition.new
        new_call.execute_controller @confirmation_controller.new(new_call, @confirmation_metadata), lambda { |call| condition.broadcast }
        condition.wait
      end

      if new_call.alive? && new_call.active? && status.result != :answer
        logger.info "#dial joining call #{new_call.id} to #{@call.id}"
        pre_join_tasks new_call
        @call.answer
        new_call.join @join_target, @join_options
        unless @join_target == @call
          @call.join @join_target, @join_options
        end
        status.answer!
      elsif status.result == :answer
        join_status.lost_confirmation!
      end
    end

    @call_targets[new_call] = [target, specific_options]

    yield new_call if block_given?

    @calls << new_call
  end

  status.calls = @calls
end
rejoin(target = nil, join_options = nil) click to toggle source

Rejoin parties that were previously split @param [Call, String, Hash] target The target to join calls to. See Call#join for details. @param [Hash] join_options Options to specify the kind of join operation to perform. See `Call#join` for details.

# File lib/adhearsion/call_controller/dial.rb, line 263
def rejoin(target = nil, join_options = nil)
  target ||= join_target
  join_options ||= @join_options
  logger.info "Rejoining to #{target}"
  ignoring_ended_calls do
    unless target == @call
      @join_target = target
      @call.join target, join_options
    end
  end
  @calls.each do |call|
    ignoring_ended_calls { call.join target, join_options }
  end
end
run(controller) click to toggle source

Prep outbound calls, link call lifecycles and place outbound calls

# File lib/adhearsion/call_controller/dial.rb, line 105
def run(controller)
  track_originating_call
  start_ringback controller
  prep_calls
  place_calls
end
skip_cleanup() click to toggle source

Do not hangup outbound calls when the Dial operation finishes. This allows outbound calls to continue with other processing once they are unjoined.

# File lib/adhearsion/call_controller/dial.rb, line 311
def skip_cleanup
  @skip_cleanup = true
end
split(targets = {}) click to toggle source

Split calls party to the dial Marks the end time in the status of each join, but does not unblock dial until one of the calls ends Optionally executes call controllers on calls once split, where 'current_dial' is available in controller metadata in order to perform further operations on the Dial, including rejoining and termination. @param [Hash] targets Target call controllers to execute on call legs once split @option options [Adhearsion::CallController] :main The call controller class to execute on the 'main' call leg (the one who initiated the dial) @option options [Proc] :main_callback A block to call when the :main controller completes @option options [Adhearsion::CallController] :others The call controller class to execute on the 'other' call legs (the ones created as a result of the dial) @option options [Proc] :others_callback A block to call when the :others controller completes on an individual call

# File lib/adhearsion/call_controller/dial.rb, line 230
def split(targets = {})
  @splitting = true
  calls_to_split = @calls.map do |call|
    ignoring_ended_calls do
      [call.id, call] if call.active?
    end
  end.compact
  logger.info "Splitting off peer calls #{calls_to_split.map(&:first).join ", "}"
  calls_to_split.each do |id, call|
    ignoring_ended_calls do
      logger.debug "Unjoining peer #{call.id} from #{join_target}"
      ignoring_missing_joins { call.unjoin join_target }
      if split_controller = targets[:others]
        logger.info "Executing controller #{split_controller} on split call #{call.id}"
        call.execute_controller split_controller.new(call, 'current_dial' => self), targets[:others_callback]
      end
    end
  end
  ignoring_ended_calls do
    if join_target != @call
      logger.debug "Unjoining main call #{@call.id} from #{join_target}"
      @call.unjoin join_target
    end
    if split_controller = targets[:main]
      logger.info "Executing controller #{split_controller} on main call"
      @call.execute_controller split_controller.new(@call, 'current_dial' => self), targets[:main_callback]
    end
  end
end
start_ringback(controller) click to toggle source

Starts ringback on the specified controller

@param [Adhearsion::CallController] controller the controller on which to play ringback

# File lib/adhearsion/call_controller/dial.rb, line 127
def start_ringback(controller)
  return unless @ringback
  @ringback_component = if @ringback.respond_to?(:call)
    @ringback.call
  else
    controller.play! @ringback, repeat_times: 0
  end
end
terminate_ringback() click to toggle source

Terminates any ringback that might be playing

# File lib/adhearsion/call_controller/dial.rb, line 139
def terminate_ringback
  return unless @ringback_component
  return unless @ringback_component.executing?
  @ringback_component.stop!
end
track_originating_call() click to toggle source

Links the lifecycle of the originating call to the Dial operation such that the Dial is unblocked when the originating call ends

# File lib/adhearsion/call_controller/dial.rb, line 114
def track_originating_call
  @call.on_end do |_|
    logger.debug "Root call ended, unblocking connected calls"
    @waiters.each do |latch|
      latch.countdown! until latch.count == 0
    end
  end
end

Protected Instance Methods

root_call() click to toggle source
# File lib/adhearsion/call_controller/dial.rb, line 351
def root_call
  @call
end

Private Instance Methods

ignoring_ended_calls() { || ... } click to toggle source
# File lib/adhearsion/call_controller/dial.rb, line 418
def ignoring_ended_calls
  yield
rescue Celluloid::DeadActorError, Adhearsion::Call::Hangup, Adhearsion::Call::ExpiredError
  # This actor may previously have been shut down due to the call ending
end
ignoring_missing_joins() { || ... } click to toggle source
# File lib/adhearsion/call_controller/dial.rb, line 412
def ignoring_missing_joins
  yield
rescue Punchblock::ProtocolError => e
  raise unless e.name == :service_unavailable
end
join_target() click to toggle source
# File lib/adhearsion/call_controller/dial.rb, line 362
def join_target
  @join_target || @call
end
logger_id() click to toggle source

@private

# File lib/adhearsion/call_controller/dial.rb, line 358
def logger_id
  "#{self.class}: #{@id}"
end
on_all_except(call) { |target_call| ... } click to toggle source
# File lib/adhearsion/call_controller/dial.rb, line 403
def on_all_except(call)
  @calls.each do |target_call|
    ignoring_ended_calls do
      next if target_call.id == call.id
      yield target_call
    end
  end
end
pre_confirmation_tasks(call) click to toggle source
# File lib/adhearsion/call_controller/dial.rb, line 391
def pre_confirmation_tasks(call)
  on_all_except call do |target_call|
    logger.info "#dial hanging up call #{target_call.id} because this call has been answered by another channel"
    target_call.hangup
  end
end
pre_join_tasks(call) click to toggle source
# File lib/adhearsion/call_controller/dial.rb, line 398
def pre_join_tasks(call)
  @pre_join.call(call) if @pre_join
  terminate_ringback
end
set_defaults() click to toggle source
# File lib/adhearsion/call_controller/dial.rb, line 366
def set_defaults
  @status = DialStatus.new

  @latch = CountDownLatch.new @targets.size
  @waiters = [@latch]

  @options[:from] ||= @call.from

  _for = @options.delete :for
  @options[:timeout] ||= _for if _for

  @confirmation_controller = @options.delete :confirm
  @confirmation_metadata = @options.delete :confirm_metadata

  @pre_join = @options.delete :pre_join
  @ringback = @options.delete :ringback

  @join_options = @options.delete(:join_options) || {}
  @join_target = @options.delete(:join_target) || @call

  @cleanup_controller = @options.delete :cleanup
  @cleanup_metadata = @options.delete :cleanup_metadata || @confirmation_metadata
  @skip_cleanup = false
end