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.
- data/README.md +12 -11
- data/TODO +5 -5
- data/lib/librevox.rb +16 -5
- data/lib/librevox/applications.rb +58 -51
- data/lib/librevox/command_socket.rb +5 -4
- data/lib/librevox/commands.rb +37 -13
- data/lib/librevox/listener/base.rb +28 -24
- data/lib/librevox/listener/inbound.rb +1 -1
- data/lib/librevox/listener/outbound.rb +13 -21
- data/lib/librevox/response.rb +4 -4
- data/librevox.gemspec +8 -5
- data/spec/helper.rb +82 -2
- data/spec/librevox/listener.rb +49 -64
- data/spec/librevox/listener/spec_inbound.rb +2 -2
- data/spec/librevox/listener/spec_outbound.rb +142 -97
- data/spec/librevox/spec_applications.rb +15 -10
- data/spec/librevox/spec_commands.rb +41 -6
- data/spec/librevox/spec_response.rb +1 -1
- metadata +63 -8
- data/spec/librevox/spec_command_socket.rb +0 -111
@@ -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 <<
|
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
|
21
|
-
# which
|
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
|
31
|
-
@listener.
|
29
|
+
def command *args, &block
|
30
|
+
@listener.command super(*args), &block
|
32
31
|
end
|
33
32
|
end
|
34
33
|
|
35
|
-
|
36
|
-
|
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
|
40
|
-
send_data "#{
|
41
|
-
|
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
|
-
|
60
|
-
|
61
|
-
|
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
|
-
|
75
|
-
|
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
|
-
|
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
|
@@ -6,21 +6,20 @@ module Librevox
|
|
6
6
|
class Outbound < Base
|
7
7
|
include Librevox::Applications
|
8
8
|
|
9
|
-
def
|
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"
|
13
|
+
msg << "execute-app-arg: #{args}\n" if args && !args.empty?
|
14
14
|
|
15
15
|
send_data "#{msg}\n"
|
16
16
|
|
17
|
-
@
|
17
|
+
@application_queue << Fiber.current
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
end
|
19
|
+
Fiber.yield
|
20
|
+
update_session
|
22
21
|
|
23
|
-
|
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 <<
|
43
|
+
@application_queue << Fiber.new {}
|
45
44
|
send_data "linger\n\n"
|
46
|
-
@application_queue <<
|
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.
|
54
|
+
@application_queue.shift.resume if @application_queue.any?
|
58
55
|
end
|
59
56
|
|
60
57
|
super
|
61
58
|
end
|
62
59
|
|
63
|
-
def
|
64
|
-
|
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
|
72
|
-
|
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
|
data/lib/librevox/response.rb
CHANGED
@@ -9,17 +9,17 @@ module Librevox
|
|
9
9
|
class Response
|
10
10
|
attr_accessor :headers, :content
|
11
11
|
|
12
|
-
def initialize
|
12
|
+
def initialize headers="", content=""
|
13
13
|
self.headers = headers
|
14
14
|
self.content = content
|
15
15
|
end
|
16
16
|
|
17
|
-
def 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=
|
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
|
44
|
+
def headers_2_hash *args
|
45
45
|
EM::Protocols::HeaderAndContentProtocol.headers_2_hash *args
|
46
46
|
end
|
47
47
|
end
|
data/librevox.gemspec
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "librevox"
|
3
|
-
s.version = "0.
|
4
|
-
s.date = "2010-
|
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
|
|
data/spec/helper.rb
CHANGED
@@ -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
|
data/spec/librevox/listener.rb
CHANGED
@@ -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
|
-
|
49
|
+
event "OTHER_EVENT"
|
44
50
|
@listener.read_data.should == "something else"
|
45
51
|
|
46
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
76
|
-
|
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
|
-
|
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
|
96
|
-
|
97
|
-
|
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
|
-
|
103
|
-
@listener.
|
104
|
-
@listener.
|
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
|
-
|
149
|
-
@listener.
|
150
|
-
@listener.
|
132
|
+
api_response "Reply-Text" => "+OK"
|
133
|
+
@listener.should send_command "api foo bar baz"
|
134
|
+
@listener.should send_nothing
|
151
135
|
|
152
|
-
|
136
|
+
api_response :body => "+YAY"
|
137
|
+
@listener.should send_command "response +YAY"
|
153
138
|
end
|
154
139
|
end
|
155
140
|
end
|