kenwiesner-adhearsioncw 0.8.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +26 -0
- data/EVENTS +11 -0
- data/LICENSE +456 -0
- data/Rakefile +127 -0
- data/adhearsion.gemspec +149 -0
- data/app_generators/ahn/USAGE +5 -0
- data/app_generators/ahn/ahn_generator.rb +91 -0
- data/app_generators/ahn/templates/.ahnrc +34 -0
- data/app_generators/ahn/templates/README +8 -0
- data/app_generators/ahn/templates/Rakefile +25 -0
- data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
- data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
- data/app_generators/ahn/templates/components/disabled/restful_rpc/README.markdown +11 -0
- data/app_generators/ahn/templates/components/disabled/restful_rpc/example-client.rb +48 -0
- data/app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.rb +87 -0
- data/app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.yml +34 -0
- data/app_generators/ahn/templates/components/disabled/restful_rpc/spec/restful_rpc_spec.rb +263 -0
- data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.rb +104 -0
- data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.yml +2 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.yml +12 -0
- data/app_generators/ahn/templates/components/simon_game/simon_game.rb +56 -0
- data/app_generators/ahn/templates/config/startup.rb +50 -0
- data/app_generators/ahn/templates/dialplan.rb +3 -0
- data/app_generators/ahn/templates/events.rb +32 -0
- data/bin/ahn +28 -0
- data/bin/ahnctl +68 -0
- data/bin/jahn +42 -0
- data/examples/asterisk_manager_interface/standalone.rb +51 -0
- data/lib/adhearsion/cli.rb +223 -0
- data/lib/adhearsion/component_manager/component_tester.rb +55 -0
- data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
- data/lib/adhearsion/component_manager.rb +207 -0
- data/lib/adhearsion/events_support.rb +84 -0
- data/lib/adhearsion/foundation/all.rb +9 -0
- data/lib/adhearsion/foundation/blank_slate.rb +5 -0
- data/lib/adhearsion/foundation/custom_daemonizer.rb +45 -0
- data/lib/adhearsion/foundation/event_socket.rb +203 -0
- data/lib/adhearsion/foundation/future_resource.rb +36 -0
- data/lib/adhearsion/foundation/global.rb +1 -0
- data/lib/adhearsion/foundation/metaprogramming.rb +17 -0
- data/lib/adhearsion/foundation/numeric.rb +13 -0
- data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
- data/lib/adhearsion/foundation/relationship_properties.rb +42 -0
- data/lib/adhearsion/foundation/string.rb +26 -0
- data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
- data/lib/adhearsion/foundation/thread_safety.rb +7 -0
- data/lib/adhearsion/host_definitions.rb +67 -0
- data/lib/adhearsion/initializer/asterisk.rb +81 -0
- data/lib/adhearsion/initializer/configuration.rb +254 -0
- data/lib/adhearsion/initializer/database.rb +50 -0
- data/lib/adhearsion/initializer/drb.rb +31 -0
- data/lib/adhearsion/initializer/freeswitch.rb +22 -0
- data/lib/adhearsion/initializer/rails.rb +41 -0
- data/lib/adhearsion/initializer.rb +373 -0
- data/lib/adhearsion/logging.rb +92 -0
- data/lib/adhearsion/tasks/database.rb +5 -0
- data/lib/adhearsion/tasks/deprecations.rb +59 -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 +16 -0
- data/lib/adhearsion/version.rb +9 -0
- data/lib/adhearsion/voip/asterisk/agi_server.rb +84 -0
- data/lib/adhearsion/voip/asterisk/commands.rb +1314 -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/manager_interface/ami_lexer.rb +1589 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +286 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
- data/lib/adhearsion/voip/asterisk/manager_interface.rb +597 -0
- data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
- data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
- data/lib/adhearsion/voip/asterisk.rb +4 -0
- data/lib/adhearsion/voip/call.rb +453 -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 +218 -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 +71 -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 +37 -0
- data/lib/theatre/README.markdown +64 -0
- data/lib/theatre/callback_definition_loader.rb +84 -0
- data/lib/theatre/guid.rb +23 -0
- data/lib/theatre/invocation.rb +121 -0
- data/lib/theatre/namespace_manager.rb +153 -0
- data/lib/theatre/version.rb +2 -0
- data/lib/theatre.rb +151 -0
- metadata +182 -0
@@ -0,0 +1,263 @@
|
|
1
|
+
unless defined? Adhearsion
|
2
|
+
if File.exists? File.dirname(__FILE__) + "/../../../adhearsion/lib/adhearsion.rb"
|
3
|
+
# If you wish to freeze a copy of Adhearsion to this app, simply place a copy of Adhearsion
|
4
|
+
# into a folder named "adhearsion" within this app's main directory.
|
5
|
+
require File.dirname(__FILE__) + "/../../../adhearsion/lib/adhearsion.rb"
|
6
|
+
elsif File.exists? File.dirname(__FILE__) + "/../../../../../../lib/adhearsion.rb"
|
7
|
+
# This file may be ran from the within the Adhearsion framework code (before a project has been generated)
|
8
|
+
require File.dirname(__FILE__) + "/../../../../../../lib/adhearsion.rb"
|
9
|
+
else
|
10
|
+
require 'rubygems'
|
11
|
+
gem 'adhearsion', '>= 0.7.999'
|
12
|
+
require 'adhearsion'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'adhearsion/component_manager/spec_framework'
|
17
|
+
|
18
|
+
RESTFUL_RPC = ComponentTester.new("restful_rpc", File.dirname(__FILE__) + "/../..")
|
19
|
+
|
20
|
+
##### This is here for a reference
|
21
|
+
#{"CONTENT_LENGTH" => "12",
|
22
|
+
# "CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
23
|
+
# "GATEWAY_INTERFACE" => "CGI/1.1",
|
24
|
+
# "HTTP_ACCEPT" => "application/xml",
|
25
|
+
# "HTTP_ACCEPT_ENCODING" => "gzip, deflate",
|
26
|
+
# "HTTP_AUTHORIZATION" => "Basic amlja3N0YTpyb2ZsY29wdGVyeg==",
|
27
|
+
# "HTTP_HOST" => "localhost:5000",
|
28
|
+
# "HTTP_VERSION" => "HTTP/1.1",
|
29
|
+
# "PATH_INFO" => "/rofl",
|
30
|
+
# "QUERY_STRING" => "",
|
31
|
+
# "rack.errors" => StringIO.new(""),
|
32
|
+
# "rack.input" => StringIO.new('["o","hai!"]'),
|
33
|
+
# "rack.multiprocess" => false,
|
34
|
+
# "rack.multithread" => true,
|
35
|
+
# "rack.run_once" => false,
|
36
|
+
# "rack.url_scheme" => "http",
|
37
|
+
# "rack.version" => [0, 1],
|
38
|
+
# "REMOTE_ADDR" => "::1",
|
39
|
+
# "REMOTE_HOST" => "localhost",
|
40
|
+
# "REMOTE_USER" => "jicksta",
|
41
|
+
# "REQUEST_METHOD" => "POST"
|
42
|
+
# "REQUEST_PATH" => "/",
|
43
|
+
# "REQUEST_URI" => "http://localhost:5000/rofl",
|
44
|
+
# "SCRIPT_NAME" => "",
|
45
|
+
# "SERVER_NAME" => "localhost",
|
46
|
+
# "SERVER_PORT" => "5000",
|
47
|
+
# "SERVER_PROTOCOL" => "HTTP/1.1",
|
48
|
+
# "SERVER_SOFTWARE" => "WEBrick/1.3.1 (Ruby/1.8.6/2008-03-03)"}
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
describe "The VALID_IP_ADDRESS regular expression" do
|
53
|
+
|
54
|
+
it "should match only valid IP addresses" do
|
55
|
+
valid_ip_addresses = ["192.168.1.98", "10.0.1.200", "255.255.255.0", "123.*.4.*"]
|
56
|
+
invalid_ip_addresses = ["10.0.1.1 foo", "bar 255.255.255.0", "0*0*0*0", "1234"]
|
57
|
+
|
58
|
+
valid_ip_addresses. each { |ip| RESTFUL_RPC::VALID_IP_ADDRESS.should =~ ip }
|
59
|
+
invalid_ip_addresses.each { |ip| RESTFUL_RPC::VALID_IP_ADDRESS.should_not =~ ip }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "The initialization block" do
|
64
|
+
|
65
|
+
it "should create a new Thread" do
|
66
|
+
mock_component_config_with :restful_rpc => {}
|
67
|
+
mock(Thread).new { nil }
|
68
|
+
RESTFUL_RPC.initialize!
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should run the Rack adapter specified in the configuration" do
|
72
|
+
mock(Thread).new.yields
|
73
|
+
mock_component_config_with :restful_rpc => {"adapter" => "Mongrel"}
|
74
|
+
mock(Rack::Handler::Mongrel).run is_a(Proc), :Port => 5000
|
75
|
+
RESTFUL_RPC.initialize!
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should wrap the RESTFUL_API_HANDLER in an Rack::Auth::Basic object if authentication is enabled" do
|
79
|
+
mock(Thread).new.yields
|
80
|
+
mock_component_config_with :restful_rpc => {"authentication" => {"foo" => "bar"}}
|
81
|
+
|
82
|
+
proper_authenticator = lambda do |obj|
|
83
|
+
request = OpenStruct.new :credentials => ["foo", "bar"]
|
84
|
+
obj.is_a?(Rack::Auth::Basic) && obj.send(:valid?, request)
|
85
|
+
end
|
86
|
+
|
87
|
+
mock(Rack::Handler::Mongrel).run(satisfy(&proper_authenticator), :Port => 5000)
|
88
|
+
RESTFUL_RPC.initialize!
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'should wrap the RESTFUL_API_HANDLER in ShowStatus and ShowExceptions objects when show_exceptions is enabled' do
|
92
|
+
mock(Thread).new.yields
|
93
|
+
mock_component_config_with :restful_rpc => {"show_exceptions" => true}
|
94
|
+
|
95
|
+
mock.proxy(Rack::ShowExceptions).new(is_a(Proc))
|
96
|
+
mock.proxy(Rack::ShowStatus).new is_a(Rack::ShowExceptions)
|
97
|
+
|
98
|
+
mock(Rack::Handler::Mongrel).run is_a(Rack::ShowStatus), :Port => 5000
|
99
|
+
RESTFUL_RPC.initialize!
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'Private helper methods' do
|
105
|
+
|
106
|
+
describe "the RESTFUL_API_HANDLER lambda" do
|
107
|
+
|
108
|
+
it "should return a 200 for requests which execute a method that has been defined in the methods_for(:rpc) context" do
|
109
|
+
component_manager = Adhearsion::Components::ComponentManager.new('/path/shouldnt/matter')
|
110
|
+
|
111
|
+
mock(Adhearsion::Components).component_manager { component_manager }
|
112
|
+
component_manager.load_code <<-RUBY
|
113
|
+
methods_for(:rpc) do
|
114
|
+
def testing_123456(one,two)
|
115
|
+
[two.reverse, one.reverse]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
RUBY
|
119
|
+
|
120
|
+
input = StringIO.new %w[jay phillips].to_json
|
121
|
+
|
122
|
+
mock_component_config_with :restful_rpc => {"path_nesting" => "/"}
|
123
|
+
|
124
|
+
env = {"PATH_INFO" => "/testing_123456", "rack.input" => input}
|
125
|
+
|
126
|
+
response = RESTFUL_RPC::RESTFUL_API_HANDLER.call(env)
|
127
|
+
response.should be_kind_of(Array)
|
128
|
+
response.should have(3).items
|
129
|
+
response.first.should equal(200)
|
130
|
+
JSON.parse(response.last).should eql(%w[jay phillips].map(&:reverse).reverse)
|
131
|
+
end
|
132
|
+
|
133
|
+
it "should return a 400 when no data is POSTed" do
|
134
|
+
env = {"rack.input" => StringIO.new(""), "REQUEST_URI" => "/foobar"}
|
135
|
+
RESTFUL_RPC::RESTFUL_API_HANDLER.call(env).first.should equal(400)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should work with a high level test of a successful method invocation" do
|
139
|
+
|
140
|
+
component_manager = Adhearsion::Components::ComponentManager.new('/path/shouldnt/matter')
|
141
|
+
|
142
|
+
mock(Adhearsion::Components).component_manager { component_manager }
|
143
|
+
|
144
|
+
component_manager.load_code '
|
145
|
+
methods_for(:rpc) do
|
146
|
+
def rofl(one,two)
|
147
|
+
"Hai! #{one} #{two}"
|
148
|
+
end
|
149
|
+
end'
|
150
|
+
|
151
|
+
env = {
|
152
|
+
"CONTENT_LENGTH" => "12",
|
153
|
+
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
|
154
|
+
"GATEWAY_INTERFACE" => "CGI/1.1",
|
155
|
+
"HTTP_ACCEPT" => "application/xml",
|
156
|
+
"HTTP_ACCEPT_ENCODING" => "gzip, deflate",
|
157
|
+
"HTTP_AUTHORIZATION" => "Basic amlja3N0YTpyb2ZsY29wdGVyeg==",
|
158
|
+
"HTTP_HOST" => "localhost:5000",
|
159
|
+
"HTTP_VERSION" => "HTTP/1.1",
|
160
|
+
"PATH_INFO" => "/rofl",
|
161
|
+
"QUERY_STRING" => "",
|
162
|
+
"rack.errors" => StringIO.new(""),
|
163
|
+
"rack.input" => StringIO.new('["o","hai!"]'),
|
164
|
+
"rack.multiprocess" => false,
|
165
|
+
"rack.multithread" => true,
|
166
|
+
"rack.run_once" => false,
|
167
|
+
"rack.url_scheme" => "http",
|
168
|
+
"rack.version" => [0, 1],
|
169
|
+
"REMOTE_ADDR" => "::1",
|
170
|
+
"REMOTE_HOST" => "localhost",
|
171
|
+
"REMOTE_USER" => "jicksta",
|
172
|
+
"REQUEST_METHOD" => "POST",
|
173
|
+
"REQUEST_PATH" => "/",
|
174
|
+
"REQUEST_URI" => "http://localhost:5000/rofl",
|
175
|
+
"SCRIPT_NAME" => "",
|
176
|
+
"SERVER_NAME" => "localhost",
|
177
|
+
"SERVER_PORT" => "5000",
|
178
|
+
"SERVER_PROTOCOL" => "HTTP/1.1",
|
179
|
+
"SERVER_SOFTWARE" => "WEBrick/1.3.1 (Ruby/1.8.6/2008-03-03)" }
|
180
|
+
|
181
|
+
response = RESTFUL_RPC::RESTFUL_API_HANDLER.call(env)
|
182
|
+
JSON.parse(response.last).should == ["Hai! o hai!"]
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
it "should contain backtrace information when show_errors is enabled and an exception occurs" do
|
187
|
+
mock_component_config_with :restful_api => {"show_errors" => true}
|
188
|
+
pending
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
describe 'the ip_allowed?() method' do
|
194
|
+
|
195
|
+
before :each do
|
196
|
+
@method = RESTFUL_RPC.helper_method :ip_allowed?
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'should raise a ConfigurationError if "access" is not one of "everyone", "whitelist" or "blacklist"' do
|
200
|
+
good_access_values = %w[everyone whitelist blacklist]
|
201
|
+
bad_access_values = %w[foo bar qaz qwerty everone blaclist whitlist]
|
202
|
+
|
203
|
+
good_access_values.each do |access_value|
|
204
|
+
mock_component_config_with :restful_rpc => {"access" => access_value, "whitelist" => [], "blacklist" => []}
|
205
|
+
lambda { @method.call("10.0.0.1") }.should_not raise_error
|
206
|
+
end
|
207
|
+
|
208
|
+
bad_access_values.each do |access_value|
|
209
|
+
mock_component_config_with :restful_rpc => {"access" => access_value, "authentication" => false}
|
210
|
+
lambda { @method.call("10.0.0.1") }.should raise_error(Adhearsion::Components::ConfigurationError)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe 'whitelists' do
|
215
|
+
|
216
|
+
it "should parse *'s as wildcards" do
|
217
|
+
mock_component_config_with :restful_rpc => {"access" => "whitelist", "whitelist" => ["10.*.*.*"]}
|
218
|
+
@method.call("10.1.2.3").should be_true
|
219
|
+
end
|
220
|
+
|
221
|
+
it "should allow IPs which are explictly specified" do
|
222
|
+
mock_component_config_with :restful_rpc => {"access" => "whitelist", "whitelist" => ["4.3.2.1"]}
|
223
|
+
@method.call("4.3.2.1").should be_true
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should not allow IPs which are not explicitly specified" do
|
227
|
+
mock_component_config_with :restful_rpc => {"access" => "whitelist", "whitelist" => %w[ 1.2.3.4 4.3.2.1]}
|
228
|
+
@method.call("2.2.2.2").should be_false
|
229
|
+
end
|
230
|
+
|
231
|
+
end
|
232
|
+
|
233
|
+
describe 'blacklists' do
|
234
|
+
|
235
|
+
it "should parse *'s as wildcards" do
|
236
|
+
mock_component_config_with :restful_rpc => {"access" => "blacklist", "blacklist" => ["10.*.*.*"]}
|
237
|
+
@method.call("10.1.2.3").should be_false
|
238
|
+
end
|
239
|
+
|
240
|
+
it "should not allow IPs which are explicitly specified" do
|
241
|
+
mock_component_config_with :restful_rpc => {"access" => "blacklist", "blacklist" => ["9.8.7.6", "9.8.7.5"]}
|
242
|
+
@method.call("9.8.7.5").should be_false
|
243
|
+
end
|
244
|
+
|
245
|
+
it "should allow IPs which are not explicitly specified" do
|
246
|
+
mock_component_config_with :restful_rpc => {"access" => "blacklist", "blacklist" => ["10.20.30.40"]}
|
247
|
+
@method.call("1.1.1.1").should be_true
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
251
|
+
|
252
|
+
describe '"everyone" access' do
|
253
|
+
it "should return true for any IP given, irrespective of the configuration" do
|
254
|
+
ip_addresses = %w[100.200.100.200 0.0.0.0 *.0.0.*]
|
255
|
+
ip_addresses.each do |address|
|
256
|
+
RESTFUL_RPC.helper_method(:ip_allowed?).call(address).should equal(true)
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'md5'
|
2
|
+
require 'open-uri'
|
3
|
+
|
4
|
+
SANDBOX_VERSION = 1.0
|
5
|
+
|
6
|
+
initialization do
|
7
|
+
# We shouldn't start initializing until after the AGI server has initialized.
|
8
|
+
Events.register_callback(:after_initialized) do
|
9
|
+
|
10
|
+
config = if COMPONENTS.sandbox.has_key? "connect_to"
|
11
|
+
{"connect_to" => COMPONENTS.sandbox["connect_to"]}
|
12
|
+
else
|
13
|
+
begin
|
14
|
+
yaml_data = open("http://sandbox.adhearsion.com/component/#{SANDBOX_VERSION}").read
|
15
|
+
YAML.load yaml_data
|
16
|
+
rescue SocketError
|
17
|
+
ahn_log.sandbox.error "Could not connect to the sandbox server! Skipping sandbox initialization!"
|
18
|
+
next
|
19
|
+
rescue => e
|
20
|
+
ahn_log.sandbox.error "COULD NOT RETRIEVE SANDBOX CONNECTION INFORMATION! Not initializing sandbox component!"
|
21
|
+
next
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
# The "connect_to" key is what this version supports
|
27
|
+
if config.kind_of?(Hash) && config.has_key?("connect_to")
|
28
|
+
config = config['connect_to']
|
29
|
+
|
30
|
+
host, port = config.values_at "host", "port"
|
31
|
+
|
32
|
+
username, password = COMPONENTS.sandbox["username"].to_s, COMPONENTS.sandbox["password"].to_s
|
33
|
+
|
34
|
+
if username.blank? || password.blank? || username == "user123"
|
35
|
+
ahn_log.sandbox.error "You must specify your username and password in this component's config file!"
|
36
|
+
next
|
37
|
+
end
|
38
|
+
|
39
|
+
# Part of the AGI-superset protocol we use to log in.
|
40
|
+
identifying_hash = MD5.md5(username + ":" + password).to_s
|
41
|
+
|
42
|
+
if host.nil? || port.nil?
|
43
|
+
ahn_log.sandbox.error "Invalid YAML returned from server! Skipping sandbox initialization!"
|
44
|
+
next
|
45
|
+
end
|
46
|
+
|
47
|
+
Thread.new do
|
48
|
+
loop do
|
49
|
+
begin
|
50
|
+
ahn_log.sandbox.debug "Establishing outbound AGI socket"
|
51
|
+
socket = TCPSocket.new(host, port)
|
52
|
+
socket.puts identifying_hash
|
53
|
+
response = socket.gets
|
54
|
+
unless response
|
55
|
+
next
|
56
|
+
end
|
57
|
+
response.chomp!
|
58
|
+
case
|
59
|
+
when "authentication accepted"
|
60
|
+
ahn_log.sandbox "Authentication accepted"
|
61
|
+
|
62
|
+
start_signal = socket.gets
|
63
|
+
next unless start_signal
|
64
|
+
start_signal.chomp!
|
65
|
+
|
66
|
+
if start_signal
|
67
|
+
ahn_log.sandbox "Incoming call from remote sandbox server!"
|
68
|
+
begin
|
69
|
+
Adhearsion::Initializer::AsteriskInitializer.agi_server.server.serve(socket)
|
70
|
+
rescue => e
|
71
|
+
ahn_log.error "Non-fatal exception in the AGI server: #{e.inspect} \n" + e.backtrace.join("\n")
|
72
|
+
ensure
|
73
|
+
socket.close rescue nil
|
74
|
+
end
|
75
|
+
ahn_log.sandbox "AGI server finished serving call. Reconnecting to sandbox."
|
76
|
+
else
|
77
|
+
ahn_log.sandbox "Remote Asterisk server received no call. Reconnecting..."
|
78
|
+
end
|
79
|
+
when "authentication failed"
|
80
|
+
ahn_log.sandbox.error "Your username or password is invalid! Skipping sandbox initialization..."
|
81
|
+
break
|
82
|
+
when /^wait (\d+)$/
|
83
|
+
sleep response[/^wait (\d+)$/,1].to_i
|
84
|
+
else
|
85
|
+
ahn_log.sandbox.error "Invalid login acknowledgement! Skipping sandbox initialization!"
|
86
|
+
break
|
87
|
+
end
|
88
|
+
rescue Errno::ECONNREFUSED
|
89
|
+
ahn_log.sandbox.error "Could not connect to the sandbox server! Sandbox component stopping..."
|
90
|
+
break
|
91
|
+
rescue => e
|
92
|
+
ahn_log.error "Unrecognized error: #{e.inspect} \n" + e.backtrace.join("\n")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
else
|
98
|
+
ahn_log.sandbox.error "COULD NOT RETRIEVE SANDBOX CONNECTION INFORMATION! Not initializing sandbox component!"
|
99
|
+
end
|
100
|
+
rescue => e
|
101
|
+
ahn_log.sandbox.error "Encountered an error when connecting to the sandbox! #{e.message}\n" + e.backtrace.join("\n")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
What is Stomp?
|
2
|
+
==============
|
3
|
+
|
4
|
+
Stomp is a very simple message-queue protocol with which two separate systems can communicate. Because the protocol is so simple, there are many Stomp server implementations from which you can choose. Some of these include
|
5
|
+
|
6
|
+
- ActiveMQ (http://activemq.com)
|
7
|
+
- Ruby "stompserver" gem (gem install stompserver)
|
8
|
+
- RabbitMQ (http://rabbitmq.com)
|
9
|
+
|
10
|
+
If you wish to get up and running with a development environment, the Ruby stompserver gem is a fantastic starting point. For a critical production system, ActiveMQ should probably be used but it bears the cumbersome paradigm of many "enterprisey" Java applications.
|
11
|
+
|
12
|
+
How does it work?
|
13
|
+
=================
|
14
|
+
|
15
|
+
Stomp is used when certain processes have defined responsibilities. For example, your Adhearsion application's responsibility is to communicate with your Asterisk machine. Other processes (e.g. a Rails web application) will probably need to instruct Adhearsion to do something. Instructions may include
|
16
|
+
|
17
|
+
- Start a new call between two given phone numbers
|
18
|
+
- Have a particular call do something based on a new event
|
19
|
+
- Hangup a call
|
20
|
+
|
21
|
+
Below is a diagram which should give you a better idea of how it works.
|
22
|
+
|
23
|
+
Process Process Process (e.g. Rails)
|
24
|
+
\ | /
|
25
|
+
\ | /
|
26
|
+
Stomp Server (e.g. ActiveMQ)
|
27
|
+
|
|
28
|
+
|
|
29
|
+
Process (e.g. Adhearsion)
|
30
|
+
|
31
|
+
Note: Adhearsion could also be the sender of messages through the Stomp server which are consumed by a number of handlers.
|
32
|
+
|
33
|
+
Setting up a Ruby Stomp server
|
34
|
+
==============================
|
35
|
+
|
36
|
+
Install the pure-Ruby Stomp server by doing "gem install stompserver". This will add the "stompserver" command to your system. When running it without any parameters, it starts without requiring authentication. If you're wanting to get a quick experiment running, I recommend simply doing that.
|
37
|
+
|
38
|
+
Open the `stomp_gateway.yml` file in the stomp_gateway component folder. Comment out the four settings at the top of the file named "user", "pass", "host" and "port" by prepending a "#" to their line. This will cause the component to choose defaults for those properties. The component's defaults will match the expected credentials for the experimental stompserver you're already running on your computer.
|
39
|
+
|
40
|
+
You also need specify a subscription name in
|
41
|
+
|
42
|
+
events.stomp.start_call.each do |event|
|
43
|
+
# The "event" variable holds a Stomp::Message object.
|
44
|
+
name = event.headers
|
45
|
+
end
|
46
|
+
|
47
|
+
You a
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'stomp'
|
2
|
+
|
3
|
+
# TODO: Recover from a disconnect!
|
4
|
+
|
5
|
+
initialization do
|
6
|
+
user = COMPONENTS.stomp_gateway[:user] || ""
|
7
|
+
pass = COMPONENTS.stomp_gateway[:pass] || ""
|
8
|
+
host = COMPONENTS.stomp_gateway[:host] || "localhost"
|
9
|
+
port = COMPONENTS.stomp_gateway[:port] || 61613
|
10
|
+
|
11
|
+
::StompGatewayConnection = Stomp::Client.open(user, pass, host, port)
|
12
|
+
|
13
|
+
subscriptions = COMPONENTS.stomp_gateway["subscriptions"]
|
14
|
+
|
15
|
+
ahn_log.stomp_gateway "Connection established. Subscriptions: #{subscriptions.inspect}"
|
16
|
+
|
17
|
+
Events.register_namespace_name "/stomp"
|
18
|
+
|
19
|
+
subscriptions.each do |subscription|
|
20
|
+
Events.register_namespace_name "/stomp/#{subscription}"
|
21
|
+
::StompGatewayConnection.subscribe subscription do |event|
|
22
|
+
Adhearsion::Events.trigger ["stomp", subscription], event
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
methods_for :global do
|
29
|
+
def send_stomp(destination, message, headers={})
|
30
|
+
::StompGatewayConnection.send(destination, message, headers)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# In the future, I may add a methods_for(:events) method which allows synchronous messaging.
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Comment out any of these properties to use a default.
|
2
|
+
|
3
|
+
user: stomp_user
|
4
|
+
pass: secret_password
|
5
|
+
host: localhost
|
6
|
+
port: 61613
|
7
|
+
|
8
|
+
### Add your list of subscriptions below that this gateway should proxy to events.rb
|
9
|
+
|
10
|
+
# subscriptions:
|
11
|
+
# - start_call
|
12
|
+
# - hungup_call
|
@@ -0,0 +1,56 @@
|
|
1
|
+
methods_for :dialplan do
|
2
|
+
def simon_game
|
3
|
+
SimonGame.new(self).start
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
class SimonGame
|
8
|
+
|
9
|
+
def initialize(call)
|
10
|
+
@call = call
|
11
|
+
reset
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
loop do
|
16
|
+
say_number
|
17
|
+
collect_attempt
|
18
|
+
verify_attempt
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def random_number
|
23
|
+
rand(10).to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
def update_number
|
27
|
+
@number << random_number
|
28
|
+
end
|
29
|
+
|
30
|
+
def say_number
|
31
|
+
update_number
|
32
|
+
@call.say_digits @number
|
33
|
+
end
|
34
|
+
|
35
|
+
def collect_attempt
|
36
|
+
@attempt = @call.input @number.length
|
37
|
+
end
|
38
|
+
|
39
|
+
def verify_attempt
|
40
|
+
if attempt_correct?
|
41
|
+
@call.play 'good'
|
42
|
+
else
|
43
|
+
@call.play %W[#{@number.length-1} times wrong-try-again-smarty]
|
44
|
+
reset
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def attempt_correct?
|
49
|
+
@attempt == @number
|
50
|
+
end
|
51
|
+
|
52
|
+
def reset
|
53
|
+
@attempt, @number = '', ''
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
unless defined? Adhearsion
|
2
|
+
if File.exists? File.dirname(__FILE__) + "/../adhearsion/lib/adhearsion.rb"
|
3
|
+
# If you wish to freeze a copy of Adhearsion to this app, simply place a copy of Adhearsion
|
4
|
+
# into a folder named "adhearsion" within this app's main directory.
|
5
|
+
require File.dirname(__FILE__) + "/../adhearsion/lib/adhearsion.rb"
|
6
|
+
else
|
7
|
+
require 'rubygems'
|
8
|
+
gem 'adhearsion', '>= 0.8.2'
|
9
|
+
require 'adhearsion'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Adhearsion::Configuration.configure do |config|
|
14
|
+
|
15
|
+
# Supported levels (in increasing severity) -- :debug < :info < :warn < :error < :fatal
|
16
|
+
config.logging :level => :info
|
17
|
+
|
18
|
+
# Whether incoming calls be automatically answered. Defaults to true.
|
19
|
+
# config.automatically_answer_incoming_calls = false
|
20
|
+
|
21
|
+
# Whether the other end hanging up should end the call immediately. Defaults to true.
|
22
|
+
# config.end_call_on_hangup = false
|
23
|
+
|
24
|
+
# Whether to end the call immediately if an unrescued exception is caught. Defaults to true.
|
25
|
+
# config.end_call_on_error = false
|
26
|
+
|
27
|
+
# By default Asterisk is enabled with the default settings
|
28
|
+
config.enable_asterisk
|
29
|
+
# config.asterisk.enable_ami :host => "127.0.0.1", :username => "admin", :password => "password", :events => true
|
30
|
+
|
31
|
+
# config.enable_drb
|
32
|
+
|
33
|
+
# Streamlined Rails integration! The first argument should be a relative or absolute path to
|
34
|
+
# the Rails app folder with which you're integrating. The second argument must be one of the
|
35
|
+
# the following: :development, :production, or :test.
|
36
|
+
|
37
|
+
# config.enable_rails :path => 'gui', :env => :development
|
38
|
+
|
39
|
+
# Note: You CANNOT do enable_rails and enable_database at the same time. When you enable Rails,
|
40
|
+
# it will automatically connect to same database Rails does and load the Rails app's models.
|
41
|
+
|
42
|
+
# Configure a database to use ActiveRecord-backed models. See ActiveRecord::Base.establish_connection
|
43
|
+
# for the appropriate settings here.
|
44
|
+
# config.enable_database :adapter => 'mysql',
|
45
|
+
# :username => 'joe',
|
46
|
+
# :password => 'secret',
|
47
|
+
# :host => 'db.example.org'
|
48
|
+
end
|
49
|
+
|
50
|
+
Adhearsion::Initializer.start_from_init_file(__FILE__, File.dirname(__FILE__) + "/..")
|
@@ -0,0 +1,32 @@
|
|
1
|
+
##
|
2
|
+
# In this file you can define callbacks for different aspects of the framework. Below is an example:
|
3
|
+
##
|
4
|
+
#
|
5
|
+
# events.asterisk.before_call.each do |call|
|
6
|
+
# # This simply logs the extension for all calls going through this Adhearsion app.
|
7
|
+
# extension = call.variables[:extension]
|
8
|
+
# ahn_log "Got a new call with extension #{extension}"
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
##
|
12
|
+
# Asterisk Manager Interface example:
|
13
|
+
#
|
14
|
+
# events.asterisk.manager_interface.each do |event|
|
15
|
+
# ahn_log.events event.inspect
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# This assumes you gave :events => true to the config.asterisk.enable_ami method in config/startup.rb
|
19
|
+
#
|
20
|
+
##
|
21
|
+
# Here is a list of the events included by default:
|
22
|
+
#
|
23
|
+
# - events.asterisk.manager_interface
|
24
|
+
# - events.after_initialized
|
25
|
+
# - events.shutdown
|
26
|
+
# - events.asterisk.before_call
|
27
|
+
# - events.asterisk.failed_call
|
28
|
+
# - events.asterisk.hungup_call
|
29
|
+
#
|
30
|
+
#
|
31
|
+
# Note: events are mostly for components to register and expose to you.
|
32
|
+
##
|
data/bin/ahn
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This is the main executable file.
|
4
|
+
|
5
|
+
# Adhearsion, open source collaboration framework
|
6
|
+
# Copyright (C) 2006,2007,2008 Jay Phillips
|
7
|
+
#
|
8
|
+
# This library is free software; you can redistribute it and/or modify it under
|
9
|
+
# the terms of the GNU Lesser General Public License as published by the Free
|
10
|
+
# Software Foundation; either version 2.1 of the License, or (at your option)
|
11
|
+
# any later version.
|
12
|
+
#
|
13
|
+
# This library is distributed in the hope that it will be useful, but WITHOUT
|
14
|
+
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
15
|
+
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
16
|
+
# details.
|
17
|
+
#
|
18
|
+
# You should have received a copy of the GNU Lesser General Public License along
|
19
|
+
# with this library; if not, write to the Free Software Foundation, Inc.,
|
20
|
+
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
21
|
+
|
22
|
+
$:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib")
|
23
|
+
|
24
|
+
require 'rubygems'
|
25
|
+
require 'adhearsion'
|
26
|
+
require 'adhearsion/cli'
|
27
|
+
|
28
|
+
Adhearsion::CLI::AhnCommand.execute!
|