adhearsion 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +8 -0
- data/adhearsion.gemspec +1 -1
- data/app_generators/ahn/templates/events.rb +7 -0
- data/lib/adhearsion/cli.rb +14 -10
- data/lib/adhearsion/component_manager.rb +7 -0
- data/lib/adhearsion/console.rb +49 -0
- data/lib/adhearsion/events_support.rb +1 -0
- data/lib/adhearsion/initializer.rb +23 -4
- data/lib/adhearsion/logging.rb +4 -1
- data/lib/adhearsion/version.rb +6 -3
- data/lib/adhearsion/voip/asterisk/agi_server.rb +2 -3
- data/lib/adhearsion/voip/asterisk/commands.rb +6 -4
- data/lib/adhearsion/voip/asterisk/config_manager.rb +4 -11
- data/lib/adhearsion/voip/asterisk/manager_interface.rb +7 -1
- data/lib/adhearsion/voip/call.rb +23 -4
- data/lib/theatre.rb +2 -7
- data/lib/theatre/invocation.rb +0 -2
- data/spec/ahn_command_spec.rb +20 -8
- data/spec/initialization_spec.rb +1 -1
- data/spec/voip/asterisk/commands_spec.rb +7 -0
- data/spec/voip/asterisk/config_manager_spec.rb +1 -1
- metadata +379 -402
data/CHANGELOG
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
1.1.0
|
2
|
+
- Added interactive call control console: ahn start console <path>
|
3
|
+
- Added centralized exception handler through eventing system
|
4
|
+
- Support for using ahn_hoptoad to send Adhearsion exceptions to Hoptoad
|
5
|
+
- Adhearsion.active_calls can now use hash syntax to find calls by ID
|
6
|
+
- Added Adhearsion::Calls#to_h
|
7
|
+
- Add a Monitor to synchronize access to an AGI connection
|
8
|
+
|
1
9
|
1.0.3
|
2
10
|
- Fix the play() command regression when passing an array of strings. This was breaking the SimonGame
|
3
11
|
- Deprecate ManagerInterface#send_action_asynchronously
|
data/adhearsion.gemspec
CHANGED
@@ -21,7 +21,6 @@ Gem::Specification.new do |s|
|
|
21
21
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
22
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
23
|
s.require_paths = ["lib"]
|
24
|
-
s.has_rdoc = true
|
25
24
|
|
26
25
|
if s.respond_to? :specification_version then
|
27
26
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
@@ -38,6 +37,7 @@ Gem::Specification.new do |s|
|
|
38
37
|
s.add_runtime_dependency("i18n")
|
39
38
|
s.add_runtime_dependency("rubigen", [">= 1.5.6"])
|
40
39
|
s.add_runtime_dependency("rake")
|
40
|
+
s.add_runtime_dependency("pry")
|
41
41
|
|
42
42
|
# Development dependencies
|
43
43
|
s.add_development_dependency('rubigen', [">= 1.5.6"])
|
@@ -20,6 +20,7 @@
|
|
20
20
|
##
|
21
21
|
# Here is a list of the events included by default:
|
22
22
|
#
|
23
|
+
# - events.exception
|
23
24
|
# - events.asterisk.manager_interface
|
24
25
|
# - events.after_initialized
|
25
26
|
# - events.shutdown
|
@@ -30,3 +31,9 @@
|
|
30
31
|
#
|
31
32
|
# Note: events are mostly for components to register and expose to you.
|
32
33
|
##
|
34
|
+
|
35
|
+
events.exception.each do |e|
|
36
|
+
ahn_log.error "#{e.class}: #{e.message}"
|
37
|
+
ahn_log.error e.backtrace.join("\n\t")
|
38
|
+
end
|
39
|
+
|
data/lib/adhearsion/cli.rb
CHANGED
@@ -6,7 +6,7 @@ module Adhearsion
|
|
6
6
|
USAGE = <<USAGE
|
7
7
|
Usage:
|
8
8
|
ahn create /path/to/directory
|
9
|
-
ahn start [daemon] [/path/to/directory]
|
9
|
+
ahn start [console|daemon] [/path/to/directory]
|
10
10
|
ahn version|-v|--v|-version|--version
|
11
11
|
ahn help|-h|--h|--help|-help
|
12
12
|
|
@@ -39,23 +39,27 @@ USAGE
|
|
39
39
|
when 'start'
|
40
40
|
pid_file_regexp = /^--pid-file=(.+)$/
|
41
41
|
if args.size > 3
|
42
|
-
|
42
|
+
raise CommandHandler::CLIException, "Too many arguments supplied!" if args.size > 3
|
43
43
|
elsif args.size == 3
|
44
|
-
|
44
|
+
raise CommandHandler::CLIException, "Unrecognized final argument #{args.last}" unless args.last =~ pid_file_regexp
|
45
45
|
pid_file = args.pop[pid_file_regexp, 1]
|
46
46
|
else
|
47
47
|
pid_file = nil
|
48
48
|
end
|
49
49
|
|
50
|
-
if args.
|
50
|
+
if args.size == 2
|
51
51
|
path = args.last
|
52
|
-
|
52
|
+
if args.first =~ /daemon|console/
|
53
|
+
mode = args.first.to_sym
|
54
|
+
else
|
55
|
+
raise CommandHandler::CLIException, "Invalid start mode requested: #{args.first}"
|
56
|
+
end
|
53
57
|
elsif args.size == 1
|
54
|
-
path,
|
58
|
+
path, mode = args.first, :foreground
|
55
59
|
else
|
56
|
-
|
60
|
+
raise CommandHandler::CLIException, "Invalid format for the start CLI command!"
|
57
61
|
end
|
58
|
-
[:start, path,
|
62
|
+
[:start, path, mode, pid_file]
|
59
63
|
when '-'
|
60
64
|
[:start, Dir.pwd]
|
61
65
|
when "enable", "disable"
|
@@ -192,9 +196,9 @@ component_name.upcase = ComponentTester.new("#{component_name}", File.dirname(__
|
|
192
196
|
end
|
193
197
|
end
|
194
198
|
|
195
|
-
def start(path,
|
199
|
+
def start(path, mode=:foreground, pid_file=nil)
|
196
200
|
raise PathInvalid, path unless File.exists? path + "/.ahnrc"
|
197
|
-
Adhearsion::Initializer.start path, :
|
201
|
+
Adhearsion::Initializer.start path, :mode => mode, :pid_file => pid_file
|
198
202
|
end
|
199
203
|
|
200
204
|
def version
|
@@ -134,6 +134,13 @@ module Adhearsion
|
|
134
134
|
end
|
135
135
|
end
|
136
136
|
container
|
137
|
+
rescue StandardError => e
|
138
|
+
# Non-fatal errors
|
139
|
+
Events.trigger(['exception'], e)
|
140
|
+
rescue Exception => e
|
141
|
+
# Fatal errors. Log them and keep passing them upward
|
142
|
+
Events.trigger(['exception'], e)
|
143
|
+
raise e
|
137
144
|
end
|
138
145
|
|
139
146
|
class ComponentDefinitionContainer < Module
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'pry'
|
2
|
+
|
3
|
+
module Adhearsion
|
4
|
+
module Console
|
5
|
+
include Adhearsion
|
6
|
+
|
7
|
+
class << self
|
8
|
+
##
|
9
|
+
# Start the Adhearsion console
|
10
|
+
#
|
11
|
+
def run
|
12
|
+
Pry.prompt = [ proc {|obj, nest_level| "AHN#{' ' * nest_level}> " },
|
13
|
+
proc {|obj, nest_level| "AHN#{' ' * nest_level}? " } ]
|
14
|
+
pry
|
15
|
+
end
|
16
|
+
|
17
|
+
def logger
|
18
|
+
Adhearsion::Logging
|
19
|
+
end
|
20
|
+
|
21
|
+
def calls
|
22
|
+
Adhearsion.active_calls
|
23
|
+
end
|
24
|
+
|
25
|
+
def use(call)
|
26
|
+
unless call.is_a? Adhearsion::Call
|
27
|
+
raise ArgumentError unless Adhearsion.active_calls[call]
|
28
|
+
call = Adhearsion.active_calls[call]
|
29
|
+
end
|
30
|
+
Pry.prompt = [ proc { "AHN<#{call.channel}> "},
|
31
|
+
proc { "AHN<#{call.channel}? "} ]
|
32
|
+
|
33
|
+
# Pause execution of the thread currently controlling the call
|
34
|
+
call.with_command_lock do
|
35
|
+
CallWrapper.new(call).pry
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class CallWrapper
|
41
|
+
attr_accessor :call
|
42
|
+
|
43
|
+
def initialize(call)
|
44
|
+
@call = call
|
45
|
+
extend Adhearsion::VoIP::Commands.for('asterisk')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -125,7 +125,7 @@ module Adhearsion
|
|
125
125
|
def initialize(path=nil, options={})
|
126
126
|
@@started = true
|
127
127
|
@path = path
|
128
|
-
@
|
128
|
+
@mode = options[:mode]
|
129
129
|
@pid_file = options[:pid_file].nil? ? ENV['PID_FILE'] : options[:pid_file]
|
130
130
|
@loaded_init_files = options[:loaded_init_files]
|
131
131
|
self.class.ahn_root = path
|
@@ -137,6 +137,7 @@ module Adhearsion
|
|
137
137
|
resolve_pid_file_path
|
138
138
|
resolve_log_file_path
|
139
139
|
daemonize! if should_daemonize?
|
140
|
+
launch_console if need_console?
|
140
141
|
switch_to_root_directory
|
141
142
|
catch_termination_signal
|
142
143
|
create_pid_file if pid_file
|
@@ -168,7 +169,7 @@ module Adhearsion
|
|
168
169
|
elsif pid_file then pid_file
|
169
170
|
elsif pid_file.equal?(false) then nil
|
170
171
|
# FIXME @pid_file = @daemon? Assignment or equality? I'm assuming equality.
|
171
|
-
else @pid_file = @daemon ? default_pid_path : nil
|
172
|
+
else @pid_file = (@mode == :daemon) ? default_pid_path : nil
|
172
173
|
end
|
173
174
|
end
|
174
175
|
|
@@ -315,7 +316,11 @@ Adhearsion will abort until you fix this. Sorry for the incovenience.
|
|
315
316
|
end
|
316
317
|
|
317
318
|
def should_daemonize?
|
318
|
-
@daemon
|
319
|
+
@mode == :daemon
|
320
|
+
end
|
321
|
+
|
322
|
+
def need_console?
|
323
|
+
@mode == :console
|
319
324
|
end
|
320
325
|
|
321
326
|
def daemonize!
|
@@ -324,6 +329,20 @@ Adhearsion will abort until you fix this. Sorry for the incovenience.
|
|
324
329
|
daemonize log_file
|
325
330
|
end
|
326
331
|
|
332
|
+
def launch_console
|
333
|
+
require 'adhearsion/console'
|
334
|
+
Thread.new do
|
335
|
+
begin
|
336
|
+
puts "Starting console"
|
337
|
+
Adhearsion::Console.run
|
338
|
+
Adhearsion.shutdown!
|
339
|
+
rescue Exception => e
|
340
|
+
puts e.message
|
341
|
+
puts e.backtrace.join("\n")
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
327
346
|
def initialize_log_file
|
328
347
|
Dir.mkdir(ahn_app_log_directory) unless File.directory? ahn_app_log_directory
|
329
348
|
file_logger = Log4r::FileOutputter.new("Main Adhearsion log file", :filename => log_file, :trunc => false)
|
@@ -382,7 +401,7 @@ Adhearsion will abort until you fix this. Sorry for the incovenience.
|
|
382
401
|
begin
|
383
402
|
IMPORTANT_THREADS[index].join
|
384
403
|
rescue => e
|
385
|
-
ahn_log.error "Error after join()ing Thread #{
|
404
|
+
ahn_log.error "Error after join()ing Thread #{Thread.inspect}. #{e.message}"
|
386
405
|
ensure
|
387
406
|
index = index + 1
|
388
407
|
end
|
data/lib/adhearsion/logging.rb
CHANGED
@@ -24,12 +24,15 @@ module Adhearsion
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
27
|
+
alias :level= :logging_level=
|
27
28
|
|
28
|
-
def logging_level
|
29
|
+
def logging_level(level = nil)
|
30
|
+
return self.logging_level= level unless level.nil?
|
29
31
|
@@logging_level_lock.synchronize do
|
30
32
|
return @@logging_level ||= Log4r::INFO
|
31
33
|
end
|
32
34
|
end
|
35
|
+
alias :level :logging_level
|
33
36
|
end
|
34
37
|
|
35
38
|
class AdhearsionLogger < Log4r::Logger
|
data/lib/adhearsion/version.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Adhearsion #:nodoc:
|
2
2
|
module VERSION #:nodoc:
|
3
3
|
MAJOR = 1 unless defined? MAJOR
|
4
|
-
MINOR =
|
5
|
-
TINY =
|
4
|
+
MINOR = 1 unless defined? MINOR
|
5
|
+
TINY = 0 unless defined? TINY
|
6
6
|
|
7
7
|
STRING = [MAJOR, MINOR, TINY].join('.') unless defined? STRING
|
8
8
|
end
|
@@ -13,13 +13,16 @@ module Adhearsion #:nodoc:
|
|
13
13
|
attr_reader :major, :minor, :revision
|
14
14
|
|
15
15
|
def initialize(version="")
|
16
|
-
|
16
|
+
version = "" if version.nil?
|
17
|
+
@major, @minor, @revision, @patchlevel = version.split(".", 4).map(&:to_i)
|
18
|
+
@major = 0 unless @major
|
17
19
|
end
|
18
20
|
|
19
21
|
def <=>(other)
|
20
22
|
return @major <=> other.major if ((@major <=> other.major) != 0)
|
21
23
|
return @minor <=> other.minor if ((@minor <=> other.minor) != 0)
|
22
24
|
return @revision <=> other.revision if ((@revision <=> other.revision) != 0)
|
25
|
+
return 0
|
23
26
|
end
|
24
27
|
|
25
28
|
def self.sort
|
@@ -59,9 +59,8 @@ module Adhearsion
|
|
59
59
|
ahn_log.agi "Ignoring meta-AGI request"
|
60
60
|
call.hangup!
|
61
61
|
# TBD: (may have more hooks than what Jay has defined in hooks.rb)
|
62
|
-
rescue => e
|
63
|
-
|
64
|
-
ahn_log.agi.error e.backtrace.join("\n\t")
|
62
|
+
rescue SyntaxError, StandardError => e
|
63
|
+
Events.trigger(['exception'], e)
|
65
64
|
ensure
|
66
65
|
Adhearsion.remove_inactive_call call rescue nil
|
67
66
|
end
|
@@ -94,10 +94,12 @@ module Adhearsion
|
|
94
94
|
#
|
95
95
|
# @see http://www.voip-info.org/wiki/view/Asterisk+FastAGI More information about FAGI
|
96
96
|
def raw_response(message = nil)
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
97
|
+
@call.with_command_lock do
|
98
|
+
raise ArgumentError.new("illegal NUL in message #{message.inspect}") if message =~ /\0/
|
99
|
+
ahn_log.agi.debug ">>> #{message}"
|
100
|
+
write message if message
|
101
|
+
read
|
102
|
+
end
|
101
103
|
end
|
102
104
|
|
103
105
|
def response(command, *arguments)
|
@@ -39,8 +39,10 @@ module Adhearsion
|
|
39
39
|
private
|
40
40
|
|
41
41
|
def read_configuration
|
42
|
-
normalized_file = self.class.normalize_configuration
|
43
|
-
normalized_file.split(/^\[([-_\w]+)\]$/)[1..-1]
|
42
|
+
normalized_file = self.class.normalize_configuration File.open(@filename, 'r'){|f| f.read}
|
43
|
+
sections = normalized_file.split(/^\[([-_\w]+)\]$/)[1..-1]
|
44
|
+
return [] if sections.nil?
|
45
|
+
sections.each_slice(2).map do |(name,properties)|
|
44
46
|
[name, hash_from_properties(properties)]
|
45
47
|
end
|
46
48
|
end
|
@@ -53,15 +55,6 @@ module Adhearsion
|
|
53
55
|
property_hash
|
54
56
|
end
|
55
57
|
end
|
56
|
-
|
57
|
-
def execute(command)
|
58
|
-
%x[command]
|
59
|
-
end
|
60
|
-
|
61
|
-
def read_command
|
62
|
-
"cat #{filename}"
|
63
|
-
end
|
64
|
-
|
65
58
|
end
|
66
59
|
end
|
67
60
|
end
|
@@ -458,7 +458,13 @@ WARN
|
|
458
458
|
end
|
459
459
|
|
460
460
|
def start_actions_writer_loop
|
461
|
-
@actions_writer_thread = Thread.new
|
461
|
+
@actions_writer_thread = Thread.new do
|
462
|
+
begin
|
463
|
+
actions_writer_loop
|
464
|
+
rescue => e
|
465
|
+
Events.trigger(['exception'], e)
|
466
|
+
end
|
467
|
+
end
|
462
468
|
end
|
463
469
|
|
464
470
|
def stop_actions_writer_loop
|
data/lib/adhearsion/voip/call.rb
CHANGED
@@ -24,6 +24,8 @@ module Adhearsion
|
|
24
24
|
##
|
25
25
|
# This manages the list of calls the Adhearsion service receives
|
26
26
|
class Calls
|
27
|
+
attr_reader :semaphore, :calls
|
28
|
+
|
27
29
|
def initialize
|
28
30
|
@semaphore = Monitor.new
|
29
31
|
@calls = {}
|
@@ -60,6 +62,7 @@ module Adhearsion
|
|
60
62
|
return calls[id]
|
61
63
|
end
|
62
64
|
end
|
65
|
+
alias :[] :find
|
63
66
|
|
64
67
|
def clear!
|
65
68
|
atomically do
|
@@ -75,16 +78,23 @@ module Adhearsion
|
|
75
78
|
end
|
76
79
|
end
|
77
80
|
|
81
|
+
def each
|
82
|
+
calls.each_pair{|id, call| yield id, call }
|
83
|
+
end
|
84
|
+
|
78
85
|
def to_a
|
79
86
|
calls.values
|
80
87
|
end
|
81
88
|
|
89
|
+
def to_h
|
90
|
+
calls
|
91
|
+
end
|
92
|
+
|
82
93
|
private
|
83
|
-
attr_reader :semaphore, :calls
|
84
94
|
|
85
|
-
|
86
|
-
|
87
|
-
|
95
|
+
def atomically(&block)
|
96
|
+
semaphore.synchronize(&block)
|
97
|
+
end
|
88
98
|
|
89
99
|
end
|
90
100
|
|
@@ -259,6 +269,15 @@ module Adhearsion
|
|
259
269
|
@hungup_call
|
260
270
|
end
|
261
271
|
|
272
|
+
# Lock the socket for a command. Can be used to allow the console to take
|
273
|
+
# control of the thread in between AGI commands coming from the dialplan.
|
274
|
+
def with_command_lock
|
275
|
+
@command_monitor ||= Monitor.new
|
276
|
+
@command_monitor.synchronize do
|
277
|
+
yield
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
262
281
|
# Adhearsion indexes calls by this identifier so they may later be found and manipulated. For calls from Asterisk, this
|
263
282
|
# method uses the following properties for uniqueness, falling back to the next if one is for some reason unavailable:
|
264
283
|
#
|
data/lib/theatre.rb
CHANGED
@@ -130,19 +130,14 @@ module Theatre
|
|
130
130
|
|
131
131
|
protected
|
132
132
|
|
133
|
-
# This will use the Adhearsion logger eventually.
|
134
|
-
def warn(exception)
|
135
|
-
# STDERR.puts exception.message, *exception.backtrace
|
136
|
-
end
|
137
|
-
|
138
133
|
def thread_loop
|
139
134
|
loop do
|
140
135
|
begin
|
141
136
|
next_invocation = @master_queue.pop
|
142
137
|
return :stopped if next_invocation.equal? :THEATRE_SHUTDOWN!
|
143
138
|
next_invocation.start
|
144
|
-
rescue => error
|
145
|
-
|
139
|
+
rescue Exception => error
|
140
|
+
Adhearsion::Events.trigger(['exception'], error)
|
146
141
|
end
|
147
142
|
end
|
148
143
|
end
|