sant0sk1-adhearsion 0.7.999

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. data/LICENSE +456 -0
  2. data/README.txt +5 -0
  3. data/Rakefile +75 -0
  4. data/adhearsion.gemspec +136 -0
  5. data/app_generators/ahn/USAGE +5 -0
  6. data/app_generators/ahn/ahn_generator.rb +77 -0
  7. data/app_generators/ahn/templates/.ahnrc +36 -0
  8. data/app_generators/ahn/templates/README +8 -0
  9. data/app_generators/ahn/templates/Rakefile +18 -0
  10. data/app_generators/ahn/templates/components/simon_game/configuration.rb +0 -0
  11. data/app_generators/ahn/templates/components/simon_game/lib/simon_game.rb +61 -0
  12. data/app_generators/ahn/templates/components/simon_game/test/test_helper.rb +14 -0
  13. data/app_generators/ahn/templates/components/simon_game/test/test_simon_game.rb +31 -0
  14. data/app_generators/ahn/templates/config/startup.rb +53 -0
  15. data/app_generators/ahn/templates/dialplan.rb +4 -0
  16. data/bin/ahn +28 -0
  17. data/bin/ahnctl +68 -0
  18. data/bin/jahn +32 -0
  19. data/lib/adhearsion.rb +32 -0
  20. data/lib/adhearsion/blank_slate.rb +5 -0
  21. data/lib/adhearsion/cli.rb +106 -0
  22. data/lib/adhearsion/component_manager.rb +277 -0
  23. data/lib/adhearsion/core_extensions/all.rb +9 -0
  24. data/lib/adhearsion/core_extensions/array.rb +0 -0
  25. data/lib/adhearsion/core_extensions/custom_daemonizer.rb +45 -0
  26. data/lib/adhearsion/core_extensions/global.rb +1 -0
  27. data/lib/adhearsion/core_extensions/hash.rb +0 -0
  28. data/lib/adhearsion/core_extensions/metaprogramming.rb +17 -0
  29. data/lib/adhearsion/core_extensions/numeric.rb +4 -0
  30. data/lib/adhearsion/core_extensions/proc.rb +0 -0
  31. data/lib/adhearsion/core_extensions/publishable.rb +73 -0
  32. data/lib/adhearsion/core_extensions/relationship_properties.rb +40 -0
  33. data/lib/adhearsion/core_extensions/string.rb +26 -0
  34. data/lib/adhearsion/core_extensions/thread.rb +13 -0
  35. data/lib/adhearsion/core_extensions/thread_safety.rb +7 -0
  36. data/lib/adhearsion/core_extensions/time.rb +0 -0
  37. data/lib/adhearsion/distributed/gateways/dbus_gateway.rb +0 -0
  38. data/lib/adhearsion/distributed/gateways/osa_gateway.rb +0 -0
  39. data/lib/adhearsion/distributed/gateways/rest_gateway.rb +9 -0
  40. data/lib/adhearsion/distributed/gateways/soap_gateway.rb +9 -0
  41. data/lib/adhearsion/distributed/gateways/xmlrpc_gateway.rb +9 -0
  42. data/lib/adhearsion/distributed/peer_finder.rb +0 -0
  43. data/lib/adhearsion/distributed/remote_cli.rb +0 -0
  44. data/lib/adhearsion/events_support.rb +26 -0
  45. data/lib/adhearsion/hooks.rb +57 -0
  46. data/lib/adhearsion/host_definitions.rb +63 -0
  47. data/lib/adhearsion/initializer.rb +246 -0
  48. data/lib/adhearsion/initializer/asterisk.rb +59 -0
  49. data/lib/adhearsion/initializer/configuration.rb +236 -0
  50. data/lib/adhearsion/initializer/database.rb +49 -0
  51. data/lib/adhearsion/initializer/drb.rb +25 -0
  52. data/lib/adhearsion/initializer/freeswitch.rb +22 -0
  53. data/lib/adhearsion/initializer/rails.rb +40 -0
  54. data/lib/adhearsion/logging.rb +92 -0
  55. data/lib/adhearsion/tasks.rb +15 -0
  56. data/lib/adhearsion/tasks/database.rb +5 -0
  57. data/lib/adhearsion/tasks/generating.rb +20 -0
  58. data/lib/adhearsion/tasks/lint.rb +4 -0
  59. data/lib/adhearsion/tasks/testing.rb +37 -0
  60. data/lib/adhearsion/version.rb +9 -0
  61. data/lib/adhearsion/voip/asterisk.rb +10 -0
  62. data/lib/adhearsion/voip/asterisk/agi_server.rb +81 -0
  63. data/lib/adhearsion/voip/asterisk/ami.rb +147 -0
  64. data/lib/adhearsion/voip/asterisk/ami/actions.rb +238 -0
  65. data/lib/adhearsion/voip/asterisk/ami/machine.rb +871 -0
  66. data/lib/adhearsion/voip/asterisk/ami/machine.rl +109 -0
  67. data/lib/adhearsion/voip/asterisk/ami/parser.rb +262 -0
  68. data/lib/adhearsion/voip/asterisk/commands.rb +1284 -0
  69. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
  70. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
  71. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
  72. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
  73. data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
  74. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
  75. data/lib/adhearsion/voip/call.rb +436 -0
  76. data/lib/adhearsion/voip/call_routing.rb +64 -0
  77. data/lib/adhearsion/voip/commands.rb +9 -0
  78. data/lib/adhearsion/voip/constants.rb +39 -0
  79. data/lib/adhearsion/voip/conveniences.rb +18 -0
  80. data/lib/adhearsion/voip/dial_plan.rb +207 -0
  81. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
  82. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
  83. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
  84. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
  85. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +71 -0
  86. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
  87. data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
  88. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
  89. data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
  90. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
  91. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
  92. data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
  93. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
  94. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
  95. data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
  96. data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
  97. 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,5 @@
1
+ task:migrate do
2
+ require 'active_record'
3
+ %w.db/migrate db/ahn..each
4
+ ActiveRecord::Migrator.migrate 'db/migrate', ENV['VERSION'] ? ENV['VERSION'].to_i : nil
5
+ end
@@ -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,4 @@
1
+ task :sanity do
2
+ puts "Performing many checks on your Adhearsion application!"
3
+ # TODO: Anything that should be brought to the user's attention should be placed here!
4
+ 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,9 @@
1
+ module Adhearsion #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0 unless defined? MAJOR
4
+ MINOR = 7 unless defined? MINOR
5
+ TINY = 999 unless defined? TINY
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.') unless defined? STRING
8
+ end
9
+ 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