Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Strip back to bare codebase

* Retain minimal Stream
  • Loading branch information...
commit d01c540fddf40b63d50d2ab2fdac7c2e514602ba 1 parent 11aa989
@benlangfeld benlangfeld authored
View
2  Gemfile
@@ -1,5 +1,3 @@
source "http://rubygems.org"
gemspec
-
-gem 'celluloid-io', :git => 'https://github.com/celluloid/celluloid-io.git'
View
11 Rakefile
@@ -8,19 +8,12 @@ RSpec::Core::RakeTask.new(:spec) do |spec|
spec.rspec_opts = '--color'
end
-require 'cucumber'
-require 'cucumber/rake/task'
-require 'ci/reporter/rake/cucumber'
-Cucumber::Rake::Task.new(:features) do |t|
- t.cucumber_opts = %w{--tags ~@jruby} unless defined?(JRUBY_VERSION)
-end
-
Cucumber::Rake::Task.new(:wip) do |t|
t.cucumber_opts = %w{-p wip}
end
-task :default => [:ragel, :spec, :features]
-task :ci => [:ragel, 'ci:setup:rspec', :spec, 'ci:setup:cucumber', :features]
+task :default => [:ragel, :spec]
+task :ci => [:ragel, 'ci:setup:rspec', :spec]
require 'yard'
YARD::Rake::YardocTask.new
View
2  cucumber.yml
@@ -1,2 +0,0 @@
-default: --tags ~@wip
-wip: --wip --tags @wip
View
260 features/lexer.feature
@@ -1,260 +0,0 @@
-Feature: Lexing AMI
- As a RubyFS user
- I want to lex the AMI protocol
- So that I can control Asterisk asynchronously
-
- Scenario: Lexing only the initial AMI version header
- Given a new lexer
- And a version header for AMI 1.0
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And the version should be set to 1.0
-
- Scenario: Lexing the initial AMI header and a login attempt
- Given a new lexer
- And a version header for AMI 1.0
- And a normal login success with events
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And 1 message should have been received
-
- Scenario: Lexing the initial AMI header and then a Response:Follows section
- Given a new lexer
- And a version header for AMI 1.0
- And a multi-line Response:Follows body of ragel_description
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And the 'follows' body of 1 message received should equal ragel_description
-
- Scenario: Lexing a Response:Follows section with no body
- Given a new lexer
- And a version header for AMI 1.0
- And a multi-line Response:Follows body of empty_String
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And the 'follows' body of 1 message received should equal empty_string
-
- Scenario: Lexing a multi-line Response:Follows simulating the "core show channels" command
- Given a new lexer
- And a version header for AMI 1.0
- Given a multi-line Response:Follows body of show_channels_from_wayne
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And the 'follows' body of 1 message received should equal show_channels_from_wayne
-
- Scenario: Lexing a multi-line Response:Follows simulating the "core show uptime" command
- Given a new lexer
- And a version header for AMI 1.0
- Given a multi-line Response:Follows response simulating uptime
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And the first message received should have a key "System uptime" with value "46 minutes, 30 seconds"
-
- Scenario: Lexing a Response:Follows section which has a colon not on the first line
- Given a new lexer
- And a multi-line Response:Follows body of with_colon_after_first_line
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And 1 message should have been received
- And the 'follows' body of 1 message received should equal with_colon_after_first_line
-
- @wip
- Scenario: Lexing an immediate response with a colon in it.
- Given a new lexer
- And an immediate response with text "markq has 0 calls (max unlimited) in 'ringall' strategy (0s holdtime), W:0, C:0, A:0, SL:0.0% within 0s\r\n No Members\r\n No Callers\r\n\r\n\r\n\r\n"
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And 1 message should have been received
- And 1 message should be an immediate response with text "markq has 0 calls (max unlimited) in 'ringall' strategy (0s holdtime), W:0, C:0, A:0, SL:0.0% within 0s\r\n No Members\r\n No Callers"
-
- Scenario: Lexing the initial AMI header and then an "Authentication Required" error.
- Given a new lexer
- And a version header for AMI 1.0
- And an Authentication Required error
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
-
- Scenario: Lexing the initial AMI header and then a Response:Follows section
- Given a new lexer
- And a version header for AMI 1.0
- And a multi-line Response:Follows body of ragel_description
- And a multi-line Response:Follows body of ragel_description
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And the 'follows' body of 2 messages received should equal ragel_description
-
- Scenario: Lexing a stanza without receiving an AMI header
- Given a new lexer
- And a normal login success with events
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And 1 message should have been received
-
- Scenario: Receiving an immediate response as soon as the socket is opened
- Given a new lexer
- And an immediate response with text "Immediate responses are so ridiculous"
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And 1 message should have been received
- And 1 message should be an immediate response with text "Immediate responses are so ridiculous"
-
- Scenario: Receiving an immediate message surrounded by real messages
- Given a new lexer
- And a normal login success with events
- And an immediate response with text "No queues have been created."
- And a normal login success with events
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And 3 messages should have been received
- And 1 message should be an immediate response with text "No queues have been created."
-
- Scenario: Receiving a Pong after a simulated login
- Given a new lexer
- And a version header for AMI 1.0
- And a normal login success with events
- And a Pong response with an ActionID of randomness
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And 2 messages should have been received
-
- Scenario: Ten Pong responses in a row
- Given a new lexer
- And 5 Pong responses without an ActionID
- And 5 Pong responses with an ActionID of randomness
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And 10 messages should have been received
-
- Scenario: A Pong with an ActionID
- Given a new lexer
- And a Pong response with an ActionID of 1224469850.61673
-
- When the buffer is lexed
-
- Then the first message received should have a key "ActionID" with value "1224469850.61673"
-
- Scenario: A response containing a floating point value
- Given a new lexer
- And a custom stanza named "call"
- And the custom stanza named "call" has key "ActionID" with value "1224469850.61673"
- And the custom stanza named "call" has key "Uniqueid" with value "1173223225.10309"
-
- When the custom stanza named "call" is added to the buffer
- And the buffer is lexed
-
- Then the 1st message received should have a key "Uniqueid" with value "1173223225.10309"
-
- Scenario: Receiving a message with custom key/value pairs
- Given a new lexer
- And a custom stanza named "person"
- And the custom stanza named "person" has key "ActionID" with value "1224469850.61673"
- And the custom stanza named "person" has key "Name" with value "Jay Phillips"
- And the custom stanza named "person" has key "Age" with value "21"
- And the custom stanza named "person" has key "Location" with value "San Francisco, CA"
- And the custom stanza named "person" has key "x-header" with value "<FooBAR>"
- And the custom stanza named "person" has key "Channel" with value "IAX2/127.0.0.1/4569-9904"
- And the custom stanza named "person" has key "I have spaces" with value "i have trailing padding "
-
- When the custom stanza named "person" is added to the buffer
- And the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And the first message received should have a key "Name" with value "Jay Phillips"
- And the first message received should have a key "ActionID" with value "1224469850.61673"
- And the first message received should have a key "Name" with value "Jay Phillips"
- And the first message received should have a key "Age" with value "21"
- And the first message received should have a key "Location" with value "San Francisco, CA"
- And the first message received should have a key "x-header" with value "<FooBAR>"
- And the first message received should have a key "Channel" with value "IAX2/127.0.0.1/4569-9904"
- And the first message received should have a key "I have spaces" with value "i have trailing padding "
-
- Scenario: Executing a stanza that was partially received
- Given a new lexer
- And a normal login success with events split into two pieces
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And 1 message should have been received
-
- Scenario: Receiving an AMI error followed by a normal event
- Given a new lexer
- And an AMI error whose message is "Missing action in request"
- And a normal login success with events
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And 1 AMI error should have been received
- And the 1st AMI error should have the message "Missing action in request"
- And 1 message should have been received
-
- Scenario: Lexing an immediate response
- Given a new lexer
- And a normal login success with events
- And an immediate response with text "Yes, plain English is sent sometimes over AMI."
- And a normal login success with events
-
- When the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And 3 messages should have been received
- And 1 message should be an immediate response with text "Yes, plain English is sent sometimes over AMI."
-
- Scenario: Lexing an AMI event
- Given a new lexer
- And a custom event with name "NewChannelEvent" identified by "this_event"
- And a custom header for event identified by "this_event" whose key is "Foo" and value is "Bar"
- And a custom header for event identified by "this_event" whose key is "Channel" and value is "IAX2/127.0.0.1:4569-9904"
- And a custom header for event identified by "this_event" whose key is "AppData" and value is "agi://localhost"
-
- When the custom event identified by "this_event" is added to the buffer
- And the buffer is lexed
-
- Then the protocol should have lexed without syntax errors
- And 1 event should have been received
- And the 1st event should have the name "NewChannelEvent"
- And the 1st event should have key "Foo" with value "Bar"
- And the 1st event should have key "Channel" with value "IAX2/127.0.0.1:4569-9904"
- And the 1st event should have key "AppData" with value "agi://localhost"
-
- Scenario: Lexing an immediate packet with a colon in it (syntax error)
- Given a new lexer
- And syntactically invalid immediate_packet_with_colon
- And a stanza break
-
- When the buffer is lexed
-
- Then 0 messages should have been received
- And the protocol should have lexed with 1 syntax error
- And the syntax error fixture named immediate_packet_with_colon should have been encountered
View
207 features/step_definitions/lexer_steps.rb
@@ -1,207 +0,0 @@
-Given "a new lexer" do
- @lexer = IntrospectiveManagerStreamLexer.new
- @custom_stanzas = {}
- @custom_events = {}
-
- @GivenPong = lambda do |with_or_without, action_id, number|
- number = number == "a" ? 1 : number.to_i
- data = case with_or_without
- when "with" then "Response: Pong\r\nActionID: #{action_id}\r\n\r\n"
- when "without" then "Response: Pong\r\n\r\n"
- else raise "Do not recognize preposition #{with_or_without.inspect}. Should be either 'with' or 'without'"
- end
- number.times do
- @lexer << data
- end
- end
-end
-
-Given "a version header for AMI $version" do |version|
- @lexer << "Asterisk Call Manager/1.0\r\n"
-end
-
-Given "a normal login success with events" do
- @lexer << fixture('login/standard/success')
-end
-
-Given "a normal login success with events split into two pieces" do
- stanza = fixture('login/standard/success')
- @lexer << stanza[0...3]
- @lexer << stanza[3..-1]
-end
-
-Given "a stanza break" do
- @lexer << "\r\n\r\n"
-end
-
-Given "a multi-line Response:Follows body of $method_name" do |method_name|
- multi_line_response_body = send(:follows_body_text, method_name)
-
- multi_line_response = format_newlines(<<-RESPONSE + "\r\n") % multi_line_response_body
-Response: Follows\r
-Privilege: Command\r
-ActionID: 123123\r
-%s\r
---END COMMAND--\r\n\r
- RESPONSE
-
- @lexer << multi_line_response
-end
-
-Given "a multi-line Response:Follows response simulating uptime" do
- uptime_response = "Response: Follows\r
-Privilege: Command\r
-System uptime: 46 minutes, 30 seconds\r
---END COMMAND--\r\n\r\n"
- @lexer << uptime_response
-end
-
-Given "syntactically invalid $name" do |name|
- @lexer << send(:syntax_error_data, name)
-end
-
-Given /^(\d+) Pong responses with an ActionID of ([\d\w.]+)$/ do |number, action_id|
- @GivenPong.call "with", action_id, number
-end
-
-Given /^a Pong response with an ActionID of ([\d\w.]+)$/ do |action_id|
- @GivenPong.call "with", action_id, 1
-end
-
-Given /^(\d+) Pong responses without an ActionID$/ do |number|
- @GivenPong.call "without", Time.now.to_f, number
-end
-
-Given /^a custom stanza named "(\w+)"$/ do |name|
- @custom_stanzas[name] = "Response: Success\r\n"
-end
-
-Given 'the custom stanza named "$name" has key "$key" with value "$value"' do |name,key,value|
- @custom_stanzas[name] << "#{key}: #{value}\r\n"
-end
-
-Given 'an AMI error whose message is "$message"' do |message|
- @lexer << "Response: Error\r\nMessage: #{message}\r\n\r\n"
-end
-
-Given 'an immediate response with text "$text"' do |text|
- @lexer << "#{text}\r\n\r\n"
-end
-
-Given 'a custom event with name "$event_name" identified by "$identifier"' do |event_name, identifer|
- @custom_events[identifer] = {:Event => event_name }
-end
-
-Given 'a custom header for event identified by "$identifier" whose key is "$key" and value is "$value"' do |identifier, key, value|
- @custom_events[identifier][key] = value
-end
-
-Given "an Authentication Required error" do
- @lexer << "Response: Error\r\nActionID: BPJeKqW2-SnVg-PyFs-vkXT-7AWVVPD0N3G7\r\nMessage: Authentication Required\r\n\r\n"
-end
-
-Given "a follows packet with a colon in it" do
- @lexer << follows_body_text("with_colon")
-end
-
-########################################
-#### WHEN
-########################################
-
-When 'the custom stanza named "$name" is added to the buffer' do |name|
- @lexer << (@custom_stanzas[name] + "\r\n")
-end
-
-When 'the custom event identified by "$identifier" is added to the buffer' do |identifier|
- custom_event = @custom_events[identifier].clone
- event_name = custom_event.delete :Event
- stringified_event = "Event: #{event_name}\r\n"
- custom_event.each_pair do |key,value|
- stringified_event << "#{key}: #{value}\r\n"
- end
- stringified_event << "\r\n"
- @lexer << stringified_event
-end
-
-When "the buffer is lexed" do
- @lexer.resume!
-end
-
-########################################
-#### THEN
-########################################
-
-Then "the protocol should have lexed without syntax errors" do
- current_pointer = @lexer.send(:instance_variable_get, :@current_pointer)
- data_ending_pointer = @lexer.send(:instance_variable_get, :@data_ending_pointer)
- current_pointer.should == data_ending_pointer
- @lexer.syntax_errors.size.should equal(0)
-end
-
-Then /^the protocol should have lexed with (\d+) syntax errors?$/ do |number|
- @lexer.syntax_errors.size.should == number.to_i
-end
-
-Then "the syntax error fixture named $name should have been encountered" do |name|
- irregularity = send(:syntax_error_data, name)
- @lexer.syntax_errors.find { |error| error == irregularity }.should_not be_nil
-end
-
-Then /^(\d+) messages? should have been received$/ do |number_received|
- @lexer.received_messages.size.should == number_received.to_i
-end
-
-Then /^the 'follows' body of (\d+) messages? received should equal (\w+)$/ do |number, method_name|
- multi_line_response = follows_body_text method_name
- @lexer.received_messages.should_not be_empty
- @lexer.received_messages.select do |message|
- message.text_body == multi_line_response
- end.size.should == number.to_i
-end
-
-Then "the version should be set to $version" do |version|
- @lexer.ami_version.should eql(version.to_f)
-end
-
-Then /^the ([\w\d]*) message received should have a key "([^\"]*)" with value "([^\"]*)"$/ do |ordered,key,value|
- ordered = ordered[/^(\d+)\w+$/, 1].to_i - 1
- @lexer.received_messages[ordered][key].should eql(value)
-end
-
-Then "$number AMI error should have been received" do |number|
- @lexer.ami_errors.size.should equal(number.to_i)
-end
-
-Then 'the $order AMI error should have the message "$message"' do |order, message|
- order = order[/^(\d+)\w+$/, 1].to_i - 1
- @lexer.ami_errors[order].should be_kind_of(RubyFS::Error)
- @lexer.ami_errors[order].message.should eql(message)
-end
-
-Then '$number message should be an immediate response with text "$text"' do |number, text|
- matching_immediate_responses = @lexer.received_messages.select do |response|
- response.kind_of?(RubyFS::Response) && response.text_body == text
- end
- matching_immediate_responses.size.should equal(number.to_i)
- matching_immediate_responses.first["ActionID"].should eql(nil)
-end
-
-Then 'the $order event should have the name "$name"' do |order, name|
- order = order[/^(\d+)\w+$/, 1].to_i - 1
- @lexer.received_messages.select do |response|
- response.kind_of?(RubyFS::Event)
- end[order].name.should eql(name)
-end
-
-Then '$number event should have been received' do |number|
- @lexer.received_messages.select do |response|
- response.kind_of?(RubyFS::Event)
- end.size.should equal(number.to_i)
-end
-
-Then 'the $order event should have key "$key" with value "$value"' do |order, key, value|
- order = order[/^(\d+)\w+$/, 1].to_i - 1
- @lexer.received_messages.select do |response|
- response.kind_of?(RubyFS::Event)
- end[order][key].should eql(value)
-end
View
30 features/support/ami_fixtures.yml
@@ -1,30 +0,0 @@
-:login:
- :standard:
- :client:
- Action: Login
- Username: :string
- Secret: :string
- Events: {one_of: ["on", "off"]}
- :success:
- Response: Success
- Message: Authentication accepted
- :fail:
- Response: Error
- Message: Authentication failed
-
-:errors:
- :missing_action:
- Response: Error
- Message: Missing action in request
-
-:pong:
- :with_action_id:
- ActionID: 1287381.1238
- Response: Pong
- :without_action_id:
- Response: Pong
- :with_extra_keys:
- ActionID: 1287381.1238
- Response: Pong
- Blah: This is something arbitrary
- Blahhh: something else arbitrary
View
16 features/support/env.rb
@@ -1,16 +0,0 @@
-require 'simplecov'
-require 'simplecov-rcov'
-class SimpleCov::Formatter::MergedFormatter
- def format(result)
- SimpleCov::Formatter::HTMLFormatter.new.format(result)
- SimpleCov::Formatter::RcovFormatter.new.format(result)
- end
-end
-SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
-SimpleCov.start do
- add_filter "/vendor/"
-end
-
-require 'cucumber'
-require 'rspec'
-require 'ruby_fs'
View
22 features/support/introspective_lexer.rb
@@ -1,22 +0,0 @@
-class IntrospectiveManagerStreamLexer < RubyFS::Lexer
- attr_reader :received_messages, :syntax_errors, :ami_errors
-
- def initialize(*args)
- super
- @received_messages = []
- @syntax_errors = []
- @ami_errors = []
- end
-
- def message_received(message = @current_message)
- @received_messages << message
- end
-
- def error_received(error_message)
- @ami_errors << error_message
- end
-
- def syntax_error_encountered(ignored_chunk)
- @syntax_errors << ignored_chunk
- end
-end
View
97 features/support/lexer_helper.rb
@@ -1,97 +0,0 @@
-FIXTURES = YAML.load_file File.dirname(__FILE__) + "/ami_fixtures.yml"
-
-def fixture(path, overrides = {})
- path_segments = path.split '/'
- selected_event = path_segments.inject(FIXTURES.clone) do |hash, segment|
- raise ArgumentError, path + " not found!" unless hash
- hash[segment.to_sym]
- end
-
- # Downcase all keys in the event and the overrides
- selected_event = selected_event.inject({}) do |downcased_hash,(key,value)|
- downcased_hash[key.to_s.downcase] = value
- downcased_hash
- end
-
- overrides = overrides.inject({}) do |downcased_hash,(key,value)|
- downcased_hash[key.to_s.downcase] = value
- downcased_hash
- end
-
- # Replace variables in the selected_event with any overrides, ignoring case of the key
- keys_with_variables = selected_event.select { |(key, value)| value.kind_of?(Symbol) || value.kind_of?(Hash) }
-
- keys_with_variables.each do |original_key, variable_type|
- # Does an override an exist in the supplied list?
- if overriden_pair = overrides.find { |(key, value)| key == original_key }
- # We have an override! Let's replace the template value in the event with the overriden value
- selected_event[original_key] = overriden_pair.last
- else
- # Based on the type, let's generate a placeholder.
- selected_event[original_key] = case variable_type
- when :string
- rand(100000).to_s
- when Hash
- if variable_type.has_key? "one_of"
- # Choose a random possibility
- possibilities = variable_type['one_of']
- possibilities[rand(possibilities.size)]
- else
- raise "Unrecognized Hash fixture property! ##{variable_type.keys.to_sentence}"
- end
- else
- raise "Unrecognized fixture variable type #{variable_type}!"
- end
- end
- end
-
- hash_to_stanza(selected_event).tap do |event|
- selected_event.each_pair do |key, value|
- event.meta_def(key) { value }
- end
- end
-end
-
-def hash_to_stanza(hash)
- ordered_hash = hash.to_a
- starter = hash.find { |(key, value)| key.strip =~ /^(Response|Action)$/i }
- ordered_hash.unshift ordered_hash.delete(starter) if starter
- ordered_hash.inject(String.new) do |stanza,(key, value)|
- stanza + "#{key}: #{value}\r\n"
- end + "\r\n"
-end
-
-def format_newlines(string)
- # HOLY FUCK THIS IS UGLY
- tmp_replacement = random_string
- string.gsub("\r\n", tmp_replacement).
- gsub("\n", "\r\n").
- gsub(tmp_replacement, "\r\n")
-end
-
-def random_string
- (rand(1_000_000_000_000) + 1_000_000_000).to_s
-end
-
-def follows_body_text(name)
- case name
- when "ragel_description"
- "Ragel is a software development tool that allows user actions to
- be embedded into the transitions of a regular expression's corresponding state machine,
- eliminating the need to switch from the regular expression engine and user code execution
- environment and back again."
- when "with_colon_after_first_line"
- "Host Username Refresh State Reg.Time \r\nlax.teliax.net:5060 jicksta 105 Registered Tue, 11 Nov 2008 02:29:55"
- when "show_channels_from_wayne"
- "Channel Location State Application(Data)\r\n0 active channels\r\n0 active calls"
- when "empty_string"
- ""
- end
-end
-
-def syntax_error_data(name)
- case name
- when "immediate_packet_with_colon"
- "!IJ@MHY:!&@B*!B @ ! @^! @ !@ !\r!@ ! @ !@ ! !!m, \n\\n\n"
- end
-end
View
8 lib/ruby_fs.rb
@@ -2,7 +2,6 @@
uuidtools
future-resource
logger
- girl_friday
countdownlatch
celluloid/io
}.each { |f| require f }
@@ -15,13 +14,6 @@ module RubyFS
end
%w{
- action
- client
- error
- event
- lexer
- metaprogramming
- response
stream
version
}.each { |f| require "ruby_fs/#{f}" }
View
147 lib/ruby_fs/action.rb
@@ -1,147 +0,0 @@
-module RubyFS
- class Action
- attr_reader :name, :headers, :action_id
-
- attr_accessor :state
-
- CAUSAL_EVENT_NAMES = %w[queuestatus sippeers iaxpeers parkedcalls dahdishowchannels coreshowchannels
- dbget status agents konferencelist] unless defined? CAUSAL_EVENT_NAMES
-
- def initialize(name, headers = {}, &block)
- @name = name.to_s.downcase.freeze
- @headers = headers.freeze
- @action_id = UUIDTools::UUID.random_create.to_s
- @response = FutureResource.new
- @response_callback = block
- @state = :new
- @events = []
- @event_lock = Mutex.new
- end
-
- [:new, :sent, :complete].each do |state|
- define_method("#{state}?") { @state == state }
- end
-
- def replies_with_action_id?
- !UnsupportedActionName::UNSUPPORTED_ACTION_NAMES.include? name
- end
-
- ##
- # When sending an action with "causal events" (i.e. events which must be collected to form a proper
- # response), AMI should send a particular event which instructs us that no more events will be sent.
- # This event is called the "causal event terminator".
- #
- # Note: you must supply both the name of the event and any headers because it's possible that some uses of an
- # action (i.e. same name, different headers) have causal events while other uses don't.
- #
- # @param [String] name the name of the event
- # @param [Hash] the headers associated with this event
- # @return [String] the downcase()'d name of the event name for which to wait
- #
- def has_causal_events?
- CAUSAL_EVENT_NAMES.include? name
- end
-
- ##
- # Used to determine the event name for an action which has causal events.
- #
- # @param [String] action_name
- # @return [String] The corresponding event name which signals the completion of the causal event sequence.
- #
- def causal_event_terminator_name
- return unless has_causal_events?
- case name
- when "sippeers", "iaxpeers"
- "peerlistcomplete"
- when "dbget"
- "dbgetresponse"
- when "konferencelist"
- "conferencelistcomplete"
- else
- name + "complete"
- end
- end
-
- ##
- # Converts this action into a protocol-valid String, ready to be sent over a socket.
- #
- def to_s
- @textual_representation ||= (
- "Action: #{@name}\r\nActionID: #{@action_id}\r\n" +
- @headers.map { |(key,value)| "#{key}: #{value}" }.join("\r\n") +
- (@headers.any? ? "\r\n\r\n" : "\r\n")
- )
- end
-
- #
- # If the response has simply not been received yet from Asterisk, the calling Thread will block until it comes
- # in. Once the response comes in, subsequent calls immediately return a reference to the ManagerInterfaceResponse
- # object.
- #
- def response(timeout = nil)
- @response.resource(timeout).tap do |resp|
- raise resp if resp.is_a? Exception
- end
- end
-
- def response=(other)
- @state = :complete
- @response.resource = other
- @response_callback.call other if @response_callback
- end
-
- def <<(message)
- case message
- when Error
- self.response = message
- when Event
- raise StandardError, 'This action should not trigger events. Maybe it is now a causal action? This is most likely a bug in RubyFS' unless has_causal_events?
- @event_lock.synchronize do
- @events << message
- end
- self.response = @pending_response if message.name.downcase == causal_event_terminator_name
- when Response
- if has_causal_events?
- @pending_response = message
- else
- self.response = message
- end
- end
- end
-
- def events
- @event_lock.synchronize do
- @events.dup
- end
- end
-
- def eql?(other)
- to_s == other.to_s
- end
- alias :== :eql?
-
- def sync_timeout
- name.downcase == 'originate' && !headers[:async] ? 60 : 10
- end
-
- ##
- # This class will be removed once this AMI library fully supports all known protocol anomalies.
- #
- class UnsupportedActionName < ArgumentError
- UNSUPPORTED_ACTION_NAMES = %w[queues] unless defined? UNSUPPORTED_ACTION_NAMES
-
- # Blacklist some actions depends on the Asterisk version
- def self.preinitialize(version)
- if version < 1.8
- %w[iaxpeers muteaudio mixmonitormute aocmessage].each do |action|
- UNSUPPORTED_ACTION_NAMES << action
- end
- end
- end
-
- def initialize(name)
- 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."
- end
- end
- end
-end # RubyFS
View
202 lib/ruby_fs/client.rb
@@ -1,202 +0,0 @@
-module RubyFS
- class Client
- attr_reader :options, :action_queue, :events_stream, :actions_stream
-
- def initialize(options)
- @options = options
- @logger = options[:logger]
- @logger.level = options[:log_level] || Logger::DEBUG if @logger
- @event_handler = @options[:event_handler]
- @state = :stopped
-
- stop_writing_actions
-
- @pending_actions = {}
- @sent_actions = {}
- @actions_lock = Mutex.new
-
- @action_queue = GirlFriday::WorkQueue.new(:actions, :size => 1, :error_handler => ErrorHandler) do |action|
- @actions_write_blocker.wait
- _send_action action
- begin
- action.response action.sync_timeout
- rescue Timeout::Error => e
- logger.error "Timed out waiting for a response to #{action}"
- rescue RubyFS::Error
- nil
- end
- end
-
- @message_processor = GirlFriday::WorkQueue.new(:messages, :size => 1, :error_handler => ErrorHandler) do |message|
- handle_message message
- end
-
- @event_processor = GirlFriday::WorkQueue.new(:events, :size => 2, :error_handler => ErrorHandler) do |event|
- handle_event event
- end
- end
-
- [:started, :stopped, :ready].each do |state|
- define_method("#{state}?") { @state == state }
- end
-
- def start
- @events_stream = start_stream lambda { |event| @event_processor << event }
- @actions_stream = start_stream lambda { |message| @message_processor << message }
- @state = :started
- streams.each(&:join)
- end
-
- def stop
- streams.each do |stream|
- begin
- stream.terminate
- rescue => e
- logger.error e if logger
- end
- end
- end
-
- def send_action(action, headers = {}, &block)
- (action.is_a?(Action) ? action : Action.new(action, headers, &block)).tap do |action|
- logger.trace "[QUEUE]: #{action.inspect}" if logger
- register_pending_action action
- action_queue << action
- end
- end
-
- def handle_message(message)
- logger.trace "[RECV-ACTIONS]: #{message.inspect}" if logger
- case message
- when Stream::Connected
- login_actions
- when Stream::Disconnected
- stop_writing_actions
- stop
- when Event
- action = @current_action_with_causal_events
- if action
- message.action = action
- action << message
- @current_action_with_causal_events = nil if action.complete?
- else
- if message.name == 'FullyBooted'
- pass_event message
- start_writing_actions
- else
- raise StandardError, "Got an unexpected event on actions socket! This AMI command may have a multi-message response. Try making Adhearsion treat it as causal action #{message.inspect}"
- end
- end
- when Response, Error
- action = sent_action_with_id message.action_id
- raise StandardError, "Received an AMI response with an unrecognized ActionID!! This may be an bug! #{message.inspect}" unless action
- message.action = action
-
- # By this point the write loop will already have started blocking by calling the response() method on the
- # action. Because we must collect more events before we wake the write loop up again, let's create these
- # instance variable which will needed when the subsequent causal events come in.
- @current_action_with_causal_events = action if action.has_causal_events?
-
- action << message
- end
- end
-
- def handle_event(event)
- logger.trace "[RECV-EVENTS]: #{event.inspect}" if logger
- case event
- when Stream::Connected
- login_events
- when Stream::Disconnected
- stop
- else
- pass_event event
- end
- end
-
- def _send_action(action)
- logger.trace "[SEND]: #{action.inspect}" if logger
- transition_action_to_sent action
- actions_stream.send_action action
- action.state = :sent
- action
- end
-
- private
-
- def pass_event(event)
- @event_handler.call event if @event_handler.respond_to? :call
- end
-
- def register_pending_action(action)
- @actions_lock.synchronize do
- @pending_actions[action.action_id] = action
- end
- end
-
- def transition_action_to_sent(action)
- @actions_lock.synchronize do
- @pending_actions.delete action.action_id
- @sent_actions[action.action_id] = action
- end
- end
-
- def sent_action_with_id(action_id)
- @actions_lock.synchronize do
- @sent_actions.delete action_id
- end
- end
-
- def start_writing_actions
- @actions_write_blocker.countdown!
- end
-
- def stop_writing_actions
- @actions_write_blocker = CountDownLatch.new 1
- end
-
- def login_actions
- action = login_action do |response|
- pass_event response if response.is_a? Error
- send_action 'Events', 'EventMask' => 'Off'
- end
-
- register_pending_action action
- Thread.new { _send_action action }
- end
-
- def login_events
- login_action.tap do |action|
- events_stream.send_action action
- end
- end
-
- def login_action(&block)
- Action.new 'Login',
- 'Username' => options[:username],
- 'Secret' => options[:password],
- 'Events' => 'On',
- &block
- end
-
- def start_stream(callback)
- Stream.new @options[:host], @options[:port], callback
- end
-
- def logger
- super
- rescue NoMethodError
- @logger
- end
-
- def streams
- [actions_stream, events_stream].compact
- end
-
- class ErrorHandler
- def handle(error)
- puts error.message
- puts error.backtrace.join("\n")
- end
- end
- end
-end
View
25 lib/ruby_fs/error.rb
@@ -1,25 +0,0 @@
-module RubyFS
- class Error < StandardError
- attr_accessor :message, :action
-
- def initialize
- @headers = Hash.new
- end
-
- def [](key)
- @headers[key]
- end
-
- def []=(key,value)
- @headers[key] = value
- end
-
- def action_id
- @headers['ActionID']
- end
-
- def inspect
- "#<#{self.class} #{[:message, :headers].map { |c| "#{c}=#{self.__send__(c).inspect rescue nil}" }.compact * ', '}>"
- end
- end
-end # RubyFS
View
16 lib/ruby_fs/event.rb
@@ -1,16 +0,0 @@
-require 'ruby_fs/response'
-
-module RubyFS
- class Event < Response
- attr_reader :name
-
- def initialize(name)
- super()
- @name = name
- end
-
- def inspect_attributes
- [:name] + super
- end
- end
-end # RubyFS
View
303 lib/ruby_fs/lexer.rl.rb
@@ -1,303 +0,0 @@
-module RubyFS
- class Lexer
-
- KILOBYTE = 1024
- BUFFER_SIZE = 128 * KILOBYTE unless defined? BUFFER_SIZE
-
- ##
- # IMPORTANT! See method documentation for adjust_pointers!
- #
- # @see adjust_pointers
- #
- POINTERS = [
- :@current_pointer,
- :@token_start,
- :@token_end,
- :@version_start,
- :@event_name_start,
- :@current_key_position,
- :@current_value_position,
- :@last_seen_value_end,
- :@error_reason_start,
- :@follows_text_start,
- :@current_syntax_error_start,
- :@immediate_response_start
- ]
-
- %%{
- machine ami_protocol_parser;
-
- # All required Ragel actions are implemented as Ruby methods.
-
- # Executed after a "Response: Success" or "Response: Pong"
- action init_success { init_success }
-
- action init_response_follows { init_response_follows }
-
- action init_error { init_error }
-
- action message_received { message_received @current_message }
- action error_received { error_received @current_message }
-
- action version_starts { version_starts }
- action version_stops { version_stops }
-
- action key_starts { key_starts }
- action key_stops { key_stops }
-
- action value_starts { value_starts }
- action value_stops { value_stops }
-
- action error_reason_starts { error_reason_starts }
- action error_reason_stops { error_reason_stops }
-
- action syntax_error_starts { syntax_error_starts }
- action syntax_error_stops { syntax_error_stops }
-
- action immediate_response_starts { immediate_response_starts }
- action immediate_response_stops { immediate_response_stops }
-
- action follows_text_starts { follows_text_starts }
- action follows_text_stops { follows_text_stops }
-
- action event_name_starts { event_name_starts }
- action event_name_stops { event_name_stops }
-
- include ami_protocol_parser_machine "lexer_machine.rl";
-
- }%%##
-
- attr_accessor :ami_version
-
- def initialize(delegate = nil)
- @delegate = delegate
- @data = ""
- @current_pointer = 0
- @ragel_stack = []
- @ami_version = 0.0
-
- %%{
- # All other variables become local, letting Ruby garbage collect them. This
- # prevents us from having to manually reset them.
-
- variable data @data;
- variable p @current_pointer;
- variable pe @data_ending_pointer;
- variable cs @current_state;
- variable ts @token_start;
- variable te @token_end;
- variable act @ragel_act;
- variable eof @eof;
- variable stack @ragel_stack;
- variable top @ragel_stack_top;
-
- write data;
- write init;
- }%%##
- end
-
- def <<(new_data)
- extend_buffer_with new_data
- resume!
- end
-
- def resume!
- %%{ write exec; }%%##
- end
-
- def extend_buffer_with(new_data)
- length = new_data.size
-
- if length > BUFFER_SIZE
- raise Exception, "ERROR: Buffer overrun! Input size (#{new_data.size}) larger than buffer (#{BUFFER_SIZE})"
- end
-
- if length + @data.size > BUFFER_SIZE
- if @data.size != @current_pointer
- if @current_pointer < length
- # We are about to shift more bytes off the array than we have
- # parsed. This will cause the parser to lose state so
- # integrity cannot be guaranteed.
- raise Exception, "ERROR: Buffer overrun! AMI parser cannot guarantee sanity. New data size: #{new_data.size}; Current pointer at #{@current_pointer}; Data size: #{@data.size}"
- end
- end
- @data.slice! 0...length
- adjust_pointers -length
- end
- @data << new_data
- @data_ending_pointer = @data.size
- end
-
- protected
-
- ##
- # This method will adjust all pointers into the buffer according
- # to the supplied offset. This is necessary any time the buffer
- # changes, for example when the sliding window is incremented forward
- # after new data is received.
- #
- # It is VERY IMPORTANT that when any additional pointers are defined
- # that they are added to this method. Unpredictable results may
- # otherwise occur!
- #
- # @see https://adhearsion.lighthouseapp.com/projects/5871-adhearsion/tickets/72-ami-lexer-buffer-offset#ticket-72-26
- #
- # @param offset Adjust pointers by offset. May be negative.
- #
- def adjust_pointers(offset)
- POINTERS.each do |ptr|
- value = instance_variable_get(ptr)
- instance_variable_set(ptr, value + offset) if !value.nil?
- end
- end
-
- ##
- # Called after a response or event has been successfully parsed.
- #
- # @param [Response, Event] message The message just received
- #
- def message_received(message)
- @delegate.message_received message
- end
-
- ##
- # Called when there is an Error: stanza on the socket. Could be caused by executing an unrecognized command, trying
- # to originate into an invalid priority, etc. Note: many errors' responses are actually tightly coupled to a
- # Event which comes directly after it. Often the message will say something like "Channel status
- # will follow".
- #
- # @param [String] reason The reason given in the Message: header for the error stanza.
- #
- def error_received(message)
- @delegate.error_received message
- end
-
- ##
- # Called when there's a syntax error on the socket. This doesn't happen as often as it should because, in many cases,
- # it's impossible to distinguish between a syntax error and an immediate packet.
- #
- # @param [String] ignored_chunk The offending text which caused the syntax error.
- def syntax_error_encountered(ignored_chunk)
- @delegate.syntax_error_encountered ignored_chunk
- end
-
- def init_success
- @current_message = Response.new
- end
-
- def init_response_follows
- @current_message = Response.new
- end
-
- def init_error
- @current_message = Error.new
- end
-
- def version_starts
- @version_start = @current_pointer
- end
-
- def version_stops
- self.ami_version = @data[@version_start...@current_pointer].to_f
- @version_start = nil
- end
-
- def event_name_starts
- @event_name_start = @current_pointer
- end
-
- def event_name_stops
- event_name = @data[@event_name_start...@current_pointer]
- @event_name_start = nil
- @current_message = Event.new(event_name)
- end
-
- def key_starts
- @current_key_position = @current_pointer
- end
-
- def key_stops
- @current_key = @data[@current_key_position...@current_pointer]
- end
-
- def value_starts
- @current_value_position = @current_pointer
- end
-
- def value_stops
- @current_value = @data[@current_value_position...@current_pointer]
- @last_seen_value_end = @current_pointer + 2 # 2 for \r\n
- add_pair_to_current_message
- end
-
- def error_reason_starts
- @error_reason_start = @current_pointer
- end
-
- def error_reason_stops
- @current_message.message = @data[@error_reason_start...@current_pointer]
- end
-
- def follows_text_starts
- @follows_text_start = @current_pointer
- end
-
- def follows_text_stops
- text = @data[@last_seen_value_end..@current_pointer]
- text.sub! /\r?\n--END COMMAND--/, ""
- @current_message.text_body = text
- @follows_text_start = nil
- end
-
- def add_pair_to_current_message
- @current_message[@current_key] = @current_value
- reset_key_and_value_positions
- end
-
- def reset_key_and_value_positions
- @current_key, @current_value, @current_key_position, @current_value_position = nil
- end
-
- def syntax_error_starts
- @current_syntax_error_start = @current_pointer # Adding 1 since the pointer is still set to the last successful match
- end
-
- def syntax_error_stops
- # Subtracting 3 from @current_pointer below for "\r\n" which separates a stanza
- offending_data = @data[@current_syntax_error_start...@current_pointer - 1]
- syntax_error_encountered offending_data
- @current_syntax_error_start = nil
- end
-
- def immediate_response_starts
- @immediate_response_start = @current_pointer
- end
-
- def immediate_response_stops
- message = @data[@immediate_response_start...(@current_pointer -1)]
- message_received Response.from_immediate_response(message)
- end
-
- ##
- # This method is used primarily in debugging.
- #
- def view_buffer(message = nil)
- message ||= "Viewing the buffer"
-
- buffer = @data.clone
- buffer.insert(@current_pointer, "\033[0;31m\033[1;31m^\033[0m")
-
- buffer.gsub!("\r", "\\\\r")
- buffer.gsub!("\n", "\\n\n")
-
- puts <<-INSPECTION
-VVVVVVVVVVVVVVVVVVVVVVVVVVVVV
-#### #{message}
-#############################
-#{buffer}
-#############################
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- INSPECTION
- end
- end
-end
View
87 lib/ruby_fs/lexer_machine.rl
@@ -1,87 +0,0 @@
-%%{ #%
-
-#########
-## This file is written with the Ragel programming language and parses the Asterisk Manager Interface protocol. It depends
-## upon Ragel actions which should be implemented in another Ragel-parsed file which includes this file.
-##
-## Ragel was used because the AMI protocol is extremely non-deterministic and, in the edge cases, requires something both
-## very robust and something which can recover from syntax errors.
-##
-## Note: This file is language agnostic. From this AMI parsers in many other languages can be generated.
-#########
-
-machine ami_protocol_parser_machine;
-
-cr = "\r"; # A carriage return. Used before (almost) every newline character.
-lf = "\n"; # Newline. Used (with cr) to separate key/value pairs and stanzas.
-crlf = cr lf; # Means "carriage return and line feed". Used to separate key/value pairs and stanzas
-loose_newline = cr? lf; # Used sometimes when the AMI protocol is nondeterministic about the delimiter
-
-white = [\t ]; # Single whitespace character, either a tab or a space
-colon = ":" [ ]**; # Separates keys from values. "A colon followed by any number of spaces"
-stanza_break = crlf crlf; # The seperator between two stanzas.
-rest_of_line = (any* -- crlf); # Match all characters until the next line seperator.
-
-Prompt = "Asterisk Call Manager/" digit+ >version_starts "." digit+ %version_stops crlf;
-
-Key = ((alnum | print) -- (cr | lf | ":"))+;
-KeyValuePair = Key >key_starts %key_stops colon rest_of_line >value_starts %value_stops crlf;
-
-FollowsDelimiter = loose_newline "--END COMMAND--";
-
-Response = "Response"i colon;
-
-Success = Response "Success"i %init_success crlf @{ fgoto success; };
-Pong = Response "Pong"i %init_success crlf @{ fgoto success; };
-Event = "Event"i colon %event_name_starts rest_of_line %event_name_stops crlf @{ fgoto success; };
-Error = Response "Error"i %init_error crlf (("Message"i colon rest_of_line >error_reason_starts crlf >error_reason_stops) | KeyValuePair)+ crlf @error_received;
-Follows = Response "Follows"i crlf @init_response_follows @{ fgoto response_follows; };
-
-# For "Response: Follows"
-FollowsBody = (any* -- FollowsDelimiter) >follows_text_starts FollowsDelimiter @follows_text_stops crlf;
-
-ImmediateResponse = (any+ -- (loose_newline | ":")) >immediate_response_starts loose_newline @immediate_response_stops @{fret;};
-SyntaxError = (any+ -- crlf) >syntax_error_starts crlf @syntax_error_stops;
-
-irregularity := |*
- ImmediateResponse; # Performs the fret in the ImmediateResponse FSM
- SyntaxError => { fret; };
-*|;
-
-# When a new socket is established, Asterisk will send the version of the protocol per the Prompt machine. Because it's
-# tedious for unit tests to always send this, we'll put some intelligence into this parser to support going straight into
-# the protocol-parsing machine. It's also conceivable that a variant of AMI would not send this initial information.
-main := |*
- Prompt => { fgoto protocol; };
- any => {
- # If this scanner's look-ahead capability didn't match the prompt, let's ignore the need for a prompt
- fhold;
- fgoto protocol;
- };
-*|;
-
-protocol := |*
- Prompt;
- Success;
- Pong;
- Event;
- Error;
- Follows crlf;
- crlf => { fgoto protocol; }; # If we get a crlf out of place, let's just ignore it.
- any => {
- # If NONE of the above patterns match, we consider this a syntax error. The irregularity machine can recover gracefully.
- fhold;
- fcall irregularity;
- };
-*|;
-
-success := KeyValuePair* crlf @message_received @{fgoto protocol;};
-
-# For the "Response: Follows" protocol abnormality. What happens if there's a protocol irregularity in this state???
-response_follows := |*
- KeyValuePair+;
- FollowsBody;
- crlf @{ message_received @current_message; fgoto protocol; };
-*|;
-
-}%%
View
17 lib/ruby_fs/metaprogramming.rb
@@ -1,17 +0,0 @@
-class Object
- def metaclass
- class << self
- self
- end
- end
-
- def meta_eval(&block)
- metaclass.instance_eval &block
- end
-
- def meta_def(name, &block)
- meta_eval do
- define_method name, &block
- end
- end
-end
View
57 lib/ruby_fs/response.rb
@@ -1,57 +0,0 @@
-module RubyFS
- ##
- # This is the object containing a response from Asterisk.
- #
- # Note: not all responses have an ActionID!
- #
- class Response
- class << self
- def from_immediate_response(text)
- new.tap do |instance|
- instance.text_body = text
- end
- end
- end
-
- attr_accessor :action,
- :text_body # For "Response: Follows" sections
- attr_reader :events
-
- def initialize
- @headers = Hash.new
- end
-
- def has_text_body?
- !!@text_body
- end
-
- def headers
- @headers.clone
- end
-
- def [](arg)
- @headers[arg.to_s]
- end
-
- def []=(key,value)
- @headers[key.to_s] = value
- end
-
- def action_id
- @headers['ActionID']
- end
-
- def inspect
- "#<#{self.class} #{inspect_attributes.map { |c| "#{c}=#{self.__send__(c).inspect rescue nil}" }.compact * ', '}>"
- end
-
- def inspect_attributes
- [:headers, :text_body, :events, :action]
- end
-
- def eql?(o, *fields)
- o.is_a?(self.class) && (fields + inspect_attributes).all? { |f| self.__send__(f) == o.__send__(f) }
- end
- alias :== :eql?
- end
-end # RubyFS
View
26 lib/ruby_fs/stream.rb
@@ -17,7 +17,6 @@ def initialize(host, port, event_callback)
super()
@event_callback = event_callback
logger.debug "Starting up..."
- @lexer = Lexer.new self
@socket = TCPSocket.from_ruby_socket ::TCPSocket.new(host, port)
post_init
run!
@@ -36,35 +35,28 @@ def run
def post_init
@state = :started
- @event_callback.call Connected.new
+ fire_event Connected.new
end
def send_data(data)
- @socket.write data
- end
-
- def send_action(action)
- logger.debug "[SEND] #{action.to_s}"
- send_data action.to_s
+ logger.debug "[SEND] #{data.to_s}"
+ @socket.write data.to_s
end
def receive_data(data)
logger.debug "[RECV] #{data}"
- @lexer << data
- end
-
- def message_received(message)
- logger.debug "[RECV] #{message.inspect}"
- @event_callback.call message
+ fire_event data
end
- alias :error_received :message_received
-
def finalize
logger.debug "Finalizing stream"
@socket.close if @socket
@state = :stopped
- @event_callback.call Disconnected.new
+ fire_event Disconnected.new
+ end
+
+ def fire_event(event)
+ @event_callback.call event
end
def logger
View
2  ruby_ami.gemspec
@@ -21,12 +21,10 @@ Gem::Specification.new do |s|
s.add_runtime_dependency %q<uuidtools>, [">= 0"]
s.add_runtime_dependency %q<celluloid-io>, ["~> 0.11.0"]
s.add_runtime_dependency %q<future-resource>, [">= 0"]
- s.add_runtime_dependency %q<girl_friday>, [">= 0"]
s.add_runtime_dependency %q<countdownlatch>, ["~> 1.0"]
s.add_development_dependency %q<bundler>, ["~> 1.0"]
s.add_development_dependency %q<rspec>, [">= 2.5.0"]
- s.add_development_dependency %q<cucumber>, [">= 0"]
s.add_development_dependency %q<ci_reporter>, [">= 1.6.3"]
s.add_development_dependency %q<yard>, ["~> 0.6.0"]
s.add_development_dependency %q<rake>, [">= 0"]
View
187 spec/ruby_fs/action_spec.rb
@@ -1,187 +0,0 @@
-require 'spec_helper'
-
-module RubyFS
- describe Action do
- let(:name) { 'foobar' }
- let(:headers) { {'foo' => 'bar'} }
-
- subject do
- Action.new name, headers do |response|
- @foo = response
- end
- end
-
- it { should be_new }
-
- describe "SIPPeers actions" do
- subject { Action.new('SIPPeers') }
- its(:has_causal_events?) { should be true }
- end
-
- describe "Queues actions" do
- subject { Action.new('Queues') }
- its(:replies_with_action_id?) { should == false }
- end
-
- describe "IAXPeers actions" do
- before { pending }
- # FIXME: This test relies on the side effect that earlier tests have run
- # and initialized the UnsupportedActionName::UNSUPPORTED_ACTION_NAMES
- # constant for an "unknown" version of Asterisk. This should be fixed
- # to be more specific about which version of Asterisk is under test.
- # IAXPeers is supported (with Action IDs!) since Asterisk 1.8
- subject { Action.new('IAXPeers') }
- its(:replies_with_action_id?) { should == false }
- end
-
- describe "the ParkedCalls terminator event" do
- subject { Action.new('ParkedCalls') }
- its(:causal_event_terminator_name) { should == "parkedcallscomplete" }
- end
-
- it "should properly convert itself into a String when additional headers are given" do
- string = Action.new("Hawtsawce", "Monkey" => "Zoo").to_s
- string.should =~ /^Action: Hawtsawce\r\n/i
- string.should =~ /[^\n]\r\n\r\n$/
- string.should =~ /^(\w+:\s*[\w-]+\r\n){3}\r\n$/
- end
-
- it "should properly convert itself into a String when no additional headers are given" do
- Action.new("Ping").to_s.should =~ /^Action: Ping\r\nActionID: [\w-]+\r\n\r\n$/i
- Action.new("ParkedCalls").to_s.should =~ /^Action: ParkedCalls\r\nActionID: [\w-]+\r\n\r\n$/i
- end
-
- it 'should be able to be marked as sent' do
- subject.state = :sent
- subject.should be_sent
- end
-
- it 'should be able to be marked as complete' do
- subject.state = :complete
- subject.should be_complete
- end
-
- describe '#<<' do
- describe 'for a non-causal action' do
- context 'with a response' do
- let(:response) { Response.new }
-
- it 'should set the response' do
- subject << response
- subject.response.should be response
- end
- end
-
- context 'with an error' do
- let(:error) { Error.new.tap { |e| e.message = 'AMI error' } }
-
- it 'should set the response and raise the error when reading it' do
- subject << error
- lambda { subject.response }.should raise_error Error, 'AMI error'
- end
- end
-
- context 'with an event' do
- it 'should raise an error' do
- lambda { subject << Event.new('foo') }.should raise_error StandardError, /causal action/
- end
- end
- end
-
- describe 'for a causal action' do
- let(:name) { 'Status' }
-
- context 'with a response' do
- let(:message) { Response.new }
-
- before { subject << message }
-
- it { should_not be_complete }
- end
-
- context 'with an event' do
- let(:event) { Event.new 'foo' }
-
- before { subject << event }
-
- its(:events) { should == [event] }
- end
-
- context 'with a terminating event' do
- let(:response) { Response.new }
- let(:event) { Event.new 'StatusComplete' }
-
- before do
- subject << response
- subject.should_not be_complete
- subject << event
- end
-
- its(:events) { should == [event] }
-
- it { should be_complete }
-
- its(:response) { should be response }
- end
- end
- end
-
- describe 'setting the response' do
- let(:response) { :bar }
-
- before { subject.response = response }
-
- it { should be_complete }
- its(:response) { should == response }
-
- it 'should call the response callback with the response' do
- @foo.should == response
- end
- end
-
- describe 'comparison' do
- describe 'with another Action' do
- context 'with identical name and headers' do
- let(:other) { Action.new name, headers }
- it { should == other }
- end
-
- context 'with identical name and different headers' do
- let(:other) { Action.new name, 'boo' => 'baz' }
- it { should_not == other }
- end
-
- context 'with different name and identical headers' do
- let(:other) { Action.new 'BARBAZ', headers }
- it { should_not == other }
- end
- end
-
- it { should_not == :foo }
- end
-
- describe "#sync_timeout" do
- it "should be 10 seconds" do
- subject.sync_timeout.should be == 10
- end
-
- context "for an asynchronous Originate" do
- let(:name) { 'Originate' }
- let(:headers) { {:async => true} }
-
- it "should be 60 seconds" do
- subject.sync_timeout.should be == 10
- end
- end
-
- context "for a synchronous Originate" do
- let(:name) { 'Originate' }
- let(:headers) { {:async => false} }
-
- it "should be 60 seconds" do
- subject.sync_timeout.should be == 60
- end
- end
- end
- end # Action
-end # RubyFS
View
348 spec/ruby_fs/client_spec.rb
@@ -1,348 +0,0 @@
-require 'spec_helper'
-
-module RubyFS
- describe Client do
- let(:event_handler) { [] }
-
- let(:options) do
- {
- :host => '127.0.0.1',
- :port => 50000 - rand(1000),
- :username => 'username',
- :password => 'password',
- :event_handler => lambda { |event| event_handler << event }
- }
- end
-
- subject { Client.new options }
-
- it { should be_stopped }
-
- its(:options) { should == options }
-
- its(:action_queue) { should be_a GirlFriday::WorkQueue }
-
- its(:streams) { should == [] }
-
- describe 'starting up' do
- before do
- ms = MockServer.new
- ms.expects(:receive_data).at_least_once
- s = ServerMock.new options[:host], options[:port], ms
- Thread.new { subject.start }
- sleep 0.2
- end
-
- it { should be_started }
-
- its(:events_stream) { should be_a Stream }
- its(:actions_stream) { should be_a Stream }
- end
-
- describe 'logging in streams' do
- context 'when the actions stream connects' do
- let(:mock_actions_stream) { mock 'Actions Stream' }
-
- let :expected_login_action do
- Action.new 'Login',
- 'Username' => 'username',
- 'Secret' => 'password',
- 'Events' => 'On'
- end
-
- before do
- Action.any_instance.stubs(:response).returns(true)
- subject.stubs(:actions_stream).returns mock_actions_stream
- end
-
- it 'should log in' do
- mock_actions_stream.expects(:send_action).with do |action|
- action.to_s.should == expected_login_action.to_s
- end
-
- subject.handle_message(Stream::Connected.new).join
- end
- end
-
- context 'when the events stream connects' do
- let(:mock_events_stream) { mock 'Events Stream' }
-
- let :expected_login_action do
- Action.new 'Login',
- 'Username' => 'username',
- 'Secret' => 'password',
- 'Events' => 'On'
- end
-
- before do
- subject.stubs(:events_stream).returns mock_events_stream
- end
-
- it 'should log in' do
- mock_events_stream.expects(:send_action).with expected_login_action
-
- subject.handle_event Stream::Connected.new
-
- event_handler.should be_empty
- end
- end
- end
-
- describe 'when the events stream disconnects' do
- it 'should stop' do
- subject.expects(:stop).once
- subject.handle_event Stream::Disconnected.new
- event_handler.should be_empty
- end
- end
-
- describe 'when the actions stream disconnects' do
- before do
- Action.any_instance.stubs(:response).returns(true)
- end
-
- it 'should prevent further actions being sent' do
- subject.expects(:_send_action).once
-
- GirlFriday::WorkQueue.immediate!
- subject.handle_message Stream::Connected.new
- GirlFriday::WorkQueue.queue!
- subject.handle_message Stream::Disconnected.new
-
- action = Action.new 'foo'
- subject.send_action action
-
- sleep 2
-
- action.should be_new
- end
-
- it 'should stop' do
- subject.expects(:stop).once
- subject.handle_message Stream::Disconnected.new
- end
- end
-
- describe 'when an event is received' do
- let(:event) { Event.new 'foobar' }
-
- it 'should call the event handler' do
- subject.handle_event event
- event_handler.should == [event]
- end
- end
-
- describe 'when a FullyBooted event is received on the actions connection' do
- let(:event) { Event.new 'FullyBooted' }
-
- let(:mock_actions_stream) { mock 'Actions Stream' }
-
- let :expected_login_action do
- Action.new 'Login',
- 'Username' => 'username',
- 'Secret' => 'password',
- 'Events' => 'On'
- end
-
- let :expected_events_off_action do
- Action.new 'Events', 'EventMask' => 'Off'
- end
-
- it 'should call the event handler' do
- subject.handle_message event
- event_handler.should == [event]
- end
-
- it 'should begin writing actions' do
- subject.expects(:start_writing_actions).once
- subject.handle_message event
- end
-
- it 'should turn off events' do
- Action.any_instance.stubs(:response).returns true
- subject.stubs(:actions_stream).returns mock_actions_stream
-
- mock_actions_stream.expects(:send_action).once.with expected_login_action
- mock_actions_stream.expects(:send_action).once.with expected_events_off_action
-
- login_action = subject.handle_message(Stream::Connected.new).join
- login_action.value.response = true
-
- subject.handle_message event
- sleep 0.5
- end
- end
-
- describe 'sending actions' do
- let(:action_name) { 'Login' }
- let :headers do
- {
- 'Username' => 'username',
- 'Secret' => 'password'
- }
- end
- let(:expected_action) { Action.new action_name, headers }
-
- let :expected_response do
- Response.new.tap do |response|
- response['ActionID'] = expected_action.action_id
- response['Message'] = 'Action completed'
- end
- end
-
- let(:mock_actions_stream) { mock 'Actions Stream' }
-
- before do
- subject.stubs(:actions_stream).returns mock_actions_stream
- subject.stubs(:login_actions).returns nil
- end
-
- it 'should queue up actions to be sent' do
- subject.handle_message Stream::Connected.new
- subject.action_queue.expects(:<<).with expected_action
- subject.send_action action_name, headers
- end
-
- describe 'forcibly for testing' do
- before do
- subject.actions_stream.expects(:send_action).with expected_action
- subject._send_action expected_action
- end
-
- it 'should mark the action sent' do
- expected_action.should be_sent
- end
-
- let(:receive_response) { subject.handle_message expected_response }
-
- describe 'when a response is received' do
- it 'should be sent to the action' do
- expected_action.expects(:<<).once.with expected_response
- receive_response
- end
-
- it 'should know its action' do
- receive_response
- expected_response.action.should be expected_action
- end
- end
-
- describe 'when an error is received' do
- let :expected_response do
- Error.new.tap do |response|
- response['ActionID'] = expected_action.action_id
- response['Message'] = 'Action failed'
- end
- end
-
- it 'should be sent to the action' do
- expected_action.expects(:<<).once.with expected_response
- receive_response
- end
-
- it 'should know its action' do
- receive_response
- expected_response.action.should be expected_action
- end
- end
-
- describe 'when an event is received' do
- let(:event) { Event.new 'foo' }
-
- let(:receive_event) { subject.handle_message event }
-
- context 'for a causal event' do
- let(:expected_action) { Action.new 'Status' }
-
- it 'should be sent to the action' do
- expected_action.expects(:<<).once.with expected_response
- expected_action.expects(:<<).once.with event
- receive_response
- receive_event
- end
-
- it 'should know its action' do
- expected_action.stubs :<<
- receive_response
- receive_event
- event.action.should be expected_action
- end
- end
-
- context 'for a causal action which is complete' do
- let(:expected_action) { Action.new 'Status' }
-
- before do
- expected_action.stubs(:complete?).returns true
- end
-
- it 'should raise an error' do
- receive_response
- receive_event
- lambda { subject.handle_message Event.new('bar') }.should raise_error StandardError, /causal action/
- end
- end
-
- context 'for a non-causal action' do
- it 'should raise an error' do
- lambda { receive_event }.should raise_error StandardError, /causal action/
- end
- end
- end
- end
-
- describe 'from the queue' do
- it 'should send actions to the stream and set their responses' do
- subject.actions_stream.expects(:send_action).with expected_action
- subject.handle_message Event.new('FullyBooted')
-
- Thread.new do
- GirlFriday::WorkQueue.immediate!
- subject.send_action expected_action
- GirlFriday::WorkQueue.queue!
- end
-
- sleep 0.1
-
- subject.handle_message expected_response
- expected_action.response.should be expected_response
- end
-
- it 'should not send another action if the first action has not yet received a response' do
- subject.actions_stream.expects(:send_action).once.with expected_action
- subject.handle_message Event.new('FullyBooted')
- actions = []
-
- 2.times do
- action = Action.new action_name, headers
- actions << action
- subject.send_action action
- end
-
- sleep 2
-
- actions.should have(2).actions
- actions[0].should be_sent
- actions[1].should be_new
- end
- end
- end
-
- describe '#stop' do
- let(:mock_actions_stream) { mock 'Actions Stream' }
- let(:mock_events_stream) { mock 'Events Stream' }
-
- let(:streams) { [mock_actions_stream, mock_events_stream] }
-
- before do
- subject.stubs(:actions_stream).returns mock_actions_stream
- subject.stubs(:events_stream).returns mock_events_stream
- end
-
- it 'should close both streams' do
- streams.each { |s| s.expects :terminate }
- subject.stop
- end
- end
- end
-end
View
7 spec/ruby_fs/error_spec.rb
@@ -1,7 +0,0 @@
-require 'spec_helper'
-
-module RubyFS
- describe Error do
- pending
- end # Error
-end # RubyFS
View
73 spec/ruby_fs/event_spec.rb
@@ -1,73 +0,0 @@
-require 'spec_helper'
-
-module RubyFS
- describe Event do
- describe "equality" do
- context "with the same name and the same headers" do
- let :event1 do
- Event.new('Hangup').tap do |e|
- e['Channel'] = 'SIP/101-3f3f'
- e['Uniqueid'] = '1094154427.10'
- e['Cause'] = '0'
- end
- end
-
- let :event2 do
- Event.new('Hangup').tap do |e|
- e['Channel'] = 'SIP/101-3f3f'
- e['Uniqueid'] = '1094154427.10'
- e['Cause'] = '0'
- end
- end
-
- it "should be equal" do
- event1.should be == event2
- end
- end
-
- context "with a different name and the same headers" do
- let :event1 do
- Event.new('Hangup').tap do |e|
- e['Channel'] = 'SIP/101-3f3f'
- e['Uniqueid'] = '1094154427.10'
- e['Cause'] = '0'
- end
- end
-
- let :event2 do
- Event.new('Foo').tap do |e|
- e['Channel'] = 'SIP/101-3f3f'
- e['Uniqueid'] = '1094154427.10'
- e['Cause'] = '0'
- end
- end
-
- it "should not be equal" do
- event1.should_not be == event2
- end
- end
-
- context "with the same name and different headers" do
- let :event1 do
- Event.new('Hangup').tap do |e|
- e['Channel'] = 'SIP/101-3f3f'
- e['Uniqueid'] = '1094154427.10'
- e['Cause'] = '0'
- end
- end
-
- let :event2 do
- Event.new('Hangup').tap do |e|
- e['Channel'] = 'SIP/101-3f3f'
- e['Uniqueid'] = '1094154427.10'
- e['Cause'] = '1'
- end
- end
-
- it "should not be equal" do
- event1.should_not be == event2
- end
- end
- end
- end # Event
-end # RubyFS
View
51 spec/ruby_fs/response_spec.rb
@@ -1,51 +0,0 @@
-require 'spec_helper'
-
-module RubyFS
- describe Response do
- describe "equality" do
- context "with the same headers" do
- let :event1 do
- Response.new.tap do |e|
- e['Channel'] = 'SIP/101-3f3f'
- e['Uniqueid'] = '1094154427.10'
- e['Cause'] = '0'
- end
- end
-
- let :event2 do
- Response.new.tap do |e|
- e['Channel'] = 'SIP/101-3f3f'
- e['Uniqueid'] = '1094154427.10'
- e['Cause'] = '0'
- end
- end
-
- it "should be equal" do
- event1.should be == event2
- end
- end
-
- context "with different headers" do
- let :event1 do
- Response.new.tap do |e|
- e['Channel'] = 'SIP/101-3f3f'
- e['Uniqueid'] = '1094154427.10'
- e['Cause'] = '0'
- end
- end
-
- let :event2 do
- Response.new.tap do |e|
- e['Channel'] = 'SIP/101-3f3f'
- e['Uniqueid'] = '1094154427.10'
- e['Cause'] = '1'
- end
- end
-
- it "should not be equal" do
- event1.should_not be == event2
- end
- end
- end
- end # Response
-end # RubyFS
View
66 spec/ruby_fs/stream_spec.rb
@@ -50,83 +50,27 @@ def expect_disconnected_event
end
end
- it "can send a command" do
+ it "can send data" do
expect_connected_event
expect_disconnected_event
- action = Action.new('Command', 'Command' => 'RECORD FILE evil', 'ActionID' => 666, 'Events' => 'On')
- mocked_server(1, lambda { @stream.send_action action }) do |val, server|
- val.should == action.to_s
+ mocked_server(1, lambda { @stream.send_data "foo" }) do |val, server|
+ val.should == "foo"
end
end
end
it 'sends events to the client when the stream is ready' do
mocked_server(1, lambda { @stream.send_data 'Foo' }) do |val, server|
- server.send_data <<-EVENT
-Event: Hangup
-Channel: SIP/101-3f3f
-Uniqueid: 1094154427.10
-Cause: 0
-
- EVENT
+ server.send_data 'foo'
end
client_messages.should be == [
Stream::Connected.new,
- Event.new('Hangup').tap do |e|
- e['Channel'] = 'SIP/101-3f3f'
- e['Uniqueid'] = '1094154427.10'
- e['Cause'] = '0'
- end,
+ 'foo',
Stream::Disconnected.new
]
end
- it 'sends responses to the client when the stream is ready' do
- mocked_server(1, lambda { @stream.send_data 'Foo' }) do |val, server|
- server.send_data <<-EVENT
-Response: Success
-ActionID: ee33eru2398fjj290
-Message: Authentication accepted
-
- EVENT
- end
-
- client_messages.should be == [
- Stream::Connected.new,
- Response.new.tap do |r|
- r['ActionID'] = 'ee33eru2398fjj290'
- r['Message'] = 'Authentication accepted'
- end,
- Stream::Disconnected.new
- ]
- end
-
- it 'sends error to the client when the stream is ready and a bad command was send' do
- client.expects(:message_received).times(3).with do |r|
- case @sequence
- when 1
- r.should be_a Stream::Connected
- when 2
- r.should be_a Error
- r['ActionID'].should == 'ee33eru2398fjj290'
- r['Message'].should == 'You stupid git'
- when 3
- r.should be_a Stream::Disconnected
- end
- @sequence += 1
- end
-
- mocked_server(1, lambda { @stream.send_data 'Foo' }) do |val, server|
- server.send_data <<-EVENT
-Response: Error
-ActionID: ee33eru2398fjj290
-Message: You stupid git
-
- EVENT
- end
- end
-
it 'puts itself in the stopped state and fires a disconnected event when unbound' do
expect_connected_event
expect_disconnected_event
Please sign in to comment.
Something went wrong with that request. Please try again.