librevox 0.9 → 1.0.0.alpha2

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.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Librevox
2
4
  # All applications should call `application` with the following parameters:
3
5
  #
@@ -8,8 +10,14 @@ module Librevox
8
10
  module Applications
9
11
  # Answers an incoming call or session.
10
12
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_answer
11
- def answer &block
12
- application "answer", &block
13
+ def answer
14
+ application "answer"
15
+ end
16
+
17
+ # Parks a call, keeping it active without routing it anywhere.
18
+ # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_park
19
+ def park
20
+ application "park"
13
21
  end
14
22
 
15
23
  # Make an attended transfer
@@ -17,27 +25,26 @@ module Librevox
17
25
  # att_xfer("user/davis")
18
26
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_att_xfer
19
27
  # @todo Add support for origination_cancel_key
20
- def att_xfer endpoint, &block
21
- application "att_xfer", endpoint, &block
28
+ def att_xfer(endpoint)
29
+ application "att_xfer", endpoint
22
30
  end
23
31
 
24
32
  # Binds an application to the specified call legs.
25
- # @example
26
- # bind_meta_app :key => 2,
27
- # :listen_to => "a",
28
- # :respond_on => "s",
29
- # :application => "execute_extension",
30
- # :parameters => "dx XML features"
33
+ # @example
34
+ # bind_meta_app key: 2,
35
+ # listen_to: "a",
36
+ # respond_on: "s",
37
+ # application: "execute_extension",
38
+ # parameters: "dx XML features"
31
39
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_bind_meta_app
32
- def bind_meta_app args={}, &block
40
+ def bind_meta_app(args = {})
33
41
  arg_string =
34
42
  args.values_at(:key, :listen_to, :respond_on, :application).join(" ")
35
43
  arg_string += "::#{args[:parameters]}" if args[:parameters]
36
44
 
37
- application "bind_meta_app", arg_string, &block
45
+ application "bind_meta_app", arg_string
38
46
  end
39
47
 
40
-
41
48
  # Bridges an incoming call to an endpoint, optionally taking an array of
42
49
  # channel variables to set. If given an array of arrays, each contained
43
50
  # array of endpoints will be called simultaneously, with the next array
@@ -47,19 +54,16 @@ module Librevox
47
54
  # bridge "user/coltrane", "user/backup-office"
48
55
  # #=> user/coltrane,user/backup-office
49
56
  # @example With channel variables
50
- # bridge "user/coltrane", "user/backup-office", :some_var => "value"
57
+ # bridge "user/coltrane", "user/backup-office", some_var: "value"
51
58
  # #=> {some_var=value}user/coltrane,user/backup-office
52
59
  # @example With failover
53
60
  # bridge ['user/coltrane', 'user/davis'], ['user/sun-ra', 'user/taylor']
54
61
  # #=> user/coltrane,user/davis|user/sun-ra,user/taylor
55
62
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_bridge
56
- def bridge *args, &block
63
+ def bridge(*args)
57
64
  variables = if args.last.is_a? Hash
58
- # We need to sort the key/value pairs to facilitate testing.
59
- # This can be removed once 1.8-compat is dropped.
60
- key_value_pairs = args.pop.sort {|x,y| x.to_s <=> y.to_s}
61
- key_value_pairs.map! {|k,v| "#{k}=#{v}"}
62
- "{#{key_value_pairs.join(",")}}"
65
+ pairs = args.pop.map {|k,v| "#{k}=#{v}"}
66
+ "{#{pairs.join(",")}}"
63
67
  else
64
68
  ""
65
69
  end
@@ -70,38 +74,38 @@ module Librevox
70
74
  args.join ","
71
75
  end
72
76
 
73
- application "bridge", variables + endpoints, &block
77
+ application "bridge", variables + endpoints
74
78
  end
75
79
 
76
80
  # Deflect a call by sending a REFER. Takes a SIP URI as argument, rerouting
77
81
  # the call to that SIP URI.
78
82
  #
79
83
  # Beware that REFER only can be used on established calls. If a call hasn't
80
- # been established with e.g. the {#answer} application, you should use
84
+ # been established with e.g. the {#answer} application, you should use
81
85
  # {#redirect} instead.
82
86
  # @example
83
87
  # deflect "sip:miles@davis.com"
84
88
  # @see #redirect
85
89
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_deflect
86
- def deflect uri, &block
87
- application "deflect", uri, &block
90
+ def deflect(uri)
91
+ application "deflect", uri
88
92
  end
89
93
 
