ruby_ami 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|