bougyman-freeswitcher 0.1.4 → 0.3.0

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/lib/fsr/app.rb CHANGED
@@ -25,9 +25,7 @@ module FSR
25
25
 
26
26
  def self.register(application, obj)
27
27
  APPLICATIONS[application.to_sym] = obj
28
-
29
- code = REGISTER_CODE % [application, application]
30
- App.module_eval(code)
28
+ App.module_eval(REGISTER_CODE % [application, application])
31
29
  end
32
30
 
33
31
  def self.list
@@ -3,6 +3,8 @@ require "fsr/app"
3
3
  module FSR
4
4
  module App
5
5
  class Answer < Application
6
+ # Answer a call
7
+
6
8
  def initialize
7
9
  end
8
10
 
@@ -3,6 +3,8 @@ module FSR
3
3
  #http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_play_and_get_digits
4
4
  module App
5
5
  class PlayAndGetDigits < Application
6
+ attr_reader :chan_var
7
+
6
8
  def initialize(sound_file, invalid_file, min = 0, max = 10, tries = 3, timeout = 7000, terminators = ["#"], chan_var = "fsr_read_dtmf", regexp = "\d")
7
9
  @sound_file = sound_file
8
10
  @invalid_file = invalid_file
@@ -22,7 +24,15 @@ module FSR
22
24
  def sendmsg
23
25
  "call-command: execute\nexecute-app-name: %s\nexecute-app-arg: %s\nevent-lock:true\n\n" % ["play_and_get_digits", arguments.join(" ")]
24
26
  end
25
-
27
+ SENDMSG_METHOD = %q|
28
+ def play_and_get_digits(*args, &block)
29
+ me = super(*args)
30
+ @read_var = "variable_#{me.chan_var}"
31
+ sendmsg me
32
+ @queue.unshift Proc.new { update_session }
33
+ @queue.unshift block if block_given?
34
+ end
35
+ |
26
36
  end
27
37
 
28
38
  register(:play_and_get_digits, PlayAndGetDigits)
data/lib/fsr/app/read.rb CHANGED
@@ -3,6 +3,7 @@ module FSR
3
3
  # http://wiki.freeswitch.org/wiki/Misc._Dialplan_Tools_read
4
4
  module App
5
5
  class Read < Application
6
+ attr_reader :chan_var
6
7
  def initialize(sound_file, min = 0, max = 10, chan_var = "fsr_read_dtmf", timeout = 10000, terminators = ["#"])
7
8
  @sound_file, @min, @max, @chan_var, @timeout, @terminators = sound_file, min, max, chan_var, timeout, terminators
8
9
  end
@@ -14,9 +15,18 @@ module FSR
14
15
  def sendmsg
15
16
  "call-command: execute\nexecute-app-name: %s\nexecute-app-arg: %s\nevent-lock:true\n\n" % [app_name, arguments.join(" ")]
16
17
  end
17
-
18
+ SENDMSG_METHOD = %q|
19
+ def read(*args, &block)
20
+ me = super(*args)
21
+ @read_var = "variable_#{me.chan_var}"
22
+ sendmsg me
23
+ @queue.unshift Proc.new { update_session }
24
+ @queue.unshift block if block_given?
25
+ end
26
+ |
18
27
  end
19
-
20
28
  register(:read, Read)
29
+
30
+
21
31
  end
22
32
  end
@@ -1,11 +1,13 @@
1
1
  require "fsr/app"
2
2
  module FSR
3
- #http://wiki.freeswitch.org/wiki/Mod_commands#uuid_getvar
3
+ #http://wiki.freeswitch.org/wiki/Mod_commands#uuid_setvar
4
4
  module App
5
5
  class UuidGetVar < Application
6
+ attr_reader :var, :uuid
7
+
6
8
  def initialize(uuid, var)
7
9
  @uuid = uuid # Unique channel ID
8
- @var = var # Channel variable you wish to 'get'
10
+ @var = var # Channel variable you wish to 'set'
9
11
  end
10
12
 
11
13
  def arguments