90
94
  # Exports a channel variable from the A leg to the B leg. Variables and
91
95
  # their values will be replicated in any new channels created from the one
92
96
  # export was called.
93
- #
94
- # Set :local => false if the variable should only be exported to the B-leg.
97
+ #
98
+ # Set `local: false` if the variable should only be exported to the B-leg.
95
99
  #
96
100
  # @example
97
101
  # export "some_var"
98
102
  # @example Only export to B-leg
99
- # export "some_var", :local => false
103
+ # export "some_var", local: false
100
104
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_export
101
- def export var, args={}, &block
102
- nolocal = args[:local] == false ? "nolocal:" : "" # ugly!!111
105
+ def export(var, args = {})
106
+ nolocal = args[:local] == false ? "nolocal:" : ""
103
107
 
104
- application "export", "#{nolocal}#{var}", &block
108
+ application "export", "#{nolocal}#{var}"
105
109
  end
106
110
 
107
111
  # Generate TGML tones
@@ -110,8 +114,8 @@ module Librevox
110
114
  # @example Generate a DTMF string
111
115
  # gentones "0800500005"
112
116
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_gentones
113
- def gentones tgml , &block
114
- application "gentones", tgml, &block
117
+ def gentones(tgml)
118
+ application "gentones", tgml
115
119
  end
116
120
 
117
121
  # Hang up current channel
@@ -120,21 +124,21 @@ module Librevox
120
124
  # @example Hang up with a reason
121
125
  # hangup "USER_BUSY"
122
126
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_hangup
123
- def hangup cause="", &block
124
- application "hangup", cause, &block
127
+ def hangup(cause = "")
128
+ application "hangup", cause
125
129
  end
126
130
 
127
131
  # Plays a sound file and reads DTMF presses.
128
- # @example
132
+ # @example
129
133
  # play_and_get_digits "please-enter.wav", "wrong-choice.wav",
130
- # :min => 1,
131
- # :max => 2,
132
- # :tries => 3,
133
- # :terminators => "#",
134
- # :timeout => 5000,
135
- # :regexp => '\d+'
134
+ # min: 1,
135
+ # max: 2,
136
+ # tries: 3,
137
+ # terminators: "#",
138
+ # timeout: 5000,
139
+ # regexp: '\d+'
136
140
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_play_and_get_digits
137
- def play_and_get_digits file, invalid_file, args={}, &block
141
+ def play_and_get_digits(file, invalid_file, args = {})
138
142
  min = args[:min] || 1
139
143
  max = args[:max] || 2
140
144
  tries = args[:tries] || 3
@@ -146,29 +150,29 @@ module Librevox
146
150
  args = [min, max, tries, timeout, terminators, file, invalid_file,
147
151
  variable, regexp].join " "
148
152
 
149
- params = {:variable => variable}
153
+ params = {variable: variable}
150
154
 
151
- application "play_and_get_digits", args, params, &block
155
+ application "play_and_get_digits", args, params
152
156
  end
153
157
 
154
158
  # Plays a sound file on the current channel.
155
159
  # @example
156
160
  # playback "/path/to/file.wav"
157
161
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_playback
158
- def playback file, &block
159
- application "playback", file, &block
162
+ def playback(file)
163
+ application "playback", file
160
164
  end
161
165
 
162
166
  # Pre-answer establishes early media but does not answer.
163
167
  # @example
164
- # pre_anser
168
+ # pre_answer
165
169
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_pre_answer
166
- def pre_answer &block
167
- application "pre_answer", &block
170
+ def pre_answer
171
+ application "pre_answer"
168
172
  end
169
173
 
170
174
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_read
171
- def read file, args={}, &block
175
+ def read(file, args = {})
172
176
  min = args[:min] || 1
173
177
  max = args[:max] || 2
174
178
  terminators = args[:terminators] || "#"
@@ -178,9 +182,9 @@ module Librevox
178
182
  arg_string = "%s %s %s %s %s %s" % [min, max, file, variable, timeout,
179
183
  terminators]
180
184
 
181
- params = {:variable => variable}
185
+ params = {variable: variable}
182
186
 
183
- application "read", arg_string, params, &block
187
+ application "read", arg_string, params
184
188
  end
185
189
 
186
190
  # Records a message, with an optional limit on the maximum duration of the
@@ -188,11 +192,11 @@ module Librevox
188
192
  # @example Without limit
189
193
  # record "/path/to/new/file.wac"
190
194
  # @example With 20 second limit
