ruby_ami 1.3.4 → 2.0.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.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/CHANGELOG.md +5 -2
- data/README.md +14 -15
- data/features/support/lexer_helper.rb +18 -0
- data/lib/ruby_ami/action.rb +19 -65
- data/lib/ruby_ami/agi_result_parser.rb +2 -2
- data/lib/ruby_ami/client.rb +28 -159
- data/lib/ruby_ami/error.rb +2 -2
- data/lib/ruby_ami/event.rb +2 -2
- data/lib/ruby_ami/response.rb +6 -8
- data/lib/ruby_ami/stream.rb +76 -11
- data/lib/ruby_ami/version.rb +1 -1
- data/lib/ruby_ami.rb +0 -9
- data/ruby_ami.gemspec +0 -4
- data/spec/ruby_ami/action_spec.rb +27 -78
- data/spec/ruby_ami/agi_result_parser_spec.rb +0 -9
- data/spec/ruby_ami/client_spec.rb +17 -287
- data/spec/ruby_ami/event_spec.rb +24 -30
- data/spec/ruby_ami/response_spec.rb +12 -20
- data/spec/ruby_ami/stream_spec.rb +142 -49
- data/spec/spec_helper.rb +2 -3
- data/spec/support/mock_server.rb +0 -4
- metadata +2 -59
- data/lib/ruby_ami/metaprogramming.rb +0 -18
data/lib/ruby_ami/stream.rb
CHANGED
@@ -18,11 +18,13 @@ module RubyAMI
|
|
18
18
|
|
19
19
|
finalizer :finalize
|
20
20
|
|
21
|
-
def initialize(host, port, event_callback, logger = Logger, timeout = 0)
|
21
|
+
def initialize(host, port, username, password, event_callback, logger = Logger, timeout = 0)
|
22
22
|
super()
|
23
|
-
@host, @port, @event_callback, @logger, @timeout = host, port, event_callback, logger, timeout
|
23
|
+
@host, @port, @username, @password, @event_callback, @logger, @timeout = host, port, username, password, event_callback, logger, timeout
|
24
24
|
logger.debug "Starting up..."
|
25
25
|
@lexer = Lexer.new self
|
26
|
+
@sent_actions = {}
|
27
|
+
@causal_actions = {}
|
26
28
|
end
|
27
29
|
|
28
30
|
[:started, :stopped, :ready].each do |state|
|
@@ -37,27 +39,33 @@ module RubyAMI
|
|
37
39
|
loop { receive_data @socket.readpartial(4096) }
|
38
40
|
rescue Errno::ECONNREFUSED, SocketError => e
|
39
41
|
logger.error "Connection failed due to #{e.class}. Check your config and the server."
|
40
|
-
current_actor.terminate!
|
41
42
|
rescue EOFError
|
42
43
|
logger.info "Client socket closed!"
|
43
|
-
current_actor.terminate!
|
44
44
|
rescue Timeout::Error
|
45
45
|
logger.error "Timeout exceeded while trying to connect."
|
46
|
-
|
46
|
+
ensure
|
47
|
+
async.terminate
|
47
48
|
end
|
48
49
|
|
49
50
|
def post_init
|
50
51
|
@state = :started
|
51
|
-
|
52
|
+
fire_event Connected.new
|
53
|
+
login @username, @password if @username && @password
|
52
54
|
end
|
53
55
|
|
54
56
|
def send_data(data)
|
55
57
|
@socket.write data
|
56
58
|
end
|
57
59
|
|
58
|
-
def send_action(
|
59
|
-
|
60
|
-
|
60
|
+
def send_action(name, headers = {})
|
61
|
+
condition = Celluloid::Condition.new
|
62
|
+
action = dispatch_action name, headers do |response|
|
63
|
+
condition.signal response
|
64
|
+
end
|
65
|
+
condition.wait
|
66
|
+
action.response.tap do |resp|
|
67
|
+
abort resp if resp.is_a? Exception
|
68
|
+
end
|
61
69
|
end
|
62
70
|
|
63
71
|
def receive_data(data)
|
@@ -67,7 +75,20 @@ module RubyAMI
|
|
67
75
|
|
68
76
|
def message_received(message)
|
69
77
|
logger.trace "[RECV] #{message.inspect}"
|
70
|
-
|
78
|
+
case message
|
79
|
+
when Event
|
80
|
+
action = causal_action_for_event message
|
81
|
+
if action
|
82
|
+
action << message
|
83
|
+
complete_causal_action_for_event message if action.complete?
|
84
|
+
else
|
85
|
+
fire_event message
|
86
|
+
end
|
87
|
+
when Response, Error
|
88
|
+
action = sent_action_for_response message
|
89
|
+
raise StandardError, "Received an AMI response with an unrecognized ActionID! #{message.inspect}" unless action
|
90
|
+
action << message
|
91
|
+
end
|
71
92
|
end
|
72
93
|
|
73
94
|
def syntax_error_encountered(ignored_chunk)
|
@@ -78,11 +99,55 @@ module RubyAMI
|
|
78
99
|
|
79
100
|
private
|
80
101
|
|
102
|
+
def login(username, password, event_mask = 'On')
|
103
|
+
dispatch_action 'Login',
|
104
|
+
'Username' => username,
|
105
|
+
'Secret' => password,
|
106
|
+
'Events' => event_mask
|
107
|
+
end
|
108
|
+
|
109
|
+
def dispatch_action(*args, &block)
|
110
|
+
action = Action.new *args, &block
|
111
|
+
logger.trace "[SEND] #{action.to_s}"
|
112
|
+
register_sent_action action
|
113
|
+
send_data action.to_s
|
114
|
+
action
|
115
|
+
end
|
116
|
+
|
117
|
+
def fire_event(event)
|
118
|
+
@event_callback.call event
|
119
|
+
end
|
120
|
+
|
121
|
+
def register_sent_action(action)
|
122
|
+
@sent_actions[action.action_id] = action
|
123
|
+
register_causal_action action if action.has_causal_events?
|
124
|
+
end
|
125
|
+
|
126
|
+
def sent_action_with_id(action_id)
|
127
|
+
@sent_actions.delete action_id
|
128
|
+
end
|
129
|
+
|
130
|
+
def sent_action_for_response(response)
|
131
|
+
sent_action_with_id response.action_id
|
132
|
+
end
|
133
|
+
|
134
|
+
def register_causal_action(action)
|
135
|
+
@causal_actions[action.action_id] = action
|
136
|
+
end
|
137
|
+
|
138
|
+
def causal_action_for_event(event)
|
139
|
+
@causal_actions[event.action_id]
|
140
|
+
end
|
141
|
+
|
142
|
+
def complete_causal_action_for_event(event)
|
143
|
+
@causal_actions.delete event.action_id
|
144
|
+
end
|
145
|
+
|
81
146
|
def finalize
|
82
147
|
logger.debug "Finalizing stream"
|
83
148
|
@socket.close if @socket
|
84
149
|
@state = :stopped
|
85
|
-
|
150
|
+
fire_event Disconnected.new
|
86
151
|
end
|
87
152
|
end
|
88
153
|
end
|
data/lib/ruby_ami/version.rb
CHANGED
data/lib/ruby_ami.rb
CHANGED
@@ -1,8 +1,4 @@
|
|
1
1
|
%w{
|
2
|
-
future-resource
|
3
|
-
logger
|
4
|
-
girl_friday
|
5
|
-
countdownlatch
|
6
2
|
celluloid/io
|
7
3
|
}.each { |f| require f }
|
8
4
|
|
@@ -14,10 +10,6 @@ module RubyAMI
|
|
14
10
|
def self.new_uuid
|
15
11
|
SecureRandom.uuid
|
16
12
|
end
|
17
|
-
|
18
|
-
def self.rbx?
|
19
|
-
RbConfig::CONFIG['RUBY_INSTALL_NAME'] == 'rbx'
|
20
|
-
end
|
21
13
|
end
|
22
14
|
|
23
15
|
%w{
|
@@ -29,7 +21,6 @@ end
|
|
29
21
|
error
|
30
22
|
event
|
31
23
|
lexer
|
32
|
-
metaprogramming
|
33
24
|
response
|
34
25
|
stream
|
35
26
|
version
|
data/ruby_ami.gemspec
CHANGED
@@ -19,16 +19,12 @@ Gem::Specification.new do |s|
|
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
21
|
s.add_runtime_dependency %q<celluloid-io>, ["~> 0.13"]
|
22
|
-
s.add_runtime_dependency %q<future-resource>, [">= 0"]
|
23
|
-
s.add_runtime_dependency %q<girl_friday>, [">= 0"]
|
24
|
-
s.add_runtime_dependency %q<countdownlatch>, ["~> 1.0"]
|
25
22
|
|
26
23
|
s.add_development_dependency %q<bundler>, ["~> 1.0"]
|
27
24
|
s.add_development_dependency %q<rspec>, ["~> 2.5"]
|
28
25
|
s.add_development_dependency %q<cucumber>, [">= 0"]
|
29
26
|
s.add_development_dependency %q<yard>, ["~> 0.6"]
|
30
27
|
s.add_development_dependency %q<rake>, [">= 0"]
|
31
|
-
s.add_development_dependency %q<mocha>, [">= 0"]
|
32
28
|
s.add_development_dependency %q<guard-rspec>
|
33
29
|
s.add_development_dependency %q<guard-shell>
|
34
30
|
s.add_development_dependency %q<ruby_gntp>
|
@@ -7,34 +7,18 @@ module RubyAMI
|
|
7
7
|
let(:headers) { {'foo' => 'bar'} }
|
8
8
|
|
9
9
|
subject do
|
10
|
-
|
11
|
-
@
|
10
|
+
described_class.new name, headers do |response|
|
11
|
+
@callback_result = response
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
it {
|
15
|
+
it { should_not be_complete }
|
16
16
|
|
17
17
|
describe "SIPPeers actions" do
|
18
18
|
subject { Action.new('SIPPeers') }
|
19
19
|
its(:has_causal_events?) { should be true }
|
20
20
|
end
|
21
21
|
|
22
|
-
describe "Queues actions" do
|
23
|
-
subject { Action.new('Queues') }
|
24
|
-
its(:replies_with_action_id?) { should == false }
|
25
|
-
end
|
26
|
-
|
27
|
-
describe "IAXPeers actions" do
|
28
|
-
before { pending }
|
29
|
-
# FIXME: This test relies on the side effect that earlier tests have run
|
30
|
-
# and initialized the UnsupportedActionName::UNSUPPORTED_ACTION_NAMES
|
31
|
-
# constant for an "unknown" version of Asterisk. This should be fixed
|
32
|
-
# to be more specific about which version of Asterisk is under test.
|
33
|
-
# IAXPeers is supported (with Action IDs!) since Asterisk 1.8
|
34
|
-
subject { Action.new('IAXPeers') }
|
35
|
-
its(:replies_with_action_id?) { should == false }
|
36
|
-
end
|
37
|
-
|
38
22
|
describe "the ParkedCalls terminator event" do
|
39
23
|
subject { Action.new('ParkedCalls') }
|
40
24
|
its(:causal_event_terminator_name) { should == "parkedcallscomplete" }
|
@@ -52,34 +36,34 @@ module RubyAMI
|
|
52
36
|
Action.new("ParkedCalls").to_s.should =~ /^Action: ParkedCalls\r\nActionID: [\w-]+\r\n\r\n$/i
|
53
37
|
end
|
54
38
|
|
55
|
-
it 'should be able to be marked as sent' do
|
56
|
-
subject.state = :sent
|
57
|
-
subject.should be_sent
|
58
|
-
end
|
59
|
-
|
60
|
-
it 'should be able to be marked as complete' do
|
61
|
-
subject.state = :complete
|
62
|
-
subject.should be_complete
|
63
|
-
end
|
64
|
-
|
65
39
|
describe '#<<' do
|
66
40
|
describe 'for a non-causal action' do
|
67
41
|
context 'with a response' do
|
68
42
|
let(:response) { Response.new }
|
69
43
|
|
44
|
+
before { subject << response }
|
45
|
+
|
70
46
|
it 'should set the response' do
|
71
|
-
subject << response
|
72
47
|
subject.response.should be response
|
73
48
|
end
|
49
|
+
|
50
|
+
it 'should call the callback' do
|
51
|
+
@callback_result.should be response
|
52
|
+
end
|
53
|
+
|
54
|
+
it { should be_complete }
|
74
55
|
end
|
75
56
|
|
76
57
|
context 'with an error' do
|
77
58
|
let(:error) { Error.new.tap { |e| e.message = 'AMI error' } }
|
78
59
|
|
79
|
-
|
80
|
-
|
81
|
-
|
60
|
+
before { subject << error }
|
61
|
+
|
62
|
+
it 'should set the response' do
|
63
|
+
subject.response.should == error
|
82
64
|
end
|
65
|
+
|
66
|
+
it { should be_complete }
|
83
67
|
end
|
84
68
|
|
85
69
|
context 'with an event' do
|
@@ -91,11 +75,10 @@ module RubyAMI
|
|
91
75
|
|
92
76
|
describe 'for a causal action' do
|
93
77
|
let(:name) { 'Status' }
|
78
|
+
let(:response) { Response.new }
|
94
79
|
|
95
80
|
context 'with a response' do
|
96
|
-
|
97
|
-
|
98
|
-
before { subject << message }
|
81
|
+
before { subject << response }
|
99
82
|
|
100
83
|
it { should_not be_complete }
|
101
84
|
end
|
@@ -103,14 +86,15 @@ module RubyAMI
|
|
103
86
|
context 'with an event' do
|
104
87
|
let(:event) { Event.new 'foo' }
|
105
88
|
|
106
|
-
before { subject << event }
|
89
|
+
before { subject << response << event }
|
107
90
|
|
108
|
-
|
91
|
+
it "should add the events to the response" do
|
92
|
+
subject.response.events.should == [event]
|
93
|
+
end
|
109
94
|
end
|
110
95
|
|
111
96
|
context 'with a terminating event' do
|
112
|
-
let(:
|
113
|
-
let(:event) { Event.new 'StatusComplete' }
|
97
|
+
let(:event) { Event.new 'StatusComplete' }
|
114
98
|
|
115
99
|
before do
|
116
100
|
subject << response
|
@@ -118,7 +102,9 @@ module RubyAMI
|
|
118
102
|
subject << event
|
119
103
|
end
|
120
104
|
|
121
|
-
|
105
|
+
it "should add the events to the response" do
|
106
|
+
subject.response.events.should == [event]
|
107
|
+
end
|
122
108
|
|
123
109
|
it { should be_complete }
|
124
110
|
|
@@ -127,19 +113,6 @@ module RubyAMI
|
|
127
113
|
end
|
128
114
|
end
|
129
115
|
|
130
|
-
describe 'setting the response' do
|
131
|
-
let(:response) { :bar }
|
132
|
-
|
133
|
-
before { subject.response = response }
|
134
|
-
|
135
|
-
it { should be_complete }
|
136
|
-
its(:response) { should == response }
|
137
|
-
|
138
|
-
it 'should call the response callback with the response' do
|
139
|
-
@foo.should == response
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
116
|
describe 'comparison' do
|
144
117
|
describe 'with another Action' do
|
145
118
|
context 'with identical name and headers' do
|
@@ -160,29 +133,5 @@ module RubyAMI
|
|
160
133
|
|
161
134
|
it { should_not == :foo }
|
162
135
|
end
|
163
|
-
|
164
|
-
describe "#sync_timeout" do
|
165
|
-
it "should be 10 seconds" do
|
166
|
-
subject.sync_timeout.should be == 10
|
167
|
-
end
|
168
|
-
|
169
|
-
context "for an asynchronous Originate" do
|
170
|
-
let(:name) { 'Originate' }
|
171
|
-
let(:headers) { {:async => true} }
|
172
|
-
|
173
|
-
it "should be 60 seconds" do
|
174
|
-
subject.sync_timeout.should be == 10
|
175
|
-
end
|
176
|
-
end
|
177
|
-
|
178
|
-
context "for a synchronous Originate" do
|
179
|
-
let(:name) { 'Originate' }
|
180
|
-
let(:headers) { {:async => false} }
|
181
|
-
|
182
|
-
it "should be 60 seconds" do
|
183
|
-
subject.sync_timeout.should be == 60
|
184
|
-
end
|
185
|
-
end
|
186
|
-
end
|
187
136
|
end # Action
|
188
137
|
end # RubyAMI
|
@@ -48,14 +48,5 @@ module RubyAMI
|
|
48
48
|
its(:data) { should == 'foo=bar' }
|
49
49
|
its(:data_hash) { should == {'foo' => 'bar'} }
|
50
50
|
end
|
51
|
-
|
52
|
-
context 'with a 5xx error' do
|
53
|
-
let(:result_string) { "510%20Invalid%20or%20unknown%20command%0A" }
|
54
|
-
|
55
|
-
its(:code) { should == 510 }
|
56
|
-
its(:result) { should be_nil }
|
57
|
-
its(:data) { should == 'Invalid or unknown command' }
|
58
|
-
its(:data_hash) { should be_nil }
|
59
|
-
end
|
60
51
|
end
|
61
52
|
end
|