larynx 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +191 -0
- data/Rakefile +55 -0
- data/bin/larynx +7 -0
- data/examples/guess.rb +39 -0
- data/examples/guess_form.rb +34 -0
- data/examples/multiple_apps.rb +63 -0
- data/lib/larynx/application.rb +24 -0
- data/lib/larynx/call_handler.rb +174 -0
- data/lib/larynx/callbacks.rb +32 -0
- data/lib/larynx/command.rb +73 -0
- data/lib/larynx/commands.rb +88 -0
- data/lib/larynx/fields.rb +143 -0
- data/lib/larynx/form.rb +19 -0
- data/lib/larynx/logger.rb +8 -0
- data/lib/larynx/observable.rb +35 -0
- data/lib/larynx/prompt.rb +88 -0
- data/lib/larynx/response.rb +57 -0
- data/lib/larynx/restartable_timer.rb +26 -0
- data/lib/larynx/session.rb +20 -0
- data/lib/larynx/version.rb +3 -0
- data/lib/larynx.rb +109 -0
- data/spec/fixtures/answer.rb +125 -0
- data/spec/fixtures/channel_data.rb +147 -0
- data/spec/fixtures/dtmf.rb +52 -0
- data/spec/fixtures/execute.rb +52 -0
- data/spec/fixtures/execute_complete.rb +133 -0
- data/spec/fixtures/reply_ok.rb +6 -0
- data/spec/larynx/call_handler_spec.rb +290 -0
- data/spec/larynx/command_spec.rb +76 -0
- data/spec/larynx/eventmachince_spec.rb +14 -0
- data/spec/larynx/fields_spec.rb +194 -0
- data/spec/larynx/prompt_spec.rb +222 -0
- data/spec/larynx_spec.rb +4 -0
- data/spec/spec_helper.rb +47 -0
- metadata +96 -0
@@ -0,0 +1,73 @@
|
|
1
|
+
module Larynx
|
2
|
+
class Command
|
3
|
+
include Callbacks
|
4
|
+
attr_reader :command
|
5
|
+
|
6
|
+
define_callback :before, :after
|
7
|
+
|
8
|
+
def initialize(command, params=nil, &block)
|
9
|
+
@command, @params, @callbacks = command, params, {}
|
10
|
+
after(&block) if block_given?
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
@command
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
@command
|
19
|
+
end
|
20
|
+
|
21
|
+
def interruptable?
|
22
|
+
false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class CallCommand < Command
|
27
|
+
def name
|
28
|
+
"#{@command}#{" #{@params}" if @params}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
cmd = "#{@command}"
|
33
|
+
cmd << " #{@params}" if @params
|
34
|
+
cmd << "\n\n"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class ApiCommand < Command
|
39
|
+
def name
|
40
|
+
"#{@command}#{" #{@params}" if @params}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
cmd = "api #{@command}"
|
45
|
+
cmd << " #{@params}" if @params
|
46
|
+
cmd << "\n\n"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class AppCommand < Command
|
51
|
+
def initialize(command, params=nil, options={}, &block)
|
52
|
+
super command, params, &block
|
53
|
+
@options = options.reverse_merge(:bargein => true)
|
54
|
+
end
|
55
|
+
|
56
|
+
def name
|
57
|
+
"#{@command}#{" '#{@params}'" if @params}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def to_s
|
61
|
+
cmd = "sendmsg\n"
|
62
|
+
cmd << "call-command: execute\n"
|
63
|
+
cmd << "execute-app-name: #{@command}\n"
|
64
|
+
cmd << "execute-app-arg: #{@params}\n" if @params
|
65
|
+
cmd << "event-lock: #{@options[:lock]}\n" if @options[:lock]
|
66
|
+
cmd << "\n"
|
67
|
+
end
|
68
|
+
|
69
|
+
def interruptable?
|
70
|
+
@options[:bargein]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Larynx
|
2
|
+
module Commands
|
3
|
+
|
4
|
+
def connect(&block)
|
5
|
+
execute CallCommand.new('connect', &block)
|
6
|
+
end
|
7
|
+
|
8
|
+
def myevents(&block)
|
9
|
+
execute CallCommand.new('myevents', &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
def filter(type, &block)
|
13
|
+
execute CallCommand.new('filter', type, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def linger(&block)
|
17
|
+
execute CallCommand.new('linger', &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def answer(&block)
|
21
|
+
execute AppCommand.new('answer', &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def hangup(&block)
|
25
|
+
execute AppCommand.new('hangup', &block)
|
26
|
+
end
|
27
|
+
|
28
|
+
def playback(data, options={}, &block)
|
29
|
+
execute AppCommand.new('playback', data, options, &block)
|
30
|
+
end
|
31
|
+
alias_method :play, :playback
|
32
|
+
|
33
|
+
def speak(data, options={}, &block)
|
34
|
+
execute AppCommand.new('speak', data, options, &block)
|
35
|
+
end
|
36
|
+
|
37
|
+
def phrase(data, options={}, &block)
|
38
|
+
execute AppCommand.new('phrase', data, options, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# Executes read command with some default values.
|
43
|
+
# Allows length option which expands into minimum and maximum length values. Length can be a range.
|
44
|
+
# Passes user input into callback block.
|
45
|
+
#
|
46
|
+
# Defaults:
|
47
|
+
# timeout: 5000 or 5 seconds
|
48
|
+
# termchar: #
|
49
|
+
#
|
50
|
+
# Example:
|
51
|
+
#
|
52
|
+
# read(:minimum => 1, :maximum => 2, :sound_file => 'en/us/callie/conference/8000/conf-pin.wav') {|input|
|
53
|
+
# speak "You entered #{input}"
|
54
|
+
# }
|
55
|
+
#
|
56
|
+
# Or
|
57
|
+
#
|
58
|
+
# read(:length => 1..2, :sound_file => 'en/us/callie/conference/8000/conf-pin.wav') {|input|
|
59
|
+
# speak "You entered #{input}"
|
60
|
+
# }
|
61
|
+
#
|
62
|
+
def read(options={}, &block)
|
63
|
+
options.reverse_merge!(:timeout => 5000, :var_name => 'read_result', :termchar => '#')
|
64
|
+
options[:bargein] = false
|
65
|
+
|
66
|
+
if length = options.delete(:length)
|
67
|
+
values = length.is_a?(Range) ? [length.first, length.last] : [length, length]
|
68
|
+
options.merge!(:minimum => values[0], :maximum => values[1])
|
69
|
+
end
|
70
|
+
|
71
|
+
order = [:minimum, :maximum, :sound_file, :var_name, :timeout, :termchar]
|
72
|
+
data = order.inject('') {|data, key| data += " #{options[key]}"; data }.strip
|
73
|
+
|
74
|
+
execute AppCommand.new('read', data, options).after {
|
75
|
+
block.call(response.body[:variable_read_result])
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
def prompt(options={}, &block)
|
80
|
+
execute Prompt.new(self, options, &block).command
|
81
|
+
end
|
82
|
+
|
83
|
+
def break!
|
84
|
+
execute AppCommand.new('break'), true
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Larynx
|
2
|
+
class NoPromptDefined < StandardError; end
|
3
|
+
|
4
|
+
module Fields
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend ClassMethods
|
8
|
+
base.send :include, InstanceMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
attr_accessor :fields
|
13
|
+
|
14
|
+
def field(name, options={}, &block)
|
15
|
+
@fields ||= []
|
16
|
+
@fields << Field.new(name, options, &block)
|
17
|
+
attr_accessor name
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
module InstanceMethods
|
23
|
+
|
24
|
+
def next_field(field_name=nil)
|
25
|
+
@current_field ||= 0
|
26
|
+
@current_field = index_of_field(field_name) if field_name
|
27
|
+
if field = self.class.fields[@current_field]
|
28
|
+
field.run(self)
|
29
|
+
@current_field += 1
|
30
|
+
field
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def index_of_field(name)
|
35
|
+
field = self.class.fields.find {|f| f.name == name }
|
36
|
+
self.class.fields.index(field)
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
class Field
|
42
|
+
include Callbacks
|
43
|
+
|
44
|
+
attr_reader :name
|
45
|
+
define_callback :setup, :validate, :invalid, :success, :failure
|
46
|
+
|
47
|
+
def initialize(name, options, &block)
|
48
|
+
@name, @callbacks = name, {}
|
49
|
+
@options = options.reverse_merge(:attempts => 3)
|
50
|
+
@prompt_queue = []
|
51
|
+
|
52
|
+
instance_eval(&block)
|
53
|
+
raise(Larynx::NoPromptDefined, 'A field requires a prompt to be defined') if @prompt_queue.empty?
|
54
|
+
end
|
55
|
+
|
56
|
+
def prompt(options)
|
57
|
+
add_prompt(options)
|
58
|
+
end
|
59
|
+
|
60
|
+
def reprompt(options)
|
61
|
+
raise 'A reprompt can only be used after a prompt' if @prompt_queue.empty?
|
62
|
+
add_prompt(options)
|
63
|
+
end
|
64
|
+
|
65
|
+
def add_prompt(options)
|
66
|
+
repeats = options.delete(:repeats) || 1
|
67
|
+
options.merge!(@options.slice(:length, :min_length, :max_length))
|
68
|
+
@prompt_queue += ([options] * repeats)
|
69
|
+
end
|
70
|
+
|
71
|
+
def current_prompt
|
72
|
+
options = (@prompt_queue[@attempt-1] || @prompt_queue.last).dup
|
73
|
+
method = ([:play, :speak, :phrase] & options.keys).first
|
74
|
+
message = options[method].is_a?(Symbol) ? @app.send(options[method]) : options[method]
|
75
|
+
options[method] = message
|
76
|
+
|
77
|
+
Prompt.new(call, options) {|input|
|
78
|
+
set_instance_variables(input)
|
79
|
+
evaluate_input
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def execute_prompt
|
84
|
+
call.execute current_prompt.command
|
85
|
+
end
|
86
|
+
|
87
|
+
def increment_attempts
|
88
|
+
@attempt += 1
|
89
|
+
end
|
90
|
+
|
91
|
+
def fire_callback(callback)
|
92
|
+
if block = @callbacks[callback]
|
93
|
+
@app.instance_eval(&block)
|
94
|
+
else
|
95
|
+
true
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def valid?
|
100
|
+
@value.size >= minimum_length && fire_callback(:validate)
|
101
|
+
end
|
102
|
+
|
103
|
+
def evaluate_input
|
104
|
+
if valid?
|
105
|
+
fire_callback(:success)
|
106
|
+
else
|
107
|
+
fire_callback(:invalid)
|
108
|
+
if @attempt < @options[:attempts]
|
109
|
+
increment_attempts
|
110
|
+
execute_prompt
|
111
|
+
else
|
112
|
+
fire_callback(:failure)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def set_instance_variables(input)
|
118
|
+
@value = input
|
119
|
+
@app.send("#{@name}=", input)
|
120
|
+
end
|
121
|
+
|
122
|
+
def maximum_length
|
123
|
+
@options[:max_length] || @options[:length]
|
124
|
+
end
|
125
|
+
|
126
|
+
def minimum_length
|
127
|
+
@options[:min_length] || @options[:length] || 1
|
128
|
+
end
|
129
|
+
|
130
|
+
def run(app)
|
131
|
+
@app = app
|
132
|
+
@attempt = 1
|
133
|
+
fire_callback(:setup)
|
134
|
+
execute_prompt
|
135
|
+
end
|
136
|
+
|
137
|
+
def call
|
138
|
+
@app.call
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
data/lib/larynx/form.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Larynx
|
2
|
+
class Form < Application
|
3
|
+
include Fields
|
4
|
+
|
5
|
+
def self.setup(&block)
|
6
|
+
@@setup = block
|
7
|
+
end
|
8
|
+
|
9
|
+
def run
|
10
|
+
instance_eval &@@setup if @@setup
|
11
|
+
next_field
|
12
|
+
end
|
13
|
+
|
14
|
+
def restart_form
|
15
|
+
@current_field = 0
|
16
|
+
run
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Larynx
|
2
|
+
module Observable
|
3
|
+
|
4
|
+
def add_observer(object)
|
5
|
+
@observers ||= []
|
6
|
+
@observers << object
|
7
|
+
end
|
8
|
+
|
9
|
+
def remove_observer(object)
|
10
|
+
@observers && @observers.delete(object)
|
11
|
+
end
|
12
|
+
|
13
|
+
def clear_observers!
|
14
|
+
@observers = []
|
15
|
+
end
|
16
|
+
|
17
|
+
# Like an observer stack which only notifies top observer
|
18
|
+
def notify_current_observer(event, data=nil)
|
19
|
+
return unless @observers
|
20
|
+
obs = @observers.last
|
21
|
+
if obs.respond_to?(event)
|
22
|
+
data ? obs.send(event, data) : obs.send(event)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def notify_observers(event, data=nil)
|
27
|
+
return unless @observers
|
28
|
+
@observers.each do |obs|
|
29
|
+
next unless obs.respond_to?(event)
|
30
|
+
data ? obs.send(event, data) : obs.send(event)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Larynx
|
2
|
+
class NoPromptCommandValue < StandardError; end
|
3
|
+
|
4
|
+
# The prompt class neatly wraps up a convention where you prompt for input of
|
5
|
+
# certain length. The prompt waits until the required input length is reached,
|
6
|
+
# the user presses the terminator button or the time runs out. Think of the
|
7
|
+
# play_and_get_digits command except it works for speak as well. It also
|
8
|
+
# provides a bargein option to allow or prevent the user from interrupting
|
9
|
+
# the speech or playback.
|
10
|
+
#
|
11
|
+
# Pass a block to the method as a callback which receives input as an argument.
|
12
|
+
#
|
13
|
+
class Prompt
|
14
|
+
attr_reader :call
|
15
|
+
|
16
|
+
def initialize(call, options, &block)
|
17
|
+
@call, @options, @block = call, options, block
|
18
|
+
@options.reverse_merge!(:bargein => true, :timeout => 10, :interdigit_timeout => 3, :termchar => '#')
|
19
|
+
raise NoPromptCommandValue, "No output command value supplied. Use one of playback, speak or phrase keys." if command_name.blank?
|
20
|
+
end
|
21
|
+
|
22
|
+
def command
|
23
|
+
@command ||= AppCommand.new(command_name, message, :bargein => @options[:bargein]).
|
24
|
+
before { call.clear_input }.
|
25
|
+
after {
|
26
|
+
if prompt_finished?
|
27
|
+
finalise
|
28
|
+
else
|
29
|
+
call.add_observer self
|
30
|
+
add_digit_timer
|
31
|
+
add_input_timer
|
32
|
+
end
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def input
|
37
|
+
(call.input.last == termchar ? call.input[0..-2] : call.input).join
|
38
|
+
end
|
39
|
+
|
40
|
+
def prompt_finished?
|
41
|
+
call.input.last == termchar || call.input.size == maximum_length
|
42
|
+
end
|
43
|
+
|
44
|
+
def termchar
|
45
|
+
@options[:termchar]
|
46
|
+
end
|
47
|
+
|
48
|
+
def maximum_length
|
49
|
+
@options[:max_length] || @options[:length]
|
50
|
+
end
|
51
|
+
|
52
|
+
def command_name
|
53
|
+
([:play, :speak, :phrase] & @options.keys).first.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
def message
|
57
|
+
@options[command_name.to_sym]
|
58
|
+
end
|
59
|
+
|
60
|
+
def finalise
|
61
|
+
call.remove_observer self
|
62
|
+
@block.call(input)
|
63
|
+
end
|
64
|
+
|
65
|
+
def dtmf_received(digit)
|
66
|
+
if prompt_finished?
|
67
|
+
call.stop_timer(:input)
|
68
|
+
call.cancel_timer(:digit)
|
69
|
+
else
|
70
|
+
call.restart_timer(:digit)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_digit_timer
|
75
|
+
call.timer(:digit, @options[:interdigit_timeout]) {
|
76
|
+
call.cancel_timer :input
|
77
|
+
finalise
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_input_timer
|
82
|
+
call.timer(:input, @options[:timeout]) {
|
83
|
+
call.cancel_timer :digit
|
84
|
+
finalise
|
85
|
+
}
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module Larynx
|
2
|
+
class Response
|
3
|
+
attr_reader :header, :body
|
4
|
+
|
5
|
+
def initialize(header, body)
|
6
|
+
@header = CallHandler.headers_2_hash(header)
|
7
|
+
if body
|
8
|
+
@body = body.match(/:/) ? CallHandler.headers_2_hash(body) : body
|
9
|
+
@body.each {|k,v| v.chomp! if v.is_a?(String)}
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def reply?
|
14
|
+
@header[:content_type] == 'command/reply'
|
15
|
+
end
|
16
|
+
|
17
|
+
def event?
|
18
|
+
@header[:content_type] == 'text/event-plain'
|
19
|
+
end
|
20
|
+
|
21
|
+
def ok?
|
22
|
+
@header[:reply_text] =~ /\+OK/
|
23
|
+
end
|
24
|
+
|
25
|
+
def error?
|
26
|
+
@header[:reply_text] =~ /ERR/
|
27
|
+
end
|
28
|
+
|
29
|
+
def executing?
|
30
|
+
event_name == 'CHANNEL_EXECUTE'
|
31
|
+
end
|
32
|
+
|
33
|
+
def executed?
|
34
|
+
event_name == 'CHANNEL_EXECUTE_COMPLETE'
|
35
|
+
end
|
36
|
+
|
37
|
+
def command_name
|
38
|
+
@body[:application] if @body
|
39
|
+
end
|
40
|
+
|
41
|
+
def event_name
|
42
|
+
@body[:event_name] if @body
|
43
|
+
end
|
44
|
+
|
45
|
+
def dtmf?
|
46
|
+
@body[:event_name] == 'DTMF' if @body
|
47
|
+
end
|
48
|
+
|
49
|
+
def speech?
|
50
|
+
@body[:event_name] == 'DETECTED_SPEECH' if @body
|
51
|
+
end
|
52
|
+
|
53
|
+
def disconnect?
|
54
|
+
@header[:content_type] == 'text/disconnect-notice'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Larynx
|
2
|
+
# Adds restart to EM timer class. Implementation influenced by EM::PeriodicTimer class
|
3
|
+
# so hopefully it should not cause any issues.
|
4
|
+
class RestartableTimer < EM::Timer
|
5
|
+
def initialize(interval, callback=nil, &block)
|
6
|
+
@interval = interval
|
7
|
+
@code = callback || block
|
8
|
+
schedule
|
9
|
+
end
|
10
|
+
|
11
|
+
# Restart the timer
|
12
|
+
def restart
|
13
|
+
cancel
|
14
|
+
schedule
|
15
|
+
end
|
16
|
+
|
17
|
+
def schedule
|
18
|
+
@signature = EM::add_timer(@interval, method(:fire))
|
19
|
+
end
|
20
|
+
|
21
|
+
def fire
|
22
|
+
@code.call
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Larynx
|
2
|
+
class Session
|
3
|
+
attr_reader :variables
|
4
|
+
|
5
|
+
def initialize(data)
|
6
|
+
@variables = data
|
7
|
+
end
|
8
|
+
|
9
|
+
def method_missing(method, *args, &block)
|
10
|
+
if @variables.has_key?(method.to_sym)
|
11
|
+
@variables[method.to_sym]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](key)
|
16
|
+
@variables[key]
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
data/lib/larynx.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'active_support'
|
4
|
+
require 'logger'
|
5
|
+
require 'daemons/daemonize'
|
6
|
+
|
7
|
+
require 'larynx/version'
|
8
|
+
require 'larynx/logger'
|
9
|
+
require 'larynx/observable'
|
10
|
+
require 'larynx/callbacks'
|
11
|
+
require 'larynx/session'
|
12
|
+
require 'larynx/response'
|
13
|
+
require 'larynx/command'
|
14
|
+
require 'larynx/commands'
|
15
|
+
require 'larynx/prompt'
|
16
|
+
require 'larynx/application'
|
17
|
+
require 'larynx/fields'
|
18
|
+
require 'larynx/form'
|
19
|
+
require 'larynx/restartable_timer'
|
20
|
+
require 'larynx/call_handler'
|
21
|
+
|
22
|
+
module Larynx
|
23
|
+
class << self
|
24
|
+
include Callbacks
|
25
|
+
|
26
|
+
define_callback :connect, :answer, :hungup
|
27
|
+
|
28
|
+
def parse_options(args=ARGV)
|
29
|
+
@options = {
|
30
|
+
:ip => "0.0.0.0",
|
31
|
+
:port => 8084,
|
32
|
+
:pid_file => './larynx.pid',
|
33
|
+
:log_file => './larynx.log'
|
34
|
+
}
|
35
|
+
opts = OptionParser.new
|
36
|
+
opts.banner = "Usage: larynx [options]"
|
37
|
+
opts.separator ''
|
38
|
+
opts.separator "Larynx is a tool to develop FreeSWITCH IVR applications in Ruby."
|
39
|
+
opts.on('-i', '--ip IP', 'Listen for connections on this IP') {|ip| @options[:ip] = ip }
|
40
|
+
opts.on('-p', '--port PORT', 'Listen on this port', Integer) {|port| @options[:port] = port }
|
41
|
+
opts.on('-d', '--daemonize', 'Run as daemon') { @options[:daemonize] = true }
|
42
|
+
opts.on('-l', '--log-file FILE', 'Defaults to /app/root/larynx.log') {|log| @options[:log_file] = log }
|
43
|
+
opts.on( '--pid-file FILE', 'Defaults to /app/root/larynx.pid') {|pid| @options[:pid_file] = pid }
|
44
|
+
opts.on('-h', '--help', 'This is it') { $stderr.puts opts; exit 0 }
|
45
|
+
opts.on('-v', '--version') { $stderr.puts "Larynx version #{Larynx::VERSION}"; exit 0 }
|
46
|
+
opts.parse!(args)
|
47
|
+
end
|
48
|
+
|
49
|
+
def setup_logger
|
50
|
+
logger = Larynx::Logger.new(@options[:log_file])
|
51
|
+
logger.level = Logger::INFO
|
52
|
+
Object.const_set "LARYNX_LOGGER", logger
|
53
|
+
end
|
54
|
+
|
55
|
+
def graceful_exit
|
56
|
+
LARYNX_LOGGER.info "Shutting down Larynx"
|
57
|
+
EM.stop_server @em_signature
|
58
|
+
@em_signature = nil
|
59
|
+
remove_pid_file if @options[:daemonize]
|
60
|
+
exit 130
|
61
|
+
end
|
62
|
+
|
63
|
+
def daemonize
|
64
|
+
Daemonize.daemonize
|
65
|
+
Dir.chdir LARYNX_ROOT
|
66
|
+
File.open(@options[:pid_file], 'w+') {|f| f.write("#{Process.pid}\n") }
|
67
|
+
end
|
68
|
+
|
69
|
+
def remove_pid_file
|
70
|
+
File.delete @options[:pid_file]
|
71
|
+
end
|
72
|
+
|
73
|
+
def trap_signals
|
74
|
+
trap('TERM') { graceful_exit }
|
75
|
+
trap('INT') { graceful_exit }
|
76
|
+
end
|
77
|
+
|
78
|
+
def setup_app
|
79
|
+
if ARGV[0].nil?
|
80
|
+
$stderr.puts "You must specify an application file"
|
81
|
+
exit -1
|
82
|
+
end
|
83
|
+
Object.const_set "LARYNX_ROOT", File.expand_path(File.dirname(ARGV[0]))
|
84
|
+
require File.expand_path(ARGV[0])
|
85
|
+
end
|
86
|
+
|
87
|
+
def start_server
|
88
|
+
LARYNX_LOGGER.info "Larynx starting up on #{@options[:ip]}:#{@options[:port]}"
|
89
|
+
EM::run {
|
90
|
+
@em_signature = EM::start_server @options[:ip], @options[:port], Larynx::CallHandler
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def run
|
95
|
+
parse_options(ARGV)
|
96
|
+
setup_app
|
97
|
+
daemonize if @options[:daemonize]
|
98
|
+
setup_logger
|
99
|
+
trap_signals
|
100
|
+
start_server
|
101
|
+
end
|
102
|
+
|
103
|
+
def running?
|
104
|
+
!@em_signature.nil?
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
Larynx.run unless defined?(TEST)
|