adhearsion-cw 1.0.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data/CHANGELOG +109 -0
  2. data/EVENTS +11 -0
  3. data/Gemfile +3 -0
  4. data/LICENSE +456 -0
  5. data/Rakefile +134 -0
  6. data/adhearsion.gemspec +174 -0
  7. data/app_generators/ahn/USAGE +5 -0
  8. data/app_generators/ahn/ahn_generator.rb +97 -0
  9. data/app_generators/ahn/templates/.ahnrc +34 -0
  10. data/app_generators/ahn/templates/Gemfile +7 -0
  11. data/app_generators/ahn/templates/README +8 -0
  12. data/app_generators/ahn/templates/Rakefile +27 -0
  13. data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
  14. data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
  15. data/app_generators/ahn/templates/components/disabled/restful_rpc/README.markdown +11 -0
  16. data/app_generators/ahn/templates/components/disabled/restful_rpc/example-client.rb +48 -0
  17. data/app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.rb +91 -0
  18. data/app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.yml +34 -0
  19. data/app_generators/ahn/templates/components/disabled/restful_rpc/spec/restful_rpc_spec.rb +251 -0
  20. data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.rb +104 -0
  21. data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.yml +2 -0
  22. data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
  23. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
  24. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.yml +12 -0
  25. data/app_generators/ahn/templates/components/disabled/xmpp_gateway/README.markdown +3 -0
  26. data/app_generators/ahn/templates/components/disabled/xmpp_gateway/xmpp_gateway.rb +11 -0
  27. data/app_generators/ahn/templates/components/disabled/xmpp_gateway/xmpp_gateway.yml +0 -0
  28. data/app_generators/ahn/templates/components/simon_game/simon_game.rb +56 -0
  29. data/app_generators/ahn/templates/config/startup.rb +74 -0
  30. data/app_generators/ahn/templates/dialplan.rb +3 -0
  31. data/app_generators/ahn/templates/events.rb +32 -0
  32. data/bin/ahn +29 -0
  33. data/bin/ahnctl +68 -0
  34. data/bin/jahn +43 -0
  35. data/examples/asterisk_manager_interface/standalone.rb +51 -0
  36. data/lib/adhearsion/cli.rb +296 -0
  37. data/lib/adhearsion/component_manager/component_tester.rb +53 -0
  38. data/lib/adhearsion/component_manager/spec_framework.rb +18 -0
  39. data/lib/adhearsion/component_manager.rb +272 -0
  40. data/lib/adhearsion/events_support.rb +84 -0
  41. data/lib/adhearsion/foundation/all.rb +15 -0
  42. data/lib/adhearsion/foundation/blank_slate.rb +3 -0
  43. data/lib/adhearsion/foundation/custom_daemonizer.rb +45 -0
  44. data/lib/adhearsion/foundation/event_socket.rb +205 -0
  45. data/lib/adhearsion/foundation/future_resource.rb +36 -0
  46. data/lib/adhearsion/foundation/metaprogramming.rb +17 -0
  47. data/lib/adhearsion/foundation/numeric.rb +13 -0
  48. data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
  49. data/lib/adhearsion/foundation/relationship_properties.rb +42 -0
  50. data/lib/adhearsion/foundation/string.rb +26 -0
  51. data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
  52. data/lib/adhearsion/foundation/thread_safety.rb +7 -0
  53. data/lib/adhearsion/host_definitions.rb +67 -0
  54. data/lib/adhearsion/initializer/asterisk.rb +87 -0
  55. data/lib/adhearsion/initializer/configuration.rb +321 -0
  56. data/lib/adhearsion/initializer/database.rb +60 -0
  57. data/lib/adhearsion/initializer/drb.rb +31 -0
  58. data/lib/adhearsion/initializer/freeswitch.rb +22 -0
  59. data/lib/adhearsion/initializer/ldap.rb +57 -0
  60. data/lib/adhearsion/initializer/rails.rb +41 -0
  61. data/lib/adhearsion/initializer/xmpp.rb +42 -0
  62. data/lib/adhearsion/initializer.rb +394 -0
  63. data/lib/adhearsion/logging.rb +92 -0
  64. data/lib/adhearsion/tasks/components.rb +32 -0
  65. data/lib/adhearsion/tasks/database.rb +5 -0
  66. data/lib/adhearsion/tasks/deprecations.rb +59 -0
  67. data/lib/adhearsion/tasks/generating.rb +20 -0
  68. data/lib/adhearsion/tasks/lint.rb +4 -0
  69. data/lib/adhearsion/tasks/testing.rb +37 -0
  70. data/lib/adhearsion/tasks.rb +17 -0
  71. data/lib/adhearsion/version.rb +35 -0
  72. data/lib/adhearsion/voip/asterisk/agi_server.rb +115 -0
  73. data/lib/adhearsion/voip/asterisk/commands.rb +1581 -0
  74. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
  75. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +102 -0
  76. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
  77. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
  78. data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
  79. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1681 -0
  80. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +341 -0
  81. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
  82. data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
  83. data/lib/adhearsion/voip/asterisk/manager_interface.rb +705 -0
  84. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
  85. data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
  86. data/lib/adhearsion/voip/asterisk.rb +4 -0
  87. data/lib/adhearsion/voip/call.rb +498 -0
  88. data/lib/adhearsion/voip/call_routing.rb +64 -0
  89. data/lib/adhearsion/voip/commands.rb +9 -0
  90. data/lib/adhearsion/voip/constants.rb +39 -0
  91. data/lib/adhearsion/voip/conveniences.rb +18 -0
  92. data/lib/adhearsion/voip/dial_plan.rb +250 -0
  93. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
  94. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
  95. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
  96. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
  97. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +69 -0
  98. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
  99. data/lib/adhearsion/voip/dsl/numerical_string.rb +128 -0
  100. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
  101. data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
  102. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
  103. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
  104. data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
  105. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
  106. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
  107. data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +57 -0
  108. data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
  109. data/lib/adhearsion/xmpp/connection.rb +61 -0
  110. data/lib/adhearsion.rb +46 -0
  111. data/lib/theatre/README.markdown +64 -0
  112. data/lib/theatre/callback_definition_loader.rb +84 -0
  113. data/lib/theatre/guid.rb +23 -0
  114. data/lib/theatre/invocation.rb +121 -0
  115. data/lib/theatre/namespace_manager.rb +153 -0
  116. data/lib/theatre/version.rb +2 -0
  117. data/lib/theatre.rb +151 -0
  118. metadata +323 -0