@@ -16,6 +18,15 @@ module FSR
16
18
  "call-command: execute\nexecute-app-name: %s\nexecute-app-arg: %s\nevent-lock:true\n\n" % [app_name, arguments.join(" ")]
17
19
  end
18
20
 
21
+ SENDMSG_METHOD = %q|
22
+ def uuid_getvar(*args, &block)
23
+ me = super(*args)
24
+ @uuid_var = me.var
25
+ api_call = "api uuid_getvar #{me.uuid} #{me.var}\n\n"
26
+ send_data(api_call)
27
+ @queue.unshift block if block_given?
28
+ end
29
+ |
19
30
  end
20
31
 
21
32
  register(:uuid_getvar, UuidGetVar)
@@ -3,10 +3,12 @@ module FSR
3
3
  #http://wiki.freeswitch.org/wiki/Mod_commands#uuid_setvar
4
4
  module App
5
5
  class UuidSetVar < Application
6
+ attr_reader :var, :uuid, :assignment
7
+
6
8
  def initialize(uuid, var, assignment)
7
9
  @uuid = uuid # Unique channel ID
8
10
  @var = var # Channel variable you wish to 'set'
9
- @assignment
11
+ @assignment = assignment
10
12
  end
11
13
 
12
14
  def arguments
@@ -17,6 +19,14 @@ module FSR
17
19
  "call-command: execute\nexecute-app-name: %s\nexecute-app-arg: %s\nevent-lock:true\n\n" % [app_name, arguments.join(" ")]
18
20
  end
19
21
 
22
+ SENDMSG_METHOD = %q|
23
+ def uuid_setvar(*args, &block)
24
+ me = super(*args)
25
+ api_call = "api uuid_setvar #{me.uuid} #{me.var} #{me.assignment}\n\n"
26
+ send_data(api_call)
27
+ @queue.unshift block if block_given?
28
+ end
29
+ |
20
30
  end
21
31
 
22
32
  register(:uuid_setvar, UuidSetVar)
data/lib/fsr/cmd.rb CHANGED
@@ -1,10 +1,28 @@
1
1
  module FSR
2
2
  module Cmd
3
3
  class Command
4
+ DEFAULT_OPTIONS = {
5
+ :origination_caller_id_name => FSR::DEFAULT_CALLER_ID_NAME,
6
+ :origination_caller_id_number => FSR::DEFAULT_CALLER_ID_NUMBER,
7
+ :originate_timeout => 30,
8
+ :ignore_early_media => true
9
+ }
10
+
11
+ protected
12
+ def default_options(args = {}, defaults = nil, &block)
13
+ opts = if defaults.nil?
14
+ DEFAULT_OPTIONS.merge(args)
15
+ else
16
+ raise(ArgumentError, "defaults argument must ba a hash") unless defaults.kind_of?(Hash)
17
+ defaults.merge(args)
18
+ end
19
+ yield opts if block_given?
20
+ end
21
+
4
22
  end
5
23
 
6
24
  COMMANDS = {}
7
- LOAD_PATH = [File.join(FSR::ROOT, "fsr", "cmd")]
25
+ LOAD_PATH = [Pathname(FSR::ROOT).join( "fsr", "cmd")]
8
26
 
9
27
  def self.register(command, obj)
10
28
  COMMANDS[command.to_sym] = obj
@@ -28,8 +46,8 @@ module FSR
28
46
  end
29
47
  Log.debug "Trying load paths"
30
48
  # If we find a file named the same as the command passed in LOAD_PATH, load it
31
- if found_command_path = LOAD_PATH.detect { |command_path| File.file?(File.join(command_path, "#{command}.rb")) }
32
- command_file = Pathname.new(found_command_path).join(command)
49
+ if found_command_path = LOAD_PATH.detect { |command_path| command_path.join("#{command}.rb").file? }
50
+ command_file = found_command_path.join(command)
33
51
  Log.debug "Trying to load #{command_file}"
34
52
  if force_reload
35
53
  load command_file.to_s + ".rb"
@@ -44,7 +62,7 @@ module FSR
44
62
  # Load all of the commands we find in Cmd::LOAD_PATH
45
63
  def self.load_all(force_reload = false)
