eric-adhearsion 0.7.999 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (150) hide show
  1. data/CHANGELOG +8 -2
  2. data/EVENTS +11 -0
  3. data/Rakefile +96 -24
  4. data/adhearsion.gemspec +148 -0
  5. data/app_generators/ahn/ahn_generator.rb +24 -9
  6. data/app_generators/ahn/templates/.ahnrc +25 -3
  7. data/app_generators/ahn/templates/Rakefile +22 -2
  8. data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
  9. data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
  10. data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
  11. data/app_generators/ahn/templates/components/disabled/stomp_gateway/config.yml +12 -0
  12. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
  13. data/app_generators/ahn/templates/components/simon_game/{lib/simon_game.rb → simon_game.rb} +14 -19
  14. data/app_generators/ahn/templates/config/startup.rb +3 -6
  15. data/app_generators/ahn/templates/dialplan.rb +2 -3
  16. data/app_generators/ahn/templates/events.rb +32 -0
  17. data/bin/jahn +10 -0
  18. data/examples/asterisk_manager_interface/standalone.rb +51 -0
  19. data/lib/adhearsion.rb +17 -11
  20. data/lib/adhearsion/cli.rb +141 -24
  21. data/lib/adhearsion/component_manager.rb +169 -238
  22. data/lib/adhearsion/component_manager/component_tester.rb +55 -0
  23. data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
  24. data/lib/adhearsion/events_support.rb +84 -0
  25. data/lib/adhearsion/{core_extensions → foundation}/all.rb +0 -0
  26. data/lib/adhearsion/{blank_slate.rb → foundation/blank_slate.rb} +0 -0
  27. data/lib/adhearsion/{core_extensions → foundation}/custom_daemonizer.rb +0 -0
  28. data/lib/adhearsion/foundation/event_socket.rb +203 -0
  29. data/lib/adhearsion/foundation/future_resource.rb +36 -0
  30. data/lib/adhearsion/{core_extensions → foundation}/global.rb +0 -0
  31. data/lib/adhearsion/{core_extensions → foundation}/metaprogramming.rb +0 -0
  32. data/lib/adhearsion/foundation/numeric.rb +13 -0
  33. data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
  34. data/lib/adhearsion/{core_extensions → foundation}/relationship_properties.rb +2 -0
  35. data/lib/adhearsion/foundation/string.rb +26 -0
  36. data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
  37. data/lib/adhearsion/{core_extensions → foundation}/thread_safety.rb +0 -0
  38. data/lib/adhearsion/host_definitions.rb +5 -1
  39. data/lib/adhearsion/initializer.rb +229 -73
  40. data/lib/adhearsion/initializer/asterisk.rb +33 -11
  41. data/lib/adhearsion/initializer/configuration.rb +58 -6
  42. data/lib/adhearsion/initializer/database.rb +3 -46
  43. data/lib/adhearsion/initializer/drb.rb +9 -3
  44. data/lib/adhearsion/initializer/freeswitch.rb +3 -3
  45. data/lib/adhearsion/initializer/rails.rb +1 -1
  46. data/lib/adhearsion/tasks.rb +2 -1
  47. data/lib/adhearsion/tasks/deprecations.rb +59 -0
  48. data/lib/adhearsion/version.rb +3 -3
  49. data/lib/adhearsion/voip/asterisk.rb +2 -2
  50. data/lib/adhearsion/voip/asterisk/agi_server.rb +9 -6
  51. data/lib/adhearsion/voip/asterisk/commands.rb +106 -4
  52. data/lib/adhearsion/voip/asterisk/manager_interface.rb +562 -0
  53. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1754 -0
  54. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +286 -0
  55. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
  56. data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
  57. data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
  58. data/lib/adhearsion/voip/call.rb +51 -2
  59. data/lib/adhearsion/voip/dial_plan.rb +74 -61
  60. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +1 -1
  61. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +2 -6
  62. data/lib/adhearsion/voip/dsl/numerical_string.rb +2 -2
  63. data/lib/adhearsion/voip/freeswitch/oes_server.rb +2 -2
  64. data/lib/theatre.rb +151 -0
  65. data/lib/theatre/README.markdown +64 -0
  66. data/lib/theatre/callback_definition_loader.rb +84 -0
  67. data/lib/theatre/guid.rb +23 -0
  68. data/lib/theatre/invocation.rb +121 -0
  69. data/lib/theatre/namespace_manager.rb +153 -0
  70. data/lib/theatre/version.rb +2 -0
  71. metadata +63 -138
  72. data/Manifest.txt +0 -149
  73. data/README.txt +0 -6
  74. data/ahn_generators/component/USAGE +0 -5
  75. data/ahn_generators/component/component_generator.rb +0 -57
  76. data/ahn_generators/component/templates/configuration.rb +0 -0
  77. data/ahn_generators/component/templates/lib/lib.rb.erb +0 -3
  78. data/ahn_generators/component/templates/test/test.rb.erb +0 -12
  79. data/ahn_generators/component/templates/test/test_helper.rb +0 -14
  80. data/app_generators/ahn/templates/components/simon_game/configuration.rb +0 -0
  81. data/app_generators/ahn/templates/components/simon_game/test/test_helper.rb +0 -14
  82. data/app_generators/ahn/templates/components/simon_game/test/test_simon_game.rb +0 -31
  83. data/lib/adhearsion/core_extensions/array.rb +0 -0
  84. data/lib/adhearsion/core_extensions/guid.rb +0 -5
  85. data/lib/adhearsion/core_extensions/hash.rb +0 -0
  86. data/lib/adhearsion/core_extensions/numeric.rb +0 -4
  87. data/lib/adhearsion/core_extensions/proc.rb +0 -0
  88. data/lib/adhearsion/core_extensions/pseudo_uuid.rb +0 -11
  89. data/lib/adhearsion/core_extensions/publishable.rb +0 -73
  90. data/lib/adhearsion/core_extensions/string.rb +0 -26
  91. data/lib/adhearsion/core_extensions/thread.rb +0 -13
  92. data/lib/adhearsion/core_extensions/time.rb +0 -0
  93. data/lib/adhearsion/distributed/gateways/dbus_gateway.rb +0 -0
  94. data/lib/adhearsion/distributed/gateways/osa_gateway.rb +0 -0
  95. data/lib/adhearsion/distributed/gateways/rest_gateway.rb +0 -9
  96. data/lib/adhearsion/distributed/gateways/soap_gateway.rb +0 -9
  97. data/lib/adhearsion/distributed/gateways/xmlrpc_gateway.rb +0 -9
  98. data/lib/adhearsion/distributed/peer_finder.rb +0 -0
  99. data/lib/adhearsion/distributed/remote_cli.rb +0 -0
  100. data/lib/adhearsion/hooks.rb +0 -57
  101. data/lib/adhearsion/initializer/paths.rb +0 -55
  102. data/lib/adhearsion/services/scheduler.rb +0 -5
  103. data/lib/adhearsion/voip/asterisk/ami.rb +0 -147
  104. data/lib/adhearsion/voip/asterisk/ami/actions.rb +0 -238
  105. data/lib/adhearsion/voip/asterisk/ami/machine.rb +0 -871
  106. data/lib/adhearsion/voip/asterisk/ami/machine.rl +0 -109
  107. data/lib/adhearsion/voip/asterisk/ami/parser.rb +0 -262
  108. data/script/destroy +0 -14
  109. data/script/generate +0 -14
  110. data/spec/fixtures/dialplan.rb +0 -3
  111. data/spec/initializer/test_configuration.rb +0 -267
  112. data/spec/initializer/test_loading.rb +0 -162
  113. data/spec/initializer/test_paths.rb +0 -43
  114. data/spec/silence.rb +0 -10
  115. data/spec/test_ahn_command.rb +0 -149
  116. data/spec/test_code_quality.rb +0 -87
  117. data/spec/test_component_manager.rb +0 -97
  118. data/spec/test_constants.rb +0 -8
  119. data/spec/test_drb.rb +0 -104
  120. data/spec/test_helper.rb +0 -94
  121. data/spec/test_hooks.rb +0 -37
  122. data/spec/test_host_definitions.rb +0 -79
  123. data/spec/test_initialization.rb +0 -105
  124. data/spec/test_logging.rb +0 -80
  125. data/spec/test_relationship_properties.rb +0 -54
  126. data/spec/voip/asterisk/ami_response_definitions.rb +0 -23
  127. data/spec/voip/asterisk/config_file_generators/test_agents.rb +0 -253
  128. data/spec/voip/asterisk/config_file_generators/test_queues.rb +0 -325
  129. data/spec/voip/asterisk/config_file_generators/test_voicemail.rb +0 -306
  130. data/spec/voip/asterisk/menu_command/test_calculated_match.rb +0 -111
  131. data/spec/voip/asterisk/menu_command/test_matchers.rb +0 -98
  132. data/spec/voip/asterisk/mock_ami_server.rb +0 -176
  133. data/spec/voip/asterisk/test_agi_server.rb +0 -451
  134. data/spec/voip/asterisk/test_ami.rb +0 -227
  135. data/spec/voip/asterisk/test_commands.rb +0 -2006
  136. data/spec/voip/asterisk/test_config_manager.rb +0 -129
  137. data/spec/voip/dsl/dispatcher_spec_helper.rb +0 -45
  138. data/spec/voip/dsl/test_dialing_dsl.rb +0 -268
  139. data/spec/voip/dsl/test_dispatcher.rb +0 -82
  140. data/spec/voip/dsl/test_parser.rb +0 -87
  141. data/spec/voip/freeswitch/test_basic_connection_manager.rb +0 -39
  142. data/spec/voip/freeswitch/test_inbound_connection_manager.rb +0 -39
  143. data/spec/voip/freeswitch/test_oes_server.rb +0 -9
  144. data/spec/voip/test_call_routing.rb +0 -127
  145. data/spec/voip/test_dialplan_manager.rb +0 -372
  146. data/spec/voip/test_numerical_string.rb +0 -48
  147. data/spec/voip/test_phone_number.rb +0 -36
  148. data/test/test_ahn_generator.rb +0 -59
  149. data/test/test_component_generator.rb +0 -52
  150. data/test/test_generator_helper.rb +0 -20
