ruby_fs 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG.md CHANGED
@@ -1,4 +1,11 @@
1
1
  # develop
2
2
 
3
+ # 0.2.0
4
+ * Feature: Common command helper methods
5
+ * Feature: Trace level logging & easy logging override
6
+ * Feature: Clean shutdown
7
+ * Feature: Separate stream creation and connection/execution
8
+ * API Documentation
9
+
3
10
  # 0.1.0
4
11
  * Initial release
data/README.md CHANGED
@@ -8,9 +8,13 @@ RubyFS is a FreeSWITCH EventSocket client library in Ruby and based on Celluloid
8
8
  ```ruby
9
9
  require 'ruby_fs'
10
10
 
11
- client = RubyFS::Stream.new '127.0.0.1', 8021, 'ClueCon', lambda { |e| p e }
11
+ stream = RubyFS::Stream.new '127.0.0.1', 8021, 'ClueCon', lambda { |e| p e }
12
12
 
13
- client.start
13
+ stream.run
14
+
15
+ stream.api 'originate sofia/mydomain.com/ext@yourvsp.com 1000' do |response|
16
+ puts "Originate response was #{response.inspect}"
17
+ end
14
18
  ```
15
19
 
16
20
  ## Links
data/lib/ruby_fs/event.rb CHANGED
@@ -2,6 +2,8 @@ require 'ruby_fs/response'
2
2
 
3
3
  module RubyFS
4
4
  class Event < Response
5
+ #
6
+ # @return [String[ The name of the event
5
7
  def event_name
6
8
  content[:event_name]
7
9
  end
@@ -19,63 +19,139 @@ module RubyFS
19
19
 
20
20
  def initialize(host, port, secret, event_callback, events = true)
21
21
  super()
22
- @secret, @event_callback, @events = secret, event_callback, events
22
+ @host, @port, @secret, @event_callback, @events = host, port, secret, event_callback, events
23
23
  @command_callbacks = []
24
- logger.debug "Starting up..."
25
24
  @lexer = Lexer.new method(:receive_request)
26
- @socket = TCPSocket.from_ruby_socket ::TCPSocket.new(host, port)
27
- post_init
28
25
  end
29
26
 
30
27
  [:started, :stopped, :ready].each do |state|
31
28
  define_method("#{state}?") { @state == state }
32
29
  end
33
30
 
31
+ #
32
+ # Connect to the server and begin handling data
34
33
  def run
34
+ logger.debug "Starting up..."
35
+ @socket = TCPSocket.from_ruby_socket ::TCPSocket.new(@host, @port)
36
+ post_init
35
37
  loop { receive_data @socket.readpartial(4096) }
36
- rescue EOFError
38
+ rescue EOFError, IOError
37
39
  logger.info "Client socket closed!"
38
- current_actor.terminate!
39
- end
40
-
41
- def post_init
42
- @state = :started
43
- fire_event Connected.new
40
+ terminate
44
41
  end
45
42
 
43
+ #
44
+ # Send raw string data to the FS server
45
+ #
46
+ # @param [#to_s] data the data to send over the socket
46
47
  def send_data(data)
47
- logger.debug "[SEND] #{data.to_s}"
48
+ logger.trace "[SEND] #{data.to_s}"
48
49
  @socket.write data.to_s
49
50
  end
50
51
 
52
+ #
53
+ # Send a FreeSWITCH command with options and a callback for the response
54
+ #
55
+ # @param [#to_s] command the command to run
56
+ # @param [optional, Hash] options the command's options, where keys have _ substituted for -
57
+ #
58
+ # @yield [response] Handle the command's response
59
+ # @yieldparam [RubyFS::Response] response the command's response object
51
60
  def command(command, options = {}, &block)
52
61
  @command_callbacks << (block || lambda { |reply| logger.debug "Reply to a command (#{command}) without a callback: #{reply.inspect}" })
53
62
  string = "#{command}\n"
54
63
  options.each_pair do |key, value|
55
- string << "#{key.to_s.gsub '_', '-'}: #{value}\n"
64
+ string << "#{key.to_s.gsub '_', '-'}: #{value}\n" if value
56
65
  end
57
66
  string << "\n"
58
67
  send_data string
59
68
  end
60
69
 