@@ -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
@@ -0,0 +1,123 @@
1
+ require File.join(File.dirname(__FILE__), 'calculated_match')
2
+
3
+ module Adhearsion
4
+ module VoIP
5
+ class MatchCalculator
6
+
7
+ class << self
8
+
9
+ def build_with_pattern(pattern, match_payload, &block)
10
+ class_for_pattern_type(pattern.class.name).new(pattern, match_payload, &block)
11
+ end
12
+
13
+ def inherited(klass)
14
+ subclasses << klass
15
+ end
16
+
17
+ private
18
+
19
+ def class_for_pattern_type(pattern_type)
20
+ sought_class_name = "Adhearsion::VoIP::#{pattern_type.camelize}MatchCalculator"
21
+ subclasses.find { |klass| klass.name == sought_class_name }
22
+ end
23
+
24
+ def subclasses
25
+ @@subclasses ||= []
26
+ end
27
+
28
+ end
29
+
30
+ attr_reader :pattern, :match_payload
31
+ def initialize(pattern, match_payload)
32
+ @pattern, @match_payload = pattern, match_payload
33
+ end
34
+
35
+ protected
36
+
37
+ def new_calculated_match(options)
38
+ CalculatedMatch.new({:pattern => pattern, :match_payload => match_payload}.merge(options))
39
+ end
40
+
41
+ def coerce_to_numeric(victim)
42
+ victim.kind_of?(Numeric) ? victim : (victim.to_s =~ /^\d+$/ ? victim.to_s.to_i : nil )
43
+ end
44
+ end
45
+
46
+ class RangeMatchCalculator < MatchCalculator
47
+
48
+ def initialize(pattern, match_payload)
49
+ raise unless pattern.first.kind_of?(Numeric) && pattern.last.kind_of?(Numeric)
50
+ super
51
+ end
52
+
53
+ def match(query)
54
+ numerical_query = coerce_to_numeric query
55
+ if numerical_query
56
+ exact_match = pattern.include?(numerical_query) ? query : nil
57
+ potential_matches = numbers_in_range_like numerical_query
58
+ potential_matches.reject! { |m| m.to_s == exact_match.to_s } if exact_match
59
+
60
+ new_calculated_match :query => query, :exact_matches => exact_match,
61
+ :potential_matches => potential_matches
62
+ else
63
+ CalculatedMatch.failed_match!(pattern, query, match_payload)
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ # Returns all numbers in the range (@pattern) that +begin with+ the number given
70
+ # as the first argument.
71
+ #
72
+ # NOTE! If you're having trouble reading what this method is actually doing, it's
73
+ # effectively a much more efficient version of this:
74
+ #
75
+ # pattern.to_a.select { |x| x.to_s.starts_with? num.to_s }.flatten
76
+ #
77
+ # Huge thanks to Dave Troy (http://davetroy.blogspot.com) for this awesomely
78
+ # efficient code!
79
+ def numbers_in_range_like(num)
80
+ return (pattern === 0 ? [0] : nil) if num == 0
81
+ raise ArgumentError unless num.kind_of?(Numeric)
82
+ Array.new.tap do |matches|
83
+ first, last = pattern.first, pattern.last
84
+ power = 0
85
+ while num < last
86
+ ones_count = 10**power - 1
87
+ matches.concat((([num, first].max)..[(num+ones_count), last].min).to_a)
88
+ num *= 10
89
+ power += 1
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ class FixnumMatchCalculator < MatchCalculator
96
+ def match(query)
97
+ numeric_query = coerce_to_numeric query
98
+ exact_match, potential_match = nil
99
+ if pattern == numeric_query
100
+ exact_match = pattern
101
+ elsif pattern.to_s.starts_with? query.to_s
102
+ potential_match = pattern
103
+ end
104
+ new_calculated_match :query => query, :exact_matches => exact_match, :potential_matches => potential_match
105
+ end
106
+ end
107
+
108
+ class StringMatchCalculator < MatchCalculator
109
+ def match(query)
110
+ args = { :query => query, :exact_matches => nil,
111
+ :potential_matches => nil }
112
+
113
+ if pattern == query.to_s
114
+ args[:exact_matches] = [pattern]
115
+ elsif pattern.starts_with? query.to_s
116
+ args[:potential_matches] = [pattern]
117
+ end
118
+
119
+ new_calculated_match args
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,57 @@
1
+ require File.join(File.dirname(__FILE__), 'matchers.rb')
2
+
3
+ module Adhearsion
4
+ module VoIP
5
+ class MenuBuilder
6
+
7
+ def initialize
8
+ @patterns = []
9
+ @menu_callbacks = {}
10
+ end
11
+
12
+ def method_missing(match_payload, *patterns, &block)
13
+ if patterns.any?
14
+ patterns.each do |pattern|
15
+ @patterns << MatchCalculator.build_with_pattern(pattern, match_payload)
16
+ end
17
+ else
18
+ raise ArgumentError, "You cannot call this method without patterns!"
19
+ end
20
+ nil
21
+ end
22
+
23
+ def weighted_match_calculators
24
+ @patterns
25
+ end
26
+
27
+ def execute_hook_for(symbol, input)
28
+ callback = @menu_callbacks[symbol]
29
+ callback.call input if callback
30
+ end
31
+
32
+ def on_invalid(&block)
33
+ raise LocalJumpError, "Must supply a block!" unless block_given?
34
+ @menu_callbacks[:invalid] = block
35
+ end
36
+
37
+ def on_premature_timeout(&block)
38
+ raise LocalJumpError, "Must supply a block!" unless block_given?
39
+ @menu_callbacks[:premature_timeout] = block
40
+ end
41
+
42
+ def on_failure(&block)
43
+ raise LocalJumpError, "Must supply a block!" unless block_given?
44
+ @menu_callbacks[:failure] = block
45
+ end
46
+
47
+ def calculate_matches_for(result)
48
+ CalculatedMatchCollection.new.tap do |collection|
49
+ weighted_match_calculators.each do |pattern|
50
+ collection << pattern.match(result)
51
+ end
52
+ end
53
+ end
54
+
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,149 @@
1
+ require 'adhearsion/voip/menu_state_machine/menu_builder'
2
+ module Adhearsion
3
+ module VoIP
4
+ class Menu
5
+
6
+ DEFAULT_MAX_NUMBER_OF_TRIES = 1
7
+ DEFAULT_TIMEOUT = 5 # seconds
8
+
9
+ relationships :menu_builder => MenuBuilder
10
+
11
+ attr_reader :builder, :timeout, :tries_count, :max_number_of_tries
12
+ def initialize(options={}, &block)
13
+ @tries_count = 0 # Counts the number of tries the menu's been executed
14
+
15
+ @timeout = options[:timeout] || DEFAULT_TIMEOUT
16
+ @max_number_of_tries = options[:tries] || DEFAULT_MAX_NUMBER_OF_TRIES
17
+
18
+ @builder = menu_builder.new
19
+ yield @builder
20
+
21
+ initialize_digit_buffer
22
+ end
23
+
24
+ def <<(other)
25
+ digit_buffer << other
26
+ end
27
+
28
+ def digit_buffer
29
+ @digit_buffer
30
+ end
31
+
32
+ def digit_buffer_string
33
+ digit_buffer.to_s
34
+ end
35
+
36
+ def digit_buffer_empty?
37
+ digit_buffer.empty?
38
+ end
39
+
40
+ def continue
41
+ raise MenuGetAnotherDigitOrTimeout if digit_buffer_empty?
42
+
43
+ calculated_matches = builder.calculate_matches_for digit_buffer_string
44
+
45
+ if calculated_matches.exact_match_count >= 1
46
+ first_exact_match = calculated_matches.exact_matches.first
47
+ if calculated_matches.potential_match_count.zero?
48
+ # Match found with no extenuating ambiguities! Go with the first exact match
49
+ menu_result_found! first_exact_match.match_payload, digit_buffer_string
50
+ else
51
+ get_another_digit_or_finish!(first_exact_match.match_payload, first_exact_match.query)
52
+ end
53
+ elsif calculated_matches.potential_match_count >= 1
54
+ get_another_digit_or_timeout!
55
+ else
56
+ invalid!
57
+ end
58
+ end
59
+
60
+ def should_continue?
61
+ tries_count < max_number_of_tries
62
+ end
63
+
64
+ def restart!
65
+ @tries_count += 1
66
+ digit_buffer.clear!
67
+ end
68
+
69
+ def execute_invalid_hook
70
+ builder.execute_hook_for(:invalid, digit_buffer_string)
71
+ end
72
+
73
+ def execute_timeout_hook
74
+ builder.execute_hook_for(:premature_timeout, digit_buffer_string)
75
+ end
76
+
77
+ def execute_failure_hook
78
+ builder.execute_hook_for(:failure, digit_buffer_string)
79
+ end
80
+
81
+ protected
82
+
83
+ # If you're using a more complex class in subclasses, you may want to override this method in addition to the
84
+ # digit_buffer, digit_buffer_empty, and digit_buffer_string methods
85
+ def initialize_digit_buffer
86
+ @digit_buffer = ClearableStringBuffer.new
87
+ end
88
+
89
+ def invalid!
90
+ raise MenuResultInvalid
91
+ end
92
+
93
+ def menu_result_found!(match_payload, new_extension)
94
+ raise MenuResultFound.new(match_payload, new_extension)
95
+ end
96
+
97
+ def get_another_digit_or_finish!(match_payload, new_extension)
98
+ raise MenuGetAnotherDigitOrFinish.new(match_payload, new_extension)
99
+ end
100
+
101
+ def get_another_digit_or_timeout!
102
+ raise MenuGetAnotherDigitOrTimeout
103
+ end
104
+
105
+ # The superclass from which all message-like exceptions descend. It should never
106
+ # be instantiated directly.
107
+ class MenuResult < StandardError; end
108
+
109
+ # Raised when the user's input matches
110
+ class MenuResultFound < MenuResult
111
+ attr_reader :match_payload, :new_extension
112
+ def initialize(match_payload, new_extension)
113
+ super()
114
+ @match_payload = match_payload
115
+ @new_extension = new_extension
116
+ end
117
+ end
118
+
119
+ module MenuGetAnotherDigit; end
120
+
121
+ class MenuGetAnotherDigitOrFinish < MenuResultFound
122
+ include MenuGetAnotherDigit
123
+ end
124
+
125
+ class MenuGetAnotherDigitOrTimeout < MenuResult
126
+ include MenuGetAnotherDigit
127
+ end
128
+
129
+ class MenuResultFound < MenuResult; end
130
+
131
+ # Raised when the user's input matches no patterns
132
+ class MenuResultInvalid < MenuResult; end
133
+
134
+ # For our default purposes, we need the digit_buffer to behave much like a normal String except that it should
135
+ # handle its own resetting (clearing).
136
+ class ClearableStringBuffer < String
137
+ def clear!
138
+ replace ""
139
+ end
140
+
141
+ def <<(other)
142
+ super other.to_s
143
+ end
144
+
145
+ end
146
+
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,61 @@
1
+ module Adhearsion
2
+ module XMPP
3
+ module Connection
4
+
5
+ mattr_accessor :client
6
+ class << self
7
+
8
+ # Open the XMPP connection
9
+ #
10
+ # @param [String] jid the client/component JID to connect to
11
+ # @param [String] password
12
+ # @param [String] server
13
+ # @param [Integer] port
14
+ def start(jid, password, server, port)
15
+ Blather.logger = ahn_log.xmpp
16
+ setup_client_object(jid, password, server, port)
17
+ register_event_namespaces
18
+ register_default_client_handlers
19
+ Events.register_callback(:after_initialized) do
20
+ connect
21
+ end
22
+ end
23
+
24
+ # Close the XMPP connection
25
+ def stop
26
+ shutdown
27
+ end
28
+
29
+ private
30
+
31
+ def setup_client_object(jid, password, server, port)
32
+ self.client = Blather::Client.setup(jid, password, server, port)
33
+ end
34
+
35
+ def connect
36
+ EventMachine.run {client.connect}
37
+ end
38
+
39
+ def register_event_namespaces
40
+ Events.register_namespace_name "/xmpp"
41
+ end
42
+
43
+ def register_default_client_handlers
44
+ client.register_handler(:ready) do
45
+ ahn_log.xmpp.info "Connected to XMPP server! Send messages to #{client.jid.stripped}."
46
+ end
47
+
48
+ client.register_handler(:disconnected) do
49
+ if Adhearsion.status == :running
50
+ ahn_log.xmpp.warning "XMPP Disconnected. Reconnecting."
51
+ connect
52
+ end
53
+ # TODO: fix this to reconnect XMPP cleanly
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+ end
61
+ end
data/lib/adhearsion.rb ADDED
@@ -0,0 +1,46 @@
1
+ # Check the Ruby version
2
+ STDERR.puts "WARNING: You are running Adhearsion in an unsupported
3
+ version of Ruby (Ruby #{RUBY_VERSION} #{RUBY_RELEASE_DATE})!
4
+ Please upgrade to at least Ruby v1.8.5." if RUBY_VERSION < "1.8.5"
5
+
6
+ $: << File.expand_path(File.dirname(__FILE__))
7
+
8
+ require 'rubygems'
9
+ require 'bundler/setup'
10
+
11
+ require 'adhearsion/version'
12
+ require 'adhearsion/voip/call'
13
+ require 'adhearsion/voip/dial_plan'
14
+ require 'adhearsion/voip/asterisk/special_dial_plan_managers'
15
+ require 'adhearsion/foundation/all'
16
+ require 'adhearsion/events_support'
17
+ require 'adhearsion/logging'
18
+ require 'adhearsion/component_manager'
19
+ require 'adhearsion/initializer/configuration'
20
+ require 'adhearsion/initializer'
21
+ require 'adhearsion/voip/dsl/numerical_string'
22
+ require 'adhearsion/voip/dsl/dialplan/parser'
23
+ require 'adhearsion/voip/commands'
24
+ require 'adhearsion/voip/asterisk/commands'
25
+ require 'adhearsion/voip/dsl/dialing_dsl'
26
+ require 'adhearsion/voip/call_routing'
27
+
28
+ begin
29
+ # Try ActiveSupport >= 2.3.0
30
+ require 'active_support/all'
31
+ rescue LoadError
32
+ # Assume ActiveSupport < 2.3.0
33
+ require 'active_support'
34
+ end
35
+
36
+ module Adhearsion
37
+ # Sets up the Gem require path.
38
+ AHN_INSTALL_DIR = File.expand_path(File.dirname(__FILE__) + "/..")
39
+ AHN_CONFIG = Configuration.new
40
+
41
+ ##
42
+ # This Array holds all the Threads whose life matters. Adhearsion will not exit until all of these have died.
43
+ #
44
+ IMPORTANT_THREADS = []
45
+
46
+ end
@@ -0,0 +1,64 @@
1
+ Theatre
2
+ =======
3
+
4
+ Present status: stable
5
+
6
+ A library for choreographing a dynamic pool of hierarchically organized actors. This was originally extracted from the [Adhearsion](http://adhearsion.com) framework by Jay Phillips.
7
+
8
+ In the Adhearsion framework, it was necessary to develop an internal message-passing system that could work either synchronously or asynchronously. This is used by the framework itself and for framework extensions (called _components_) to talk with each other. The source of the events is completely undefined -- events could originate from within the framework out outside the framework. For example, a Message Queue such as [Stomp](http://stomp.codehaus.org) can wire incoming events into Theatre and catch events going to a particular destination so it can proxy them out to the server.
9
+
10
+ Motivations and Design Decisions
11
+ --------------------------------
12
+
13
+ * Must maintain Ruby 1.8 and JRuby compatibility
14
+ * Must be Thread-safe
15
+ * Must provide some level of transparency into the events going through it
16
+ * Must be dynamic enough to reallocate the number of triggerrs based on load
17
+ * Must help facilitate test-driven development of Actor functionality
18
+ * Must allow external persistence in case of a crash
19
+
20
+ Example
21
+ -------
22
+
23
+ Below is an example taken from Adhearsion for executing framework-level callbacks. Note: the framework treats this callback synchronously.
24
+
25
+ events.framework.asterisk.before_call.each do |event|
26
+ # Pull headers from event and respond to it here.
27
+ end
28
+
29
+ Below is an example of integration with [Stomp](http://stomp.codehaus.org/), a simple yet robust open-protocol message queue.
30
+
31
+ events.stomp.new_call.each do |event|
32
+ # Handle all events from the Stomp MQ server whose name is "new_call" (the String)
33
+ end
34
+
35
+ This will filter all events whose name is "new_call" and yield the Stomp::Message to the block.
36
+
37
+ Framework terminology
38
+ --------------------
39
+
40
+ Below are definitions of terms I use in Theatre. See the respective links for more information.
41
+
42
+ * **callback**: This is the block given to the `each` method which triggers events coming in.
43
+ * **payload**: This is the "message" sent to the Theatre and is what will ultimately be yielded to the callback
44
+ * **[Actor](http://en.wikipedia.org/wiki/Actor_model)**: This refers to concurrent responders to events in a concurrent system.
45
+
46
+ Synchronous vs. Asynchronous
47
+ ----------------------------
48
+
49
+ With Theatre, all events are asynchronous with the optional ability to synchronously block until the event is scheduled, triggered, and has returned. If you wish to synchronously trigger the event, simple call `wait` on an `Invocation` object returned from `trigger` and then check the `Invocation#current_state` property for `:success` or `:error`. Optionally the `Invocation#success?` and `Invocation#error?` methods also provide more intuitive access to the finished state. If the event finished with `:success`, you may retrieve the returned value of the event Proc by calling `Invocation#returned_value`. If the event finished with `:error`, you may get the Exception with `Invocation#error`.
50
+
51
+ Because many callbacks can be registered for a particular namespace, each needing its own Invocation object, the `Theatre#trigger` method returns an Array of Invocation objects.
52
+
53
+ # We'll assume only one callback is registered and call #first on the Array of Invocations returned by #trigger
54
+ invocation = my_theatre.trigger("your/namespace/here", YourSpecialClass.new("this can be anything")).first
55
+ invocation.wait
56
+ raise invocation.error if invocation.error?
57
+ log "Actor finished with return value #{invocation.returned_value}"
58
+
59
+ Ruby 1.8 vs. Ruby 1.9 vs. JRuby
60
+ -------------------------------
61
+
62
+ Theatre was created for Ruby 1.8 because no good Actor system existed on Ruby 1.8 that met Adhearsion's needs (e.g. hierarchal with synchronous and asynchronous modes. If you wish to achieve real processor-level concurrency, use JRuby.
63
+
64
+ Presently Ruby 1.9 compatibility is not a priority but patches for compatibility will be accepted, as long as they preserve compatibility with both MRI and JRuby.
@@ -0,0 +1,84 @@
1
+ module Theatre
2
+
3
+ ##
4
+ # This class provides the a wrapper aroung which an events.rb file can be instance_eval'd.
5
+ #
6
+ class CallbackDefinitionLoader
7
+
8
+ attr_reader :theatre, :root_name
9
+ def initialize(theatre, root_name=:events)
10
+ @theatre = theatre
11
+ @root_name = root_name
12
+
13
+ create_recorder_method root_name
14
+ end
15
+
16
+ def anonymous_recorder
17
+ BlankSlateMessageRecorder.new(&method(:callback_registered))
18
+ end
19
+
20
+ ##
21
+ # Parses the given Ruby source code file and returns this object.
22
+ #
23
+ # @param [String, File] file The filename or File object for the Ruby source code file to parse.
24
+ #
25
+ def load_events_file(file)
26
+ file = File.open(file) if file.kind_of? String
27
+ instance_eval file.read, file.path
28
+ self
29
+ end
30
+
31
+ ##
32
+ # Parses the given Ruby source code and returns this object.
33
+ #
34
+ # NOTE: Only use this if you're generating the code yourself! If you're loading a file from the filesystem, you should
35
+ # use load_events_file() since load_events_file() will properly attribute errors in the code to the file from which the
36
+ # code was loaded.
37
+ #
38
+ # @param [String] code The Ruby source code to parse
39
+ #
40
+ def load_events_code(code)
41
+ instance_eval code
42
+ self
43
+ end
44
+
45
+ protected
46
+
47
+ ##
48
+ # Immediately register the namespace and callback with the Theatre instance given to the constructor. This method is only
49
+ # called when a new BlankSlateMessageRecorder is instantiated and receives #each().
50
+ #
51
+ def callback_registered(namespaces, callback)
52
+ # Get rid of all arguments passed to the namespaces. Will support arguments in the future.
53
+ namespaces = namespaces.map { |namespace| namespace.first }
54
+
55
+ theatre.namespace_manager.register_callback_at_namespace namespaces, callback
56
+ end
57
+
58
+ def create_recorder_method(record_method_name)
59
+ (class << self; self; end).send(:alias_method, record_method_name, :anonymous_recorder)
60
+ end
61
+
62
+ class BlankSlateMessageRecorder
63
+
64
+ (instance_methods.map{|m| m.to_sym} - [:instance_eval, :object_id]).each { |method| undef_method method unless method.to_s =~ /^__/ }
65
+
66
+ def initialize(&notify_on_completion)
67
+ @notify_on_completion = notify_on_completion
68
+ @namespaces = []
69
+ end
70
+
71
+ def method_missing(*method_name_and_args)
72
+ raise ArgumentError, "Supplying a block is not supported" if block_given?
73
+ @namespaces << method_name_and_args
74
+ self
75
+ end
76
+
77
+ def each(&callback)
78
+ @notify_on_completion.call(@namespaces, callback)
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,23 @@
1
+ # Right now Adhearsion also defines this method. The eventual solution will be to extract the Adhearsion features on which
2
+ # Theatre depends and make that a dependent library.
3
+
4
+ unless respond_to? :new_guid
5
+
6
+ def random_character
7
+ case random_digit = rand(62)
8
+ when 0...10 then random_digit.to_s
9
+ when 10...36 then (random_digit + 55).chr
10
+ when 36...62 then (random_digit + 61).chr
11
+ end
12
+ end
13
+
14
+ def random_string(length_of_string=8)
15
+ Array.new(length_of_string) { random_character }.join
16
+ end
17
+
18
+ # This GUID implementation doesn't adhere to the RFC which wants to make certain segments based on the MAC address of a
19
+ # network interface card and other wackiness. It's sufficiently random for our needs.
20
+ def new_guid
21
+ [8,4,4,4,12].map { |segment_length| random_string(segment_length) }.join('-')
22
+ end
23
+ end