librevox 0.2.1 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,19 +7,18 @@ module Librevox
7
7
  class Base < EventMachine::Protocols::HeaderAndContentProtocol
8
8
  class << self
9
9
  def hooks
10
- @hooks ||= []
10
+ @hooks ||= Hash.new {|hash, key| hash[key] = []}
11
11
  end
12
12
 
13
13
  def event event, &block
14
- hooks << [event, block]
14
+ hooks[event] << block
15
15
  end
16
16
  end
17
17
 
18
18
  # In some cases there are both applications and commands with the same
19
19
  # name, e.g. fifo. But we can't have two `fifo`-methods, so we include
20
- # commands in CommandDelegate, and wrap all commands in the `api` call,
21
- # which forwards the call to the CommandDelegate instance, which in turn
22
- # forwards the #run_cmd-call from the command back to the listener. Yay.
20
+ # commands in CommandDelegate, and expose all commands through the `api`
21
+ # method, which wraps a CommandDelegate instance.
23
22
  class CommandDelegate
24
23
  include Librevox::Commands
25
24
 
@@ -27,25 +26,32 @@ module Librevox
27
26
  @listener = listener
28
27
  end
29
28
 
30
- def run_cmd *args, &block
31
- @listener.run_cmd *args, &block
29
+ def command *args, &block
30
+ @listener.command super(*args), &block
32
31
  end
33
32
  end
34
33
 
35
- def api cmd, *args, &block
36
- @command_delegate.send(cmd, *args, &block)
34
+ # Exposes an instance of {CommandDelegate}, which includes {Librevox::Commands}.
35
+ # @example
36
+ # api.status
37
+ # api.fsctl :pause
38
+ # api.uuid_park "592567a2-1be4-11df-a036-19bfdab2092f"
39
+ # @see Librevox::Commands
40
+ def api
41
+ @command_delegate ||= CommandDelegate.new(self)
37
42
  end
38
43
 
39
- def run_cmd cmd, &block
40
- send_data "#{cmd}\n\n"
41
- @command_queue << (block || lambda {})
44
+ def command msg
45
+ send_data "#{msg}\n\n"
46
+
47
+ @command_queue << Fiber.current
48
+ Fiber.yield
42
49
  end
43
50
 
44
51
  attr_accessor :response
45
52
  alias :event :response
46
53
 
47
54
  def post_init
48
- @command_delegate = CommandDelegate.new(self)
49
55
  @command_queue = []
50
56
  end
51
57
 
@@ -56,9 +62,11 @@ module Librevox
56
62
 
57
63
  def handle_response
58
64
  if response.api_response? && @command_queue.any?
59
- invoke_command_queue
60
- elsif response.event?
61
- on_event response.dup
65
+ @command_queue.shift.resume response
66
+ end
67
+
68
+ if response.event?
69
+ Fiber.new {on_event response.dup}.resume
62
70
  invoke_event_hooks
63
71
  end
64
72
  end
@@ -71,17 +79,13 @@ module Librevox
71
79
 
72
80
  private
73
81
  def invoke_event_hooks
