librevox 0.2.1 → 0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|