class Adhearsion::CallController::Dial::Dial
Attributes
Public Class Methods
# 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
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
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
# File lib/adhearsion/call_controller/dial.rb, line 345 def delete_logger ::Logging::Repository.instance.delete logger_id end
# File lib/adhearsion/call_controller/dial.rb, line 100 def inspect "#<#{self.class}[#{@id}] to=#{@to.inspect} options=#{@options.inspect}>" end
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
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
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 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
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
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 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
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
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
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
# File lib/adhearsion/call_controller/dial.rb, line 351 def root_call @call end
Private Instance Methods
# 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
# 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
# File lib/adhearsion/call_controller/dial.rb, line 362 def join_target @join_target || @call end
@private
# File lib/adhearsion/call_controller/dial.rb, line 358 def logger_id "#{self.class}: #{@id}" end
# 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
# 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
# File lib/adhearsion/call_controller/dial.rb, line 398 def pre_join_tasks(call) @pre_join.call(call) if @pre_join terminate_ringback end
# 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