191
- # record "/path/to/new/file.wac", :limit => 20
195
+ # record "/path/to/new/file.wac", limit: 20
192
196
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_record
193
- def record path, params={}, &block
197
+ def record(path, params = {})
194
198
  args = [path, params[:limit]].compact.join(" ")
195
- application "record", args, &block
199
+ application "record", args
196
200
  end
197
201
 
198
202
  # Redirect a channel to another endpoint. You must take care to not
@@ -206,48 +210,57 @@ module Librevox
206
210
  # redirect "sip:freddie@hubbard.org"
207
211
  # @see #deflect
208
212
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_redirect
209
- def redirect uri, &block
210
- application "redirect", uri, &block
213
+ def redirect(uri)
214
+ application "redirect", uri
211
215
  end
212
216
 
213
217
  # Send SIP session respond code.
214
218
  # @example Send 403 Forbidden
215
219
  # respond 403
216
220
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_respond
217
- def respond code, &block
218
- application "respond", code.to_s, &block
221
+ def respond(code)
222
+ application "respond", code.to_s
219
223
  end
220
224
 
221
225
  # Sets a channel variable.
222
226
  # @example
223
227
  # set "some_var", "some value"
224
228
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_set
225
- def set variable, value, &block
226
- application "set", "#{variable}=#{value}", &block
229
+ def set(variable, value)
230
+ application "set", "#{variable}=#{value}"
231
+ end
232
+
233
+ # Sets multiple channel variables in a single application call.
234
+ # @example
235
+ # multiset "var1" => "val1", "var2" => "val2"
236
+ # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_multiset
237
+ def multiset(vars)
238
+ args = "^^|" + vars.map { |k, v| "#{k}=#{v}" }.join("|")
239
+ application "multiset", args
227
240
  end
228
241
 
229
242
  # Transfers the current channel to a new context.
230
243
  # @example
231
244
  # transfer "new_context"
232
245
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_transfer
233
- def transfer context, &block
234
- application "transfer", context, &block
246
+ def transfer(context)
247
+ application "transfer", context
235
248
  end
236
249
 
237
250
  # Unbinds a previously bound key with bind_meta_app
238
251
  # @example
239
252
  # unbind_meta_app 3
240
253
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_unbind_meta_app
241
- def unbind_meta_app key, &block
242
- application "unbind_meta_app", key.to_s, &block
254
+ def unbind_meta_app(key)
255
+ application "unbind_meta_app", key.to_s
243
256
  end
244
257
 
245
258
  # Unset a channel variable.
246
259
  # @example
247
260
  # unset "foo"
248
261
  # @see http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_unset
249
- def unset variable, &block
250
- application "unset", variable, &block
262
+ def unset(variable)
263
+ application "unset", variable
251
264
  end
252
265
  end
253
266
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/stream'
4
+
5
+ module Librevox
6
+ class Client
7
+ def initialize(handler, endpoint, **options)
8
+ @handler = handler
9
+ @endpoint = endpoint
10
+ @options = options
11
+ end
12
+
13
+ attr :endpoint
14
+
15
+ def connect(socket)
16
+ stream = IO::Stream(socket)
17
+ connection = Protocol::Connection.new(stream)
18
+
19
+ listener = @handler.new(connection, @options)
20
+
21
+ session_task = Async { listener.run_session }
22
+ connection.read_loop { |msg| listener.receive_message(msg) }
23
+ ensure
24
+ session_task&.stop
25
+ connection.close
26
+ end
27
+
28
+ def run
29
+ loop do
30
+ @endpoint.connect do |socket|
31
+ connect(socket)
32
+ end
33
+ rescue IOError, Errno::ECONNREFUSED, Errno::ECONNRESET => e
34
+ Librevox.logger.error "Connection lost: #{e.message}. Reconnecting in 1s."
35
+ sleep 1
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,13 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'socket'
2
- require 'librevox/response'
3
- require 'librevox/commands'
4
- require 'librevox/applications'
4
+ require 'io/stream'
5
5
 
6
6
  module Librevox
7
7
  class CommandSocket
8
8
  include Librevox::Commands
9
9
 
10
- def initialize args={}
10
+ def initialize(args = {})
11
11
  @server = args[:server] || "127.0.0.1"
12
12
  @port = args[:port] || "8021"
13
13
  @auth = args[:auth] || "ClueCon"
@@ -17,39 +17,25 @@ module Librevox
17
17
 
18
18
  def connect
19
19
  @socket = TCPSocket.open(@server, @port)
