ruby_fs 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +9 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +3 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +20 -0
- data/README.md +32 -0
- data/Rakefile +15 -0
- data/lib/ruby_fs/command_reply.rb +6 -0
- data/lib/ruby_fs/event.rb +9 -0
- data/lib/ruby_fs/lexer.rb +148 -0
- data/lib/ruby_fs/response.rb +19 -0
- data/lib/ruby_fs/stream.rb +105 -0
- data/lib/ruby_fs/version.rb +3 -0
- data/lib/ruby_fs.rb +22 -0
- data/ruby_ami.gemspec +37 -0
- data/spec/ruby_fs/command_reply_spec.rb +7 -0
- data/spec/ruby_fs/event_spec.rb +11 -0
- data/spec/ruby_fs/response_spec.rb +109 -0
- data/spec/ruby_fs/stream_spec.rb +239 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/support/mock_server.rb +43 -0
- metadata +320 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Ben Langfeld
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# RubyFS [![Build Status](https://secure.travis-ci.org/adhearsion/ruby_fs.png?branch=master)](http://travis-ci.org/adhearsion/ruby_fs)
|
2
|
+
RubyFS is a FreeSWITCH EventSocket client library in Ruby and based on Celluloid actors with the sole purpose of providing a connection to the EventSocket API. RubyFS does not provide any features beyond connection management and protocol parsing. Actions are sent over the wire, and responses come back via callbacks. It's up to you to match these up into something useful. In this regard, RubyFS is very similar to [Blather](https://github.com/sprsquish/blather) for XMPP or [Punchblock](https://github.com/adhearsion/punchblock), the Ruby 3PCC library. In fact, Punchblock uses RubyFS under the covers for its FreeSWITCH implementation.
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
gem install ruby_fs
|
6
|
+
|
7
|
+
## Usage
|
8
|
+
```ruby
|
9
|
+
require 'ruby_fs'
|
10
|
+
|
11
|
+
client = RubyFS::Stream.new '127.0.0.1', 8021, 'ClueCon', lambda { |e| p e }
|
12
|
+
|
13
|
+
client.start
|
14
|
+
```
|
15
|
+
|
16
|
+
## Links
|
17
|
+
* [Source](https://github.com/adhearsion/ruby_fs)
|
18
|
+
* [Documentation](http://rdoc.info/github/adhearsion/ruby_fs/master/frames)
|
19
|
+
* [Bug Tracker](https://github.com/adhearsion/ruby_fs/issues)
|
20
|
+
|
21
|
+
## Note on Patches/Pull Requests
|
22
|
+
|
23
|
+
* Fork the project.
|
24
|
+
* Make your feature addition or bug fix.
|
25
|
+
* Add tests for it. This is important so I don't break it in a future version unintentionally.
|
26
|
+
* Commit, do not mess with rakefile, version, or history.
|
27
|
+
* If you want to have your own version, that is fine but bump version in a commit by itself so I can ignore when I pull
|
28
|
+
* Send me a pull request. Bonus points for topic branches.
|
29
|
+
|
30
|
+
## Copyright
|
31
|
+
|
32
|
+
Copyright (c) 2012 Ben Langfeld. MIT licence (see LICENSE for details).
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
|
3
|
+
require 'rspec/core'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'ci/reporter/rake/rspec'
|
6
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
7
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
8
|
+
spec.rspec_opts = '--color'
|
9
|
+
end
|
10
|
+
|
11
|
+
task :default => [:spec]
|
12
|
+
task :ci => ['ci:setup:rspec', :spec]
|
13
|
+
|
14
|
+
require 'yard'
|
15
|
+
YARD::Rake::YardocTask.new
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module RubyFS
|
2
|
+
class Lexer
|
3
|
+
ContentLengthPattern = /Content-length:\s*(\d+)/i
|
4
|
+
MaxLineLength = 16 * 1024
|
5
|
+
MaxBinaryLength = 32 * 1024 * 1024
|
6
|
+
|
7
|
+
def initialize(callback)
|
8
|
+
@callback = callback
|
9
|
+
|
10
|
+
@data_mode = :lines
|
11
|
+
@delimiter = "\n"
|
12
|
+
@linebuffer = []
|
13
|
+
|
14
|
+
init_for_request
|
15
|
+
end
|
16
|
+
|
17
|
+
def receive_data(data)
|
18
|
+
return unless data && data.length > 0
|
19
|
+
|
20
|
+
case @data_mode
|
21
|
+
when :lines
|
22
|
+
receive_line_data data
|
23
|
+
when :text
|
24
|
+
receive_text_data data
|
25
|
+
end
|
26
|
+
end
|
27
|
+
alias :<< :receive_data
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def receive_line_data(data)
|
32
|
+
if ix = data.index(@delimiter)
|
33
|
+
@linebuffer << data[0...ix]
|
34
|
+
ln = @linebuffer.join
|
35
|
+
@linebuffer.clear
|
36
|
+
ln.chomp! if @delimiter == "\n"
|
37
|
+
receive_line ln
|
38
|
+
receive_data data[(ix + @delimiter.length)..-1]
|
39
|
+
else
|
40
|
+
@linebuffer << data
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def receive_text_data(data)
|
45
|
+
if @textsize
|
46
|
+
needed = @textsize - @textpos
|
47
|
+
will_take = data.length > needed ? needed : data.length
|
48
|
+
|
49
|
+
@textbuffer << data[0...will_take]
|
50
|
+
tail = data[will_take..-1]
|
51
|
+
|
52
|
+
@textpos += will_take
|
53
|
+
if @textpos >= @textsize
|
54
|
+
set_line_mode
|
55
|
+
receive_binary_data @textbuffer.join
|
56
|
+
end
|
57
|
+
|
58
|
+
receive_data tail
|
59
|
+
else
|
60
|
+
receive_binary_data data
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def receive_line(line)
|
65
|
+
case @hc_mode
|
66
|
+
when :discard_blanks
|
67
|
+
unless line == ""
|
68
|
+
@hc_mode = :headers
|
69
|
+
receive_line line
|
70
|
+
end
|
71
|
+
when :headers
|
72
|
+
if line == ""
|
73
|
+
raise "unrecognized state" unless @headers.length > 0
|
74
|
+
# @hc_content_length will be nil, not 0, if there was no content-length header.
|
75
|
+
if @content_length.to_i > 0
|
76
|
+
set_binary_mode @content_length
|
77
|
+
else
|
78
|
+
dispatch_request
|
79
|
+
end
|
80
|
+
else
|
81
|
+
@headers << line
|
82
|
+
if ContentLengthPattern =~ line
|
83
|
+
# There are some attacks that rely on sending multiple content-length
|
84
|
+
# headers. This is a crude protection, but needs to become tunable.
|
85
|
+
raise "extraneous content-length header" if @content_length
|
86
|
+
@content_length = $1.to_i
|
87
|
+
end
|
88
|
+
end
|
89
|
+
else
|
90
|
+
raise "internal error, unsupported mode"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def receive_binary_data(text)
|
95
|
+
@content = text
|
96
|
+
dispatch_request
|
97
|
+
end
|
98
|
+
|
99
|
+
def dispatch_request
|
100
|
+
@callback.call headers_2_hash(@headers), @content if @callback.respond_to?(:call)
|
101
|
+
init_for_request
|
102
|
+
end
|
103
|
+
|
104
|
+
def init_for_request
|
105
|
+
@hc_mode = :discard_blanks
|
106
|
+
@headers = []
|
107
|
+
@content_length = nil
|
108
|
+
@content = ""
|
109
|
+
end
|
110
|
+
|
111
|
+
# Called internally but also exposed to user code, for the case in which
|
112
|
+
# processing of binary data creates a need to transition back to line mode.
|
113
|
+
# We support an optional parameter to "throw back" some data, which might
|
114
|
+
# be an unprocessed chunk of the transmitted binary data, or something else
|
115
|
+
# entirely.
|
116
|
+
def set_line_mode(data = "")
|
117
|
+
@data_mode = :lines
|
118
|
+
(@linebuffer ||= []).clear
|
119
|
+
receive_data data.to_s
|
120
|
+
end
|
121
|
+
|
122
|
+
def set_text_mode(size = nil)
|
123
|
+
if size == 0
|
124
|
+
set_line_mode
|
125
|
+
else
|
126
|
+
@data_mode = :text
|
127
|
+
(@textbuffer ||= []).clear
|
128
|
+
@textsize = size # which can be nil, signifying no limit
|
129
|
+
@textpos = 0
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def set_binary_mode(size = nil)
|
134
|
+
set_text_mode size
|
135
|
+
end
|
136
|
+
|
137
|
+
def headers_2_hash(headers)
|
138
|
+
{}.tap do |hash|
|
139
|
+
headers.each do |h|
|
140
|
+
if /\A([^\s:]+)\s*:\s*/ =~ h
|
141
|
+
tail = $'.dup
|
142
|
+
hash[$1.downcase.gsub(/-/, "_").intern] = tail
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RubyFS
|
2
|
+
class Response
|
3
|
+
attr_reader :headers, :content
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
def_delegator :content, :[]
|
7
|
+
def_delegator :content, :has_key?
|
8
|
+
|
9
|
+
def initialize(headers = nil, content = nil)
|
10
|
+
@headers, @content = headers || {}, content || {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
other.is_a?(self.class) && [:headers, :content].all? do |att|
|
15
|
+
other.send(att) == self.send(att)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
require 'ruby_fs/lexer'
|
4
|
+
|
5
|
+
module RubyFS
|
6
|
+
class Stream
|
7
|
+
class ConnectionStatus
|
8
|
+
def eql?(other)
|
9
|
+
other.is_a? self.class
|
10
|
+
end
|
11
|
+
|
12
|
+
alias :== :eql?
|
13
|
+
end
|
14
|
+
|
15
|
+
Connected = Class.new ConnectionStatus
|
16
|
+
Disconnected = Class.new ConnectionStatus
|
17
|
+
|
18
|
+
include Celluloid::IO
|
19
|
+
|
20
|
+
def initialize(host, port, secret, event_callback, events = true)
|
21
|
+
super()
|
22
|
+
@secret, @event_callback, @events = secret, event_callback, events
|
23
|
+
@command_callbacks = []
|
24
|
+
logger.debug "Starting up..."
|
25
|
+
@lexer = Lexer.new method(:receive_request)
|
26
|
+
@socket = TCPSocket.from_ruby_socket ::TCPSocket.new(host, port)
|
27
|
+
post_init
|
28
|
+
end
|
29
|
+
|
30
|
+
[:started, :stopped, :ready].each do |state|
|
31
|
+
define_method("#{state}?") { @state == state }
|
32
|
+
end
|
33
|
+
|
34
|
+
def run
|
35
|
+
loop { receive_data @socket.readpartial(4096) }
|
36
|
+
rescue EOFError
|
37
|
+
logger.info "Client socket closed!"
|
38
|
+
current_actor.terminate!
|
39
|
+
end
|
40
|
+
|
41
|
+
def post_init
|
42
|
+
@state = :started
|
43
|
+
fire_event Connected.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def send_data(data)
|
47
|
+
logger.debug "[SEND] #{data.to_s}"
|
48
|
+
@socket.write data.to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
def command(command, options = {}, &block)
|
52
|
+
@command_callbacks << (block || lambda { |reply| logger.debug "Reply to a command (#{command}) without a callback: #{reply.inspect}" })
|
53
|
+
string = "#{command}\n"
|
54
|
+
options.each_pair do |key, value|
|
55
|
+
string << "#{key.to_s.gsub '_', '-'}: #{value}\n"
|
56
|
+
end
|
57
|
+
string << "\n"
|
58
|
+
send_data string
|
59
|
+
end
|
60
|
+
|
61
|
+
def receive_data(data)
|
62
|
+
logger.debug "[RECV] #{data}"
|
63
|
+
@lexer << data
|
64
|
+
end
|
65
|
+
|
66
|
+
def finalize
|
67
|
+
logger.debug "Finalizing stream"
|
68
|
+
@socket.close if @socket
|
69
|
+
@state = :stopped
|
70
|
+
fire_event Disconnected.new
|
71
|
+
end
|
72
|
+
|
73
|
+
def fire_event(event)
|
74
|
+
@event_callback.call event
|
75
|
+
end
|
76
|
+
|
77
|
+
def logger
|
78
|
+
Logger
|
79
|
+
end
|
80
|
+
|
81
|
+
def receive_request(headers, content)
|
82
|
+
case headers[:content_type]
|
83
|
+
when 'text/event-json'
|
84
|
+
fire_event Event.new(headers, json_content_2_hash(content))
|
85
|
+
when 'command/reply'
|
86
|
+
@command_callbacks.pop.call CommandReply.new(headers)
|
87
|
+
when 'auth/request'
|
88
|
+
command "auth #{@secret}" do
|
89
|
+
command "event json ALL" if @events
|
90
|
+
end
|
91
|
+
else
|
92
|
+
raise "Unknown request type received (#{headers.inspect})"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def json_content_2_hash(content)
|
97
|
+
json = JSON.parse content
|
98
|
+
{}.tap do |hash|
|
99
|
+
json.each_pair do |k, v|
|
100
|
+
hash[k.downcase.gsub(/-/,"_").intern] = v
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/ruby_fs.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
%w{
|
2
|
+
uuidtools
|
3
|
+
future-resource
|
4
|
+
logger
|
5
|
+
countdownlatch
|
6
|
+
celluloid/io
|
7
|
+
}.each { |f| require f }
|
8
|
+
|
9
|
+
class Logger
|
10
|
+
alias :trace :debug
|
11
|
+
end
|
12
|
+
|
13
|
+
module RubyFS
|
14
|
+
end
|
15
|
+
|
16
|
+
%w{
|
17
|
+
command_reply
|
18
|
+
event
|
19
|
+
response
|
20
|
+
stream
|
21
|
+
version
|
22
|
+
}.each { |f| require "ruby_fs/#{f}" }
|
data/ruby_ami.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "ruby_fs/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "ruby_fs"
|
7
|
+
s.version = RubyFS::VERSION
|
8
|
+
s.authors = ["Ben Langfeld"]
|
9
|
+
s.email = ["ben@langfeld.me"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Wrapping FreeSWITCH EventSocket for rubyists}
|
12
|
+
s.description = %q{A Ruby client library for the FreeSWITCH EventSocket API built on Celluloid.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "ruby_fs"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n") << 'lib/ruby_fs/lexer.rb'
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_runtime_dependency %q<uuidtools>, [">= 0"]
|
22
|
+
s.add_runtime_dependency %q<celluloid-io>, ["~> 0.11.0"]
|
23
|
+
s.add_runtime_dependency %q<future-resource>, [">= 0"]
|
24
|
+
s.add_runtime_dependency %q<countdownlatch>, ["~> 1.0"]
|
25
|
+
s.add_runtime_dependency %q<json>
|
26
|
+
|
27
|
+
s.add_development_dependency %q<bundler>, ["~> 1.0"]
|
28
|
+
s.add_development_dependency %q<rspec>, [">= 2.5.0"]
|
29
|
+
s.add_development_dependency %q<ci_reporter>, [">= 1.6.3"]
|
30
|
+
s.add_development_dependency %q<yard>, ["~> 0.6.0"]
|
31
|
+
s.add_development_dependency %q<rake>, [">= 0"]
|
32
|
+
s.add_development_dependency %q<mocha>, [">= 0"]
|
33
|
+
s.add_development_dependency %q<simplecov>, [">= 0"]
|
34
|
+
s.add_development_dependency %q<simplecov-rcov>, [">= 0"]
|
35
|
+
s.add_development_dependency %q<guard-rspec>
|
36
|
+
s.add_development_dependency %q<ruby_gntp>
|
37
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module RubyFS
|
4
|
+
describe Response do
|
5
|
+
|
6
|
+
its(:headers) { should == {} }
|
7
|
+
its(:content) { should == {} }
|
8
|
+
|
9
|
+
context "when created with headers" do
|
10
|
+
let(:headers) { {:foo => 'bar'} }
|
11
|
+
|
12
|
+
subject { Event.new headers }
|
13
|
+
|
14
|
+
its(:headers) { should == headers }
|
15
|
+
its(:content) { should == {} }
|
16
|
+
end
|
17
|
+
|
18
|
+
context "when created with content" do
|
19
|
+
let(:content) { {:foo => 'bar'} }
|
20
|
+
|
21
|
+
subject { Event.new nil, content }
|
22
|
+
|
23
|
+
its(:headers) { should == {} }
|
24
|
+
its(:content) { should == content }
|
25
|
+
|
26
|
+
it "makes content values available via #[]" do
|
27
|
+
subject.should_not have_key(:bar)
|
28
|
+
subject[:bar].should == nil
|
29
|
+
|
30
|
+
subject.should have_key(:foo)
|
31
|
+
subject[:foo].should == 'bar'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when created with headers and content" do
|
36
|
+
let(:headers) { {:foo => 'bar'} }
|
37
|
+
let(:content) { {:doo => 'dah'} }
|
38
|
+
|
39
|
+
subject { Event.new headers, content }
|
40
|
+
|
41
|
+
its(:headers) { should == headers }
|
42
|
+
its(:content) { should == content }
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "equality" do
|
46
|
+
context "with another kind of object" do
|
47
|
+
it "should not be equal" do
|
48
|
+
Event.new.should_not == :foo
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "with the same headers and the same content" do
|
53
|
+
let :event1 do
|
54
|
+
Event.new({:foo => 'bar'}, :doo => 'bah')
|
55
|
+
end
|
56
|
+
|
57
|
+
let :event2 do
|
58
|
+
Event.new({:foo => 'bar'}, :doo => 'bah')
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should be equal" do
|
62
|
+
event1.should == event2
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "with the same headers and different content" do
|
67
|
+
let :event1 do
|
68
|
+
Event.new({:foo => 'bar'}, :doo => 'bah')
|
69
|
+
end
|
70
|
+
|
71
|
+
let :event2 do
|
72
|
+
Event.new({:foo => 'bar'}, :doo => 'dah')
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should not be equal" do
|
76
|
+
event1.should_not == event2
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "with the different headers and the same content" do
|
81
|
+
let :event1 do
|
82
|
+
Event.new({:foo => 'bar'}, :doo => 'bah')
|
83
|
+
end
|
84
|
+
|
85
|
+
let :event2 do
|
86
|
+
Event.new({:foo => 'baz'}, :doo => 'bah')
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should not be equal" do
|
90
|
+
event1.should_not == event2
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context "with different headers and different content" do
|
95
|
+
let :event1 do
|
96
|
+
Event.new({:foo => 'bar'}, :foo => 'baz')
|
97
|
+
end
|
98
|
+
|
99
|
+
let :event2 do
|
100
|
+
Event.new({:foo => 'baz'}, :foo => 'bar')
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should not be equal" do
|
104
|
+
event1.should_not == event2
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module RubyFS
|
4
|
+
describe Stream do
|
5
|
+
let(:server_port) { 50000 - rand(1000) }
|
6
|
+
|
7
|
+
before do
|
8
|
+
def client.message_received(message)
|
9
|
+
@messages ||= Queue.new
|
10
|
+
@messages << message
|
11
|
+
end
|
12
|
+
|
13
|
+
def client.messages
|
14
|
+
@messages
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
let :client_messages do
|
19
|
+
messages = []
|
20
|
+
messages << client.messages.pop until client.messages.empty?
|
21
|
+
messages
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:secret) { 'ClueCon' }
|
25
|
+
let(:events) { false }
|
26
|
+
|
27
|
+
def mocked_server(times = nil, fake_client = nil, &block)
|
28
|
+
mock_target = MockServer.new
|
29
|
+
mock_target.expects(:receive_data).send(*(times ? [:times, times] : [:at_least, 1])).with &block
|
30
|
+
s = ServerMock.new '127.0.0.1', server_port, mock_target
|
31
|
+
@stream = Stream.new '127.0.0.1', server_port, secret, lambda { |m| client.message_received m }, events
|
32
|
+
@stream.run!
|
33
|
+
fake_client.call s if fake_client.respond_to? :call
|
34
|
+
s.join
|
35
|
+
@stream.join
|
36
|
+
end
|
37
|
+
|
38
|
+
def expect_connected_event
|
39
|
+
client.expects(:message_received).with Stream::Connected.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def expect_disconnected_event
|
43
|
+
client.expects(:message_received).with Stream::Disconnected.new
|
44
|
+
end
|
45
|
+
|
46
|
+
before { @sequence = 1 }
|
47
|
+
|
48
|
+
describe "after connection" do
|
49
|
+
it "should be started" do
|
50
|
+
expect_connected_event
|
51
|
+
expect_disconnected_event
|
52
|
+
mocked_server(0) do |val, server|
|
53
|
+
@stream.started?.should be_true
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
it "can send data" do
|
58
|
+
expect_connected_event
|
59
|
+
expect_disconnected_event
|
60
|
+
mocked_server(1, lambda { |server| @stream.send_data "foo" }) do |val, server|
|
61
|
+
val.should == "foo"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'sends events to the client when the stream is ready' do
|
67
|
+
mocked_server(1, lambda { |server| @stream.send_data 'Foo' }) do |val, server|
|
68
|
+
server.send_data %Q(
|
69
|
+
Content-Length: 776
|
70
|
+
Content-Type: text/event-json
|
71
|
+
|
72
|
+
{
|
73
|
+
"Event-Name": "HEARTBEAT",
|
74
|
+
"Core-UUID": "2ad09a34-c056-11e1-b095-fffeda3ce54f",
|
75
|
+
"FreeSWITCH-Hostname": "blmbp.home",
|
76
|
+
"FreeSWITCH-Switchname": "blmbp.home",
|
77
|
+
"FreeSWITCH-IPv4": "192.168.1.74",
|
78
|
+
"FreeSWITCH-IPv6": "::1",
|
79
|
+
"Event-Date-Local": "2012-06-27 19:43:32",
|
80
|
+
"Event-Date-GMT": "Wed, 27 Jun 2012 18:43:32 GMT",
|
81
|
+
"Event-Date-Timestamp": "1340822612392823",
|
82
|
+
"Event-Calling-File": "switch_core.c",
|
83
|
+
"Event-Calling-Function": "send_heartbeat",
|
84
|
+
"Event-Calling-Line-Number": "68",
|
85
|
+
"Event-Sequence": "3526",
|
86
|
+
"Event-Info": "System Ready",
|
87
|
+
"Up-Time": "0 years, 0 days, 5 hours, 56 minutes, 40 seconds, 807 milliseconds, 21 microseconds",
|
88
|
+
"Session-Count": "0",
|
89
|
+
"Max-Sessions": "1000",
|
90
|
+
"Session-Per-Sec": "30",
|
91
|
+
"Session-Since-Startup": "4",
|
92
|
+
"Idle-CPU": "100.000000"
|
93
|
+
}Content-Length: 629
|
94
|
+
Content-Type: text/event-json
|
95
|
+
|
96
|
+
{
|
97
|
+
"Event-Name": "RE_SCHEDULE",
|
98
|
+
"Core-UUID": "2ad09a34-c056-11e1-b095-fffeda3ce54f",
|
99
|
+
"FreeSWITCH-Hostname": "blmbp.home",
|
100
|
+
"FreeSWITCH-Switchname": "blmbp.home",
|
101
|
+
"FreeSWITCH-IPv4": "192.168.1.74",
|
102
|
+
"FreeSWITCH-IPv6": "::1",
|
103
|
+
"Event-Date-Local": "2012-06-27 19:43:32",
|
104
|
+
"Event-Date-GMT": "Wed, 27 Jun 2012 18:43:32 GMT",
|
105
|
+
"Event-Date-Timestamp": "1340822612392823",
|
106
|
+
"Event-Calling-File": "switch_scheduler.c",
|
107
|
+
"Event-Calling-Function": "switch_scheduler_execute",
|
108
|
+
"Event-Calling-Line-Number": "65",
|
109
|
+
"Event-Sequence": "3527",
|
110
|
+
"Task-ID": "2",
|
111
|
+
"Task-Desc": "heartbeat",
|
112
|
+
"Task-Group": "core",
|
113
|
+
"Task-Runtime": "1340822632"
|
114
|
+
})
|
115
|
+
end
|
116
|
+
|
117
|
+
client_messages.should be == [
|
118
|
+
Stream::Connected.new,
|
119
|
+
Event.new({:content_length => '776', :content_type => 'text/event-json'}, {:event_name => "HEARTBEAT", :core_uuid => "2ad09a34-c056-11e1-b095-fffeda3ce54f", :freeswitch_hostname => "blmbp.home", :freeswitch_switchname => "blmbp.home", :freeswitch_ipv4 => "192.168.1.74", :freeswitch_ipv6 => "::1", :event_date_local => "2012-06-27 19:43:32", :event_date_gmt => "Wed, 27 Jun 2012 18:43:32 GMT", :event_date_timestamp => "1340822612392823", :event_calling_file => "switch_core.c", :event_calling_function => "send_heartbeat", :event_calling_line_number => "68", :event_sequence => "3526", :event_info => "System Ready", :up_time => "0 years, 0 days, 5 hours, 56 minutes, 40 seconds, 807 milliseconds, 21 microseconds", :session_count => "0", :max_sessions => "1000", :session_per_sec => "30", :session_since_startup => "4", :idle_cpu => "100.000000"}),
|
120
|
+
Event.new({:content_length => '629', :content_type => 'text/event-json'}, {:event_name => "RE_SCHEDULE", :core_uuid => "2ad09a34-c056-11e1-b095-fffeda3ce54f", :freeswitch_hostname => "blmbp.home", :freeswitch_switchname => "blmbp.home", :freeswitch_ipv4 => "192.168.1.74", :freeswitch_ipv6 => "::1", :event_date_local => "2012-06-27 19:43:32", :event_date_gmt => "Wed, 27 Jun 2012 18:43:32 GMT", :event_date_timestamp => "1340822612392823", :event_calling_file => "switch_scheduler.c", :event_calling_function => "switch_scheduler_execute", :event_calling_line_number => "65", :event_sequence => "3527", :task_id => "2", :task_desc => "heartbeat", :task_group => "core", :task_runtime => "1340822632"}),
|
121
|
+
Stream::Disconnected.new
|
122
|
+
]
|
123
|
+
end
|
124
|
+
|
125
|
+
it "can send commands with response callbacks" do
|
126
|
+
expect_connected_event
|
127
|
+
expect_disconnected_event
|
128
|
+
handler = mock
|
129
|
+
handler.expects(:call).once.with CommandReply.new(:content_type => 'command/reply', :reply_text => '+OK accepted')
|
130
|
+
mocked_server(1, lambda { |server| @stream.command('foo') { |reply| handler.call reply } }) do |val, server|
|
131
|
+
val.should == "foo\n\n"
|
132
|
+
server.send_data %Q(
|
133
|
+
Content-Type: command/reply
|
134
|
+
Reply-Text: +OK accepted
|
135
|
+
|
136
|
+
)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
it "can send commands without response callbacks" do
|
141
|
+
expect_connected_event
|
142
|
+
expect_disconnected_event
|
143
|
+
mocked_server(1, lambda { |server| @stream.command 'foo' }) do |val, server|
|
144
|
+
val.should == "foo\n\n"
|
145
|
+
server.send_data %Q(
|
146
|
+
Content-Type: command/reply
|
147
|
+
Reply-Text: +OK accepted
|
148
|
+
|
149
|
+
)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
it "can send commands with options" do
|
154
|
+
expect_connected_event
|
155
|
+
expect_disconnected_event
|
156
|
+
mocked_server(1, lambda { |server| @stream.command 'foo', :one => 1, :foo_bar => :doo_dah }) do |val, server|
|
157
|
+
val.should == %Q(foo
|
158
|
+
one: 1
|
159
|
+
foo-bar: doo_dah
|
160
|
+
|
161
|
+
)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'authenticates when requested' do
|
166
|
+
mocked_server(1, lambda { |server| server.send_data "Content-Type: auth/request\n\n" }) do |val, server|
|
167
|
+
val.should == "auth ClueCon\n\n"
|
168
|
+
server.send_data %Q(
|
169
|
+
Content-Type: command/reply
|
170
|
+
Reply-Text: +OK accepted
|
171
|
+
|
172
|
+
)
|
173
|
+
end
|
174
|
+
|
175
|
+
client_messages.should be == [
|
176
|
+
Stream::Connected.new,
|
177
|
+
Stream::Disconnected.new
|
178
|
+
]
|
179
|
+
end
|
180
|
+
|
181
|
+
context 'with events turned on' do
|
182
|
+
let(:events) { true }
|
183
|
+
|
184
|
+
it 'sets the event mask after authenticating' do
|
185
|
+
mocked_server(2, lambda { |server| server.send_data "Content-Type: auth/request\n\n" }) do |val, server|
|
186
|
+
case @sequence
|
187
|
+
when 1
|
188
|
+
val.should == "auth ClueCon\n\n"
|
189
|
+
server.send_data %Q(
|
190
|
+
Content-Type: command/reply
|
191
|
+
Reply-Text: +OK accepted
|
192
|
+
|
193
|
+
)
|
194
|
+
when 2
|
195
|
+
val.should == "event json ALL\n\n"
|
196
|
+
server.send_data %Q(
|
197
|
+
Content-Type: command/reply
|
198
|
+
Reply-Text: +OK accepted
|
199
|
+
|
200
|
+
)
|
201
|
+
end
|
202
|
+
@sequence += 1
|
203
|
+
end
|
204
|
+
|
205
|
+
client_messages.should be == [
|
206
|
+
Stream::Connected.new,
|
207
|
+
Stream::Disconnected.new
|
208
|
+
]
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
context 'with events turned off' do
|
213
|
+
it 'does not the event mask after authenticating' do
|
214
|
+
mocked_server(1, lambda { |server| server.send_data "Content-Type: auth/request\n\n" }) do |val, server|
|
215
|
+
val.should == "auth ClueCon\n\n"
|
216
|
+
server.send_data %Q(
|
217
|
+
Content-Type: command/reply
|
218
|
+
Reply-Text: +OK accepted
|
219
|
+
|
220
|
+
)
|
221
|
+
end
|
222
|
+
|
223
|
+
client_messages.should be == [
|
224
|
+
Stream::Connected.new,
|
225
|
+
Stream::Disconnected.new
|
226
|
+
]
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
it 'puts itself in the stopped state and fires a disconnected event when unbound' do
|
231
|
+
expect_connected_event
|
232
|
+
expect_disconnected_event
|
233
|
+
mocked_server(1, lambda { |server| @stream.send_data 'Foo' }) do |val, server|
|
234
|
+
@stream.stopped?.should be false
|
235
|
+
end
|
236
|
+
@stream.alive?.should be false
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
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 'ruby_fs'
|
15
|
+
require 'mocha'
|
16
|
+
require 'countdownlatch'
|
17
|
+
|
18
|
+
Dir[File.dirname(__FILE__) + "/support/**/*.rb"].each {|f| require f}
|
19
|
+
|
20
|
+
include RubyFS
|
21
|
+
|
22
|
+
RSpec.configure do |config|
|
23
|
+
config.mock_with :mocha
|
24
|
+
config.filter_run :focus => true
|
25
|
+
config.run_all_when_everything_filtered = true
|
26
|
+
|
27
|
+
config.before :each do
|
28
|
+
uuid = UUIDTools::UUID.random_create
|
29
|
+
UUIDTools::UUID.stubs :random_create => uuid
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
MockServer = Class.new
|
2
|
+
|
3
|
+
class ServerMock
|
4
|
+
include Celluloid::IO
|
5
|
+
|
6
|
+
def initialize(host, port, mock_target = MockServer.new)
|
7
|
+
@server = TCPServer.new host, port
|
8
|
+
@mock_target = mock_target
|
9
|
+
@clients = []
|
10
|
+
run!
|
11
|
+
end
|
12
|
+
|
13
|
+
def finalize
|
14
|
+
Logger.debug "ServerMock finalizing"
|
15
|
+
@server.close if @server
|
16
|
+
@clients.each(&:close)
|
17
|
+
end
|
18
|
+
|
19
|
+
def run
|
20
|
+
after(1) { terminate }
|
21
|
+
loop { handle_connection! @server.accept }
|
22
|
+
end
|
23
|
+
|
24
|
+
def handle_connection(socket)
|
25
|
+
@clients << socket
|
26
|
+
_, port, host = socket.peeraddr
|
27
|
+
Logger.debug "MockServer Received connection from #{host}:#{port}"
|
28
|
+
loop { receive_data socket.readpartial(4096) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def receive_data(data)
|
32
|
+
Logger.debug "ServerMock receiving data: #{data}"
|
33
|
+
@mock_target.receive_data data, self
|
34
|
+
end
|
35
|
+
|
36
|
+
def send_data(data)
|
37
|
+
@clients.each { |client| client.write data }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def client
|
42
|
+
@client ||= mock('Client')
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,320 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ruby_fs
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Ben Langfeld
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-06-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: uuidtools
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: celluloid-io
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 0.11.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 0.11.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: future-resource
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: countdownlatch
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '1.0'
|
70
|
+
type: :runtime
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '1.0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: json
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :runtime
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: bundler
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ~>
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '1.0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '1.0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: rspec
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: 2.5.0
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: 2.5.0
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: ci_reporter
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: 1.6.3
|
134
|
+
type: :development
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: 1.6.3
|
142
|
+
- !ruby/object:Gem::Dependency
|
143
|
+
name: yard
|
144
|
+
requirement: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ~>
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 0.6.0
|
150
|
+
type: :development
|
151
|
+
prerelease: false
|
152
|
+
version_requirements: !ruby/object:Gem::Requirement
|
153
|
+
none: false
|
154
|
+
requirements:
|
155
|
+
- - ~>
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: 0.6.0
|
158
|
+
- !ruby/object:Gem::Dependency
|
159
|
+
name: rake
|
160
|
+
requirement: !ruby/object:Gem::Requirement
|
161
|
+
none: false
|
162
|
+
requirements:
|
163
|
+
- - ! '>='
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '0'
|
166
|
+
type: :development
|
167
|
+
prerelease: false
|
168
|
+
version_requirements: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
- !ruby/object:Gem::Dependency
|
175
|
+
name: mocha
|
176
|
+
requirement: !ruby/object:Gem::Requirement
|
177
|
+
none: false
|
178
|
+
requirements:
|
179
|
+
- - ! '>='
|
180
|
+
- !ruby/object:Gem::Version
|
181
|
+
version: '0'
|
182
|
+
type: :development
|
183
|
+
prerelease: false
|
184
|
+
version_requirements: !ruby/object:Gem::Requirement
|
185
|
+
none: false
|
186
|
+
requirements:
|
187
|
+
- - ! '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
- !ruby/object:Gem::Dependency
|
191
|
+
name: simplecov
|
192
|
+
requirement: !ruby/object:Gem::Requirement
|
193
|
+
none: false
|
194
|
+
requirements:
|
195
|
+
- - ! '>='
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
type: :development
|
199
|
+
prerelease: false
|
200
|
+
version_requirements: !ruby/object:Gem::Requirement
|
201
|
+
none: false
|
202
|
+
requirements:
|
203
|
+
- - ! '>='
|
204
|
+
- !ruby/object:Gem::Version
|
205
|
+
version: '0'
|
206
|
+
- !ruby/object:Gem::Dependency
|
207
|
+
name: simplecov-rcov
|
208
|
+
requirement: !ruby/object:Gem::Requirement
|
209
|
+
none: false
|
210
|
+
requirements:
|
211
|
+
- - ! '>='
|
212
|
+
- !ruby/object:Gem::Version
|
213
|
+
version: '0'
|
214
|
+
type: :development
|
215
|
+
prerelease: false
|
216
|
+
version_requirements: !ruby/object:Gem::Requirement
|
217
|
+
none: false
|
218
|
+
requirements:
|
219
|
+
- - ! '>='
|
220
|
+
- !ruby/object:Gem::Version
|
221
|
+
version: '0'
|
222
|
+
- !ruby/object:Gem::Dependency
|
223
|
+
name: guard-rspec
|
224
|
+
requirement: !ruby/object:Gem::Requirement
|
225
|
+
none: false
|
226
|
+
requirements:
|
227
|
+
- - ! '>='
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
none: false
|
234
|
+
requirements:
|
235
|
+
- - ! '>='
|
236
|
+
- !ruby/object:Gem::Version
|
237
|
+
version: '0'
|
238
|
+
- !ruby/object:Gem::Dependency
|
239
|
+
name: ruby_gntp
|
240
|
+
requirement: !ruby/object:Gem::Requirement
|
241
|
+
none: false
|
242
|
+
requirements:
|
243
|
+
- - ! '>='
|
244
|
+
- !ruby/object:Gem::Version
|
245
|
+
version: '0'
|
246
|
+
type: :development
|
247
|
+
prerelease: false
|
248
|
+
version_requirements: !ruby/object:Gem::Requirement
|
249
|
+
none: false
|
250
|
+
requirements:
|
251
|
+
- - ! '>='
|
252
|
+
- !ruby/object:Gem::Version
|
253
|
+
version: '0'
|
254
|
+
description: A Ruby client library for the FreeSWITCH EventSocket API built on Celluloid.
|
255
|
+
email:
|
256
|
+
- ben@langfeld.me
|
257
|
+
executables: []
|
258
|
+
extensions: []
|
259
|
+
extra_rdoc_files: []
|
260
|
+
files:
|
261
|
+
- .gitignore
|
262
|
+
- .rspec
|
263
|
+
- .travis.yml
|
264
|
+
- CHANGELOG.md
|
265
|
+
- Gemfile
|
266
|
+
- Guardfile
|
267
|
+
- LICENSE.txt
|
268
|
+
- README.md
|
269
|
+
- Rakefile
|
270
|
+
- lib/ruby_fs.rb
|
271
|
+
- lib/ruby_fs/command_reply.rb
|
272
|
+
- lib/ruby_fs/event.rb
|
273
|
+
- lib/ruby_fs/lexer.rb
|
274
|
+
- lib/ruby_fs/response.rb
|
275
|
+
- lib/ruby_fs/stream.rb
|
276
|
+
- lib/ruby_fs/version.rb
|
277
|
+
- ruby_ami.gemspec
|
278
|
+
- spec/ruby_fs/command_reply_spec.rb
|
279
|
+
- spec/ruby_fs/event_spec.rb
|
280
|
+
- spec/ruby_fs/response_spec.rb
|
281
|
+
- spec/ruby_fs/stream_spec.rb
|
282
|
+
- spec/spec_helper.rb
|
283
|
+
- spec/support/mock_server.rb
|
284
|
+
homepage: ''
|
285
|
+
licenses: []
|
286
|
+
post_install_message:
|
287
|
+
rdoc_options: []
|
288
|
+
require_paths:
|
289
|
+
- lib
|
290
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
291
|
+
none: false
|
292
|
+
requirements:
|
293
|
+
- - ! '>='
|
294
|
+
- !ruby/object:Gem::Version
|
295
|
+
version: '0'
|
296
|
+
segments:
|
297
|
+
- 0
|
298
|
+
hash: 2481817772884138362
|
299
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
300
|
+
none: false
|
301
|
+
requirements:
|
302
|
+
- - ! '>='
|
303
|
+
- !ruby/object:Gem::Version
|
304
|
+
version: '0'
|
305
|
+
segments:
|
306
|
+
- 0
|
307
|
+
hash: 2481817772884138362
|
308
|
+
requirements: []
|
309
|
+
rubyforge_project: ruby_fs
|
310
|
+
rubygems_version: 1.8.24
|
311
|
+
signing_key:
|
312
|
+
specification_version: 3
|
313
|
+
summary: Wrapping FreeSWITCH EventSocket for rubyists
|
314
|
+
test_files:
|
315
|
+
- spec/ruby_fs/command_reply_spec.rb
|
316
|
+
- spec/ruby_fs/event_spec.rb
|
317
|
+
- spec/ruby_fs/response_spec.rb
|
318
|
+
- spec/ruby_fs/stream_spec.rb
|
319
|
+
- spec/spec_helper.rb
|
320
|
+
- spec/support/mock_server.rb
|