eric-adhearsion 0.7.999

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 +3 -0
  2. data/LICENSE +456 -0
  3. data/Manifest.txt +149 -0
  4. data/README.txt +6 -0
  5. data/Rakefile +48 -0
  6. data/ahn_generators/component/USAGE +5 -0
  7. data/ahn_generators/component/component_generator.rb +57 -0
  8. data/ahn_generators/component/templates/configuration.rb +0 -0
  9. data/ahn_generators/component/templates/lib/lib.rb.erb +3 -0
  10. data/ahn_generators/component/templates/test/test.rb.erb +12 -0
  11. data/ahn_generators/component/templates/test/test_helper.rb +14 -0
  12. data/app_generators/ahn/USAGE +5 -0
  13. data/app_generators/ahn/ahn_generator.rb +76 -0
  14. data/app_generators/ahn/templates/.ahnrc +12 -0
  15. data/app_generators/ahn/templates/README +8 -0
  16. data/app_generators/ahn/templates/Rakefile +3 -0
  17. data/app_generators/ahn/templates/components/simon_game/configuration.rb +0 -0
  18. data/app_generators/ahn/templates/components/simon_game/lib/simon_game.rb +61 -0
  19. data/app_generators/ahn/templates/components/simon_game/test/test_helper.rb +14 -0
  20. data/app_generators/ahn/templates/components/simon_game/test/test_simon_game.rb +31 -0
  21. data/app_generators/ahn/templates/config/startup.rb +53 -0
  22. data/app_generators/ahn/templates/dialplan.rb +4 -0
  23. data/bin/ahn +28 -0
  24. data/bin/ahnctl +68 -0
  25. data/bin/jahn +32 -0
  26. data/lib/adhearsion/blank_slate.rb +5 -0
  27. data/lib/adhearsion/cli.rb +106 -0
  28. data/lib/adhearsion/component_manager.rb +277 -0
  29. data/lib/adhearsion/core_extensions/all.rb +9 -0
  30. data/lib/adhearsion/core_extensions/array.rb +0 -0
  31. data/lib/adhearsion/core_extensions/custom_daemonizer.rb +45 -0
  32. data/lib/adhearsion/core_extensions/global.rb +1 -0
  33. data/lib/adhearsion/core_extensions/guid.rb +5 -0
  34. data/lib/adhearsion/core_extensions/hash.rb +0 -0
  35. data/lib/adhearsion/core_extensions/metaprogramming.rb +17 -0
  36. data/lib/adhearsion/core_extensions/numeric.rb +4 -0
  37. data/lib/adhearsion/core_extensions/proc.rb +0 -0
  38. data/lib/adhearsion/core_extensions/pseudo_uuid.rb +11 -0
  39. data/lib/adhearsion/core_extensions/publishable.rb +73 -0
  40. data/lib/adhearsion/core_extensions/relationship_properties.rb +40 -0
  41. data/lib/adhearsion/core_extensions/string.rb +26 -0
  42. data/lib/adhearsion/core_extensions/thread.rb +13 -0
  43. data/lib/adhearsion/core_extensions/thread_safety.rb +7 -0
  44. data/lib/adhearsion/core_extensions/time.rb +0 -0
  45. data/lib/adhearsion/distributed/gateways/dbus_gateway.rb +0 -0
  46. data/lib/adhearsion/distributed/gateways/osa_gateway.rb +0 -0
  47. data/lib/adhearsion/distributed/gateways/rest_gateway.rb +9 -0
  48. data/lib/adhearsion/distributed/gateways/soap_gateway.rb +9 -0
  49. data/lib/adhearsion/distributed/gateways/xmlrpc_gateway.rb +9 -0
  50. data/lib/adhearsion/distributed/peer_finder.rb +0 -0
  51. data/lib/adhearsion/distributed/remote_cli.rb +0 -0
  52. data/lib/adhearsion/hooks.rb +57 -0
  53. data/lib/adhearsion/host_definitions.rb +63 -0
  54. data/lib/adhearsion/initializer/asterisk.rb +59 -0
  55. data/lib/adhearsion/initializer/configuration.rb +202 -0
  56. data/lib/adhearsion/initializer/database.rb +92 -0
  57. data/lib/adhearsion/initializer/drb.rb +25 -0
  58. data/lib/adhearsion/initializer/freeswitch.rb +22 -0
  59. data/lib/adhearsion/initializer/paths.rb +55 -0
  60. data/lib/adhearsion/initializer/rails.rb +40 -0
  61. data/lib/adhearsion/initializer.rb +217 -0
  62. data/lib/adhearsion/logging.rb +92 -0
  63. data/lib/adhearsion/services/scheduler.rb +5 -0
  64. data/lib/adhearsion/tasks/database.rb +5 -0
  65. data/lib/adhearsion/tasks/generating.rb +20 -0
  66. data/lib/adhearsion/tasks/lint.rb +4 -0
  67. data/lib/adhearsion/tasks/testing.rb +37 -0
  68. data/lib/adhearsion/tasks.rb +15 -0
  69. data/lib/adhearsion/version.rb +9 -0
  70. data/lib/adhearsion/voip/asterisk/agi_server.rb +78 -0
  71. data/lib/adhearsion/voip/asterisk/ami/actions.rb +238 -0
  72. data/lib/adhearsion/voip/asterisk/ami/machine.rb +871 -0
  73. data/lib/adhearsion/voip/asterisk/ami/machine.rl +109 -0
  74. data/lib/adhearsion/voip/asterisk/ami/parser.rb +262 -0
  75. data/lib/adhearsion/voip/asterisk/ami.rb +147 -0
  76. data/lib/adhearsion/voip/asterisk/commands.rb +1182 -0
  77. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
  78. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
  79. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
  80. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
  81. data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
  82. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
  83. data/lib/adhearsion/voip/asterisk.rb +4 -0
  84. data/lib/adhearsion/voip/call.rb +391 -0
  85. data/lib/adhearsion/voip/call_routing.rb +64 -0
  86. data/lib/adhearsion/voip/commands.rb +9 -0
  87. data/lib/adhearsion/voip/constants.rb +39 -0
  88. data/lib/adhearsion/voip/conveniences.rb +18 -0
  89. data/lib/adhearsion/voip/dial_plan.rb +205 -0
  90. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
  91. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
  92. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
  93. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
  94. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +75 -0
  95. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
  96. data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
  97. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
  98. data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
  99. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
  100. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
  101. data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
  102. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
  103. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
  104. data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
  105. data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
  106. data/lib/adhearsion.rb +31 -0
  107. data/script/destroy +14 -0
  108. data/script/generate +14 -0
  109. data/spec/fixtures/dialplan.rb +3 -0
  110. data/spec/initializer/test_configuration.rb +267 -0
  111. data/spec/initializer/test_loading.rb +162 -0
  112. data/spec/initializer/test_paths.rb +43 -0
  113. data/spec/silence.rb +10 -0
  114. data/spec/test_ahn_command.rb +149 -0
  115. data/spec/test_code_quality.rb +87 -0
  116. data/spec/test_component_manager.rb +97 -0
  117. data/spec/test_constants.rb +8 -0
  118. data/spec/test_drb.rb +104 -0
  119. data/spec/test_helper.rb +94 -0
  120. data/spec/test_hooks.rb +37 -0
  121. data/spec/test_host_definitions.rb +79 -0
  122. data/spec/test_initialization.rb +105 -0
  123. data/spec/test_logging.rb +80 -0
  124. data/spec/test_relationship_properties.rb +54 -0
  125. data/spec/voip/asterisk/ami_response_definitions.rb +23 -0
  126. data/spec/voip/asterisk/config_file_generators/test_agents.rb +253 -0
  127. data/spec/voip/asterisk/config_file_generators/test_queues.rb +325 -0
  128. data/spec/voip/asterisk/config_file_generators/test_voicemail.rb +306 -0
  129. data/spec/voip/asterisk/menu_command/test_calculated_match.rb +111 -0
  130. data/spec/voip/asterisk/menu_command/test_matchers.rb +98 -0
  131. data/spec/voip/asterisk/mock_ami_server.rb +176 -0
  132. data/spec/voip/asterisk/test_agi_server.rb +451 -0
  133. data/spec/voip/asterisk/test_ami.rb +227 -0
  134. data/spec/voip/asterisk/test_commands.rb +2006 -0
  135. data/spec/voip/asterisk/test_config_manager.rb +129 -0
  136. data/spec/voip/dsl/dispatcher_spec_helper.rb +45 -0
  137. data/spec/voip/dsl/test_dialing_dsl.rb +268 -0
  138. data/spec/voip/dsl/test_dispatcher.rb +82 -0
  139. data/spec/voip/dsl/test_parser.rb +87 -0
  140. data/spec/voip/freeswitch/test_basic_connection_manager.rb +39 -0
  141. data/spec/voip/freeswitch/test_inbound_connection_manager.rb +39 -0
  142. data/spec/voip/freeswitch/test_oes_server.rb +9 -0
  143. data/spec/voip/test_call_routing.rb +127 -0
  144. data/spec/voip/test_dialplan_manager.rb +372 -0
  145. data/spec/voip/test_numerical_string.rb +48 -0
  146. data/spec/voip/test_phone_number.rb +36 -0
  147. data/test/test_ahn_generator.rb +59 -0
  148. data/test/test_component_generator.rb +52 -0
  149. data/test/test_generator_helper.rb +20 -0
  150. metadata +254 -0
