eric-adhearsion 0.7.999
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +3 -0
- data/LICENSE +456 -0
- data/Manifest.txt +149 -0
- data/README.txt +6 -0
- data/Rakefile +48 -0
- data/ahn_generators/component/USAGE +5 -0
- data/ahn_generators/component/component_generator.rb +57 -0
- data/ahn_generators/component/templates/configuration.rb +0 -0
- data/ahn_generators/component/templates/lib/lib.rb.erb +3 -0
- data/ahn_generators/component/templates/test/test.rb.erb +12 -0
- data/ahn_generators/component/templates/test/test_helper.rb +14 -0
- data/app_generators/ahn/USAGE +5 -0
- data/app_generators/ahn/ahn_generator.rb +76 -0
- data/app_generators/ahn/templates/.ahnrc +12 -0
- data/app_generators/ahn/templates/README +8 -0
- data/app_generators/ahn/templates/Rakefile +3 -0
- data/app_generators/ahn/templates/components/simon_game/configuration.rb +0 -0
- data/app_generators/ahn/templates/components/simon_game/lib/simon_game.rb +61 -0
- data/app_generators/ahn/templates/components/simon_game/test/test_helper.rb +14 -0
- data/app_generators/ahn/templates/components/simon_game/test/test_simon_game.rb +31 -0
- data/app_generators/ahn/templates/config/startup.rb +53 -0
- data/app_generators/ahn/templates/dialplan.rb +4 -0
- data/bin/ahn +28 -0
- data/bin/ahnctl +68 -0
- data/bin/jahn +32 -0
- data/lib/adhearsion/blank_slate.rb +5 -0
- data/lib/adhearsion/cli.rb +106 -0
- data/lib/adhearsion/component_manager.rb +277 -0
- data/lib/adhearsion/core_extensions/all.rb +9 -0
- data/lib/adhearsion/core_extensions/array.rb +0 -0
- data/lib/adhearsion/core_extensions/custom_daemonizer.rb +45 -0
- data/lib/adhearsion/core_extensions/global.rb +1 -0
- data/lib/adhearsion/core_extensions/guid.rb +5 -0
- data/lib/adhearsion/core_extensions/hash.rb +0 -0
- data/lib/adhearsion/core_extensions/metaprogramming.rb +17 -0
- data/lib/adhearsion/core_extensions/numeric.rb +4 -0
- data/lib/adhearsion/core_extensions/proc.rb +0 -0
- data/lib/adhearsion/core_extensions/pseudo_uuid.rb +11 -0
- data/lib/adhearsion/core_extensions/publishable.rb +73 -0
- data/lib/adhearsion/core_extensions/relationship_properties.rb +40 -0
- data/lib/adhearsion/core_extensions/string.rb +26 -0
- data/lib/adhearsion/core_extensions/thread.rb +13 -0
- data/lib/adhearsion/core_extensions/thread_safety.rb +7 -0
- data/lib/adhearsion/core_extensions/time.rb +0 -0
- data/lib/adhearsion/distributed/gateways/dbus_gateway.rb +0 -0
- data/lib/adhearsion/distributed/gateways/osa_gateway.rb +0 -0
- data/lib/adhearsion/distributed/gateways/rest_gateway.rb +9 -0
- data/lib/adhearsion/distributed/gateways/soap_gateway.rb +9 -0
- data/lib/adhearsion/distributed/gateways/xmlrpc_gateway.rb +9 -0
- data/lib/adhearsion/distributed/peer_finder.rb +0 -0
- data/lib/adhearsion/distributed/remote_cli.rb +0 -0
- data/lib/adhearsion/hooks.rb +57 -0
- data/lib/adhearsion/host_definitions.rb +63 -0
- data/lib/adhearsion/initializer/asterisk.rb +59 -0
- data/lib/adhearsion/initializer/configuration.rb +202 -0
- data/lib/adhearsion/initializer/database.rb +92 -0
- data/lib/adhearsion/initializer/drb.rb +25 -0
- data/lib/adhearsion/initializer/freeswitch.rb +22 -0
- data/lib/adhearsion/initializer/paths.rb +55 -0
- data/lib/adhearsion/initializer/rails.rb +40 -0
- data/lib/adhearsion/initializer.rb +217 -0
- data/lib/adhearsion/logging.rb +92 -0
- data/lib/adhearsion/services/scheduler.rb +5 -0
- data/lib/adhearsion/tasks/database.rb +5 -0
- data/lib/adhearsion/tasks/generating.rb +20 -0
- data/lib/adhearsion/tasks/lint.rb +4 -0
- data/lib/adhearsion/tasks/testing.rb +37 -0
- data/lib/adhearsion/tasks.rb +15 -0
- data/lib/adhearsion/version.rb +9 -0
- data/lib/adhearsion/voip/asterisk/agi_server.rb +78 -0
- data/lib/adhearsion/voip/asterisk/ami/actions.rb +238 -0
- data/lib/adhearsion/voip/asterisk/ami/machine.rb +871 -0
- data/lib/adhearsion/voip/asterisk/ami/machine.rl +109 -0
- data/lib/adhearsion/voip/asterisk/ami/parser.rb +262 -0
- data/lib/adhearsion/voip/asterisk/ami.rb +147 -0
- data/lib/adhearsion/voip/asterisk/commands.rb +1182 -0
- data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
- data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
- data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
- data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
- data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
- data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
- data/lib/adhearsion/voip/asterisk.rb +4 -0
- data/lib/adhearsion/voip/call.rb +391 -0
- data/lib/adhearsion/voip/call_routing.rb +64 -0
- data/lib/adhearsion/voip/commands.rb +9 -0
- data/lib/adhearsion/voip/constants.rb +39 -0
- data/lib/adhearsion/voip/conveniences.rb +18 -0
- data/lib/adhearsion/voip/dial_plan.rb +205 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
- data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
- data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
- data/lib/adhearsion/voip/dsl/dialplan/parser.rb +75 -0
- data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
- data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
- data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
- data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
- data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
- data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
- data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
- data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
- data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
- data/lib/adhearsion.rb +31 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/spec/fixtures/dialplan.rb +3 -0
- data/spec/initializer/test_configuration.rb +267 -0
- data/spec/initializer/test_loading.rb +162 -0
- data/spec/initializer/test_paths.rb +43 -0
- data/spec/silence.rb +10 -0
- data/spec/test_ahn_command.rb +149 -0
- data/spec/test_code_quality.rb +87 -0
- data/spec/test_component_manager.rb +97 -0
- data/spec/test_constants.rb +8 -0
- data/spec/test_drb.rb +104 -0
- data/spec/test_helper.rb +94 -0
- data/spec/test_hooks.rb +37 -0
- data/spec/test_host_definitions.rb +79 -0
- data/spec/test_initialization.rb +105 -0
- data/spec/test_logging.rb +80 -0
- data/spec/test_relationship_properties.rb +54 -0
- data/spec/voip/asterisk/ami_response_definitions.rb +23 -0
- data/spec/voip/asterisk/config_file_generators/test_agents.rb +253 -0
- data/spec/voip/asterisk/config_file_generators/test_queues.rb +325 -0
- data/spec/voip/asterisk/config_file_generators/test_voicemail.rb +306 -0
- data/spec/voip/asterisk/menu_command/test_calculated_match.rb +111 -0
- data/spec/voip/asterisk/menu_command/test_matchers.rb +98 -0
- data/spec/voip/asterisk/mock_ami_server.rb +176 -0
- data/spec/voip/asterisk/test_agi_server.rb +451 -0
- data/spec/voip/asterisk/test_ami.rb +227 -0
- data/spec/voip/asterisk/test_commands.rb +2006 -0
- data/spec/voip/asterisk/test_config_manager.rb +129 -0
- data/spec/voip/dsl/dispatcher_spec_helper.rb +45 -0
- data/spec/voip/dsl/test_dialing_dsl.rb +268 -0
- data/spec/voip/dsl/test_dispatcher.rb +82 -0
- data/spec/voip/dsl/test_parser.rb +87 -0
- data/spec/voip/freeswitch/test_basic_connection_manager.rb +39 -0
- data/spec/voip/freeswitch/test_inbound_connection_manager.rb +39 -0
- data/spec/voip/freeswitch/test_oes_server.rb +9 -0
- data/spec/voip/test_call_routing.rb +127 -0
- data/spec/voip/test_dialplan_manager.rb +372 -0
- data/spec/voip/test_numerical_string.rb +48 -0
- data/spec/voip/test_phone_number.rb +36 -0
- data/test/test_ahn_generator.rb +59 -0
- data/test/test_component_generator.rb +52 -0
- data/test/test_generator_helper.rb +20 -0
- 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
|