adhearsion 0.8.4 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +12 -0
- data/Rakefile +6 -5
- data/adhearsion.gemspec +9 -5
- data/app_generators/ahn/ahn_generator.rb +5 -0
- data/app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.rb +1 -1
- data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.rb +1 -1
- data/app_generators/ahn/templates/components/disabled/xmpp_gateway/README.markdown +3 -0
- data/app_generators/ahn/templates/components/disabled/xmpp_gateway/xmpp_gateway.rb +11 -0
- data/app_generators/ahn/templates/components/disabled/xmpp_gateway/xmpp_gateway.yml +0 -0
- data/app_generators/ahn/templates/config/startup.rb +10 -1
- data/lib/adhearsion/cli.rb +7 -2
- data/lib/adhearsion/foundation/event_socket.rb +1 -1
- data/lib/adhearsion/foundation/future_resource.rb +1 -1
- data/lib/adhearsion/host_definitions.rb +1 -1
- data/lib/adhearsion/initializer.rb +21 -8
- data/lib/adhearsion/initializer/configuration.rb +44 -3
- data/lib/adhearsion/initializer/database.rb +11 -1
- data/lib/adhearsion/initializer/ldap.rb +7 -1
- data/lib/adhearsion/initializer/xmpp.rb +42 -0
- data/lib/adhearsion/version.rb +26 -2
- data/lib/adhearsion/voip/asterisk/agi_server.rb +2 -1
- data/lib/adhearsion/voip/asterisk/commands.rb +186 -85
- data/lib/adhearsion/voip/asterisk/manager_interface.rb +147 -50
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1 -1
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +1 -1
- data/lib/adhearsion/voip/call.rb +15 -8
- data/lib/adhearsion/voip/dial_plan.rb +21 -4
- data/lib/adhearsion/voip/dsl/dialing_dsl.rb +1 -1
- data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +2 -2
- data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +2 -2
- data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +2 -2
- data/lib/adhearsion/xmpp/connection.rb +61 -0
- data/lib/theatre/invocation.rb +1 -1
- data/lib/theatre/namespace_manager.rb +1 -1
- metadata +12 -6
- data/lib/adhearsion/foundation/global.rb +0 -1
@@ -29,7 +29,9 @@ module Adhearsion
|
|
29
29
|
#
|
30
30
|
class ManagerInterface
|
31
31
|
|
32
|
-
CAUSAL_EVENT_NAMES = [
|
32
|
+
CAUSAL_EVENT_NAMES = %w[queuestatus sippeers iaxpeers parkedcalls
|
33
|
+
dahdishowchannels coreshowchannels dbget
|
34
|
+
status konferencelist] unless defined? CAUSAL_EVENT_NAMES
|
33
35
|
|
34
36
|
class << self
|
35
37
|
|
@@ -41,13 +43,7 @@ module Adhearsion
|
|
41
43
|
|
42
44
|
def replies_with_action_id?(name, headers={})
|
43
45
|
name = name.to_s.downcase
|
44
|
-
|
45
|
-
case name
|
46
|
-
when "queues", "iaxpeers"
|
47
|
-
false
|
48
|
-
else
|
49
|
-
true
|
50
|
-
end
|
46
|
+
!UnsupportedActionName::UNSUPPORTED_ACTION_NAMES.include? name
|
51
47
|
end
|
52
48
|
|
53
49
|
##
|
@@ -76,21 +72,27 @@ module Adhearsion
|
|
76
72
|
return nil unless has_causal_events?(action_name)
|
77
73
|
action_name = action_name.to_s.downcase
|
78
74
|
case action_name
|
79
|
-
when "
|
75
|
+
when "sippeers", "iaxpeers"
|
76
|
+
"peerlistcomplete"
|
77
|
+
when "dbget"
|
78
|
+
"dbgetresponse"
|
79
|
+
when "konferencelist"
|
80
|
+
"conferencelistcomplete"
|
81
|
+
else
|
80
82
|
action_name + "complete"
|
81
|
-
when "sippeers"
|
82
|
-
"peerlistcomplete"
|
83
83
|
end
|
84
84
|
end
|
85
85
|
|
86
86
|
end
|
87
87
|
|
88
88
|
DEFAULT_SETTINGS = {
|
89
|
-
:host
|
90
|
-
:port
|
91
|
-
:username
|
92
|
-
:password
|
93
|
-
:events
|
89
|
+
:host => "localhost",
|
90
|
+
:port => 5038,
|
91
|
+
:username => "admin",
|
92
|
+
:password => "secret",
|
93
|
+
:events => true,
|
94
|
+
:auto_reconnect => true,
|
95
|
+
:event_callback => proc { |event| Events.trigger(%w[asterisk manager_interface], event) }
|
94
96
|
}.freeze unless defined? DEFAULT_SETTINGS
|
95
97
|
|
96
98
|
attr_reader *DEFAULT_SETTINGS.keys
|
@@ -106,11 +108,13 @@ module Adhearsion
|
|
106
108
|
def initialize(options={})
|
107
109
|
options = parse_options options
|
108
110
|
|
109
|
-
@host
|
110
|
-
@username
|
111
|
-
@password
|
112
|
-
@port
|
113
|
-
@events
|
111
|
+
@host = options[:host]
|
112
|
+
@username = options[:username]
|
113
|
+
@password = options[:password]
|
114
|
+
@port = options[:port]
|
115
|
+
@events = options[:events]
|
116
|
+
@auto_reconnect = options[:auto_reconnect]
|
117
|
+
@event_callback = options[:event_callback]
|
114
118
|
|
115
119
|
@sent_messages = {}
|
116
120
|
@sent_messages_lock = Mutex.new
|
@@ -136,21 +140,29 @@ module Adhearsion
|
|
136
140
|
|
137
141
|
if corresponding_action
|
138
142
|
|
143
|
+
# The "DBGet" command is causal, meaning it has an separate
|
144
|
+
# event that contains the data for command's response. However,
|
145
|
+
# unlike other causal commands, AMI does not send a
|
146
|
+
# "DBGetComplete" action indicating the causal event is
|
147
|
+
# finished. This is fixed starting in Asterisk 1.8.
|
148
|
+
if message.name.downcase == "dbgetresponse"
|
149
|
+
event_collection << message
|
150
|
+
end
|
151
|
+
|
139
152
|
# If this is the meta-event which signals no more events will follow and the response is complete.
|
140
153
|
if message.name.downcase == corresponding_action.causal_event_terminator_name
|
141
|
-
|
142
|
-
# Result found! Wake up any Threads waiting
|
154
|
+
# Wake up the waiting Thread
|
143
155
|
corresponding_action.future_resource.resource = event_collection.freeze
|
144
156
|
|
157
|
+
# Clear the stored action and event collection
|
145
158
|
@current_action_with_causal_events = nil
|
146
159
|
@event_collection_for_current_action = nil
|
147
|
-
|
148
160
|
else
|
149
161
|
event_collection << message
|
150
162
|
# We have more causal events coming.
|
151
163
|
end
|
152
164
|
else
|
153
|
-
ahn_log.ami.error "Got an unexpected event on actions socket! This may
|
165
|
+
ahn_log.ami.error "Got an unexpected event on actions socket! This AMI command may have a multi-message response. Try making Adhearsion treat it as CAUSAL_EVENT #{message.inspect}"
|
154
166
|
end
|
155
167
|
|
156
168
|
elsif message["ActionID"].nil?
|
@@ -195,7 +207,7 @@ module Adhearsion
|
|
195
207
|
def event_message_received(event)
|
196
208
|
return if event.kind_of?(ManagerInterfaceResponse) && event["Message"] == "Authentication accepted"
|
197
209
|
# TODO: convert the event name to a certain namespace.
|
198
|
-
|
210
|
+
@event_callback.call(event)
|
199
211
|
end
|
200
212
|
|
201
213
|
def event_error_received(message)
|
@@ -226,13 +238,14 @@ module Adhearsion
|
|
226
238
|
|
227
239
|
def actions_connection_established
|
228
240
|
@actions_state = :connected
|
229
|
-
|
241
|
+
start_actions_writer_loop
|
230
242
|
end
|
231
243
|
|
232
244
|
def actions_connection_disconnected
|
233
245
|
@actions_state = :disconnected
|
234
246
|
ahn_log.ami.error "AMI connection for ACTION disconnected !!!"
|
235
|
-
|
247
|
+
clear_actions_connection
|
248
|
+
establish_actions_connection if @auto_reconnect
|
236
249
|
end
|
237
250
|
|
238
251
|
def events_connection_established
|
@@ -242,14 +255,23 @@ module Adhearsion
|
|
242
255
|
def events_connection_disconnected
|
243
256
|
@events_state = :disconnected
|
244
257
|
ahn_log.ami.error "AMI connection for EVENT disconnected !!!"
|
245
|
-
|
258
|
+
clear_events_connection
|
259
|
+
establish_events_connection if @auto_reconnect
|
260
|
+
end
|
261
|
+
|
262
|
+
def clear_actions_connection
|
263
|
+
stop_actions_writer_loop
|
264
|
+
clear_actions_connection_resources
|
265
|
+
disconnect_actions_connection
|
266
|
+
end
|
267
|
+
|
268
|
+
def clear_events_connection
|
269
|
+
disconnect_events_connection
|
246
270
|
end
|
247
271
|
|
248
272
|
def disconnect!
|
249
|
-
|
250
|
-
|
251
|
-
#@write_queue << :STOP!
|
252
|
-
#raise NotImplementedError
|
273
|
+
clear_actions_connection
|
274
|
+
clear_events_connection
|
253
275
|
end
|
254
276
|
|
255
277
|
def dynamic
|
@@ -390,7 +412,7 @@ module Adhearsion
|
|
390
412
|
args[:exten] = options[:extension] if options[:extension]
|
391
413
|
args[:caller_id] = options[:caller_id] if options[:caller_id]
|
392
414
|
if options[:variables] && options[:variables].kind_of?(Hash)
|
393
|
-
args[:variable] = options[:variables].map {|pair| pair.join('=')}.join(
|
415
|
+
args[:variable] = options[:variables].map {|pair| pair.join('=')}.join(@coreSettings["ArgumentDelimiter"])
|
394
416
|
end
|
395
417
|
originate args
|
396
418
|
end
|
@@ -410,8 +432,25 @@ module Adhearsion
|
|
410
432
|
class UnsupportedActionName < ArgumentError
|
411
433
|
UNSUPPORTED_ACTION_NAMES = %w[
|
412
434
|
queues
|
413
|
-
iaxpeers
|
414
435
|
] unless defined? UNSUPPORTED_ACTION_NAMES
|
436
|
+
|
437
|
+
# Blacklist some actions depends on the Asterisk version
|
438
|
+
def self.preinitialize(version)
|
439
|
+
if version < 1.8
|
440
|
+
%w[iaxpeers muteaudio mixmonitormute aocmessage].each do |action|
|
441
|
+
UNSUPPORTED_ACTION_NAMES << action
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
if version < 1.6
|
446
|
+
%w[skinnydevices skinnyshowdevice skinnylines skinnyshowline coreshowchannels
|
447
|
+
sipshowregistry getconfigjson bridge listallvoicemailusers dbdel dbdeltree
|
448
|
+
insert jitterbufstats atxfer iaxregistry queuereload queuereset].each do |action|
|
449
|
+
UNSUPPORTED_ACTION_NAMES << action
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
|
415
454
|
def initialize(name)
|
416
455
|
super "At the moment this AMI library doesn't support the #{name.inspect} action because it causes a protocol anomaly. Support for it will be coming shortly."
|
417
456
|
end
|
@@ -424,19 +463,33 @@ module Adhearsion
|
|
424
463
|
true
|
425
464
|
end
|
426
465
|
|
427
|
-
def
|
466
|
+
def start_actions_writer_loop
|
467
|
+
@actions_writer_thread = Thread.new(&method(:actions_writer_loop))
|
468
|
+
end
|
469
|
+
|
470
|
+
def stop_actions_writer_loop
|
471
|
+
if @actions_writer_thread
|
472
|
+
@write_queue << :STOP!
|
473
|
+
@actions_writer_thread.join
|
474
|
+
@actions_writer_thread = nil
|
475
|
+
end
|
476
|
+
end
|
477
|
+
|
478
|
+
def actions_writer_loop
|
428
479
|
loop do
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
480
|
+
begin
|
481
|
+
next_action = @write_queue.shift
|
482
|
+
return :stopped if next_action.equal? :STOP!
|
483
|
+
register_action_with_metadata next_action
|
484
|
+
|
485
|
+
ahn_log.ami.debug "Sending AMI action: #{"\n>>> " + next_action.to_s.gsub(/(\r\n)+/, "\n>>> ")}"
|
486
|
+
@actions_connection.send_data next_action.to_s
|
487
|
+
# If it's "causal event" action, we must wait here until it's fully responded
|
488
|
+
next_action.response if next_action.has_causal_events?
|
489
|
+
rescue Object => e
|
490
|
+
ahn_log.ami.debug "Error in AMI writer loop: #{e.class}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
|
491
|
+
end
|
437
492
|
end
|
438
|
-
rescue => e
|
439
|
-
p e
|
440
493
|
end
|
441
494
|
|
442
495
|
##
|
@@ -462,6 +515,22 @@ module Adhearsion
|
|
462
515
|
end
|
463
516
|
end
|
464
517
|
|
518
|
+
# Give an error response to any outstanding messages -- they
|
519
|
+
# won't be completed now
|
520
|
+
def clear_actions_connection_resources
|
521
|
+
# Fail all outstanding messages and reset the message list
|
522
|
+
@sent_messages_lock.synchronize do
|
523
|
+
@sent_messages.each do |action_id, action|
|
524
|
+
error = ManagerInterfaceError.new
|
525
|
+
error.message = "Connection terminated to AMI server"
|
526
|
+
|
527
|
+
action.future_resource.resource = error
|
528
|
+
end
|
529
|
+
|
530
|
+
@sent_messages = {}
|
531
|
+
end
|
532
|
+
end
|
533
|
+
|
465
534
|
##
|
466
535
|
# Instantiates a new ManagerInterfaceActionsConnection and assigns it to @actions_connection.
|
467
536
|
#
|
@@ -469,13 +538,22 @@ module Adhearsion
|
|
469
538
|
#
|
470
539
|
def establish_actions_connection
|
471
540
|
@actions_connection = EventSocket.connect(@host, @port) do |handler|
|
472
|
-
handler.receive_data { |data| @actions_lexer << data
|
541
|
+
handler.receive_data { |data| @actions_lexer << data }
|
473
542
|
handler.connected { actions_connection_established }
|
474
543
|
handler.disconnected { actions_connection_disconnected }
|
475
544
|
end
|
476
545
|
login_actions
|
477
546
|
end
|
478
547
|
|
548
|
+
def disconnect_actions_connection
|
549
|
+
# Clean up the EventSocket we may have
|
550
|
+
if @actions_connection
|
551
|
+
@actions_connection.disconnect!
|
552
|
+
@actions_connection.join
|
553
|
+
@actions_connection = nil
|
554
|
+
end
|
555
|
+
end
|
556
|
+
|
479
557
|
##
|
480
558
|
# Instantiates a new ManagerInterfaceEventsConnection and assigns it to @events_connection.
|
481
559
|
#
|
@@ -485,7 +563,7 @@ module Adhearsion
|
|
485
563
|
|
486
564
|
# Note: the @events_connection instance variable is set in login()
|
487
565
|
@events_connection = EventSocket.connect(@host, @port) do |handler|
|
488
|
-
handler.receive_data { |data| @events_lexer << data
|
566
|
+
handler.receive_data { |data| @events_lexer << data }
|
489
567
|
handler.connected { events_connection_established }
|
490
568
|
handler.disconnected { events_connection_disconnected }
|
491
569
|
end
|
@@ -500,10 +578,29 @@ module Adhearsion
|
|
500
578
|
raise AuthenticationFailedException, "Incorrect username and password! #{response.message}"
|
501
579
|
else
|
502
580
|
ahn_log.ami "Successful AMI actions-only connection into #{@username}@#{@host}"
|
581
|
+
if @actions_lexer.ami_version < 1.1
|
582
|
+
@coreSettings = Hash.new
|
583
|
+
@coreSettings["AsteriskVersion"] = "1.4.0"
|
584
|
+
@coreSettings["AMIversion"] = "1.0"
|
585
|
+
@coreSettings["ArgumentDelimiter"] = "|"
|
586
|
+
else
|
587
|
+
@coreSettings = send_action_synchronously("CoreSettings").headers
|
588
|
+
@coreSettings["ArgumentDelimiter"] = ","
|
589
|
+
end
|
590
|
+
UnsupportedActionName::preinitialize(@coreSettings["AsteriskVersion"].to_f)
|
503
591
|
response
|
504
592
|
end
|
505
593
|
end
|
506
594
|
|
595
|
+
def disconnect_events_connection
|
596
|
+
# Clean up the EventSocket we may have
|
597
|
+
if @events_connection
|
598
|
+
@events_connection.disconnect!
|
599
|
+
@events_connection.join
|
600
|
+
@events_connection = nil
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
507
604
|
##
|
508
605
|
# Since this method is always called after the login_actions method, an AuthenticationFailedException would have already
|
509
606
|
# been raised if the username/password were off. Because this is the only action we ever need to send on this socket,
|
@@ -525,9 +622,9 @@ module Adhearsion
|
|
525
622
|
##
|
526
623
|
# Raised when calling ManagerInterface#connect!() and the server responds with an error after logging in.
|
527
624
|
#
|
528
|
-
class AuthenticationFailedException <
|
625
|
+
class AuthenticationFailedException < StandardError; end
|
529
626
|
|
530
|
-
class NotConnectedError <
|
627
|
+
class NotConnectedError < StandardError; end
|
531
628
|
|
532
629
|
##
|
533
630
|
# Each time ManagerInterface#send_action is invoked, a new ManagerInterfaceAction is instantiated.
|
data/lib/adhearsion/voip/call.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'uri'
|
2
|
+
require 'thread'
|
2
3
|
#TODO Some of this is asterisk-specific
|
3
4
|
module Adhearsion
|
4
5
|
class << self
|
@@ -16,7 +17,7 @@ module Adhearsion
|
|
16
17
|
end
|
17
18
|
end
|
18
19
|
|
19
|
-
class Hangup <
|
20
|
+
class Hangup < StandardError
|
20
21
|
# At the moment, we'll just use this to end a call-handling Thread.
|
21
22
|
end
|
22
23
|
|
@@ -52,7 +53,8 @@ module Adhearsion
|
|
52
53
|
end
|
53
54
|
end
|
54
55
|
|
55
|
-
# Searches all active calls by their unique_identifier. See Call#unique_identifier.
|
56
|
+
# Searches all active calls by their unique_identifier. See Call#unique_identifier.
|
57
|
+
# Is this actually by channel?
|
56
58
|
def find(id)
|
57
59
|
atomically do
|
58
60
|
return calls[id]
|
@@ -86,9 +88,9 @@ module Adhearsion
|
|
86
88
|
|
87
89
|
end
|
88
90
|
|
89
|
-
class UselessCallException <
|
91
|
+
class UselessCallException < StandardError; end
|
90
92
|
|
91
|
-
class MetaAgiCallException <
|
93
|
+
class MetaAgiCallException < StandardError
|
92
94
|
attr_reader :call
|
93
95
|
def initialize(call)
|
94
96
|
super()
|
@@ -113,7 +115,7 @@ module Adhearsion
|
|
113
115
|
# instance variable.
|
114
116
|
class CallMessageQueue < Queue
|
115
117
|
|
116
|
-
class InboxClosedException <
|
118
|
+
class InboxClosedException < StandardError
|
117
119
|
# this gets raised when the :cancel message is delivered to the queue and with_next_message (or similar auto-message-iteration)
|
118
120
|
# features are called.
|
119
121
|
end
|
@@ -200,10 +202,15 @@ module Adhearsion
|
|
200
202
|
end
|
201
203
|
end
|
202
204
|
|
203
|
-
|
204
|
-
|
205
|
+
# This may still be a symbol, but no longer requires the tag to be a symbol although beware
|
206
|
+
# that using a symbol would create a memory leak if used improperly
|
207
|
+
# @param [String, Symbol] label String or Symbol with which to tag this call
|
208
|
+
def tag(label)
|
209
|
+
if ![String, Symbol].include?(label.class)
|
210
|
+
raise ArgumentError, "Tag must be a String or Symbol"
|
211
|
+
end
|
205
212
|
@tag_mutex.synchronize do
|
206
|
-
@tags <<
|
213
|
+
@tags << label
|
207
214
|
end
|
208
215
|
end
|
209
216
|
|
@@ -1,6 +1,23 @@
|
|
1
1
|
# Hardcoding require for now since for some reason it's not being loaded
|
2
2
|
require 'adhearsion/voip/dsl/dialplan/control_passing_exception'
|
3
3
|
|
4
|
+
require 'adhearsion/version'
|
5
|
+
# JRuby contains a bug that breaks some of the menu functionality
|
6
|
+
# See: https://adhearsion.lighthouseapp.com/projects/5871/tickets/92-menu-method-under-jruby-does-not-appear-to-work
|
7
|
+
begin
|
8
|
+
curver = Adhearsion::PkgVersion.new(JRUBY_VERSION)
|
9
|
+
minver = Adhearsion::PkgVersion.new("1.6.0")
|
10
|
+
if curver < minver
|
11
|
+
puts "****************************************************************************"
|
12
|
+
puts "Versions of JRuby prior to 1.6.0 contain a bug that limits the prevents"
|
13
|
+
puts "using the \"+\" operator to jump from one context to another."
|
14
|
+
puts "Adhearsion has detected JRuby version #{JRUBY_VERSION}. For more information see:"
|
15
|
+
puts "https://adhearsion.lighthouseapp.com/projects/5871/tickets/92-menu-method-under-jruby-does-not-appear-to-work"
|
16
|
+
puts "****************************************************************************"
|
17
|
+
end
|
18
|
+
rescue NameError # In case JRUBY_VERSION is not defined.
|
19
|
+
end
|
20
|
+
|
4
21
|
module Adhearsion
|
5
22
|
class DialPlan
|
6
23
|
attr_accessor :loader, :entry_points
|
@@ -76,7 +93,7 @@ module Adhearsion
|
|
76
93
|
|
77
94
|
class Manager
|
78
95
|
|
79
|
-
class NoContextError <
|
96
|
+
class NoContextError < StandardError; end
|
80
97
|
|
81
98
|
class << self
|
82
99
|
def handle(call)
|
@@ -110,10 +127,10 @@ module Adhearsion
|
|
110
127
|
# Find the dialplan by the context name from the call or from the
|
111
128
|
# first path entry in the AGI URL
|
112
129
|
def entry_point_for(call)
|
113
|
-
if
|
114
|
-
entry_point
|
115
|
-
elsif call.respond_to?(:request) && m = call.request.path.match(%r{/([^/]+)})
|
130
|
+
if call.respond_to?(:request) && m = call.request.path.match(%r{/([^/]+)})
|
116
131
|
dial_plan.lookup(m[1].to_sym)
|
132
|
+
elsif entry_point = dial_plan.lookup(call.context.to_sym)
|
133
|
+
entry_point
|
117
134
|
end
|
118
135
|
end
|
119
136
|
|