ruby_fs 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,13 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+
6
+ .rvmrc
7
+ .yardoc
8
+ doc
9
+ coverage
10
+ spec/reports
11
+ features/reports
12
+ vendor
13
+ *.swp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --colour
3
+ --tty
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.2
4
+ - 1.9.3
5
+ - jruby-19mode
6
+ - rbx-19mode
7
+ - ruby-head
8
+ notifications:
9
+ irc: "irc.freenode.org#adhearsion"
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ # develop
2
+
3
+ # 0.1.0
4
+ * Initial release
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :version => 2, :cli => '--format documentation' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec/" }
5
+ end
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,6 @@
1
+ require 'ruby_fs/response'
2
+
3
+ module RubyFS
4
+ class CommandReply < Response
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ require 'ruby_fs/response'
2
+
3
+ module RubyFS
4
+ class Event < Response
5
+ def event_name
6
+ content[:event_name]
7
+ end
8
+ end
9
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module RubyFS
2
+ VERSION = "0.1.0"
3
+ 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,7 @@
1
+ require 'spec_helper'
2
+
3
+ module RubyFS
4
+ describe CommandReply do
5
+ it { should be_a Response }
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ require 'spec_helper'
2
+
3
+ module RubyFS
4
+ describe Event do
5
+ subject { described_class.new nil, :event_name => 'FOO' }
6
+
7
+ it { should be_a Response }
8
+
9
+ its(:event_name) { should == 'FOO' }
10
+ end
11
+ 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
@@ -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