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.
@@ -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