74
- self.class.hooks.each {|name,block|
75
- if name == response.event.downcase.to_sym
82
+ event = response.event.downcase.to_sym
83
+ self.class.hooks[event].each {|block|
84
+ Fiber.new {
76
85
  instance_exec response.dup, &block
77
- end
86
+ }.resume
78
87
  }
79
88
  end
80
-
81
- def invoke_command_queue
82
- block = @command_queue.shift
83
- block.call response
84
- end
85
89
  end
86
90
  end
87
91
  end
@@ -3,7 +3,7 @@ require 'librevox/listener/base'
3
3
  module Librevox
4
4
  module Listener
5
5
  class Inbound < Base
6
- def initialize(args={})
6
+ def initialize args={}
7
7
  super
8
8
 
9
9
  @auth = args[:auth] || "ClueCon"
@@ -6,21 +6,20 @@ module Librevox
6
6
  class Outbound < Base
7
7
  include Librevox::Applications
8
8
 
9
- def execute_app app, args="", params={}, &block
9
+ def application app, args=nil, params={}
10
10
  msg = "sendmsg\n"
11
11
  msg << "call-command: execute\n"
12
12
  msg << "execute-app-name: #{app}\n"
13
- msg << "execute-app-arg: #{args}\n" unless args.empty?
13
+ msg << "execute-app-arg: #{args}\n" if args && !args.empty?
14
14
 
15
15
  send_data "#{msg}\n"
16
16
 
17
- @read_channel_var = params[:read_var]
17
+ @application_queue << Fiber.current
18
18
 
19
- if @read_channel_var
20
- @application_queue << lambda {update_session}
21
- end
19
+ Fiber.yield
20
+ update_session
22
21
 
23
- @application_queue << (block || lambda {})
22
+ params[:variable] ? variable(params[:variable]) : nil
24
23
  end
25
24
 
26
25
  # This should probably be in Application#sendmsg instead.
@@ -41,36 +40,29 @@ module Librevox
41
40
 
42
41
  send_data "connect\n\n"
43
42
  send_data "myevents\n\n"
44
- @application_queue << lambda {}
43
+ @application_queue << Fiber.new {}
45
44
  send_data "linger\n\n"
46
- @application_queue << lambda {}
45
+ @application_queue << Fiber.new {session_initiated}
47
46
  end
48
47
 
49
48
  def handle_response
50
49
  if session.nil?
51
50
  @session = response.headers
52
- session_initiated
53
51
  elsif response.event? && response.event == "CHANNEL_DATA"
54
52
  @session = response.content
55
- resume_with_channel_var
56
53
  elsif response.command_reply? && !response.event?
57
- @application_queue.shift.call if @application_queue.any?
54
+ @application_queue.shift.resume if @application_queue.any?
58
55
  end
59
56
 
60
57
  super
61
58
  end
62
59
 
63
- def resume_with_channel_var
64
- if @read_channel_var
65
- variable = "variable_#{@read_channel_var}".to_sym
66
- value = @session[variable]
67
- @application_queue.shift.call(value) if @application_queue.any?
68
- end
60
+ def variable name
61
+ session[:"variable_#{name}"]
69
62
  end
70
63
 
71
- def update_session &block
72
- send_data "api uuid_dump #{session[:unique_id]}\n\n"
73
- @command_queue << (block || lambda {})
64
+ def update_session
65
+ api.command "uuid_dump", session[:unique_id]
74
66
  end
75
67
  end
76
68
  end
@@ -9,17 +9,17 @@ module Librevox
9
9
  class Response
10
10
  attr_accessor :headers, :content
11
11
 
12
- def initialize(headers="", content="")
12
+ def initialize headers="", content=""
13
13
  self.headers = headers
14
14
  self.content = content
15
15
  end
16
16
 
17
- def headers=(headers)
17
+ def headers= headers
18
18
  @headers = headers_2_hash(headers)
19
19
  @headers.each {|k,v| v.chomp! if v.is_a?(String)}
20
20
  end
21
21
 
22
- def content=(content)
22
+ def content= content
23
23
  @content = content.match(/:/) ? headers_2_hash(content) : content
24
24
  @content.each {|k,v| v.chomp! if v.is_a?(String)}
25
25
  end
@@ -41,7 +41,7 @@ module Librevox
41
41
  end
42
42
 
43
43
  private
44
- def headers_2_hash(*args)
44
+ def headers_2_hash *args
45
45
  EM::Protocols::HeaderAndContentProtocol.headers_2_hash *args
46
46
  end
47
47
  end
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "librevox"
3
- s.version = "0.2.1"
4
- s.date = "2010-02-09"
3
+ s.version = "0.3"
4
+ s.date = "2010-09-30"
5
5
  s.summary = "Ruby library for interacting with FreeSWITCH."
6
6
  s.email = "harry@vangberg.name"
7
7
  s.homepage = "http://github.com/ichverstehe/librevox"
@@ -9,11 +9,11 @@ Gem::Specification.new do |s|
9
9
  open source telephony platform FreeSwitch."
10
10
  s.authors = ["Harry Vangberg"]
11
11
  s.files = [
12
- "README.md",
12
+ "README.md",
13
13
  "LICENSE",
14
14
  "TODO",
15
15
  "Rakefile",
16
- "librevox.gemspec",
16
+ "librevox.gemspec",
17
17
  "lib/librevox.rb",
18
18
  "lib/librevox/applications.rb",
19
19
  "lib/librevox/command_socket.rb",
@@ -27,11 +27,14 @@ open source telephony platform FreeSwitch."
27
27
  "spec/helper.rb",
28
28
  "spec/librevox/listener.rb",
29
29
  "spec/librevox/spec_applications.rb",
30
- "spec/librevox/spec_command_socket.rb",
30
+ #"spec/librevox/spec_command_socket.rb",
31
31
  "spec/librevox/spec_commands.rb",
32
32
  "spec/librevox/spec_response.rb",
33
33
  "spec/librevox/listener/spec_inbound.rb",
34
34
  "spec/librevox/listener/spec_outbound.rb"
35
35
  ]
36
+ s.add_dependency "eventmachine", "~> 0.12.10"
37
+ s.add_development_dependency "bacon", "~> 1.1"
38
+ s.add_development_dependency "rr", "~> 1"
36
39
  end
37
40
 
@@ -1,6 +1,86 @@
1
- $:.unshift 'lib'
2
-
3
1
  require 'bacon'
4
2
  require 'librevox'
5
3
 
4
+ module Librevox::Test
5
+ module Matchers
6
+ def send_command command
7
+ lambda {|obj|
8
+ obj.outgoing_data.shift.should == "#{command}\n\n"
9
+ }
10
+ end
11
+
12
+ def send_nothing
13
+ lambda {|obj| obj.outgoing_data.shift.should == nil}
14
+ end
15
+
16
+ def send_application app, args=nil
17
+ lambda {|obj|
18
+ msg = <<-EOM
19
+ sendmsg
20
+ call-command: execute
21
+ execute-app-name: #{app}
22
+ EOM
23
+ msg << "execute-app-arg: #{args}\n" if args
24
+ msg << "\n"
25
+
26
+ obj.outgoing_data.shift.should == msg
27
+ }
28
+ end
29
+
30
+ def update_session session_id=nil
31
+ if session_id
32
+ lambda {|obj|
33
+ obj.outgoing_data.shift.should == "api uuid_dump #{session_id}\n\n"
34
+ }
35
+ else
36
+ lambda {|obj|
37
+ obj.outgoing_data.shift.should.match /^api uuid_dump \d+/
38
+ }
39
+ end
40
+ end
41
+ end
42
+
43
+ module ListenerHelpers
44
+ def command_reply args={}
45
+ args["Content-Type"] = "command/reply"
46
+ response args
47
+ end
48
+
49
+ def api_response args={}
50
+ args["Content-Type"] = "api/response"
51
+ response args
52
+ end
53
+
54
+ def channel_data args={}
55
+ api_response :body => {
56
+ "Event-Name" => "CHANNEL_DATA",
57
+ "Session-Var" => "Second"
58
+ }.merge(args)
59
+ end
60
+
61
+ def response args={}
62
+ body = args.delete :body
63
+ headers = args
64
+
65
+ if body.is_a? Hash
66
+ body = body.map {|k,v| "#{k}: #{v}"}.join "\n"
67
+ end
68
+
69
+ headers["Content-Length"] = body.size if body
70
+ msg = headers.map {|k, v| "#{k}: #{v}"}.join "\n"
71
+
72
+ msg << "\n\n" + body if body
73
+
74
+ @listener.receive_data msg + "\n\n"
75
+ end
76
+
77
+ def event name
78
+ body = "Event-Name: #{name}"
79
+ headers = "Content-Length: #{body.size}"
80
+
81
+ @listener.receive_data "#{headers}\n\n#{body}\n\n"
82
+ end
83
+ end
84
+ end
85
+
6
86
  Bacon.summary_on_exit
@@ -1,7 +1,8 @@
1
- require 'spec/helper'
2
-
1
+ require './spec/helper'
3
2
  require 'librevox/listener/base'
4
3
 
4
+ include Librevox::Test::ListenerHelpers
5
+
5
6
  class Librevox::Listener::Base
6
7
  attr_accessor :outgoing_data
7
8
 
@@ -19,6 +20,8 @@ class Librevox::Listener::Base
19
20
  end
20
21
  end
21
22
 
23
+ # These tests are a bit fragile, as they depend on event hooks being
24
+ # executed before on_event.
22
25
  shared "events" do
23
26
  before do
24
27
  @class = @listener.class
@@ -37,43 +40,64 @@ shared "events" do
37
40
 
38
41
  should "add event hook" do
39
42
  @class.hooks.size.should == 3
43
+ @class.hooks.each do |event, hooks|
44
+ hooks.size.should == 1
45
+ end
40
46
  end
41
47
 
42
48
  should "execute callback for event" do
43
- @listener.receive_data("Content-Length: 23\n\nEvent-Name: OTHER_EVENT\n\n")
49
+ event "OTHER_EVENT"
44
50
  @listener.read_data.should == "something else"
45
51
 
46
- @listener.receive_data("Content-Length: 22\n\nEvent-Name: SOME_EVENT\n\n")
52
+ event "SOME_EVENT"
47
53
  @listener.read_data.should == "something"
48
54
  end
49
55
 
50
56
  should "pass response duplicate as arg to hook block" do
51
- @listener.receive_data("Content-Length: 25\n\nEvent-Name: HOOK_WITH_ARG\n\n")
57
+ event "HOOK_WITH_ARG"
58
+
52
59
  reply = @listener.read_data
53
60
  reply.should =~ /^got event arg: /
54
61
  reply.should.not =~ /^got event arg: #{@listener.response.object_id}$/
55
62
  end
56
63
 
57
64
  should "expose response as event" do
58
- @listener.receive_data("Content-Length: 23\n\nEvent-Name: OTHER_EVENT\n\n")
65
+ event "OTHER_EVENT"
66
+
59
67
  @listener.event.class.should == Librevox::Response
60
68
  @listener.event.content[:event_name].should == "OTHER_EVENT"
61
69
  end
62
70
 
63
71
  should "call on_event" do
64
- @listener.receive_data("Content-Length: 23\n\nEvent-Name: THIRD_EVENT\n\n")
72
+ event "THIRD_EVENT"
73
+
65
74
  @listener.read_data.should =~ /^from on_event/
66
75
  end
67
76
 
68
77
  should "call on_event with response duplicate as argument" do
69
- @listener.receive_data("Content-Length: 23\n\nEvent-Name: THIRD_EVENT\n\n")
78
+ event "THIRD_EVENT"
79
+
70
80
  @listener.read_data.should.not =~ /^from on_event: #{@listener.response.object_id}$/
71
81
  end
82
+
83
+ should "call event hooks and on_event on CHANNEL_DATA" do
84
+ @listener.outgoing_data.clear
85
+
86
+ def @listener.on_event e
87
+ send_data "on_event: CHANNEL_DATA test"
88
+ end
89
+ @class.event(:channel_data) {send_data "event hook: CHANNEL_DATA test"}
90
+
91
+ event "CHANNEL_DATA"
92
+
93
+ @listener.outgoing_data.should.include "on_event: CHANNEL_DATA test"
94
+ @listener.outgoing_data.should.include "event hook: CHANNEL_DATA test"
95
+ end
72
96
  end
73
97
 
74
98
  module Librevox::Commands
75
- def sample_cmd(cmd, args="", &b)
76
- execute_cmd cmd, args, &b
99
+ def sample_cmd cmd, args=""
100
+ command cmd, args
77
101
  end
78
102
  end
79
103
 
@@ -82,74 +106,35 @@ shared "api commands" do
82
106
  @class = @listener.class
83
107
 
84
108
  # Establish session
85
- @listener.receive_data("Content-Type: command/reply\nTest: Testing\n\n")
109
+ command_reply "Test" => "Testing"
86
110
  end
87
111
 
88
112
  describe "multiple api commands" do
113
+ extend Librevox::Test::Matchers
114
+
89
115
  before do
90
116
  @listener.outgoing_data.clear
91
117
 
92
118
  def @listener.on_event(e) end # Don't send anything, kthx.
93
119
 
94
120
  @class.event(:api_test) {
95
- api :sample_cmd, "foo" do
96
- api :sample_cmd, "foo", "bar baz"
97
- end
121
+ api.sample_cmd "foo"
122
+ r = api.sample_cmd "foo", "bar baz"
123
+ command "response #{r.content}"
98
124
  }
99
125
  end
100
126
 
101
- should "only send one command at a time" do
102
- @listener.receive_data("Content-Type: command/reply\nContent-Length: 22\n\nEvent-Name: API_TEST\n\n")
103
- @listener.read_data.should == "api foo\n\n"
104
- @listener.read_data.should == nil
105
-
106
- @listener.receive_data("Content-Type: api/response\nReply-Text: +OK\n\n")
107
- @listener.read_data.should == "api foo bar baz\n\n"
108
- @listener.read_data.should == nil
109
- end
110
- end
111
-
112
- describe "flat api commands" do
113
- before do
114
- @listener.outgoing_data.clear
115
- @class.event(:api_flat_test) {
116
- api :sample_cmd, "foo"
117
- api :sample_cmd, "bar" do
118
- api :sample_cmd, "baz"
119
- end
120
- }
121
- end
122
-
123
- should "wait for response before calling next proc" do
124
- @listener.receive_data("Content-Type: command/reply\nContent-Length: 27\n\nEvent-Name: API_FLAT_TEST\n\n")
125
-
126
- @listener.read_data.should.not == "api baz\n\n"
127
-
128
- # response to "foo"
129
- @listener.receive_data("Content-Type: api/response\nContent-Length: 3\n\n+OK\n\n")
130
- @listener.read_data.should.not == "api baz\n\n"
131
-
132
- # response to "bar"
133
- @listener.receive_data("Content-Type: api/response\nContent-Length: 3\n\n+OK\n\n")
134
- @listener.read_data.should == "api baz\n\n"
135
- end
136
- end
137
-
138
- describe "api command with block argument" do
139
- before do
140
- @listener.outgoing_data.clear
141
- @class.event(:api_arg_test) {
142
- api :sample_cmd, "foo" do |r|
143
- send_data "response: #{r.content}"
144
- end
145
- }
146
- end
127
+ should "only send one command at a time, and return response for commands" do
128
+ command_reply :body => {"Event-Name" => "API_TEST"}
129
+ @listener.should send_command "api foo"
130
+ @listener.should send_nothing
147
131
 
148
- should "pass response" do
149
- @listener.receive_data("Content-Type: command/reply\nContent-Length: 26\n\nEvent-Name: API_ARG_TEST\n\n")
150
- @listener.receive_data("Content-Type: api/response\nContent-Length: 3\n\n+OK\n\n")
132
+ api_response "Reply-Text" => "+OK"
133
+ @listener.should send_command "api foo bar baz"
134
+ @listener.should send_nothing
151
135
 
152
- @listener.read_data.should == "response: +OK"
136
+ api_response :body => "+YAY"
137
+ @listener.should send_command "response +YAY"
153
138
  end
154
139
  end
155
140
  end