freeswitcher 0.1.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)
@@ -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)
@@ -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
@@ -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