librevox 0.2.1 → 0.3

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.
@@ -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