46
64
  LOAD_PATH.each do |load_path|
47
- Dir[File.join(load_path, "*.rb")].each { |command_file| load_command(command_file, force_reload) }
65
+ Dir[load_path.join("*.rb")].each { |command_file| load_command(command_file, force_reload) }
48
66
  end
49
67
  list
50
68
  end
@@ -6,19 +6,35 @@ module FSR
6
6
  attr_reader :fs_socket, :target_options
7
7
 
8
8
  def initialize(fs_socket = nil, args = {})
9
+ # Using an argument hash may not be the the best way to go here, but as long as we're doing
10
+ # so we'll type check it
11
+ raise(ArgumentError, "args (Passed: <<<#{args}>>>) must be a hash") unless args.kind_of?(Hash)
12
+
9
13
  # These are options that will precede the target address
10
- @target_options = args[:target_options] || {:ignore_early_media => true}
11
- raise "#{@target_options} must be a hash" unless @target_options.kind_of?(Hash)
14
+ if args[:target_options]
15
+ raise(ArgumentError, "args[:target_options] (Passed: <<<#{args[:target_options]}>>>) must be a hash") unless args[:target_options].kind_of?(Hash)
16
+ else
17
+ args[:target_options] = {}
18
+ end
19
+ @target_options = default_options(args[:target_options]) do |o|
20
+ o[:origination_caller_id_number] = args[:caller_id_number] if args[:caller_id_number]
21
+ o[:origination_caller_id_name] = args[:caller_id_name] if args[:caller_id_name]
22
+ if o[:timeout]
23
+ o[:originate_timeout] = o.delete(:timeout)
24
+ elsif args[:timeout]
25
+ o[:originate_timeout] = args[:timeout]
26
+ end
27
+ o
28
+ end
29
+ raise(ArgumentError, "Origination timeout (#{@target_options[:originate_timeout]}) must be a positive integer") unless @target_options[:originate_timeout].to_i > 0
12
30
 
13
31
  @fs_socket = fs_socket # This socket must support say and <<
14
32
  @target = args[:target] # The target address to call
33
+ raise(ArgumentError, "Cannot originate without a :target set") unless @target.to_s.size > 0
15
34
  # The origination endpoint (can be an extension (use a string) or application)
16
35
  @endpoint = args[:endpoint] || args[:application]
36
+ raise(ArgumentError, "Cannot originate without an :endpoint set") unless @endpoint.to_s.size > 0
17
37
 
18
- @target_options[:origination_caller_id_number] ||= args[:caller_id_number] || FSR::DEFAULT_CALLER_ID_NUMBER
19
- @target_options[:origination_caller_id_name] ||= args[:caller_id_name] || FSR::DEFAULT_CALLER_ID_NAME
20
- @target_options[:originate_timeout] ||= args[:timeout] || @target_options[:timeout] || 30
21
- @target_options[:ignore_early_media] ||= true
22
38
  end
23
39
 
24
40
  # Send the command to the event socket, using bgapi by default.
@@ -39,6 +55,7 @@ module FSR
39
55
  raise "Invalid endpoint"
40
56
  end
41
57
  end
58
+
42
59
  end
43
60
 
44
61
  register(:originate, Originate)
@@ -16,7 +16,7 @@ module FSR
16
16
  end
17
17
  end
18
18
 
19
- # Method to authenticate to FreeSWITCH
19
+ # login - Method to authenticate to FreeSWITCH
20
20
  def login
21
21
  #Clear buf from initial socket creation/opening
22
22
  response
@@ -13,11 +13,16 @@ module FSR
13
13
  @auth = args[:auth] || "ClueCon"
14
14
  end
15
15
 
16
+ # post_init is called upon each "new" socket connection
16
17
  def post_init
17
18
  say("auth #{@auth}")
18
19
  say('event plain ALL')
19
20
  end
20
21
 
22
+ # receive_request is the callback method when data is recieved from the socket
23
+ #
24
+ # param header headers from standard Header and Content protocol
25
+ # param content content from standard Header and Content protocol
21
26
  def receive_request(header, content)
