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