20
- @socket.print "auth #{@auth}\n\n"
20
+ stream = IO::Stream(@socket)
21
+ @connection = Protocol::Connection.new(stream)
22
+ @connection.write "auth #{@auth}\n\n"
21
23
  read_response
22
24
  end
23
25
 
24
- def command *args
25
- @socket.print "#{super(*args)}\n\n"
26
+ def command(*args)
27
+ @connection.write "#{super(*args)}\n\n"
26
28
  read_response
27
29
  end
28
30
 
29
31
  def read_response
30
- response = Librevox::Response.new
31
- until response.command_reply? or response.api_response?
32
- response.headers = read_headers
33
- end
34
-
35
- length = response.headers[:content_length].to_i
36
- response.content = @socket.read(length) if length > 0
37
-
38
- response
39
- end
40
-
41
- def read_headers
42
- headers = ""
43
-
44
- while line = @socket.gets and !line.chomp.empty?
45
- headers += line
32
+ while msg = @connection.read_message
33
+ return msg if msg.command_reply? || msg.api_response?
46
34
  end
47
-
48
- headers
49
35
  end
50
36
 
51
37
  def close
52
- @socket.close
38
+ @connection.close
53
39
  end
54
40
  end
55
41
  end
@@ -1,23 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Librevox
2
4
  # All commands should call `command` with the following parameters:
3
5
  #
4
6
  # `name` - name of the command
5
7
  # `args` - arguments as a string (optional)
6
- #
7
- # Commands *must* pass on any eventual block passed to them.
8
8
  module Commands
9
9
  # Executes a generic API command, optionally taking arguments as string.
10
10
  # @example
11
11
  # socket.command "fsctl", "hupall normal_clearing"
12
12
  # @see http://wiki.freeswitch.org/wiki/Mod_commands
13
- def command name, args=""
14
- msg = "api #{name}"
15
- msg << " #{args}" if args && !args.empty?
16
- msg
13
+ def command(name, args = "")
14
+ parts = ["api", name]
15
+ parts << args if args && !args.empty?
16
+ parts.join(" ")
17
17
  end
18
18
 
19
- def status &block
20
- command "status", &block
19
+ def status
20
+ command "status"
21
21
  end
22
22
 
23
23
  # Access the hash table that comes with FreeSWITCH.
@@ -25,53 +25,53 @@ module Librevox
25
25
  # socket.hash :insert, :realm, :key, "value"
26
26
  # socket.hash :select, :realm, :key
27
27
  # socket.hash :delete, :realm, :key
28
- def hash *args, &block
29
- command "hash", args.join("/"), &block
28
+ def hash(*args)
29
+ command "hash", args.join("/")
30
30
  end
31
31
 
32
32
  # Originate a new call.
33
33
  # @example Minimum options
34
- # socket.originate 'sofia/user/coltrane', :extension => "1234"
34
+ # socket.originate 'sofia/user/coltrane', extension: "1234"
35
35
  # @example With :dialplan and :context
36
36
  # @see http://wiki.freeswitch.org/wiki/Mod_commands#originate
37
- def originate url, args={}, &block
37
+ def originate(url, args = {})
38
38
  extension = args.delete(:extension)
39
39
  dialplan = args.delete(:dialplan)
40
40
  context = args.delete(:context)
41
41
 
42
42
  vars = args.map {|k,v| "#{k}=#{v}"}.join(",")
43
43
 
44
- arg_string = "{#{vars}}" +
44
+ arg_string = "{#{vars}}" +
45
45
  [url, extension, dialplan, context].compact.join(" ")
46
- command "originate", arg_string, &block
46
+ command "originate", arg_string
47
47
  end
48
48
 
49
49
  # FreeSWITCH control messages.
50
50
  # @example
51
51
  # socket.fsctl :hupall, :normal_clearing
52
52
  # @see http://wiki.freeswitch.org/wiki/Mod_commands#fsctl
53
- def fsctl *args, &block
54
- command "fsctl", args.join(" "), &block
53
+ def fsctl(*args)
54
+ command "fsctl", args.join(" ")
55
55
  end
56
56
 
57
- def hupall cause=nil, &block
58
- command "hupall", cause, &block
57
+ def hupall(cause = nil)
58
+ command "hupall", cause
59
59
  end
60
60
 
61
61
  # Park call.
62
62
  # @example
63
63
  # socket.uuid_park "592567a2-1be4-11df-a036-19bfdab2092f"
64
64
  # @see http://wiki.freeswitch.org/wiki/Mod_commands#uuid_park