61
- def receive_data(data)
62
- logger.debug "[RECV] #{data}"
63
- @lexer << data
70
+ #
71
+ # Send an API action
72
+ #
73
+ # @param [#to_s] action the API action to execute
74
+ #
75
+ # @yield [response] Handle the command's response
76
+ # @yieldparam [RubyFS::Response] response the command's response object
77
+ def api(action, &block)
78
+ command "api #{action}", &block
79
+ end
80
+
81
+ #
82
+ # Send an API action in the background
83
+ #
84
+ # @param [#to_s] action the API action to execute
85
+ #
86
+ # @yield [response] Handle the command's response
87
+ # @yieldparam [RubyFS::Response] response the command's response object
88
+ def bgapi(action, &block)
89
+ command "bgapi #{action}", &block
90
+ end
91
+
92
+ #
93
+ # Send a message to a particular call
94
+ #
95
+ # @param [#to_s] call the call ID to send the message to
96
+ # @param [optional, Hash] options the message options
97
+ #
98
+ # @yield [response] Handle the message's response
99
+ # @yieldparam [RubyFS::Response] response the message's response object
100
+ def sendmsg(call, options = {}, &block)
101
+ command "SendMsg #{call}", options, &block
102
+ end
103
+
104
+ #
105
+ # Execute an application on a particular call
106
+ #
107
+ # @param [#to_s] call the call ID on which to execute the application
108
+ # @param [#to_s] appname the app to execute
109
+ # @param [optional, String] options the application options
110
+ #
111
+ # @yield [response] Handle the application's response
112
+ # @yieldparam [RubyFS::Response] response the application's response object
113
+ def application(call, appname, options = nil, &block)
114
+ sendmsg call, :call_command => 'execute', :execute_app_name => appname, :execute_app_arg => options, &block
115
+ end
116
+
117
+ #
118
+ # Shutdown the stream and disconnect from the socket
119
+ def shutdown
120
+ @socket.close if @socket
64
121
  end
65
122
 
123
+ # @private
66
124
  def finalize
67
125
  logger.debug "Finalizing stream"
68
- @socket.close if @socket
69
126
  @state = :stopped
70
127
  fire_event Disconnected.new
71
128
  end
72
129
 
130
+ #
131
+ # Fire an event to the specified callback
132
+ #
133
+ # @param [Object] event the event to fire
73
134
  def fire_event(event)
74
135
  @event_callback.call event
75
136
  end
76
137
 
138
+ #
139
+ # The stream's logger object
77
140
  def logger
78
- Logger
141
+ super
142
+ rescue
143
+ @logger ||= begin
144
+ logger = Logger
145
+ logger.define_singleton_method :trace, logger.method(:debug)
146
+ logger
147
+ end
148
+ end
149
+
150
+ private
151
+
152
+ def receive_data(data)
153
+ logger.trace "[RECV] #{data}"
154
+ @lexer << data
79
155
  end
80
156
 
81
157
  def receive_request(headers, content)
@@ -101,5 +177,10 @@ module RubyFS
101
177
  end
102
178
  end
103
179
  end
180
+
181
+ def post_init
182
+ @state = :started
183
+ fire_event Connected.new
184
+ end
104
185
  end
105
186
  end
@@ -1,3 +1,3 @@
1
1
  module RubyFS
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -30,6 +30,7 @@ module RubyFS
30
30
  s = ServerMock.new '127.0.0.1', server_port, mock_target
31
31
  @stream = Stream.new '127.0.0.1', server_port, secret, lambda { |m| client.message_received m }, events
32
32
  @stream.run!
33
+ sleep 0.1
33
34
  fake_client.call s if fake_client.respond_to? :call
34
35
  s.join
35
36
  @stream.join
@@ -50,7 +51,7 @@ module RubyFS
50
51
  expect_connected_event
51
52
  expect_disconnected_event
52
53
  mocked_server(0) do |val, server|
53
- @stream.started?.should be_true
54
+ @stream.should be_started
54
55
  end
55
56
  end
56
57
 
@@ -61,6 +62,15 @@ module RubyFS
61
62
  val.should == "foo"
62
63
  end
63
64
  end
65
+
66
+ it "can be shut down" do
67
+ expect_connected_event
68
+ expect_disconnected_event
69
+ mocked_server(0, lambda { |server| @stream.shutdown }) do |val, server|
70
+ @stream.should be_started
71
+ end
72
+ @stream.should_not be_alive
73
+ end
64
74
  end
65
75
 
66
76
  it 'sends events to the client when the stream is ready' do
@@ -158,6 +168,94 @@ Reply-Text: +OK accepted
158
168
  one: 1
159
169
  foo-bar: doo_dah
160
170
 
