sant0sk1-adhearsion 0.7.999
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +456 -0
- data/README.txt +5 -0
- data/Rakefile +75 -0
- data/adhearsion.gemspec +136 -0
- data/app_generators/ahn/USAGE +5 -0
- data/app_generators/ahn/ahn_generator.rb +77 -0
- data/app_generators/ahn/templates/.ahnrc +36 -0
- data/app_generators/ahn/templates/README +8 -0
- data/app_generators/ahn/templates/Rakefile +18 -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.rb +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/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/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/events_support.rb +26 -0
- data/lib/adhearsion/hooks.rb +57 -0
- data/lib/adhearsion/host_definitions.rb +63 -0
- data/lib/adhearsion/initializer.rb +246 -0
- data/lib/adhearsion/initializer/asterisk.rb +59 -0
- data/lib/adhearsion/initializer/configuration.rb +236 -0
- data/lib/adhearsion/initializer/database.rb +49 -0
- data/lib/adhearsion/initializer/drb.rb +25 -0
- data/lib/adhearsion/initializer/freeswitch.rb +22 -0
- data/lib/adhearsion/initializer/rails.rb +40 -0
- data/lib/adhearsion/logging.rb +92 -0
- data/lib/adhearsion/tasks.rb +15 -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/version.rb +9 -0
- data/lib/adhearsion/voip/asterisk.rb +10 -0
- data/lib/adhearsion/voip/asterisk/agi_server.rb +81 -0
- data/lib/adhearsion/voip/asterisk/ami.rb +147 -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/commands.rb +1284 -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/call.rb +436 -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 +207 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -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
- metadata +167 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
# TODO: Have all of the initializer modules required and then traverse the subclasses, asking them if they're enabled. If they are enabled, then they should do their initialization stuff. Is this really necessary to develop this entirely new system when the components system exists?
|
2
|
+
|
3
|
+
module Adhearsion
|
4
|
+
class Initializer
|
5
|
+
|
6
|
+
class DatabaseInitializer
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def start
|
11
|
+
require_dependencies
|
12
|
+
require_models
|
13
|
+
@@config = Adhearsion::AHN_CONFIG.database
|
14
|
+
ActiveRecord::Base.allow_concurrency = true
|
15
|
+
establish_connection
|
16
|
+
create_call_hook_for_connection_cleanup
|
17
|
+
end
|
18
|
+
|
19
|
+
def stop
|
20
|
+
ActiveRecord::Base.remove_connection
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def create_call_hook_for_connection_cleanup
|
26
|
+
Hooks::BeforeCall.create_hook do
|
27
|
+
ActiveRecord::Base.verify_active_connections!
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def require_dependencies
|
32
|
+
require 'active_record'
|
33
|
+
end
|
34
|
+
|
35
|
+
def require_models
|
36
|
+
AHN_CONFIG.files_from_setting("paths", "models").each do |model|
|
37
|
+
load model
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def establish_connection
|
42
|
+
ActiveRecord::Base.establish_connection @@config.connection_options
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'drb'
|
2
|
+
require 'drb/acl'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
module Adhearsion
|
6
|
+
class Initializer
|
7
|
+
|
8
|
+
class DrbInitializer
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def start
|
13
|
+
config = Adhearsion::AHN_CONFIG.drb
|
14
|
+
DRb.install_acl ACL.new(config.acl) if config.acl
|
15
|
+
DRb.start_service "druby://#{config.host}:#{config.port}", Adhearsion::DrbDoor.instance
|
16
|
+
ahn_log "Starting DRb on #{config.host}:#{config.port}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def stop
|
20
|
+
DRb.stop_service
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# THIS FREESWITCH LIBRARY HASN'T BEEN INTEGRATED INTO THE REFACTORED 0.8.0 YET.
|
2
|
+
# WHAT EXISTS HERE IS OLD, MUST BE CHANGED, AND DOES NOT EVEN GET LOADED AT THE MOMENT.
|
3
|
+
require "adhearsion/voip/freeswitch/oes_server"
|
4
|
+
require "adhearsion/voip/freeswitch/event_handler"
|
5
|
+
require "adhearsion/voip/freeswitch/inbound_connection_manager"
|
6
|
+
require "adhearsion/voip/dsl/dialplan/control_passing_exception"
|
7
|
+
|
8
|
+
oes_enabled = Adhearsion::Configuration.core.voip.freeswitch.oes && Adhearsion::Configuration.core.voip.freeswitch.oes.port
|
9
|
+
|
10
|
+
|
11
|
+
if oes_enabled
|
12
|
+
|
13
|
+
port = Adhearsion::Configuration.core.voip.freeswitch.oes.port
|
14
|
+
host = Adhearsion::Configuration.core.voip.freeswitch.oes.host
|
15
|
+
|
16
|
+
server = Adhearsion::VoIP::FreeSwitch::OesServer.new port, host
|
17
|
+
|
18
|
+
Adhearsion::Hooks::AfterInitialized.create_hook { server.start }
|
19
|
+
Adhearsion::Hooks::ThreadsJoinedAfterInitialized.create_hook { server.join }
|
20
|
+
Adhearsion::Hooks::TearDown.create_hook { server.stop }
|
21
|
+
|
22
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'adhearsion/voip/asterisk'
|
2
|
+
|
3
|
+
module Adhearsion
|
4
|
+
class Initializer
|
5
|
+
class RailsInitializer
|
6
|
+
|
7
|
+
cattr_accessor :rails_root, :config, :environment
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def start
|
11
|
+
ahn_config = Adhearsion::AHN_CONFIG
|
12
|
+
self.config = ahn_config.rails
|
13
|
+
self.rails_root = config.rails_root
|
14
|
+
self.environment = config.environment
|
15
|
+
raise "You cannot enable the database and Rails at the same time!" if ahn_config.database_enabled?
|
16
|
+
raise "Error loading Rails environment in #{rails_root.inspect}. " +
|
17
|
+
"It's not a directory!" unless File.directory?(rails_root)
|
18
|
+
load_rails
|
19
|
+
if defined? ActiveRecord
|
20
|
+
ActiveRecord::Base.allow_concurrency = true
|
21
|
+
Hooks::BeforeCall.create_hook do
|
22
|
+
ActiveRecord::Base.verify_active_connections!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def load_rails
|
30
|
+
environment_file = File.expand_path(rails_root + "/config/environment.rb")
|
31
|
+
raise "There is no config/environment.rb file!" unless File.exists?(environment_file)
|
32
|
+
ENV['RAILS_ENV'] = environment.to_s
|
33
|
+
require environment_file
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'log4r'
|
2
|
+
|
3
|
+
module Adhearsion
|
4
|
+
module Logging
|
5
|
+
|
6
|
+
@@logging_level_lock = Mutex.new
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def silence!
|
11
|
+
self.logging_level = :fatal
|
12
|
+
end
|
13
|
+
|
14
|
+
def unsilence!
|
15
|
+
self.logging_level = :info
|
16
|
+
end
|
17
|
+
|
18
|
+
def logging_level=(new_logging_level)
|
19
|
+
new_logging_level = Log4r.const_get(new_logging_level.to_s.upcase)
|
20
|
+
@@logging_level_lock.synchronize do
|
21
|
+
@@logging_level = new_logging_level
|
22
|
+
Log4r::Logger.each_logger do |logger|
|
23
|
+
logger.level = new_logging_level
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def logging_level
|
29
|
+
@@logging_level_lock.synchronize do
|
30
|
+
return @@logging_level ||= Log4r::INFO
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class AdhearsionLogger < Log4r::Logger
|
36
|
+
|
37
|
+
@@outputters = [Log4r::Outputter.stdout]
|
38
|
+
|
39
|
+
class << self
|
40
|
+
def outputters
|
41
|
+
@@outputters
|
42
|
+
end
|
43
|
+
|
44
|
+
def outputters=(other)
|
45
|
+
@@outputters = other
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(*args)
|
50
|
+
super
|
51
|
+
redefine_outputters
|
52
|
+
end
|
53
|
+
|
54
|
+
def redefine_outputters
|
55
|
+
self.outputters = @@outputters
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_missing(logger_name, *args, &block)
|
59
|
+
define_logging_method(logger_name, self.class.new(logger_name.to_s))
|
60
|
+
send(logger_name, *args, &block)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def define_logging_method(name, logger)
|
66
|
+
# Can't use Module#define_method() because blocks in Ruby 1.8.x can't
|
67
|
+
# have their own block arguments.
|
68
|
+
self.class.class_eval(<<-CODE, __FILE__, __LINE__)
|
69
|
+
def #{name}(*args, &block)
|
70
|
+
logger = Log4r::Logger['#{name}']
|
71
|
+
if args.any? || block_given?
|
72
|
+
logger.info(*args, &block)
|
73
|
+
else
|
74
|
+
logger
|
75
|
+
end
|
76
|
+
end
|
77
|
+
CODE
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
DefaultAdhearsionLogger = AdhearsionLogger.new 'ahn'
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def ahn_log(*args)
|
87
|
+
if args.any?
|
88
|
+
Adhearsion::Logging::DefaultAdhearsionLogger.info(*args)
|
89
|
+
else
|
90
|
+
Adhearsion::Logging::DefaultAdhearsionLogger
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rake/testtask'
|
2
|
+
require 'adhearsion'
|
3
|
+
require 'adhearsion/tasks/database'
|
4
|
+
require 'adhearsion/tasks/testing'
|
5
|
+
require 'adhearsion/tasks/generating'
|
6
|
+
require 'adhearsion/tasks/lint'
|
7
|
+
|
8
|
+
namespace :adhearsion do
|
9
|
+
desc "Dump useful information about this application's adhearsion environment"
|
10
|
+
task :about do
|
11
|
+
puts "Adhearsion version: #{Adhearsion::VERSION::STRING}"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
task :default => "adhearsion:about"
|
@@ -0,0 +1,20 @@
|
|
1
|
+
namespace:create do
|
2
|
+
|
3
|
+
task:war do
|
4
|
+
# Hmm, this will is a tough one
|
5
|
+
end
|
6
|
+
|
7
|
+
task:rails_plugin do
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
task:migration do
|
12
|
+
name = ARGV.shift
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
namespace:delete do
|
17
|
+
task:migration do
|
18
|
+
# Take arg.underscore and remove it
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
namespace:test do
|
2
|
+
desc "Run tests for a component specified by COMPONENT=<component_name>. If no component is specified, tests will be executed for all components"
|
3
|
+
task :component do
|
4
|
+
component = ENV['COMPONENT']
|
5
|
+
components_to_test = component.nil? ? all_component_directories : [full_path_for(component)]
|
6
|
+
components_to_test.each do |component_name|
|
7
|
+
setup_and_execute(component_name)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def setup_and_execute(component_path)
|
14
|
+
task = create_test_task_for(component_path)
|
15
|
+
Rake::Task[task.name].execute
|
16
|
+
end
|
17
|
+
|
18
|
+
def create_test_task_for(component_path)
|
19
|
+
Rake::TestTask.new(task_name_for(component_path)) do |t|
|
20
|
+
t.libs = ["lib", "test"].map{|subdir| File.join(component_path, subdir)}
|
21
|
+
t.test_files = FileList["#{component_path}/test/test_*.rb"]
|
22
|
+
t.verbose = true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def task_name_for(component_path)
|
27
|
+
"test_#{component_path.split(/\//).last}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def all_component_directories
|
31
|
+
Dir['components/*']
|
32
|
+
end
|
33
|
+
|
34
|
+
def full_path_for(component)
|
35
|
+
component =~ /^components\// ? component : File.join("components", component)
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/dsl/numerical_string"
|
2
|
+
require File.dirname(__FILE__) + "/asterisk/agi_server"
|
3
|
+
require File.dirname(__FILE__) + "/asterisk/ami"
|
4
|
+
require File.dirname(__FILE__) + "/asterisk/commands"
|
5
|
+
|
6
|
+
# These will soon replace hooks
|
7
|
+
#
|
8
|
+
# [:before_call, :after_call, :hungup_call, :failed_call].each do |callback_name|
|
9
|
+
# Adhearsion::Events.framework_theatre.namespace_manager.register_namespace_name [:asterisk, callback_name]
|
10
|
+
# end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'gserver'
|
2
|
+
module Adhearsion
|
3
|
+
module VoIP
|
4
|
+
module Asterisk
|
5
|
+
module AGI
|
6
|
+
class Server
|
7
|
+
|
8
|
+
class RubyServer < GServer
|
9
|
+
|
10
|
+
def initialize(port, host)
|
11
|
+
super(port, host, (1.0/0.0)) # (1.0/0.0) == Infinity
|
12
|
+
end
|
13
|
+
|
14
|
+
def serve(io)
|
15
|
+
Hooks::BeforeCall.trigger_hooks
|
16
|
+
call = Adhearsion.receive_call_from(io)
|
17
|
+
ahn_log.agi "Handling call with variables #{call.variables.inspect}"
|
18
|
+
|
19
|
+
return DialPlan::ConfirmationManager.handle(call) if DialPlan::ConfirmationManager.confirmation_call?(call)
|
20
|
+
|
21
|
+
# This is what happens 99.9% of the time.
|
22
|
+
|
23
|
+
DialPlan::Manager.handle call
|
24
|
+
rescue DialPlan::Manager::NoContextError => e
|
25
|
+
ahn_log.agi e.message
|
26
|
+
call.hangup!
|
27
|
+
rescue FailedExtensionCallException => failed_call
|
28
|
+
begin
|
29
|
+
ahn_log.agi "Received \"failed\" meta-call with :failed_reason => #{failed_call.call.failed_reason.inspect}. Executing OnFailedCall hooks."
|
30
|
+
Hooks::OnFailedCall.trigger_hooks(failed_call.call)
|
31
|
+
call.hangup!
|
32
|
+
rescue => e
|
33
|
+
ahn_log.agi.error e
|
34
|
+
end
|
35
|
+
rescue HungupExtensionCallException => hungup_call
|
36
|
+
begin
|
37
|
+
ahn_log.agi "Received \"h\" meta-call. Executing OnHungupCall hooks."
|
38
|
+
Hooks::OnHungupCall.trigger_hooks(hungup_call.call)
|
39
|
+
call.hangup!
|
40
|
+
rescue => e
|
41
|
+
ahn_log.agi.error e
|
42
|
+
end
|
43
|
+
rescue UselessCallException
|
44
|
+
ahn_log.agi "Ignoring meta-AGI request"
|
45
|
+
call.hangup!
|
46
|
+
# TBD: (may have more hooks than what Jay has defined in hooks.rb)
|
47
|
+
rescue => e
|
48
|
+
ahn_log.agi.error e.inspect
|
49
|
+
ahn_log.agi.error e.backtrace.map { |s| " " * 5 + s }.join("\n")
|
50
|
+
ensure
|
51
|
+
Adhearsion.remove_inactive_call call rescue nil
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
DEFAULT_OPTIONS = { :server_class => RubyServer, :port => 4573, :host => "0.0.0.0" } unless defined? DEFAULT_OPTIONS
|
57
|
+
attr_reader :host, :port, :server_class, :server
|
58
|
+
|
59
|
+
def initialize(options = {})
|
60
|
+
options = DEFAULT_OPTIONS.merge options
|
61
|
+
@host, @port, @server_class = options.values_at(:host, :port, :server_class)
|
62
|
+
@server = server_class.new(port, host)
|
63
|
+
end
|
64
|
+
|
65
|
+
def start
|
66
|
+
server.start
|
67
|
+
end
|
68
|
+
|
69
|
+
def shutdown
|
70
|
+
server.stop
|
71
|
+
end
|
72
|
+
|
73
|
+
def join
|
74
|
+
server.join
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
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
|