22
27
  hash_header = headers_2_hash(header)
23
28
  hash_content = headers_2_hash(content)
@@ -29,28 +34,32 @@ module FSR
29
34
  on_event(event)
30
35
  end
31
36
 
37
+ # say encapsulates #send_data for the user
38
+ #
39
+ # param line Line of text to send to the socket
32
40
  def say(line)
33
41
  send_data("#{line}\r\n\r\n")
34
42
  end
35
-
43
+
44
+ # on_event is the callback method when an event is triggered
45
+ #
46
+ # param event The triggered event object
47
+ # return event The triggered event object
36
48
  def on_event(event)
37
49
  event
38
50
  end
39
51
 
40
- # Add or replace a block to execute when the specified event occurs
41
- #
42
- # <b>Parameters</b>
43
- # - event : What event to trigger the block on. May be
44
- # :CHANNEL_CREATE, :CHANNEL_DESTROY etc
45
- # - block : Block to execute
52
+ # add_event_hook adds an Event to listen for. When that Event is triggered, it will call the defined block
46
53
  #
47
- # <b>Returns/<b>
48
- # - nil
54
+ # @param event The event to trigger the block on. Examples, :CHANNEL_CREATE, :CHANNEL_DESTROY, etc
55
+ # @param block The block to execute when the event is triggered
49
56
  def self.add_event_hook(event, &block)
50
57
  HOOKS[event] = block
51
58
  end
52
59
 
53
- # Delete the block that was to be executed for the specified event.
60
+ # del_event_hook removes an Event.
61
+ #
62
+ # @param event The event to remove. Examples, :CHANNEL_CREATE, :CHANNEL_DESTROY, etc
54
63
  def self.del_event_hook(event)
55
64
  HOOKS.delete(event)
56
65
  end
@@ -20,7 +20,13 @@ module FSR
20
20
  "end"
21
21
  ].join("\n")
22
22
 
23
- APPLICATIONS.each { |app, obj| module_eval(SENDMSG_METHOD_DEFINITION % app.to_s) }
23
+ APPLICATIONS.each do |app, obj|
24
+ if obj.const_defined?("SENDMSG_METHOD")
25
+ module_eval(obj::SENDMSG_METHOD)
26
+ else
27
+ module_eval(SENDMSG_METHOD_DEFINITION % app.to_s)
28
+ end
29
+ end
24
30
 
25
31
  # session_initiated is called when a @session is first created.
26
32
  # Overwrite this in your worker class with the call/channel
@@ -66,6 +72,7 @@ module FSR
66
72
 
67
73
  protected
68
74
  def post_init
75
+ @read_var = nil
69
76
  @session = nil # holds the session object
70
77
  @queue = [] # Keep track of queue for state machine
71
78
  send_data("connect\n\n")
@@ -77,36 +84,86 @@ module FSR
77
84
  # to be picked up by #session_initiated or #receive_reply.
78
85
  # If your listener is listening for events, this will also renew your @session
79
86
  # each time you receive a CHANNEL_DATA event.
87
+ #
80
88
  # @param header The header of the request, as passed by HeaderAndContentProtocol
81
89
  # @param content The content of the request, as passed by HeaderAndContentProtocol
82
90
  #
83
91
  # @return [HeaderAndContentResponse] An EventMachine HeaderAndContentResponse
84
92
  def receive_request(header, content)
85
93
  hash_header = headers_2_hash(header)
86
- hash_content = headers_2_hash(content)
94
+ if content.to_s.match(/:/)
95
+ hash_content = headers_2_hash(content)
96
+ else
97
+ hash_content = content
98
+ end
87
99
  session_header_and_content = HeaderAndContentResponse.new({:headers => hash_header, :content => hash_content})
88
100
  # If we're a new session, call session initiate
