ruby_ami 1.3.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- current_actor.terminate!
46
+ ensure
47
+ async.terminate
47
48
  end
48
49
 
49
50
  def post_init
50
51
  @state = :started
51
- @event_callback.call Connected.new
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(action)
59
- logger.trace "[SEND] #{action.to_s}"
60
- send_data action.to_s
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
- @event_callback.call message
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
- @event_callback.call Disconnected.new
150
+ fire_event Disconnected.new
86
151
  end
87
152
  end
88
153
  end
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module RubyAMI
3
- VERSION = "1.3.4"
3
+ VERSION = "2.0.0"
4
4
  end
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
- Action.new name, headers do |response|
11
- @foo = response
10
+ described_class.new name, headers do |response|
11
+ @callback_result = response
12
12
  end
13
13
  end
14
14
 
15
- it { should be_new }
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
- it 'should set the response and raise the error when reading it' do
80
- subject << error
81
- lambda { subject.response }.should raise_error Error, 'AMI error'
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
- let(:message) { Response.new }
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
- its(:events) { should == [event] }
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(:response) { Response.new }
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
- its(:events) { should == [event] }
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