sevenscale-adhearsion 0.7.1000

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.
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 +1186 -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 +402 -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 +453 -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