89
- if @session.nil?
90
- @session = session_header_and_content
91
- @step = 0
92
- @state = [:uninitiated]
93
- session_initiated
94
- @state << :initiated
101
+ if session.nil?
102
+ establish_new_session(session_header_and_content)
103
+ elsif @uuid_var and session_header_and_content.headers[:content_type] == "api/response"
104
+ FSR::Log.info("@uuid_var is set => #{session_header_and_content.inspect} : #{content}")
105
+ r, @uuid_var = session_header_and_content.content.strip, nil
106
+ @queue.pop.call(r) if @queue.size > 0
95
107
  elsif session_header_and_content.content[:event_name] # If content includes an event_name, it must be a response from an api command
96
- if session_header_and_content.content[:event_name].to_s.match(/CHANNEL_DATA/i) # Anytime we see CHANNEL_DATA event, we want to update our @session
97
- session_header_and_content = HeaderAndContentResponse.new({:headers => hash_header.merge(hash_content.strip_value_newlines), :content => {}})
98
- @session = session_header_and_content
99
- @step += 1 if @state.include?(:initiated)
100
- @queue.pop.call unless @queue.empty?
101
- receive_reply(hash_header)
102
- end
108
+ check_for_updated_session(session_header_and_content, hash_content, hash_header)
103
109
  else
104
- @step += 1 if @state.include?(:initiated)
105
- @queue.pop.call unless @queue.empty?
106
- receive_reply(session_header_and_content)
110
+ update_state_machine(session_header_and_content)
111
+ end
112
+ end
113
+
114
+ protected
115
+ def queue_pop
116
+ if @queue.size > 0
117
+ if @read_var and session.headers[@read_var.to_sym]
118
+ r, @read_var = @read_var.to_sym, nil
119
+ @queue.pop.call(session.headers[r])
120
+ else
121
+ @queue.pop.call
122
+ end
107
123
  end
108
124
  end
109
125
 
126
+ private
127
+ # establish_new_session is called once during a new session.
128
+ # This establishes and sets up the variables used to hold state.
129
+ # Called from #receive_request
130
+ #
131
+ # @param header_and_content_hash HeaderAndContentProtocol object representing the session
132
+ #
133
+ # @return header_and_content_hash HeaderAndContentProtocol object representing the session
134
+ def establish_new_session(header_and_content_hash)
135
+ @session = header_and_content_hash
136
+ @step = 0
137
+ @state = [:uninitiated]
138
+ session_initiated
139
+ @state << :initiated
140
+ header_and_content_hash
141
+ end
142
+
143
+ # check_for_updated_session is called from #receive_request if a session has already been established
144
+ # This will overwrite @session with the new variables representing state.
145
+ #
146
+ # @param header_and_content_hash HeaderAndContentProtocol object
147
+ # @param hash_content hash of content from standard Header and Content Protocol
148
+ # @param hash_header hash of headers from standard Header and Content Protocol
149
+ def check_for_updated_session(header_and_content_hash, hash_content, hash_header)
150
+ if header_and_content_hash.content[:event_name].to_s.match(/CHANNEL_DATA/i) # Anytime we see CHANNEL_DATA event, we want to update our @session
151
+ header_and_content_hash = HeaderAndContentResponse.new({:headers => hash_header.merge(hash_content.strip_value_newlines), :content => {}})
152
+ @session = header_and_content_hash
153
+ update_state_machine(header_and_content_hash.headers)
154
+ end
155
+ end
156
+
157
+ # update_state_machine when called will increment @step,
158
+ # call the next block from @queue and dispatch the callback method
159
+ # #receive_reply
160
+ # @param response_header Response sent to #recieve_reply callback method
161
+ def update_state_machine(response_header)
162
+ @step += 1 if @state.include?(:initiated)
163
+ queue_pop
164
+ receive_reply(response_header)
165
+ end
166
+
110
167
  end
111
168
  end
112
169
  end
data/lib/fsr/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module FSR
2
- VERSION = "0.1.4"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -0,0 +1,12 @@
1
+ require 'spec/helper'
2
+ require "fsr/app"
3
+ FSR::App.load_application("answer")
4
+
5
+ describe "Testing FSR::App::Answer" do
6
+
7
+ it "answers the incoming call" do
8
+ ans = FSR::App::Answer.new
9
+ ans.sendmsg.should == "call-command: execute\nexecute-app-name: answer\n\n"
10
+ end
11
+
12
+ end