171
+ )
172
+ end
173
+ end
174
+
175
+ it "can send API commands with response callbacks" do
176
+ expect_connected_event
177
+ expect_disconnected_event
178
+ handler = mock
179
+ handler.expects(:call).once.with CommandReply.new(:content_type => 'command/reply', :reply_text => '+OK accepted')
180
+ mocked_server(1, lambda { |server| @stream.api('foo') { |reply| handler.call reply } }) do |val, server|
181
+ val.should == "api foo\n\n"
182
+ server.send_data %Q(
183
+ Content-Type: command/reply
184
+ Reply-Text: +OK accepted
185
+
186
+ )
187
+ end
188
+ end
189
+
190
+ it "can send background API commands with response callbacks" do
191
+ expect_connected_event
192
+ expect_disconnected_event
193
+ handler = mock
194
+ handler.expects(:call).once.with CommandReply.new(:content_type => 'command/reply', :reply_text => '+OK Job-UUID: 4e8344be-c1fe-11e1-a7bf-cf9911a69d1e', :job_uuid => '4e8344be-c1fe-11e1-a7bf-cf9911a69d1e')
195
+ mocked_server(1, lambda { |server| @stream.bgapi('foo') { |reply| handler.call reply } }) do |val, server|
196
+ val.should == "bgapi foo\n\n"
197
+ server.send_data %Q(
198
+ Content-Type: command/reply
199
+ Reply-Text: +OK Job-UUID: 4e8344be-c1fe-11e1-a7bf-cf9911a69d1e
200
+ Job-UUID: 4e8344be-c1fe-11e1-a7bf-cf9911a69d1e
201
+
202
+ )
203
+ end
204
+ end
205
+
206
+ it "can send messages to calls with options and response callbacks" do
207
+ expect_connected_event
208
+ expect_disconnected_event
209
+ handler = mock
210
+ handler.expects(:call).once.with CommandReply.new(:content_type => 'command/reply', :reply_text => '+OK accepted')
211
+ mocked_server(1, lambda { |server| @stream.sendmsg('aUUID', :call_command => 'execute') { |reply| handler.call reply } }) do |val, server|
212
+ val.should == %Q(SendMsg aUUID
213
+ call-command: execute
214
+
215
+ )
216
+ server.send_data %Q(
217
+ Content-Type: command/reply
218
+ Reply-Text: +OK accepted
219
+
220
+ )
221
+ end
222
+ end
223
+
224
+ it "can execute applications on calls without options but with response callbacks" do
225
+ expect_connected_event
226
+ expect_disconnected_event
227
+ handler = mock
228
+ handler.expects(:call).once.with CommandReply.new(:content_type => 'command/reply', :reply_text => '+OK accepted')
229
+ mocked_server(1, lambda { |server| @stream.application('aUUID', 'answer') { |reply| handler.call reply } }) do |val, server|
230
+ val.should == %Q(SendMsg aUUID
231
+ call-command: execute
232
+ execute-app-name: answer
233
+
234
+ )
235
+ server.send_data %Q(
236
+ Content-Type: command/reply
237
+ Reply-Text: +OK accepted
238
+
239
+ )
240
+ end
241
+ end
242
+
243
+ it "can execute applications on calls with options and response callbacks" do
244
+ expect_connected_event
245
+ expect_disconnected_event
246
+ handler = mock
247
+ handler.expects(:call).once.with CommandReply.new(:content_type => 'command/reply', :reply_text => '+OK accepted')
248
+ mocked_server(1, lambda { |server| @stream.application('aUUID', 'playback', '/tmp/test.wav') { |reply| handler.call reply } }) do |val, server|
249
+ val.should == %Q(SendMsg aUUID
250
+ call-command: execute
251
+ execute-app-name: playback
252
+ execute-app-arg: /tmp/test.wav
253
+
254
+ )
255
+ server.send_data %Q(
256
+ Content-Type: command/reply
257
+ Reply-Text: +OK accepted
258
+
161
259
  )
162
260
  end
163
261
  end
@@ -26,6 +26,8 @@ class ServerMock
26
26
  _, port, host = socket.peeraddr
27
27
  Logger.debug "MockServer Received connection from #{host}:#{port}"
28
28
  loop { receive_data socket.readpartial(4096) }
29
+ rescue EOFError
30
+ Logger.debug "Connection from #{host}:#{port} closed"
29
31
  end
30
32
 
31
33
  def receive_data(data)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_fs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-06-29 00:00:00.000000000 Z
12
+ date: 2012-07-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: uuidtools
@@ -295,7 +295,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
295
295
  version: '0'
296
296
  segments:
297
297
  - 0
298
- hash: 2481817772884138362
298
+ hash: 3148609615352772925
299
299
  required_rubygems_version: !ruby/object:Gem::Requirement
300
300
  none: false
301
301
  requirements:
@@ -304,7 +304,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
304
304
  version: '0'
305
305
  segments:
306
306
  - 0
307
- hash: 2481817772884138362
307
+ hash: 3148609615352772925
308
308
  requirements: []
309
309
  rubyforge_project: ruby_fs
310
310
  rubygems_version: 1.8.24