adhearsion 1.0.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +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
|