jicksta-adhearsion 0.7.999

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.
Files changed (107) hide show
  1. data/CHANGELOG +6 -0
  2. data/EVENTS +11 -0
  3. data/LICENSE +456 -0
  4. data/README.txt +5 -0
  5. data/Rakefile +120 -0
  6. data/adhearsion.gemspec +146 -0
  7. data/app_generators/ahn/USAGE +5 -0
  8. data/app_generators/ahn/ahn_generator.rb +87 -0
  9. data/app_generators/ahn/templates/.ahnrc +34 -0
  10. data/app_generators/ahn/templates/README +8 -0
  11. data/app_generators/ahn/templates/Rakefile +23 -0
  12. data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
  13. data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
  14. data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
  15. data/app_generators/ahn/templates/components/disabled/stomp_gateway/config.yml +12 -0
  16. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
  17. data/app_generators/ahn/templates/components/restful_rpc/README.markdown +11 -0
  18. data/app_generators/ahn/templates/components/restful_rpc/config.yml +34 -0
  19. data/app_generators/ahn/templates/components/restful_rpc/example-client.rb +48 -0
  20. data/app_generators/ahn/templates/components/restful_rpc/restful_rpc.rb +87 -0
  21. data/app_generators/ahn/templates/components/simon_game/simon_game.rb +56 -0
  22. data/app_generators/ahn/templates/config/startup.rb +53 -0
  23. data/app_generators/ahn/templates/dialplan.rb +3 -0
  24. data/app_generators/ahn/templates/events.rb +32 -0
  25. data/bin/ahn +28 -0
  26. data/bin/ahnctl +68 -0
  27. data/bin/jahn +42 -0
  28. data/examples/asterisk_manager_interface/standalone.rb +51 -0
  29. data/lib/adhearsion/cli.rb +223 -0
  30. data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
  31. data/lib/adhearsion/component_manager.rb +208 -0
  32. data/lib/adhearsion/events_support.rb +84 -0
  33. data/lib/adhearsion/foundation/all.rb +9 -0
  34. data/lib/adhearsion/foundation/blank_slate.rb +5 -0
  35. data/lib/adhearsion/foundation/custom_daemonizer.rb +45 -0
  36. data/lib/adhearsion/foundation/event_socket.rb +203 -0
  37. data/lib/adhearsion/foundation/future_resource.rb +36 -0
  38. data/lib/adhearsion/foundation/global.rb +1 -0
  39. data/lib/adhearsion/foundation/metaprogramming.rb +17 -0
  40. data/lib/adhearsion/foundation/numeric.rb +13 -0
  41. data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
  42. data/lib/adhearsion/foundation/relationship_properties.rb +42 -0
  43. data/lib/adhearsion/foundation/string.rb +26 -0
  44. data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
  45. data/lib/adhearsion/foundation/thread_safety.rb +7 -0
  46. data/lib/adhearsion/host_definitions.rb +67 -0
  47. data/lib/adhearsion/initializer/asterisk.rb +81 -0
  48. data/lib/adhearsion/initializer/configuration.rb +254 -0
  49. data/lib/adhearsion/initializer/database.rb +49 -0
  50. data/lib/adhearsion/initializer/drb.rb +31 -0
  51. data/lib/adhearsion/initializer/freeswitch.rb +22 -0
  52. data/lib/adhearsion/initializer/rails.rb +40 -0
  53. data/lib/adhearsion/initializer.rb +373 -0
  54. data/lib/adhearsion/logging.rb +92 -0
  55. data/lib/adhearsion/tasks/database.rb +5 -0
  56. data/lib/adhearsion/tasks/deprecations.rb +59 -0
  57. data/lib/adhearsion/tasks/generating.rb +20 -0
  58. data/lib/adhearsion/tasks/lint.rb +4 -0
  59. data/lib/adhearsion/tasks/testing.rb +37 -0
  60. data/lib/adhearsion/tasks.rb +16 -0
  61. data/lib/adhearsion/version.rb +9 -0
  62. data/lib/adhearsion/voip/asterisk/agi_server.rb +81 -0
  63. data/lib/adhearsion/voip/asterisk/commands.rb +1284 -0
  64. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
  65. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
  66. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
  67. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
  68. data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
  69. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1754 -0
  70. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +286 -0
  71. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
  72. data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
  73. data/lib/adhearsion/voip/asterisk/manager_interface.rb +562 -0
  74. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
  75. data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
  76. data/lib/adhearsion/voip/asterisk.rb +4 -0
  77. data/lib/adhearsion/voip/call.rb +440 -0
  78. data/lib/adhearsion/voip/call_routing.rb +64 -0
  79. data/lib/adhearsion/voip/commands.rb +9 -0
  80. data/lib/adhearsion/voip/constants.rb +39 -0
  81. data/lib/adhearsion/voip/conveniences.rb +18 -0
  82. data/lib/adhearsion/voip/dial_plan.rb +218 -0
  83. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
  84. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
  85. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
  86. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
  87. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +71 -0
  88. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
  89. data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
  90. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
  91. data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
  92. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
  93. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
  94. data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
  95. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
  96. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
  97. data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
  98. data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
  99. data/lib/adhearsion.rb +37 -0
  100. data/lib/theatre/README.markdown +64 -0
  101. data/lib/theatre/callback_definition_loader.rb +84 -0
  102. data/lib/theatre/guid.rb +23 -0
  103. data/lib/theatre/invocation.rb +121 -0
  104. data/lib/theatre/namespace_manager.rb +153 -0
  105. data/lib/theatre/version.rb +2 -0
  106. data/lib/theatre.rb +151 -0
  107. metadata +177 -0