@@ -0,0 +1,562 @@
1
+ require 'adhearsion/voip/asterisk/manager_interface/ami_lexer'
2
+
3
+ module Adhearsion
4
+ module VoIP
5
+ module Asterisk
6
+
7
+ ##
8
+ # Sorry, this AMI class has been deprecated. Please see http://docs.adhearsion.com/Asterisk_Manager_Interface for
9
+ # documentation on the new way of handling AMI. This new version is much better and should not require an enormous
10
+ # migration on your part.
11
+ #
12
+ class AMI
13
+ def initialize
14
+ raise "Sorry, this AMI class has been deprecated. Please see http://docs.adhearsion.com/Asterisk_Manager_Interface for documentation on the new way of handling AMI. This new version is much better and should not require an enormous migration on your part."
15
+ end
16
+ end
17
+
18
+ mattr_accessor :manager_interface
19
+
20
+ module Manager
21
+
22
+ ##
23
+ # This class abstracts a connection to the Asterisk Manager Interface. Its purpose is, first and foremost, to make
24
+ # the protocol consistent. Though the classes employed to assist this class (ManagerInterfaceAction,
25
+ # ManagerInterfaceResponse, ManagerInterfaceError, etc.) are relatively user-friendly, they're designed to be a
26
+ # building block on which to build higher-level abstractions of the Asterisk Manager Interface.
27
+ #
28
+ # For a higher-level abstraction of the Asterisk Manager Interface, see the SuperManager class.
29
+ #
30
+ class ManagerInterface
31
+
32
+ class << self
33
+
34
+ def connect(*args)
35
+ returning new(*args) do |connection|
36
+ connection.connect!
37
+ end
38
+ end
39
+
40
+ def replies_with_action_id?(name, headers={})
41
+ name = name.to_s.downcase
42
+ # TODO: Expand this case statement
43
+ case name
44
+ when "queues", "iaxpeers"
45
+ false
46
+ else
47
+ true
48
+ end
49
+ end
50
+
51
+ ##
52
+ # When sending an action with "causal events" (i.e. events which must be collected to form a proper
53
+ # response), AMI should send a particular event which instructs us that no more events will be sent.
54
+ # This event is called the "causal event terminator".
55
+ #
56
+ # Note: you must supply both the name of the event and any headers because it's possible that some uses of an
57
+ # action (i.e. same name, different headers) have causal events while other uses don't.
58
+ #
59
+ # @param [String] name the name of the event
60
+ # @param [Hash] the headers associated with this event
61
+ # @return [String] the downcase()'d name of the event name for which to wait
62
+ #
63
+ def has_causal_events?(name, headers={})
64
+ name = name.to_s.downcase
65
+ case name
66
+ when "queuestatus", "sippeers", "parkedcalls", "status"
67
+ true
68
+ else
69
+ false
70
+ end
71
+ end
72
+
73
+ ##
74
+ # Used to determine the event name for an action which has causal events.
75
+ #
76
+ # @param [String] action_name
77
+ # @return [String] The corresponding event name which signals the completion of the causal event sequence.
78
+ #
79
+ def causal_event_terminator_name_for(action_name)
80
+ return nil unless has_causal_events?(action_name)
81
+ action_name = action_name.to_s.downcase
82
+ case action_name
83
+ when "queuestatus", 'parkedcalls', "status"
84
+ action_name + "complete"
85
+ when "sippeers"
86
+ "peerlistcomplete"
87
+ end
88
+ end
89
+
90
+ end
91
+
92
+ DEFAULT_SETTINGS = {
93
+ :host => "localhost",
94
+ :port => 5038,
95
+ :username => "admin",
96
+ :password => "secret",
97
+ :events => true
98
+ }.freeze unless defined? DEFAULT_SETTINGS
99
+
100
+ attr_reader *DEFAULT_SETTINGS.keys
101
+
102
+ ##
103
+ # Creates a new Asterisk Manager Interface connection and exposes certain methods to control it. The constructor
104
+ # takes named parameters as Symbols. Note: if the :events option is given, this library will establish a separate
105
+ # socket for just events. Two sockets are used because some actions actually respond with events, making it very
106
+ # complicated to differentiate between response-type events and normal events.
107
+ #
108
+ # @param [Hash] options Available options are :host, :port, :username, :password, and :events
109
+ #
110
+ def initialize(options={})
111
+ options = parse_options options
112
+
113
+ @host = options[:host]
114
+ @username = options[:username]
115
+ @password = options[:password]
116
+ @port = options[:port]
117
+ @events = options[:events]
118
+
119
+ @sent_messages = {}
120
+ @sent_messages_lock = Mutex.new
121
+
122
+ @actions_lexer = DelegatingAsteriskManagerInterfaceLexer.new self, \
123
+ :message_received => :action_message_received,
124
+ :error_received => :action_error_received
125
+
126
+ @write_queue = Queue.new
127
+
128
+ if @events
129
+ @events_lexer = DelegatingAsteriskManagerInterfaceLexer.new self, \
130
+ :message_received => :event_message_received,
131
+ :error_received => :event_error_received
132
+ end
133
+ end
134
+
135
+ def action_message_received(message)
136
+ if message.kind_of? Manager::ManagerInterfaceEvent
137
+ # Trigger the return value of the waiting action id...
138
+ corresponding_action = @current_action_with_causal_events
139
+ event_collection = @event_collection_for_current_action
140
+
141
+ if corresponding_action
142
+
143
+ # If this is the meta-event which signals no more events will follow and the response is complete.
144
+ if message.name.downcase == corresponding_action.causal_event_terminator_name
145
+
146
+ # Result found! Wake up any Threads waiting
147
+ corresponding_action.future_resource.resource = event_collection.freeze
148
+
149
+ @current_action_with_causal_events = nil
150
+ @event_collection_for_current_action = nil
151
+
152
+ else
153
+ event_collection << message
154
+ # We have more causal events coming.
155
+ end
156
+ else
157
+ ahn_log.ami.error "Got an unexpected event on actions socket! This may be a bug! #{message.inspect}"
158
+ end
159
+
160
+ elsif message["ActionID"].nil?
161
+ # No ActionID! Release the write lock and wake up the waiter
162
+ else
163
+ action_id = message["ActionID"]
164
+ corresponding_action = data_for_message_received_with_action_id action_id
165
+ if corresponding_action
166
+ message.action = corresponding_action
167
+
168
+ if corresponding_action.has_causal_events?
169
+ # By this point the write loop will already have started blocking by calling the response() method on the
170
+ # action. Because we must collect more events before we wake the write loop up again, let's create these
171
+ # instance variable which will needed when the subsequent causal events come in.
172
+ @current_action_with_causal_events = corresponding_action
173
+ @event_collection_for_current_action = []
174
+ else
175
+ # Wake any Threads waiting on the response.
176
+ corresponding_action.future_resource.resource = message
177
+ end
178
+ else
179
+ ahn_log.ami.error "Received an AMI message with an unrecognized ActionID!! This may be an bug! #{message.inspect}"
180
+ end
181
+ end
182
+ end
183
+
184
+ def action_error_received(ami_error)
185
+ action_id = ami_error["ActionID"]
186
+
187
+ corresponding_action = data_for_message_received_with_action_id action_id
188
+
189
+ if corresponding_action
190
+ corresponding_action.future_resource.resource = ami_error
191
+ else
192
+ ahn_log.ami.error "Received an AMI error with an unrecognized ActionID!! This may be an bug! #{ami_error.inspect}"
193
+ end
194
+ end
195
+
196
+ ##
197
+ # Called only when this ManagerInterface is instantiated with events enabled.
198
+ #
199
+ def event_message_received(event)
200
+ return if event.kind_of?(ManagerInterfaceResponse) && event["Message"] == "Authentication accepted"
201
+ # TODO: convert the event name to a certain namespace.
202
+ Events.trigger %w[asterisk manager_interface], event
203
+ end
204
+
205
+ def event_error_received(message)
206
+ # Does this ever even occur?
207
+ ahn_log.ami.error "Hmmm, got an error on the AMI events-only socket! This must be a bug! #{message.inspect}"
208
+ end
209
+
210
+ ##
211
+ # Called when our Ragel parser encounters some unexpected syntax from Asterisk. Anytime this is called, it should
212
+ # be considered a bug in Adhearsion. Note: this same method is called regardless of whether the syntax error
213
+ # happened on the actions socket or on the events socket.
214
+ #
215
+ def syntax_error_encountered(ignored_chunk)
216
+ ahn_log.ami.error "ADHEARSION'S AMI PARSER ENCOUNTERED A SYNTAX ERROR! " +
217
+ "PLEASE REPORT THIS ON http://bugs.adhearsion.com! OFFENDING TEXT:\n#{ignored_chunk.inspect}"
218
+ end
219
+
220
+ ##
221
+ # Must be called after instantiation. Also see ManagerInterface::connect().
222
+ #
223
+ # @raise [AuthenticationFailedException] if username or password are rejected
224
+ #
225
+ def connect!
226
+ establish_actions_connection
227
+ establish_events_connection if @events
228
+ self
229
+ end
230
+
231
+ def actions_connection_established
232
+ @actions_state = :connected
233
+ @actions_writer_thread = Thread.new(&method(:write_loop))
234
+ end
235
+
236
+ def actions_connection_disconnected
237
+ @actions_state = :disconnected
238
+ end
239
+
240
+ def events_connection_established
241
+ @events_state = :connected
242
+ end
243
+
244
+ def actions_connection_disconnected
245
+ @events_state = :disconnected
246
+ end
247
+
248
+ def disconnect!
249
+ # PSEUDO CODE
250
+ # TODO: Go through all the waiting condition variables and raise an exception
251
+ #@write_queue << :STOP!
252
+ raise NotImplementedError
253
+ end
254
+
255
+ def dynamic
256
+ # TODO: Return an object which responds to method_missing
257
+ end
258
+
259
+ ##
260
+ # Used to directly send a new action to Asterisk. Note: NEVER supply an ActionID; these are handled internally.
261
+ #
262
+ # @param [String, Symbol] action_name The name of the action (e.g. Originate)
263
+ # @param [Hash] headers Other key/value pairs to send in this action. Note: don't provide an ActionID
264
+ # @return [FutureResource] Call resource() on this object if you wish to access the response (optional). Note: if the response has not come in yet, your Thread will wait until it does.
265
+ #
266
+ def send_action_asynchronously(action_name, headers={})
267
+ check_action_name action_name
268
+ action = ManagerInterfaceAction.new(action_name, headers)
269
+ if action.replies_with_action_id?
270
+ @write_queue << action
271
+ action
272
+ else
273
+ raise NotImplementedError
274
+ end
275
+ end
276
+
277
+ ##
278
+ # Sends an action over the AMI connection and blocks your Thread until the response comes in. If there was an error
279
+ # for some reason, the error will be raised as an ManagerInterfaceError.
280
+ #
281
+ # @param [String, Symbol] action_name The name of the action (e.g. Originate)
282
+ # @param [Hash] headers Other key/value pairs to send in this action. Note: don't provide an ActionID
283
+ # @raise [ManagerInterfaceError] When Asterisk can't execute this action, it sends back an Error which is converted into an ManagerInterfaceError object and raised. Access ManagerInterfaceError#message for the reported message from Asterisk.
284
+ # @return [ManagerInterfaceResponse, ImmediateResponse] Contains the response from Asterisk and all headers
285
+ #
286
+ def send_action_synchronously(*args)
287
+ returning send_action_asynchronously(*args).response do |response|
288
+ raise response if response.kind_of?(ManagerInterfaceError)
289
+ end
290
+ end
291
+
292
+ alias send_action send_action_synchronously
293
+
294
+
295
+ ####### #######
296
+ ########### ###########
297
+ ################# SOON-DEPRECATED COMMANDS #################
298
+ ########### ###########
299
+ ####### #######
300
+
301
+ def ping
302
+ deprecation_warning
303
+ send_action "Ping"
304
+ end
305
+
306
+ def deprecation_warning
307
+ ahn_log.ami.deprecation.warn "The implementation of the ping, originate, introduce, hangup, call_into_context " +
308
+ "and call_and_exec methods will soon be moved from this class to SuperManager. At the moment, the " +
309
+ "SuperManager abstractions are not completed. Don't worry. The migration to SuperManager will be very easy."+
310
+ " See http://docs.adhearsion.com/AMI for more information."
311
+ end
312
+
313
+ def originate(options={})
314
+ deprecation_warning
315
+ options = options.clone
316
+ options[:callerid] = options.delete :caller_id if options.has_key? :caller_id
317
+ send_action "Originate", options
318
+ end
319
+
320
+ # An introduction connects two endpoints together. The first argument is
321
+ # the first person the PBX will call. When she's picked up, Asterisk will
322
+ # play ringing while the second person is being dialed.
323
+ #
324
+ # The first argument is the person called first. Pass this as a canonical
325
+ # IAX2/server/user type argument. Destination takes the same format, but
326
+ # comma-separated Dial() arguments can be optionally passed after the
327
+ # technology.
328
+ #
329
+ # TODO: Provide an example when this works.
330
+ #
331
+ def introduce(caller, callee, opts={})
332
+ deprecation_warning
333
+ dial_args = callee
334
+ dial_args += "|#{opts[:options]}" if opts[:options]
335
+ call_and_exec caller, "Dial", :args => dial_args, :caller_id => opts[:caller_id]
336
+ end
337
+
338
+ def hangup(channel)
339
+ deprecation_warning
340
+ send_action "Hangup", :channel => channel
341
+ end
342
+
343
+ def call_and_exec(channel, app, opts={})
344
+ deprecation_warning
345
+ args = { :channel => channel, :application => app }
346
+ args[:caller_id] = opts[:caller_id] if opts[:caller_id]
347
+ args[:data] = opts[:args] if opts[:args]
348
+ originate args
349
+ end
350
+
351
+ def call_into_context(channel, context, options={})
352
+ deprecation_warning
353
+ args = {:channel => channel, :context => context}
354
+ args[:priority] = options[:priority] || 1
355
+ args[:extension] = options[:extension] if options[:extension]
356
+ args[:caller_id] = options[:caller_id] if options[:caller_id]
357
+ if options[:variables] && options[:variables].kind_of?(Hash)
358
+ args[:variable] = options[:variables].map {|pair| pair.join('=')}.join('|')
359
+ end
360
+ originate args
361
+ end
362
+
363
+ ####### #######
364
+ ########### ###########
365
+ ################# END SOON-DEPRECATED COMMANDS #################
366
+ ########### ###########
367
+ ####### #######
368
+
369
+
370
+ protected
371
+
372
+ ##
373
+ # This class will be removed once this AMI library fully supports all known protocol anomalies.
374
+ #
375
+ class UnsupportedActionName < ArgumentError
376
+ UNSUPPORTED_ACTION_NAMES = %w[
377
+ queues
378
+ iaxpeers
379
+ ] unless defined? UNSUPPORTED_ACTION_NAMES
380
+ def initialize(name)
381
+ 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."
382
+ end
383
+
384
+ end
385
+
386
+ def check_action_name(name)
387
+ name = name.to_s.downcase
388
+ raise UnsupportedActionName.new(name) if UnsupportedActionName::UNSUPPORTED_ACTION_NAMES.include? name
389
+ true
390
+ end
391
+
392
+ def write_loop
393
+ loop do
394
+ next_action = @write_queue.shift
395
+ return :stopped if next_action.equal? :STOP!
396
+ register_action_with_metadata next_action
397
+
398
+ ahn_log.ami.debug "Sending AMI action: #{"\n>>> " + next_action.to_s.gsub(/(\r\n)+/, "\n>>> ")}"
399
+ @actions_connection.send_data next_action.to_s
400
+ # If it's "causal event" action, we must wait here until it's fully responded
401
+ next_action.response if next_action.has_causal_events?
402
+ end
403
+ rescue => e
404
+ p e
405
+ end
406
+
407
+ ##
408
+ # When we send out an AMI action, we need to track the ActionID and have the other Thread handling the socket IO
409
+ # notify the sending Thread that a response has been received. This method instantiates a new FutureResource and
410
+ # keeps it around in a synchronized Hash for the IO-handling Thread to notify when a response with a matching
411
+ # ActionID is seen again. See also data_for_message_received_with_action_id() which is how the IO-handling Thread
412
+ # gets the metadata registered in the method back later.
413
+ #
414
+ # @param [ManagerInterfaceAction] action The ManagerInterfaceAction to send
415
+ # @param [Hash] headers The other key/value pairs being sent with this message
416
+ #
417
+ def register_action_with_metadata(action)
418
+ raise ArgumentError, "Must supply an action!" if action.nil?
419
+ @sent_messages_lock.synchronize do
420
+ @sent_messages[action.action_id] = action
421
+ end
422
+ end
423
+
424
+ def data_for_message_received_with_action_id(action_id)
425
+ @sent_messages_lock.synchronize do
426
+ @sent_messages.delete action_id
427
+ end
428
+ end
429
+
430
+ ##
431
+ # Instantiates a new ManagerInterfaceActionsConnection and assigns it to @actions_connection.
432
+ #
433
+ # @return [EventSocket]
434
+ #
435
+ def establish_actions_connection
436
+ @actions_connection = EventSocket.connect(@host, @port) do |handler|
437
+ handler.receive_data { |data| @actions_lexer << data }
438
+ handler.connected { actions_connection_established }
439
+ handler.disconnected { actions_connection_disconnected }
440
+ end
441
+ login_actions
442
+ end
443
+
444
+ ##
445
+ # Instantiates a new ManagerInterfaceEventsConnection and assigns it to @events_connection.
446
+ #
447
+ # @return [EventSocket]
448
+ #
449
+ def establish_events_connection
450
+
451
+ # Note: the @events_connection instance variable is set in login()
452
+ @events_connection = EventSocket.connect(@host, @port) do |handler|
453
+ handler.receive_data { |data| @events_lexer << data }
454
+ handler.connected { events_connection_established }
455
+ handler.disconnected { events_connection_disconnected }
456
+ end
457
+ login_events
458
+ ahn_log.ami "Successful AMI events-only connection into #{@username}@#{@host}"
459
+ end
460
+
461
+ def login_actions
462
+ action = send_action_asynchronously "Login", "Username" => @username, "Secret" => @password, "Events" => "Off"
463
+ response = action.response
464
+ if response.kind_of? ManagerInterfaceError
465
+ raise AuthenticationFailedException, "Incorrect username and password! #{response.message}"
466
+ else
467
+ ahn_log.ami "Successful AMI actions-only connection into #{@username}@#{@host}"
468
+ response
469
+ end
470
+ end
471
+
472
+ ##
473
+ # Since this method is always called after the login_actions method, an AuthenticationFailedException would have already
474
+ # been raised if the username/password were off. Because this is the only action we ever need to send on this socket,
475
+ # it goes straight to the EventSocket connection (bypassing the @write_queue).
476
+ #
477
+ def login_events
478
+ login_action = ManagerInterfaceAction.new "Login", "Username" => @username, "Secret" => @password, "Events" => "On"
479
+ @events_connection.send_data login_action.to_s
480
+ end
481
+
482
+ def parse_options(options)
483
+ unrecognized_keys = options.keys.map { |key| key.to_sym } - DEFAULT_SETTINGS.keys
484
+ if unrecognized_keys.any?
485
+ raise ArgumentError, "Unrecognized named argument(s): #{unrecognized_keys.to_sentence}"
486
+ end
487
+ DEFAULT_SETTINGS.merge options
488
+ end
489
+
490
+ ##
491
+ # Raised when calling ManagerInterface#connect!() and the server responds with an error after logging in.
492
+ #
493
+ class AuthenticationFailedException < Exception; end
494
+
495
+ class NotConnectedError < Exception; end
496
+
497
+ ##
498
+ # Each time ManagerInterface#send_action is invoked, a new ManagerInterfaceAction is instantiated.
499
+ #
500
+ class ManagerInterfaceAction
501
+
502
+ attr_reader :name, :headers, :future_resource, :action_id, :causal_event_terminator_name
503
+ def initialize(name, headers={})
504
+ @name = name.to_s.downcase.freeze
505
+ @headers = headers.stringify_keys.freeze
506
+ @action_id = new_action_id.freeze
507
+ @future_resource = FutureResource.new
508
+ @causal_event_terminator_name = ManagerInterface.causal_event_terminator_name_for name
509
+ end
510
+
511
+ ##
512
+ # Used internally by ManagerInterface for the actions in AMI which break the protocol's definition and do not
513
+ # reply with an ActionID.
514
+ #
515
+ def replies_with_action_id?
516
+ ManagerInterface.replies_with_action_id?(@name, @headers)
517
+ end
518
+
519
+ ##
520
+ # Some AMI actions effectively respond with many events which collectively constitute the actual response. These
521
+ # Must be handled specially by the protocol parser, so this method helps inform the parser.
522
+ #
523
+ def has_causal_events?
524
+ ManagerInterface.has_causal_events?(@name, @headers)
525
+ end
526
+
527
+ ##
528
+ # Abstracts the generation of new ActionIDs. This could be implemented virutally any way, provided each
529
+ # invocation returns something unique, so this will generate a GUID and return it.
530
+ #
531
+ # @return [String] characters in GUID format (e.g. "4C5F4E1C-A0F1-4D13-8751-C62F2F783062")
532
+ #
533
+ def new_action_id
534
+ new_guid # Implemented in lib/adhearsion/foundation/pseudo_guid.rb
535
+ end
536
+
537
+ ##
538
+ # Converts this action into a protocol-valid String, ready to be sent over a socket.
539
+ #
540
+ def to_s
541
+ @textual_representation ||= (
542
+ "Action: #{@name}\r\nActionID: #{@action_id}\r\n" +
543
+ @headers.map { |(key,value)| "#{key}: #{value}" }.join("\r\n") +
544
+ (@headers.any? ? "\r\n\r\n" : "\r\n")
545
+ )
546
+ end
547
+
548
+ ##
549
+ # If the response has simply not been received yet from Asterisk, the calling Thread will block until it comes
550
+ # in. Once the response comes in, subsequent calls immediately return a reference to the ManagerInterfaceResponse
551
+ # object.
552
+ #
553
+ def response
554
+ future_resource.resource
555
+ end
556
+
557
+ end
558
+ end
559
+ end
560
+ end
561
+ end
562
+ end