65
- def uuid_park uuid, &block
66
- command "uuid_park", uuid, &block
65
+ def uuid_park(uuid)
66
+ command "uuid_park", uuid
67
67
  end
68
68
 
69
- # Bridge two call legs together. At least one leg must be anwered.
69
+ # Bridge two call legs together. At least one leg must be answered.
70
70
  # @example
71
71
  # socket.uuid_bridge "592567a2-1be4-11df-a036-19bfdab2092f", "58b39c3a-1be4-11df-a035-19bfdab2092f"
72
72
  # @see http://wiki.freeswitch.org/wiki/Mod_commands#uuid_bridge
73
- def uuid_bridge uuid1, uuid2, &block
74
- command "uuid_bridge", "#{uuid1} #{uuid2}", &block
73
+ def uuid_bridge(uuid1, uuid2)
74
+ command "uuid_bridge", "#{uuid1} #{uuid2}"
75
75
  end
76
76
  end
77
77
  end
@@ -1,16 +1,23 @@
1
- require 'eventmachine'
2
- require 'librevox/response'
3
- require 'librevox/commands'
1
+ # frozen_string_literal: true
2
+
3
+ require 'async/queue'
4
+ require 'async/semaphore'
4
5
 
5
6
  module Librevox
6
7
  module Listener
7
- class Base < EventMachine::Protocols::HeaderAndContentProtocol
8
+ class Base
9
+ def initialize(connection = nil)
10
+ @connection = connection
11
+ @reply_queue = Async::Queue.new
12
+ @command_mutex = Async::Semaphore.new(1)
13
+ end
14
+
8
15
  class << self
9
16
  def hooks
10
17
  @hooks ||= Hash.new {|hash, key| hash[key] = []}
11
18
  end
12
19
 
13
- def event event, &block
20
+ def event(event, &block)
14
21
  hooks[event] << block
15
22
  end
16
23
  end
@@ -22,12 +29,12 @@ module Librevox
22
29
  class CommandDelegate
23
30
  include Librevox::Commands
24
31
 
25
- def initialize listener
32
+ def initialize(listener)
26
33
  @listener = listener
27
34
  end
28
35
 
29
- def command *args, &block
30
- @listener.command super(*args), &block
36
+ def command(*args)
37
+ @listener.command(super(*args))
31
38
  end
32
39
  end
33
40
 
@@ -41,47 +48,59 @@ module Librevox
41
48
  @command_delegate ||= CommandDelegate.new(self)
42
49
  end
43
50
 
44
- def command msg, &block
45
- send_data "#{msg}\n\n"
46
-
47
- @command_queue.push(block)
51
+ def command(msg)
52
+ @command_mutex.acquire do
53
+ write "#{msg}\n\n"
54
+ @reply_queue.dequeue
55
+ end
48
56
  end
49
57
 
50
58
  attr_accessor :response
51
- alias :event :response
52
-
53
- def post_init
54
- @command_queue = []
55
- end
56
59
 
57
- def receive_request header, content
58
- @response = Librevox::Response.new(header, content)
60
+ def receive_message(response)
61
+ @response = response
59
62
  handle_response
60
63
  end
61
64
 
62
65
  def handle_response
63
- if response.api_response? && @command_queue.any?
64
- @command_queue.shift.call(response)
66
+ if response.reply?
67
+ @reply_queue.push(response)
68
+ return
65
69
  end
66
70
 
67
71
  if response.event?
68
- on_event(response.dup)
69
- invoke_event_hooks
72
+ resp = response
73
+ Async do
74
+ on_event(resp)
75
+ invoke_event_hooks(resp)
76
+ end
70
77
  end
71
78
  end
72
79
 
73
80
  # override
74
- def on_event event
81
+ def on_event(event)
75
82
  end
76
83
 
77
- alias :done :close_connection_after_writing
84
+ def write(data)
85
+ @connection&.write(data)
86
+ end
87
+
88
+ def run_session
89
+ end
90
+
91
+ def disconnect
92
+ @connection&.close
93
+ end
78
94
 
79
95
  private
80
- def invoke_event_hooks
81
- event = response.event.downcase.to_sym
82
- self.class.hooks[event].each {|block|
83
- instance_exec(response.dup, &block)
84
- }
96
+
97
+ def invoke_event_hooks(resp)
98
+ event_name = resp.event.downcase.to_sym
99
+ hooks = self.class.hooks[event_name]
100
+
101
+ hooks.each do |block|
102
+ instance_exec(resp, &block)
103
+ end
85
104
  end
86
105
  end
87
106
  end