@@ -0,0 +1,109 @@
1
+ module Adhearsion
2
+ module VoIP
3
+ module Asterisk
4
+ class AMI
5
+ module Machine
6
+ %%{
7
+ machine ami;
8
+
9
+ cr = "\r";
10
+ lf = "\n";
11
+ crlf = cr lf;
12
+
13
+ action _key { mark("key") }
14
+ action key { set("key"); }
15
+ action _value { mark("value") }
16
+ action value { set("value"); }
17
+ Attr = [a-zA-Z\-]+ >_key %key ': ' (any* -- crlf) >_value %value crlf;
18
+ Privilege = "Privilege" >_key %key ': ' (any* -- crlf) >_value %value crlf;
19
+ ActionID = "ActionID" >_key %key ': ' (any* -- crlf) >_value %value crlf;
20
+
21
+ action _event { mark("event") }
22
+ action event { set("event"); @current_packet = EventPacket.new(@__ragel_event) }
23
+ Event = "Event: " alpha+ >_event %event crlf;
24
+
25
+ action _success { @current_packet = Packet.new; }
26
+ action _error { @current_packet = ErrorPacket.new; }
27
+ Response = "Response: ";
28
+ Success = Response "Success" >_success crlf;
29
+ Pong = Response "Pong" >_success crlf;
30
+ Error = Response "Error" >_error crlf;
31
+ Events = Response "Events " ("On" | "Off") >_success crlf;
32
+
33
+ action _follows { @current_packet = FollowsPacket.new; }
34
+ Follows = Response "Follows" >_follows crlf;
35
+ EndFollows = "--END COMMAND--" crlf;
36
+
37
+ # Capture the prompt. Signal any waiters.
38
+ Prompt = "Asterisk Call Manager/";
39
+ prompt := |*
40
+ graph+ >{ mark("version"); };
41
+ crlf >{ set("version"); @signal.signal } => { fgoto main; };
42
+ *|;
43
+
44
+ # For typical commands with responses with headers
45
+ response_normal := |*
46
+ Attr => { pair; };
47
+ crlf => { packet; fgoto main; };
48
+ *|;
49
+
50
+ # For immediate or raw commands
51
+ Raw = (any+ >{ mark_array("raw"); } -- lf) lf;
52
+
53
+ # For immediate or raw commands
54
+ Imm = (any+ >{ mark_array("raw") } -- crlf) crlf %{ insert("raw") };
55
+
56
+ # For raw commands
57
+ response_follows := |*
58
+ Privilege => { pair; };
59
+ ActionID => { pair; };
60
+ Raw => { insert("raw") };
61
+ EndFollows crlf => { packet; fgoto main; };
62
+ *|;
63
+
64
+ main := |*
65
+ Prompt @{ fgoto prompt; };
66
+ Success @{ fgoto response_normal; };
67
+ Pong @{ fgoto response_normal; };
68
+ Error @{ fgoto response_normal; };
69
+ Event @{ fgoto response_normal; };
70
+ Events @{ fgoto response_normal; };
71
+ Follows @{ fgoto response_follows; };
72
+
73
+ # Must also handle immediate responses with raw data
74
+ Imm crlf crlf => { @current_packet = ImmediatePacket.new; packet; };
75
+ *|;
76
+ }%%
77
+
78
+ class << self
79
+ def extended(base)
80
+ # Rename the Ragel variables. Not strictly necessary if
81
+ # we were to make accessors for them.
82
+ base.instance_eval do
83
+ %%{
84
+ variable p @__ragel_p;
85
+ variable pe @__ragel_pe;
86
+ variable cs @__ragel_cs;
87
+ variable act @__ragel_act;
88
+ variable data @__ragel_data;
89
+ variable tokstart @__ragel_tokstart;
90
+ variable tokend @__ragel_tokend;
91
+ write data nofinal;
92
+ }%%
93
+ end
94
+ end
95
+ end
96
+
97
+ private
98
+ def ragel_init
99
+ %% write init;
100
+ end
101
+
102
+ def ragel_exec
103
+ %% write exec;
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,262 @@
1
+ require 'drb'
2
+ require 'adhearsion/voip/asterisk/ami/machine'
3
+
4
+ module Adhearsion
5
+ module VoIP
6
+ module Asterisk
7
+ class AMI
8
+ class Packet < Hash
9
+ def error?
10
+ false
11
+ end
12
+
13
+ def raw?
14
+ false
15
+ end
16
+
17
+ def is_event?
18
+ false
19
+ end
20
+
21
+ # Return the hash, without the internal Action ID
22
+ def body
23
+ returning clone do |packet|
24
+ packet.delete 'ActionID'
25
+ end
26
+ end
27
+
28
+ def message
29
+ self['Message']
30
+ end
31
+ end
32
+
33
+ class EventPacket < Packet
34
+ attr_accessor :event
35
+ def initialize(event)
36
+ @event = event
37
+ super(false)
38
+ end
39
+
40
+ def is_event?
41
+ true
42
+ end
43
+ end
44
+
45
+ class ErrorPacket < Packet
46
+ def error?
47
+ true
48
+ end
49
+ end
50
+
51
+ class FollowsPacket < Packet
52
+ def raw?
53
+ true
54
+ end
55
+ end
56
+
57
+ class ImmediatePacket < Packet
58
+ def raw?
59
+ true
60
+ end
61
+ end
62
+
63
+ class Parser
64
+ # Size of the scanner buffer
65
+ BUFSIZE = 1024
66
+
67
+ attr_accessor :logger
68
+ attr_reader :events
69
+
70
+ def initialize
71
+ self.extend Machine
72
+
73
+ # Add the variables and accessors used for marking seen data
74
+ %w(event key value version).each do |name|
75
+ instance_eval <<-STR
76
+ class << self
77
+ send(:attr_accessor, "__ragel_mark_#{name}")
78
+ send(:attr_accessor, "__ragel_#{name}")
79
+ end
80
+ send("__ragel_mark_#{name}=", 0)
81
+ send("__ragel_#{name}=", nil)
82
+ STR
83
+ end
84
+
85
+ %w(raw).each do |name|
86
+ instance_eval <<-STR
87
+ class << self
88
+ send(:attr_accessor, "__ragel_mark_#{name}")
89
+ send(:attr_accessor, "__ragel_#{name}")
90
+ end
91
+ send("__ragel_mark_#{name}=", 0)
92
+ send("__ragel_#{name}=", [])
93
+ STR
94
+ end
95
+ @signal = ConditionVariable.new
96
+ @mutex = Mutex.new
97
+ @events = Queue.new
98
+ @current_packet = nil
99
+ @logger = Logger.new STDOUT
100
+ end
101
+
102
+ private
103
+
104
+ # Set the starting marker position
105
+ def mark(name)
106
+ send("__ragel_mark_#{name}=", @__ragel_p)
107
+ end
108
+
109
+ # Set the starting marker position for capturing raw data in an array
110
+ def mark_array(name)
111
+ send("__ragel_mark_#{name}=", @__ragel_p)
112
+ end
113
+
114
+ # Capture the marked data from the marker to the current position
115
+ def set(name)
116
+ mark = send("__ragel_mark_#{name}")
117
+ return if @__ragel_p == mark
118
+ send("__ragel_#{name}=", @__ragel_data[mark..@__ragel_p-1])
119
+ send("__ragel_mark_#{name}=", 0)
120
+ end
121
+
122
+ # Insert the data marked from the marker to the current position in the array
123
+ def insert(name)
124
+ mark = send("__ragel_mark_#{name}")
125
+ return if @__ragel_p == mark
126
+ var = send("__ragel_#{name}")
127
+ var << @__ragel_data[mark..@__ragel_p-1]
128
+ send("__ragel_#{name}=", var)
129
+ end
130
+
131
+ # Capture a key / value pair in a response packet
132
+ def pair
133
+ @current_packet[@__ragel_key] = @__ragel_value
134
+ end
135
+
136
+ # This method completes a packet. Add the current raw data to it if it
137
+ # is an immediate or raw response packet. If it has an action ID, it belongs
138
+ # to a command, so signal any waiters. If it does not, it is an asynchronous
139
+ # event, so add it to the event queue.
140
+ def packet
141
+ return if not @current_packet
142
+ @current_packet[:raw] = @__ragel_raw.join("\n") if @current_packet.raw?
143
+ action_id = nil
144
+ if not @current_packet.is_event? or @current_packet['ActionID']
145
+ action_id = @current_packet['ActionID'] || 0
146
+ end
147
+ logger.debug "Packet end: #{@__ragel_p}, #{@current_packet.class}, #{action_id.inspect}"
148
+ logger.debug "=====>#{@current_packet[:raw]}<=====" if @current_packet.raw?
149
+ if action_id
150
+ # Packets with IDs are associated with the action of the same ID
151
+ action = Actions::Action[action_id]
152
+ action << @current_packet
153
+ else
154
+ # Asynchronous events without IDs go into the event queue
155
+ @events.push(@current_packet)
156
+ end
157
+ @signal.broadcast
158
+ @current_packet = nil
159
+ @__ragel_raw = []
160
+ end
161
+
162
+ public
163
+ # Wait for any packets (including events) that have the specified Action ID.
164
+ # Do not stop waiting until all of the packets for the specified Action ID
165
+ # have been seen.
166
+ def wait(action)
167
+ logger.debug "Waiting for #{action.action_id.inspect}"
168
+ @mutex.synchronize do
169
+ loop do
170
+ action.check_error!
171
+ return action.packets! if action.done?
172
+ @signal.wait(@mutex)
173
+ end
174
+ end
175
+ end
176
+
177
+ # Receive an event packet from the event packet queue.
178
+ def receive
179
+ @events.pop
180
+ end
181
+
182
+ # Stop the scanner.
183
+ def stop
184
+ @mutex.synchronize do
185
+ @thread.kill if @thread
186
+ end
187
+ @thread = nil
188
+ end
189
+
190
+ # Run the scanner on the specified socket.
191
+ def run(socket)
192
+ @__ragel_eof = nil
193
+ @__ragel_data = " " * BUFSIZE
194
+ @__ragel_raw = []
195
+
196
+ ragel_init
197
+
198
+ # Synchronize, so we can wait for the command prompt before the
199
+ # scanner actually starts.
200
+ @mutex.synchronize do
201
+ @thread = Thread.new do
202
+ have = 0
203
+ loop do
204
+ # Grab as many bytes as we can for now.
205
+ space = BUFSIZE - have
206
+ raise RuntimeError, "No space" if space == 0
207
+ bytes = 0
208
+ begin
209
+ socket.synchronize do
210
+ if IO.select([socket], nil, nil, 1.0)
211
+ bytes = socket.read_nonblock(space)
212
+ else
213
+ retry
214
+ end
215
+ end
216
+ rescue Errno::EAGAIN
217
+ # Nothing available. Try again.
218
+ retry
219
+ rescue EOFError
220
+ # Socket closed. We are done.
221
+ break
222
+ end
223
+
224
+ # Adjust the pointers.
225
+ logger.debug "Got #{bytes.length} bytes, #{bytes.inspect}"
226
+ @__ragel_p = have
227
+ @__ragel_data[@__ragel_p..@__ragel_p + bytes.size - 1] = bytes
228
+ @__ragel_pe = @__ragel_p + bytes.size
229
+ logger.debug "P: #{@__ragel_p} PE: #{@__ragel_pe}"
230
+
231
+ # Run the scanner state machine.
232
+ @mutex.synchronize do
233
+ ragel_exec
234
+ end
235
+
236
+ if @__ragel_tokstart.nil? or @__ragel_tokstart == 0
237
+ have = 0
238
+ else
239
+ # Slide the window.
240
+ have = @__ragel_pe - @__ragel_tokstart
241
+ logger.debug "Sliding #{have} from #{@__ragel_tokstart} to 0 (tokend: #{@__ragel_tokend.inspect})"
242
+ @__ragel_data[0..have-1] = @__ragel_data[@__ragel_tokstart..@__ragel_tokstart + have - 1]
243
+ @__ragel_tokend -= @__ragel_tokstart if @__ragel_tokend
244
+ @__ragel_tokstart = 0
245
+ logger.debug "Data: #{@__ragel_data[0..have-1].inspect}"
246
+ end
247
+ end
248
+ @thread = nil
249
+ end
250
+ # Wait for the command prompt.
251
+ while @__ragel_version.blank?
252
+ @signal.wait(@mutex)
253
+ end
254
+ end
255
+ # Return the version number.
256
+ @__ragel_version
257
+ end
258
+ end
259
+ end
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,147 @@
1
+ require 'rubygems'
2
+ require 'pp'
3
+ require 'yaml'
4
+ require 'socket'
5
+ require 'thread'
6
+ require 'monitor'
7
+ require 'adhearsion/voip/asterisk/ami/parser'
8
+ require 'adhearsion/voip/asterisk/ami/actions'
9
+
10
+ module Adhearsion
11
+ module VoIP
12
+ module Asterisk
13
+ class AMI
14
+
15
+ include Actions
16
+
17
+ attr_reader :action_sock, :host, :user, :password, :port, :event_thread, :scanner, :version
18
+
19
+ def initialize(user, pass, host='127.0.0.1', options={})
20
+ @host, @user, @password, @port = host, user, pass, options[:port] || 5038
21
+ @events_enabled = options[:events]
22
+ end
23
+
24
+ include Adhearsion::Publishable
25
+
26
+ publish :through => :proxy do
27
+
28
+ def originate(options={})
29
+ options[:callerid] = options.delete :caller_id if options[:caller_id]
30
+ execute_ami_command! :originate, options
31
+ end
32
+
33
+ def ping
34
+ execute_ami_command! :ping
35
+ end
36
+
37
+ # An introduction connects two endpoints together. The first argument is
38
+ # the first person the PBX will call. When she's picked up, Asterisk will
39
+ # play ringing while the second person is being dialed.
40
+ #
41
+ # The first argument is the person called first. Pass this as a canonical
42
+ # IAX2/server/user type argument. Destination takes the same format, but
43
+ # comma-separated Dial() arguments can be optionally passed after the
44
+ # technology.
45
+ #
46
+ # TODO: Provide an example when this works.
47
+ def introduce(caller, callee, opts={})
48
+ dial_args = callee
49
+ dial_args += "|#{opts[:options]}" if opts[:options]
50
+ call_and_exec caller, "Dial", :args => dial_args, :caller_id => opts[:caller_id]
51
+ end
52
+
53
+ def call_and_exec(channel, app, opts={})
54
+ args = { :channel => channel, :application => app }
55
+ args[:caller_id] = opts[:caller_id] if opts[:caller_id]
56
+ args[:data] = opts[:args] if opts[:args]
57
+ originate args
58
+ end
59
+
60
+ def call_into_context(channel, context, options={})
61
+ args = {:channel => channel, :context => context}
62
+ args[:priority] = options[:priority] || 1
63
+ args[:extension] = options[:extension] if options[:extension]
64
+ args[:caller_id] = options[:caller_id] if options[:caller_id]
65
+ if options[:variables] && options[:variables].kind_of?(Hash)
66
+ args[:variable] = options[:variables].map {|pair| pair.join('=')}.join('|')
67
+ end
68
+ originate args
69
+ end
70
+
71
+ def method_missing(name, hash={}, &block)
72
+ execute_ami_command! name, hash, &block
73
+ end
74
+
75
+ end
76
+
77
+ def connect!
78
+ disconnect!
79
+ start_event_thread! if events_enabled?
80
+ login! host, user, password, port, events_enabled?
81
+ end
82
+
83
+ def disconnect!
84
+ action_sock.close if action_sock && !action_sock.closed?
85
+ event_thread.kill if event_thread
86
+ scanner.stop if scanner
87
+ end
88
+
89
+ def events_enabled?
90
+ @events_enabled
91
+ end
92
+
93
+ private
94
+
95
+ def login!(host, user, pass, port, events)
96
+ begin
97
+ @action_sock = TCPSocket.new host, port
98
+ rescue Errno::ECONNREFUSED => refusal_error
99
+ raise Errno::ECONNREFUSED, "Could not connect with AMI to Asterisk server at #{host}:#{port}. " +
100
+ "Is enabled set to 'yes' in manager.conf?"
101
+ end
102
+ action_sock.extend(MonitorMixin)
103
+ @scanner = Parser.new
104
+ @version = scanner.run(action_sock)
105
+ begin
106
+ execute_ami_command! :login, :username => user, :secret => password, :events => (events_enabled? ? "On" : "Off")
107
+ rescue ActionError
108
+ raise AuthenticationFailedException, "Invalid AMI username/password! Check manager.conf."
109
+ else
110
+ # puts "Manager connection established to #{host}:#{port} with user '#{user}'"
111
+ end
112
+ end
113
+
114
+ def execute_ami_command!(name, options={}, &block)
115
+ action = Action.build(name, options, &block)
116
+ action_sock.synchronize do
117
+ connect! if !action_sock || action_sock.closed?
118
+ action_sock.write action.to_s
119
+ end
120
+
121
+ return unless action.has_response?
122
+ scanner.wait(action)
123
+ end
124
+
125
+ def start_event_thread!
126
+ @event_thread = Thread.new(scanner) do |scanner|
127
+ loop do
128
+ # TODO: This is totally screwed up. __read_event doesn't exist.
129
+ AMI::EventHandler.handle! __read_event(scanner.events.pop)
130
+ end
131
+ end
132
+ event_thread.abort_on_exception = true
133
+ end
134
+
135
+ # Method simply defined as private to prevent method_missing from catching it.
136
+ def events() end
137
+
138
+ class EventHandler
139
+ # TODO: Refactor me!
140
+ end
141
+
142
+ class AuthenticationFailedException < Exception; end
143
+ class ActionError < RuntimeError; end
144
+ end
145
+ end
146
+ end
147
+ end