ruby_ami 0.1.1
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/.gitignore +14 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +9 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +20 -0
- data/README.md +49 -0
- data/Rakefile +69 -0
- data/cucumber.yml +2 -0
- data/features/lexer.feature +260 -0
- data/features/step_definitions/lexer_steps.rb +207 -0
- data/features/support/ami_fixtures.yml +30 -0
- data/features/support/env.rb +16 -0
- data/features/support/introspective_lexer.rb +22 -0
- data/features/support/lexer_helper.rb +103 -0
- data/lib/ruby_ami.rb +29 -0
- data/lib/ruby_ami/Guardfile +6 -0
- data/lib/ruby_ami/action.rb +143 -0
- data/lib/ruby_ami/client.rb +187 -0
- data/lib/ruby_ami/error.rb +21 -0
- data/lib/ruby_ami/event.rb +10 -0
- data/lib/ruby_ami/lexer.rl.rb +302 -0
- data/lib/ruby_ami/lexer_machine.rl +87 -0
- data/lib/ruby_ami/metaprogramming.rb +17 -0
- data/lib/ruby_ami/response.rb +44 -0
- data/lib/ruby_ami/stream.rb +60 -0
- data/lib/ruby_ami/version.rb +3 -0
- data/ruby_ami.gemspec +40 -0
- data/spec/ruby_ami/action_spec.rb +163 -0
- data/spec/ruby_ami/client_spec.rb +324 -0
- data/spec/ruby_ami/error_spec.rb +7 -0
- data/spec/ruby_ami/event_spec.rb +7 -0
- data/spec/ruby_ami/response_spec.rb +7 -0
- data/spec/ruby_ami/stream_spec.rb +153 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/mock_server.rb +16 -0
- metadata +296 -0
@@ -0,0 +1,207 @@
|
|
1
|
+
Given "a new lexer" do
|
2
|
+
@lexer = IntrospectiveManagerStreamLexer.new
|
3
|
+
@custom_stanzas = {}
|
4
|
+
@custom_events = {}
|
5
|
+
|
6
|
+
@GivenPong = lambda do |with_or_without, action_id, number|
|
7
|
+
number = number == "a" ? 1 : number.to_i
|
8
|
+
data = case with_or_without
|
9
|
+
when "with" then "Response: Pong\r\nActionID: #{action_id}\r\n\r\n"
|
10
|
+
when "without" then "Response: Pong\r\n\r\n"
|
11
|
+
else raise "Do not recognize preposition #{with_or_without.inspect}. Should be either 'with' or 'without'"
|
12
|
+
end
|
13
|
+
number.times do
|
14
|
+
@lexer << data
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Given "a version header for AMI $version" do |version|
|
20
|
+
@lexer << "Asterisk Call Manager/1.0\r\n"
|
21
|
+
end
|
22
|
+
|
23
|
+
Given "a normal login success with events" do
|
24
|
+
@lexer << fixture('login/standard/success')
|
25
|
+
end
|
26
|
+
|
27
|
+
Given "a normal login success with events split into two pieces" do
|
28
|
+
stanza = fixture('login/standard/success')
|
29
|
+
@lexer << stanza[0...3]
|
30
|
+
@lexer << stanza[3..-1]
|
31
|
+
end
|
32
|
+
|
33
|
+
Given "a stanza break" do
|
34
|
+
@lexer << "\r\n\r\n"
|
35
|
+
end
|
36
|
+
|
37
|
+
Given "a multi-line Response:Follows body of $method_name" do |method_name|
|
38
|
+
multi_line_response_body = send(:follows_body_text, method_name)
|
39
|
+
|
40
|
+
multi_line_response = format_newlines(<<-RESPONSE + "\r\n") % multi_line_response_body
|
41
|
+
Response: Follows\r
|
42
|
+
Privilege: Command\r
|
43
|
+
ActionID: 123123\r
|
44
|
+
%s\r
|
45
|
+
--END COMMAND--\r\n\r
|
46
|
+
RESPONSE
|
47
|
+
|
48
|
+
@lexer << multi_line_response
|
49
|
+
end
|
50
|
+
|
51
|
+
Given "a multi-line Response:Follows response simulating uptime" do
|
52
|
+
uptime_response = "Response: Follows\r
|
53
|
+
Privilege: Command\r
|
54
|
+
System uptime: 46 minutes, 30 seconds\r
|
55
|
+
--END COMMAND--\r\n\r\n"
|
56
|
+
@lexer << uptime_response
|
57
|
+
end
|
58
|
+
|
59
|
+
Given "syntactically invalid $name" do |name|
|
60
|
+
@lexer << send(:syntax_error_data, name)
|
61
|
+
end
|
62
|
+
|
63
|
+
Given /^(\d+) Pong responses with an ActionID of ([\d\w.]+)$/ do |number, action_id|
|
64
|
+
@GivenPong.call "with", action_id, number
|
65
|
+
end
|
66
|
+
|
67
|
+
Given /^a Pong response with an ActionID of ([\d\w.]+)$/ do |action_id|
|
68
|
+
@GivenPong.call "with", action_id, 1
|
69
|
+
end
|
70
|
+
|
71
|
+
Given /^(\d+) Pong responses without an ActionID$/ do |number|
|
72
|
+
@GivenPong.call "without", Time.now.to_f, number
|
73
|
+
end
|
74
|
+
|
75
|
+
Given /^a custom stanza named "(\w+)"$/ do |name|
|
76
|
+
@custom_stanzas[name] = "Response: Success\r\n"
|
77
|
+
end
|
78
|
+
|
79
|
+
Given 'the custom stanza named "$name" has key "$key" with value "$value"' do |name,key,value|
|
80
|
+
@custom_stanzas[name] << "#{key}: #{value}\r\n"
|
81
|
+
end
|
82
|
+
|
83
|
+
Given 'an AMI error whose message is "$message"' do |message|
|
84
|
+
@lexer << "Response: Error\r\nMessage: #{message}\r\n\r\n"
|
85
|
+
end
|
86
|
+
|
87
|
+
Given 'an immediate response with text "$text"' do |text|
|
88
|
+
@lexer << "#{text}\r\n\r\n"
|
89
|
+
end
|
90
|
+
|
91
|
+
Given 'a custom event with name "$event_name" identified by "$identifier"' do |event_name, identifer|
|
92
|
+
@custom_events[identifer] = {:Event => event_name }
|
93
|
+
end
|
94
|
+
|
95
|
+
Given 'a custom header for event identified by "$identifier" whose key is "$key" and value is "$value"' do |identifier, key, value|
|
96
|
+
@custom_events[identifier][key] = value
|
97
|
+
end
|
98
|
+
|
99
|
+
Given "an Authentication Required error" do
|
100
|
+
@lexer << "Response: Error\r\nActionID: BPJeKqW2-SnVg-PyFs-vkXT-7AWVVPD0N3G7\r\nMessage: Authentication Required\r\n\r\n"
|
101
|
+
end
|
102
|
+
|
103
|
+
Given "a follows packet with a colon in it" do
|
104
|
+
@lexer << follows_body_text("with_colon")
|
105
|
+
end
|
106
|
+
|
107
|
+
########################################
|
108
|
+
#### WHEN
|
109
|
+
########################################
|
110
|
+
|
111
|
+
When 'the custom stanza named "$name" is added to the buffer' do |name|
|
112
|
+
@lexer << (@custom_stanzas[name] + "\r\n")
|
113
|
+
end
|
114
|
+
|
115
|
+
When 'the custom event identified by "$identifier" is added to the buffer' do |identifier|
|
116
|
+
custom_event = @custom_events[identifier].clone
|
117
|
+
event_name = custom_event.delete :Event
|
118
|
+
stringified_event = "Event: #{event_name}\r\n"
|
119
|
+
custom_event.each_pair do |key,value|
|
120
|
+
stringified_event << "#{key}: #{value}\r\n"
|
121
|
+
end
|
122
|
+
stringified_event << "\r\n"
|
123
|
+
@lexer << stringified_event
|
124
|
+
end
|
125
|
+
|
126
|
+
When "the buffer is lexed" do
|
127
|
+
@lexer.resume!
|
128
|
+
end
|
129
|
+
|
130
|
+
########################################
|
131
|
+
#### THEN
|
132
|
+
########################################
|
133
|
+
|
134
|
+
Then "the protocol should have lexed without syntax errors" do
|
135
|
+
current_pointer = @lexer.send(:instance_variable_get, :@current_pointer)
|
136
|
+
data_ending_pointer = @lexer.send(:instance_variable_get, :@data_ending_pointer)
|
137
|
+
current_pointer.should equal(data_ending_pointer)
|
138
|
+
@lexer.syntax_errors.size.should equal(0)
|
139
|
+
end
|
140
|
+
|
141
|
+
Then /^the protocol should have lexed with (\d+) syntax errors?$/ do |number|
|
142
|
+
@lexer.syntax_errors.size.should equal(number.to_i)
|
143
|
+
end
|
144
|
+
|
145
|
+
Then "the syntax error fixture named $name should have been encountered" do |name|
|
146
|
+
irregularity = send(:syntax_error_data, name)
|
147
|
+
@lexer.syntax_errors.find { |error| error == irregularity }.should_not be_nil
|
148
|
+
end
|
149
|
+
|
150
|
+
Then /^(\d+) messages? should have been received$/ do |number_received|
|
151
|
+
@lexer.received_messages.size.should equal(number_received.to_i)
|
152
|
+
end
|
153
|
+
|
154
|
+
Then /^the 'follows' body of (\d+) messages? received should equal (\w+)$/ do |number, method_name|
|
155
|
+
multi_line_response = follows_body_text method_name
|
156
|
+
@lexer.received_messages.should_not be_empty
|
157
|
+
@lexer.received_messages.select do |message|
|
158
|
+
message.text_body == multi_line_response
|
159
|
+
end.size.should eql(number.to_i)
|
160
|
+
end
|
161
|
+
|
162
|
+
Then "the version should be set to $version" do |version|
|
163
|
+
@lexer.ami_version.should eql(version.to_f)
|
164
|
+
end
|
165
|
+
|
166
|
+
Then /^the ([\w\d]*) message received should have a key "([^\"]*)" with value "([^\"]*)"$/ do |ordered,key,value|
|
167
|
+
ordered = ordered[/^(\d+)\w+$/, 1].to_i - 1
|
168
|
+
@lexer.received_messages[ordered][key].should eql(value)
|
169
|
+
end
|
170
|
+
|
171
|
+
Then "$number AMI error should have been received" do |number|
|
172
|
+
@lexer.ami_errors.size.should equal(number.to_i)
|
173
|
+
end
|
174
|
+
|
175
|
+
Then 'the $order AMI error should have the message "$message"' do |order, message|
|
176
|
+
order = order[/^(\d+)\w+$/, 1].to_i - 1
|
177
|
+
@lexer.ami_errors[order].should be_kind_of(RubyAMI::Error)
|
178
|
+
@lexer.ami_errors[order].message.should eql(message)
|
179
|
+
end
|
180
|
+
|
181
|
+
Then '$number message should be an immediate response with text "$text"' do |number, text|
|
182
|
+
matching_immediate_responses = @lexer.received_messages.select do |response|
|
183
|
+
response.kind_of?(RubyAMI::Response) && response.text_body == text
|
184
|
+
end
|
185
|
+
matching_immediate_responses.size.should equal(number.to_i)
|
186
|
+
matching_immediate_responses.first["ActionID"].should eql(nil)
|
187
|
+
end
|
188
|
+
|
189
|
+
Then 'the $order event should have the name "$name"' do |order, name|
|
190
|
+
order = order[/^(\d+)\w+$/, 1].to_i - 1
|
191
|
+
@lexer.received_messages.select do |response|
|
192
|
+
response.kind_of?(RubyAMI::Event)
|
193
|
+
end[order].name.should eql(name)
|
194
|
+
end
|
195
|
+
|
196
|
+
Then '$number event should have been received' do |number|
|
197
|
+
@lexer.received_messages.select do |response|
|
198
|
+
response.kind_of?(RubyAMI::Event)
|
199
|
+
end.size.should equal(number.to_i)
|
200
|
+
end
|
201
|
+
|
202
|
+
Then 'the $order event should have key "$key" with value "$value"' do |order, key, value|
|
203
|
+
order = order[/^(\d+)\w+$/, 1].to_i - 1
|
204
|
+
@lexer.received_messages.select do |response|
|
205
|
+
response.kind_of?(RubyAMI::Event)
|
206
|
+
end[order][key].should eql(value)
|
207
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
:login:
|
2
|
+
:standard:
|
3
|
+
:client:
|
4
|
+
Action: Login
|
5
|
+
Username: :string
|
6
|
+
Secret: :string
|
7
|
+
Events: {one_of: ["on", "off"]}
|
8
|
+
:success:
|
9
|
+
Response: Success
|
10
|
+
Message: Authentication accepted
|
11
|
+
:fail:
|
12
|
+
Response: Error
|
13
|
+
Message: Authentication failed
|
14
|
+
|
15
|
+
:errors:
|
16
|
+
:missing_action:
|
17
|
+
Response: Error
|
18
|
+
Message: Missing action in request
|
19
|
+
|
20
|
+
:pong:
|
21
|
+
:with_action_id:
|
22
|
+
ActionID: 1287381.1238
|
23
|
+
Response: Pong
|
24
|
+
:without_action_id:
|
25
|
+
Response: Pong
|
26
|
+
:with_extra_keys:
|
27
|
+
ActionID: 1287381.1238
|
28
|
+
Response: Pong
|
29
|
+
Blah: This is something arbitrary
|
30
|
+
Blahhh: something else arbitrary
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require 'simplecov-rcov'
|
3
|
+
class SimpleCov::Formatter::MergedFormatter
|
4
|
+
def format(result)
|
5
|
+
SimpleCov::Formatter::HTMLFormatter.new.format(result)
|
6
|
+
SimpleCov::Formatter::RcovFormatter.new.format(result)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
|
10
|
+
SimpleCov.start do
|
11
|
+
add_filter "/vendor/"
|
12
|
+
end
|
13
|
+
|
14
|
+
require 'cucumber'
|
15
|
+
require 'rspec'
|
16
|
+
require 'ruby_ami'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class IntrospectiveManagerStreamLexer < RubyAMI::Lexer
|
2
|
+
attr_reader :received_messages, :syntax_errors, :ami_errors
|
3
|
+
|
4
|
+
def initialize(*args)
|
5
|
+
super
|
6
|
+
@received_messages = []
|
7
|
+
@syntax_errors = []
|
8
|
+
@ami_errors = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def message_received(message = @current_message)
|
12
|
+
@received_messages << message
|
13
|
+
end
|
14
|
+
|
15
|
+
def error_received(error_message)
|
16
|
+
@ami_errors << error_message
|
17
|
+
end
|
18
|
+
|
19
|
+
def syntax_error_encountered(ignored_chunk)
|
20
|
+
@syntax_errors << ignored_chunk
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
RAGEL_FILES = %w[lib/ruby_ami/lexer.rl.rb]
|
2
|
+
|
3
|
+
def regenerate_ragel
|
4
|
+
`rake ragel`
|
5
|
+
end
|
6
|
+
|
7
|
+
FIXTURES = YAML.load_file File.dirname(__FILE__) + "/ami_fixtures.yml"
|
8
|
+
|
9
|
+
def fixture(path, overrides = {})
|
10
|
+
path_segments = path.split '/'
|
11
|
+
selected_event = path_segments.inject(FIXTURES.clone) do |hash, segment|
|
12
|
+
raise ArgumentError, path + " not found!" unless hash
|
13
|
+
hash[segment.to_sym]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Downcase all keys in the event and the overrides
|
17
|
+
selected_event = selected_event.inject({}) do |downcased_hash,(key,value)|
|
18
|
+
downcased_hash[key.to_s.downcase] = value
|
19
|
+
downcased_hash
|
20
|
+
end
|
21
|
+
|
22
|
+
overrides = overrides.inject({}) do |downcased_hash,(key,value)|
|
23
|
+
downcased_hash[key.to_s.downcase] = value
|
24
|
+
downcased_hash
|
25
|
+
end
|
26
|
+
|
27
|
+
# Replace variables in the selected_event with any overrides, ignoring case of the key
|
28
|
+
keys_with_variables = selected_event.select { |(key, value)| value.kind_of?(Symbol) || value.kind_of?(Hash) }
|
29
|
+
|
30
|
+
keys_with_variables.each do |original_key, variable_type|
|
31
|
+
# Does an override an exist in the supplied list?
|
32
|
+
if overriden_pair = overrides.find { |(key, value)| key == original_key }
|
33
|
+
# We have an override! Let's replace the template value in the event with the overriden value
|
34
|
+
selected_event[original_key] = overriden_pair.last
|
35
|
+
else
|
36
|
+
# Based on the type, let's generate a placeholder.
|
37
|
+
selected_event[original_key] = case variable_type
|
38
|
+
when :string
|
39
|
+
rand(100000).to_s
|
40
|
+
when Hash
|
41
|
+
if variable_type.has_key? "one_of"
|
42
|
+
# Choose a random possibility
|
43
|
+
possibilities = variable_type['one_of']
|
44
|
+
possibilities[rand(possibilities.size)]
|
45
|
+
else
|
46
|
+
raise "Unrecognized Hash fixture property! ##{variable_type.keys.to_sentence}"
|
47
|
+
end
|
48
|
+
else
|
49
|
+
raise "Unrecognized fixture variable type #{variable_type}!"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
hash_to_stanza(selected_event).tap do |event|
|
55
|
+
selected_event.each_pair do |key, value|
|
56
|
+
event.meta_def(key) { value }
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def hash_to_stanza(hash)
|
62
|
+
ordered_hash = hash.to_a
|
63
|
+
starter = hash.find { |(key, value)| key.strip =~ /^(Response|Action)$/i }
|
64
|
+
ordered_hash.unshift ordered_hash.delete(starter) if starter
|
65
|
+
ordered_hash.inject(String.new) do |stanza,(key, value)|
|
66
|
+
stanza + "#{key}: #{value}\r\n"
|
67
|
+
end + "\r\n"
|
68
|
+
end
|
69
|
+
|
70
|
+
def format_newlines(string)
|
71
|
+
# HOLY FUCK THIS IS UGLY
|
72
|
+
tmp_replacement = random_string
|
73
|
+
string.gsub("\r\n", tmp_replacement).
|
74
|
+
gsub("\n", "\r\n").
|
75
|
+
gsub(tmp_replacement, "\r\n")
|
76
|
+
end
|
77
|
+
|
78
|
+
def random_string
|
79
|
+
(rand(1_000_000_000_000) + 1_000_000_000).to_s
|
80
|
+
end
|
81
|
+
|
82
|
+
def follows_body_text(name)
|
83
|
+
case name
|
84
|
+
when "ragel_description"
|
85
|
+
"Ragel is a software development tool that allows user actions to
|
86
|
+
be embedded into the transitions of a regular expression's corresponding state machine,
|
87
|
+
eliminating the need to switch from the regular expression engine and user code execution
|
88
|
+
environment and back again."
|
89
|
+
when "with_colon_after_first_line"
|
90
|
+
"Host Username Refresh State Reg.Time \r\nlax.teliax.net:5060 jicksta 105 Registered Tue, 11 Nov 2008 02:29:55"
|
91
|
+
when "show_channels_from_wayne"
|
92
|
+
"Channel Location State Application(Data)\r\n0 active channels\r\n0 active calls"
|
93
|
+
when "empty_string"
|
94
|
+
""
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def syntax_error_data(name)
|
99
|
+
case name
|
100
|
+
when "immediate_packet_with_colon"
|
101
|
+
"!IJ@MHY:!&@B*!B @ ! @^! @ !@ !\r!@ ! @ !@ ! !!m, \n\\n\n"
|
102
|
+
end
|
103
|
+
end
|
data/lib/ruby_ami.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
%w{
|
2
|
+
active_support/dependencies/autoload
|
3
|
+
active_support/core_ext/object/blank
|
4
|
+
active_support/core_ext/numeric/time
|
5
|
+
active_support/core_ext/numeric/bytes
|
6
|
+
active_support/hash_with_indifferent_access
|
7
|
+
|
8
|
+
uuidtools
|
9
|
+
eventmachine
|
10
|
+
future-resource
|
11
|
+
logger
|
12
|
+
girl_friday
|
13
|
+
countdownlatch
|
14
|
+
|
15
|
+
ruby_ami/metaprogramming
|
16
|
+
}.each { |f| require f }
|
17
|
+
|
18
|
+
module RubyAMI
|
19
|
+
extend ActiveSupport::Autoload
|
20
|
+
|
21
|
+
autoload :Action
|
22
|
+
autoload :Client
|
23
|
+
autoload :Error
|
24
|
+
autoload :Event
|
25
|
+
autoload :Lexer
|
26
|
+
autoload :Response
|
27
|
+
autoload :Stream
|
28
|
+
autoload :Version
|
29
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module RubyAMI
|
2
|
+
class Action
|
3
|
+
attr_reader :name, :headers, :action_id
|
4
|
+
|
5
|
+
attr_accessor :state
|
6
|
+
|
7
|
+
CAUSAL_EVENT_NAMES = %w[queuestatus sippeers iaxpeers parkedcalls dahdishowchannels coreshowchannels
|
8
|
+
dbget status agents konferencelist] unless defined? CAUSAL_EVENT_NAMES
|
9
|
+
|
10
|
+
def initialize(name, headers = {}, &block)
|
11
|
+
@name = name.to_s.downcase.freeze
|
12
|
+
@headers = headers.stringify_keys.freeze
|
13
|
+
@action_id = UUIDTools::UUID.random_create.to_s
|
14
|
+
@response = FutureResource.new
|
15
|
+
@response_callback = block
|
16
|
+
@state = :new
|
17
|
+
@events = []
|
18
|
+
@event_lock = Mutex.new
|
19
|
+
end
|
20
|
+
|
21
|
+
[:new, :sent, :complete].each do |state|
|
22
|
+
define_method("#{state}?") { @state == state }
|
23
|
+
end
|
24
|
+
|
25
|
+
def replies_with_action_id?
|
26
|
+
!UnsupportedActionName::UNSUPPORTED_ACTION_NAMES.include? name
|
27
|
+
end
|
28
|
+
|
29
|
+
##
|
30
|
+
# When sending an action with "causal events" (i.e. events which must be collected to form a proper
|
31
|
+
# response), AMI should send a particular event which instructs us that no more events will be sent.
|
32
|
+
# This event is called the "causal event terminator".
|
33
|
+
#
|
34
|
+
# Note: you must supply both the name of the event and any headers because it's possible that some uses of an
|
35
|
+
# action (i.e. same name, different headers) have causal events while other uses don't.
|
36
|
+
#
|
37
|
+
# @param [String] name the name of the event
|
38
|
+
# @param [Hash] the headers associated with this event
|
39
|
+
# @return [String] the downcase()'d name of the event name for which to wait
|
40
|
+
#
|
41
|
+
def has_causal_events?
|
42
|
+
CAUSAL_EVENT_NAMES.include? name
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Used to determine the event name for an action which has causal events.
|
47
|
+
#
|
48
|
+
# @param [String] action_name
|
49
|
+
# @return [String] The corresponding event name which signals the completion of the causal event sequence.
|
50
|
+
#
|
51
|
+
def causal_event_terminator_name
|
52
|
+
return unless has_causal_events?
|
53
|
+
case name
|
54
|
+
when "sippeers", "iaxpeers"
|
55
|
+
"peerlistcomplete"
|
56
|
+
when "dbget"
|
57
|
+
"dbgetresponse"
|
58
|
+
when "konferencelist"
|
59
|
+
"conferencelistcomplete"
|
60
|
+
else
|
61
|
+
name + "complete"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
##
|
66
|
+
# Converts this action into a protocol-valid String, ready to be sent over a socket.
|
67
|
+
#
|
68
|
+
def to_s
|
69
|
+
@textual_representation ||= (
|
70
|
+
"Action: #{@name}\r\nActionID: #{@action_id}\r\n" +
|
71
|
+
@headers.map { |(key,value)| "#{key}: #{value}" }.join("\r\n") +
|
72
|
+
(@headers.any? ? "\r\n\r\n" : "\r\n")
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
#
|
77
|
+
# If the response has simply not been received yet from Asterisk, the calling Thread will block until it comes
|
78
|
+
# in. Once the response comes in, subsequent calls immediately return a reference to the ManagerInterfaceResponse
|
79
|
+
# object.
|
80
|
+
#
|
81
|
+
def response(timeout = nil)
|
82
|
+
@response.resource(timeout).tap do |resp|
|
83
|
+
raise resp if resp.is_a? Exception
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def response=(other)
|
88
|
+
@state = :complete
|
89
|
+
@response.resource = other
|
90
|
+
@response_callback.call other if @response_callback
|
91
|
+
end
|
92
|
+
|
93
|
+
def <<(message)
|
94
|
+
case message
|
95
|
+
when Error
|
96
|
+
self.response = message
|
97
|
+
when Event
|
98
|
+
raise StandardError, 'This action should not trigger events. Maybe it is now a causal action? This is most likely a bug in RubyAMI' unless has_causal_events?
|
99
|
+
@event_lock.synchronize do
|
100
|
+
@events << message
|
101
|
+
end
|
102
|
+
self.response = @pending_response if message.name.downcase == causal_event_terminator_name
|
103
|
+
when Response
|
104
|
+
if has_causal_events?
|
105
|
+
@pending_response = message
|
106
|
+
else
|
107
|
+
self.response = message
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def events
|
113
|
+
@event_lock.synchronize do
|
114
|
+
@events.dup
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def eql?(other)
|
119
|
+
to_s == other.to_s
|
120
|
+
end
|
121
|
+
alias :== :eql?
|
122
|
+
|
123
|
+
##
|
124
|
+
# This class will be removed once this AMI library fully supports all known protocol anomalies.
|
125
|
+
#
|
126
|
+
class UnsupportedActionName < ArgumentError
|
127
|
+
UNSUPPORTED_ACTION_NAMES = %w[queues] unless defined? UNSUPPORTED_ACTION_NAMES
|
128
|
+
|
129
|
+
# Blacklist some actions depends on the Asterisk version
|
130
|
+
def self.preinitialize(version)
|
131
|
+
if version < 1.8
|
132
|
+
%w[iaxpeers muteaudio mixmonitormute aocmessage].each do |action|
|
133
|
+
UNSUPPORTED_ACTION_NAMES << action
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def initialize(name)
|
139
|
+
super "At the moment this AMI library doesn't support the #{name.inspect} action because it causes a protocol anomaly. Support for it will be coming shortly."
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end # RubyAMI
|