Class: Adhearsion::CallController::Dial::Dial

Inherits:
Object
  • Object
show all
Defined in:
lib/adhearsion/call_controller/dial.rb

Direct Known Subclasses

ParallelConfirmationDial

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(to, options, call) ⇒ Dial

Returns a new instance of Dial

Raises:



91
92
93
94
95
96
97
98
# 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

Instance Attribute Details

#statusObject

Returns the value of attribute status



89
90
91
# File 'lib/adhearsion/call_controller/dial.rb', line 89

def status
  @status
end

Instance Method Details

#await_completionObject

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



301
302
303
304
305
306
307
# 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_callsObject

Hangup any remaining calls



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# 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_loggerObject



345
346
347
# File 'lib/adhearsion/call_controller/dial.rb', line 345

def delete_logger
  ::Logging::Repository.instance.delete logger_id
end

#inspectObject



100
101
102
# File 'lib/adhearsion/call_controller/dial.rb', line 100

def inspect
  "#<#{self.class}[#{@id}] to=#{@to.inspect} options=#{@options.inspect}>"
end

#merge(other) ⇒ Object

Merge another Dial into this one, joining all calls to a mixer

Parameters:

  • other (Dial)

    the other dial operation to merge calls from



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# 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_callsObject

Dials the set of outbound calls



214
215
216
217
218
219
220
# 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 { ... } ⇒ Object

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

Yields:

  • Each call to the passed block for further setup operations



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# 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) ⇒ Object

Rejoin parties that were previously split

Parameters:

  • target (Call, String, Hash) (defaults to: nil)

    The target to join calls to. See Call#join for details.

  • join_options (Hash) (defaults to: nil)

    Options to specify the kind of join operation to perform. See `Call#join` for details.



263
264
265
266
267
268
269
270
271
272
273
274
275
276
# 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) ⇒ Object

Prep outbound calls, link call lifecycles and place outbound calls



105
106
107
108
109
110
# File 'lib/adhearsion/call_controller/dial.rb', line 105

def run(controller)
  track_originating_call
  start_ringback controller
  prep_calls
  place_calls
end

#skip_cleanupObject

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



311
312
313
# File 'lib/adhearsion/call_controller/dial.rb', line 311

def skip_cleanup
  @skip_cleanup = true
end

#split(targets = {}) ⇒ Object

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.

Parameters:

  • targets (Hash) (defaults to: {})

    Target call controllers to execute on call legs once split

  • options (Hash)

    a customizable set of options



230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# 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) ⇒ Object

Starts ringback on the specified controller

Parameters:



127
128
129
130
131
132
133
134
# 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_ringbackObject

Terminates any ringback that might be playing



139
140
141
142
143
# 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_callObject

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



114
115
116
117
118
119
120
121
# 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