@@ -0,0 +1,117 @@
1
+ module Adhearsion
2
+ module VoIP
3
+ module DSL
4
+ # In Ruby, a number with a leading zero such as 023.45 or 023 does not evaluate as expected.
5
+ # In the case of the float 023.45, you get a syntax error. In the case of 023 the number is
6
+ # parsed as being in octal form and the number 19 is returned.
7
+ #
8
+ # In Adhearsion, various strings that are entirely numeric might start with zero and that zero
9
+ # should be preserved when converting that string of numbers into a number. The numerical string
10
+ # retains the zero while still allowing it to be compared to other numbers.
11
+ #
12
+ # [[I think this leading zero thing is only part of the reason that NumericalString exists. I'm
13
+ # currently writing tests for this leading zero stuff so I thought I'd dump some of my assumptions
14
+ # about it here in the documentation.]]
15
+ class NumericalString
16
+ class << self
17
+ def starts_with_leading_zero?(string)
18
+ string.to_s[/^0\d+/]
19
+ end
20
+ end
21
+
22
+ (instance_methods - %w"__id__ __send__ __real_num __real_string").each do |m|
23
+ undef_method m
24
+ end
25
+
26
+ attr_reader :__real_num, :__real_string
27
+
28
+ def initialize(str)
29
+ @__real_string = str.to_s
30
+ @__real_num = str.to_i if @__real_string =~ /^\d+$/
31
+ end
32
+
33
+ def method_missing(name, *args, &block)
34
+ @__real_string.__send__ name, *args, &block
35
+ end
36
+
37
+ def respond_to?(m)
38
+ @__real_string.respond_to?(m) || m == :__real_num || m == :__real_string
39
+ end
40
+
41
+ end
42
+
43
+ # The PhoneNumber class is used by one object throughout Adhearsion: the AGI
44
+ # "extension" variable. Using some clevery Ruby hackery, the Extension class allows
45
+ # dialplan writers to use the best of Fixnum and String usage such as
46
+ #
47
+ # - Dialing international numbers
48
+ # - Using a regexp in a case statement for "extension"
49
+ # - Using a numerical range against extension -- e.g. (100...200)
50
+ # - Using the thousands separator
51
+ class PhoneNumber < NumericalString
52
+
53
+ # Checks against a pattern identifying US local numbers (i.e numbers
54
+ # without an area code seven digits long)
55
+ def us_local_number?() to_s =~ Adhearsion::VoIP::Constants::US_LOCAL_NUMBER end
56
+
57
+ # Checks against a pattern identifying US domestic numbers.
58
+ def us_national_number?() to_s =~ Adhearsion::VoIP::Constants::US_NATIONAL_NUMBER end
59
+
60
+ # Checks against a pattern identifying an ISN number. See http://freenum.org
61
+ # for more info.
62
+ def isn?() to_s =~ Adhearsion::VoIP::Constants::ISN end
63
+
64
+ # Useful for dialing those 1-800-FUDGEME type numbers with letters in them. Letters
65
+ # in the argument will be converted to their appropriate keypad key.
66
+ def self.from_vanity str
67
+ str.gsub(/\W/, '').upcase.tr('A-Z', '22233344455566677778889999')
68
+ end
69
+
70
+ end
71
+
72
+ end
73
+ end
74
+ end
75
+
76
+ # These monkey patches are necessary for the NumericalString to work, unfortunately.
77
+ class Class
78
+ def alias_method_once(new_name, old_name)
79
+ unless instance_methods.include?(new_name.to_s)
80
+ alias_method(new_name, old_name)
81
+ end
82
+ end
83
+ end
84
+
85
+ [Object, Range, class << String; self; end].each do |klass|
86
+ klass.alias_method_once(:pre_modified_threequal, :===)
87
+ end
88
+
89
+ class Object
90
+ def ===(arg)
91
+ if arg.respond_to? :__real_string
92
+ arg = arg.__real_num if kind_of?(Numeric) || kind_of?(Range)
93
+ pre_modified_threequal arg
94
+ else
95
+ pre_modified_threequal arg
96
+ end
97
+ end
98
+ end
99
+
100
+ class Range
101
+ alias_method_once(:original_threequal, :===)
102
+ def ===(arg)
103
+ if (arg.respond_to? :__real_string) && !arg.__real_num.nil?
104
+ arg = arg.__real_num
105
+ original_threequal arg
106
+ else
107
+ original_threequal arg
108
+ end
109
+ end
110
+ end
111
+
112
+ class << String
113
+ alias_method_once(:original_threequal, :===)
114
+ def ===(arg)
115
+ arg.respond_to?(:__real_string) || original_threequal(arg)
116
+ end
117
+ end
@@ -0,0 +1,48 @@
1
+ module Adhearsion
2
+ module VoIP
3
+ module FreeSwitch
4
+
5
+ class BasicConnectionManager
6
+
7
+ def initialize(io)
8
+ @io = io
9
+ end
10
+
11
+ # The send-command operator
12
+ def <<(str)
13
+ @io.write str + "\n\n"
14
+ end
15
+
16
+ def get_header
17
+ separate_pairs get_raw_header
18
+ end
19
+
20
+ def get_raw_header
21
+ (returning [] do |lines|
22
+ until line = @io.gets and line.chomp.empty?
23
+ lines << line.chomp
24
+ end
25
+ end) * "\n"
26
+ end
27
+
28
+ def next_event
29
+ header = get_raw_header
30
+ length = header.first[/\d+$/].to_i
31
+ # puts "Reading an event of #{length} bytes"
32
+ separate_pairs @io.read(length)
33
+ end
34
+
35
+ def separate_pairs(lines)
36
+ lines.inject({}) do |h,line|
37
+ returning h do |hash|
38
+ k,v = line.split(/\s*:\s*/)
39
+ hash[k] = URI.unescape(v).strip if k && v
40
+ end
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,58 @@
1
+ module Adhearsion
2
+ module VoIP
3
+ module FreeSwitch
4
+
5
+ # Subclass this to register a new event handler
6
+ class EventHandler
7
+
8
+ @@events = {}
9
+ @@compound_events = {}
10
+
11
+ @@connection = nil
12
+
13
+ def self.start!(hash=nil)
14
+ login hash if hash
15
+ raise "You must login to the FreeSWITCH EventSocket!" unless @@connection
16
+ loop do
17
+ # debug "Waiting for an event"
18
+ dispatch_event! @@connection.get_header
19
+ end
20
+ end
21
+
22
+ def self.dispatch_event!(data)
23
+ #puts "\nHandling an event! #{data.inspect}"
24
+ name = data['Event-Name']
25
+ normal_event = name && @@events[name.underscore.to_sym]
26
+ # puts "THIS IS WHAT I THINK IT MIGHT BE : #{normal_event.inspect} (with #{name.underscore.to_sym.inspect})"
27
+ if normal_event then normal_event.call(data)
28
+ else
29
+ #debug "Trying compound events"
30
+ @@compound_events.each do |(event, block)|
31
+ mini_event = {}
32
+ event.keys.each { |k| mini_event[k] = data[k] }
33
+ block.call(data) if event == mini_event
34
+ end
35
+ end
36
+ rescue => e
37
+ p e
38
+ puts e.backtrace.map { |x| " " * 4 + x }
39
+ end
40
+
41
+ protected
42
+
43
+ # Can be specified in the subclass
44
+ def self.login(hash)
45
+ debug "Creating a new event connection manager"
46
+ @@connection = InboundConnectionManager.new hash
47
+ debug "Enabling events"
48
+ @@connection.enable_events!
49
+ end
50
+
51
+ def self.on(event, &block)
52
+ event = event.underscore.to_sym if event.is_a? String
53
+ (event.kind_of?(Hash) ? @@compound_events : @@events)[event] = block
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,129 @@
1
+ require 'adhearsion/voip/dsl/dialplan/dispatcher'
2
+
3
+ module Adhearsion
4
+ module VoIP
5
+ module FreeSwitch
6
+ class FreeSwitchDialplanCommandFactory
7
+
8
+ def initialize(context=nil)
9
+ @context = context
10
+ end
11
+
12
+ # These should all return those objects...
13
+ def speak(text, hash={})
14
+ voice, engine = hash[:voice] || "Dianne", hash[:engine] || "cepstral"
15
+
16
+ dtmf = hash[:on_keypress]
17
+ speak_cmd = cmd 'speak', "#{engine}|#{voice}|%p" % text, :on_keypress => dtmf
18
+
19
+ if hash[:timeout] == 0
20
+ [speak_cmd, DSL::Dialplan::NoOpEventCommand.new(hash[:timeout], :on_keypress => dtmf)]
21
+ else
22
+ puts "Returning the normal speak command"
23
+ speak_cmd
24
+ end
25
+ end
26
+
27
+ def set(key, value)
28
+ cmd 'set', "#{key}=#{value}"
29
+ end
30
+
31
+ def play(*files)
32
+ hash = files.last.kind_of?(Hash) ? files.pop : {}
33
+ conference, to = hash[:conference], hash[:to]
34
+ puts "conference: #{conference.inspect}, to: #{to.inspect}, hash: #{hash.inspect}, files: #{files.inspect}"
35
+ if conference
36
+ # Normal (inbound) event socket playing to a conference
37
+ files.map do |file|
38
+ cmd "conference", "#{conference} play #{file} #{to}"
39
+ end
40
+ elsif to
41
+ # Normal event socket syntax
42
+ files.map do |file|
43
+ # TODO: Support playing to an individual leg of the call.
44
+ cmd "broadcast", "#{to} #{file} both"
45
+ end
46
+ else
47
+ # Outbound event sockets
48
+ files.map { |file| cmd('playback', file) }
49
+ end
50
+ end
51
+
52
+ def join(id)
53
+ DSL::Dialplan::ExitingEventCommand.new "conference", id.to_s
54
+ end
55
+
56
+ def hangup!
57
+ cmd "exit"
58
+ end
59
+
60
+ def return!(obj)
61
+ raise DSL::Dialplan::ReturnValue.new(obj)
62
+ end
63
+
64
+ def hangup!
65
+ raise DSL::Dialplan::Hangup
66
+ end
67
+
68
+ def wait(seconds=nil, &block)
69
+ DSL::Dialplan::NoOpEventCommand.new(seconds)
70
+ end
71
+
72
+ def record(hash={}, &block)
73
+ # TODO: Could want to record a conference or a UUID
74
+ p hash
75
+ if hash[:stop]
76
+ cmd 'stop_record_session', hash[:stop]
77
+ else
78
+ file = hash[:file] || File.join(Dir::tmpdir, String.random(32), '.wav')
79
+
80
+ raise "Cannot supply both a timeout and a block!" if hash[:timeout] && block_given?
81
+
82
+ dtmf_breaker = lambda do |digit|
83
+ return! file if digit == hash[:break_on]
84
+ end
85
+
86
+ rec_cmd = cmd "record", file, :on_keypress => dtmf_breaker
87
+ returning [] do |cmds|
88
+ cmds << play('beep') if hash[:beep]
89
+ cmds << rec_cmd
90
+ if hash[:timeout]
91
+ cmds << DSL::Dialplan::NoOpEventCommand.new(hash[:timeout])
92
+ elsif block_given?
93
+ cmds << block
94
+ cmds << record(:stop => file)
95
+ end
96
+ cmds << file
97
+ p cmds
98
+ cmds
99
+ end
100
+ end
101
+ end
102
+
103
+ def input(number=nil, hash={})
104
+ timeout, file = hash[:timeout], hash[:play] || hash[:file]
105
+ break_on = hash[:break_on] || '#'
106
+
107
+ # TODO: compile play() and set its DTMF callback to this one
108
+ digits = []
109
+ dtmf_hook = lambda do |digit|
110
+ puts "RECEIVED #{digit} WITH #{digits}"
111
+ return! digits.to_s if digit.to_s == break_on.to_s
112
+ digits << digit
113
+ return! digits.to_s if number && digits.size >= number
114
+ end
115
+ returning DSL::Dialplan::NoOpEventCommand.new do |command|
116
+ command.on_keypress &dtmf_hook
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def cmd(*args, &block)
123
+ DSL::Dialplan::EventCommand.new(*args, &block)
124
+ end
125
+
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,38 @@
1
+ require 'adhearsion/voip/freeswitch/basic_connection_manager'
2
+ module Adhearsion
3
+ module VoIP
4
+ module FreeSwitch
5
+ class InboundConnectionManager < BasicConnectionManager
6
+
7
+ DEFAULTS = { :pass => "ClueCon", :host => '127.0.0.1', :port => 8021 }
8
+
9
+ def initialize(arg)
10
+ if arg.kind_of? Hash
11
+ @opts = DEFAULTS.merge arg
12
+ @io = TCPSocket.new(@opts[:host], @opts[:port])
13
+ super @io
14
+ unless login(@opts[:pass])
15
+ raise "Your FreeSwitch Event Socket password for #{@opts[:host]} was invalid!"
16
+ end
17
+ else arg.kind_of? IO
18
+ @io = arg
19
+ super @io
20
+ end
21
+ end
22
+
23
+ def enable_events!(which='ALL')
24
+ self << "event plain #{which}"
25
+ get_raw_header
26
+ end
27
+
28
+ # Only called when nothing has been sent over the socket.
29
+ def login(pass)
30
+ get_raw_header
31
+ self << "auth #{pass}"
32
+ get_raw_header.include? "+OK"
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,195 @@
1
+ require 'gserver'
2
+
3
+ require 'adhearsion/voip/dsl/dialplan/thread_mixin'
4
+ require 'adhearsion/voip/dsl/dialplan/parser'
5
+ require 'adhearsion/voip/dsl/dialplan/dispatcher'
6
+ require 'adhearsion/voip/freeswitch/basic_connection_manager'
7
+ require 'adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory'
8
+
9
+ module Adhearsion
10
+ module VoIP
11
+ module FreeSwitch
12
+ class OesServer < GServer
13
+
14
+ def initialize(port, host=nil)
15
+ @port, @host = port || 4572, host || "0.0.0.0"
16
+ @cache_lock = Mutex.new
17
+ super @port, @host, (1.0/0.0)
18
+ log "Starting FreeSwitch OES Server"
19
+ end
20
+
21
+ def serve(io)
22
+
23
+ log "Incoming call on the FreeSwitch outbound event socket..."
24
+ Thread.me.extend DSL::Dialplan::ThreadMixin
25
+ Thread.me.call.io = io
26
+ conn = BasicConnectionManager.new io
27
+
28
+ Thread.my.call.mgr = conn
29
+ conn << "connect"
30
+ @vars = conn.get_header
31
+ answered = @vars['variable_endpoint_disposition'] == "ANSWER"
32
+
33
+ conn << "myevents"
34
+ myevents_response = conn.get_header
35
+ answered ||= myevents_response['Event-Name'] == 'CHANNEL_ANSWER'
36
+
37
+ log "Connected to Freeswitch. Waiting for answer state."
38
+
39
+ until answered
40
+ answered ||= conn.get_header['Event-Name'] == 'CHANNEL_ANSWER'
41
+ end
42
+
43
+ log "Loading cached dialplan"
44
+ contexts, dispatcher = cached_dialplan_data
45
+ log "Finished loading cached dialplans"
46
+
47
+ first_context_name = @vars['variable_context'] || @vars["Channel-Context"]
48
+ first_context = contexts[first_context_name.to_sym]
49
+
50
+ log "Found context #{first_context_name} from call variables."
51
+
52
+ # If the target context does not exist, warn and don't handle the call
53
+ unless first_context
54
+ log "No context '#{first_context_name}' found in " +
55
+ "#{AHN_CONFIG.files_from_setting("paths", "").to_sentence(:connector => "or")}. Ignoring request!"
56
+ return
57
+ end
58
+
59
+ # Enable events
60
+
61
+ # Now that we have the code, let's dispatch it back.
62
+
63
+ pretty_vars = rubyize_keys_for @vars
64
+ dispatcher.def_keys! pretty_vars
65
+ dispatcher.instance_eval(&first_context.block)
66
+
67
+ rescue => e
68
+ p e
69
+ puts e.backtrace.map {|x| " " * 4 + x }
70
+ end
71
+
72
+ def cached_dialplan_data
73
+ @cache_lock.synchronize do
74
+ log "Checking whether the contexts should be reloaded"
75
+ if should_reload_contexts?
76
+ log "Getting the contexts"
77
+ @abstract_contexts = DSL::Dialplan::DialplanParser.get_contexts
78
+ log "Creating a new OesDispatcher"
79
+ @abstract_dispatcher = OesDispatcher.new @vars['Channel-Unique-ID']
80
+ log "Done creating it"
81
+ @abstract_dispatcher.def_keys! @abstract_contexts
82
+ else
83
+ log "Should not reload context."
84
+ @abstract_dispatcher.instance_variable_set :@uuid, @vars['Channel-Unique-ID']
85
+ end
86
+ return [@abstract_contexts.clone, @abstract_dispatcher.clone]
87
+ end
88
+ end
89
+
90
+ # TODO. This is broken. Always returns true. Should cache the last reload
91
+ # time.
92
+ def should_reload_contexts?
93
+ !@abstract_contexts || !@abstract_dispatcher ||
94
+ AHN_CONFIG.files_from_setting("paths", "dialplan").map { |x| File.mtime(x) }.max < Time.now
95
+ end
96
+
97
+ def rubyize_keys_for(hash)
98
+ returning({}) do |pretty|
99
+ hash.each { |k,v| pretty[k.to_s.underscore] = v }
100
+ end
101
+ end
102
+
103
+ class OesDispatcher < DSL::Dialplan::CommandDispatcher
104
+
105
+ def initialize(uuid=nil)
106
+ super FreeSwitchDialplanCommandFactory, uuid
107
+ end
108
+
109
+ def dispatch!(event)
110
+ if event.kind_of?(DSL::Dialplan::NoOpEventCommand) && event.on_keypress
111
+ return_value = nil
112
+ dispatch = lambda do
113
+ loop do
114
+ Thread.my.call.mgr.get_raw_header
115
+ async_event = Thread.my.call.mgr.get_header
116
+ if async_event['Event-Name'] == 'DTMF'
117
+ key = async_event['DTMF-String']
118
+ return_value = event.on_keypress.call(('0'..'9').include?(key) ? key.to_i : key)
119
+ end
120
+ end
121
+ end
122
+ if event.timeout
123
+ begin
124
+ Timeout.timeout event.timeout, &dispatch
125
+ rescue Timeout::Error
126
+ break!
127
+ return_value
128
+ end
129
+ else dispatch.call
130
+ end
131
+
132
+ else
133
+ log "Not a noop. Sending #{event.app}(#{event.args.to_a * " "})"
134
+ Thread.my.call.mgr << "SendMsg\ncall-command: execute\nexecute-app-name: " +
135
+ "#{event.app}\nexecute-app-arg: #{event.args.to_a * " "}"
136
+
137
+ if event.kind_of? DSL::Dialplan::ExitingEventCommand
138
+ Thread.my.call.io.close
139
+ Thread.me.exit
140
+ end
141
+
142
+ # Useless "command/reply" +OK and content-length headers
143
+ lambda do
144
+ Thread.my.call.mgr.get_raw_header
145
+ redo if Thread.my.call.mgr.get_header['Event-Name'] == "CHANNEL_EXECUTE_COMPLETE"
146
+ end.call
147
+
148
+ # Main event information. Keep track of the Core-UUID and wait for
149
+ # it to come back to us as a CHANNEL_EXECUTE_COMPLETE event.
150
+ execution_header = Thread.my.call.mgr.get_header
151
+ execution_uuid = execution_header['Core-UUID']
152
+
153
+ loop do
154
+ log "Waiting for either a DTMF or the app to finish"
155
+ hdr = Thread.my.call.mgr.get_raw_header
156
+ log "Got head #{hdr}"
157
+
158
+ if hdr == "Content-Type: api/response\nContent-Length: 0"
159
+ break
160
+ end
161
+
162
+ async_event = Thread.my.call.mgr.get_header
163
+ event_name = async_event['Event-Name']
164
+ if event_name == 'DTMF' && event.on_keypress
165
+ key = async_event['DTMF-String']
166
+ event.on_keypress.call(('0'..'9') === key ? key.to_i : key)
167
+ elsif event_name == 'CHANNEL_EXECUTE_COMPLETE' && async_event['Core-UUID'] == execution_uuid
168
+ break async_event
169
+ else
170
+
171
+ end
172
+ end
173
+ end
174
+ rescue DSL::Dialplan::ReturnValue => r
175
+ log "Dispatch!: Got a return value with #{r.obj}"
176
+ break!
177
+ raise r
178
+ rescue DSL::Dialplan::Hangup
179
+ Thread.my.call.mgr << "SendMsg\ncall-command: hangup"
180
+ Thread.my.call.mgr.io.close rescue nil
181
+ end
182
+
183
+ def break!(uuid=@context)
184
+ log "Breaking with #{uuid}"
185
+ Thread.my.call.mgr << "api break #{uuid}"
186
+ Thread.my.call.mgr.get_raw_header
187
+ # Thread.my.call.mgr.get_raw_header
188
+ # Thread.my.call.mgr.get_raw_header
189
+ end
190
+ end
191
+
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,80 @@
1
+ module Adhearsion
2
+ module VoIP
3
+ class CalculatedMatch
4
+
5
+ # Convenience method for instantiating failed matches
6
+ def self.failed_match!(pattern, query, match_payload)
7
+ new :pattern => pattern, :query => query, :match_payload => match_payload
8
+ end
9
+
10
+ attr_reader :match_payload, :potential_matches, :exact_matches, :pattern, :query
11
+
12
+ def initialize(options={})
13
+ @pattern, @query, @match_payload = options.values_at :pattern, :query, :match_payload
14
+ @potential_matches = options[:potential_matches] ? Array(options[:potential_matches]) : []
15
+ @exact_matches = options[:exact_matches] ? Array(options[:exact_matches]) : []
16
+ end
17
+
18
+ def exact_match?
19
+ exact_matches.any?
20
+ end
21
+
22
+ def potential_match?
23
+ potential_matches.any?
24
+ end
25
+
26
+ def failed_match?
27
+ !potential_match? && !exact_match?
28
+ end
29
+
30
+ def type_of_match
31
+ if exact_match?
32
+ :exact
33
+ elsif potential_match?
34
+ :potential
35
+ end
36
+ end
37
+
38
+ end
39
+
40
+ class CalculatedMatchCollection
41
+
42
+ attr_reader :calculated_matches, :potential_matches, :exact_matches,
43
+ :actual_potential_matches, :actual_exact_matches
44
+
45
+ def initialize
46
+ @calculated_matches = []
47
+ @potential_matches = []
48
+ @exact_matches = []
49
+ @actual_potential_matches = []
50
+ @actual_exact_matches = []
51
+ end
52
+
53
+ def <<(calculated_match)
54
+ calculated_matches << calculated_match
55
+ actual_potential_matches.concat calculated_match.potential_matches
56
+ actual_exact_matches.concat calculated_match.exact_matches
57
+
58
+ potential_matches << calculated_match if calculated_match.potential_match?
59
+ exact_matches << calculated_match if calculated_match.exact_match?
60
+ end
61
+
62
+ def potential_match_count
63
+ actual_potential_matches.size
64
+ end
65
+
66
+ def exact_match_count
67
+ actual_exact_matches.size
68
+ end
69
+
70
+ def potential_match?
71
+ potential_match_count > 0
72
+ end
73
+
74
+ def exact_match?
75
+ exact_match_count > 0
76
+ end
77
+
78
+ end
79
+ end
80
+ end