adhearsion 1.2.6 → 2.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (236) hide show
  1. data/.gitignore +17 -10
  2. data/CHANGELOG.md +273 -0
  3. data/Gemfile +1 -1
  4. data/Guardfile +17 -0
  5. data/README.markdown +61 -9
  6. data/Rakefile +16 -48
  7. data/adhearsion.gemspec +21 -7
  8. data/bin/ahn +3 -1
  9. data/cucumber.yml +4 -0
  10. data/features/app_generator.feature +42 -0
  11. data/features/cli.feature +108 -0
  12. data/features/step_definitions/app_generator_steps.rb +6 -0
  13. data/features/step_definitions/cli_steps.rb +74 -0
  14. data/features/support/aruba_helper.rb +22 -0
  15. data/features/support/env.rb +37 -0
  16. data/features/support/utils.rb +8 -0
  17. data/lib/adhearsion.rb +85 -41
  18. data/lib/adhearsion/call.rb +176 -0
  19. data/lib/adhearsion/call_controller.rb +134 -0
  20. data/lib/adhearsion/call_controller/dial.rb +70 -0
  21. data/lib/adhearsion/call_controller/input.rb +173 -0
  22. data/lib/adhearsion/call_controller/menu.rb +124 -0
  23. data/lib/adhearsion/call_controller/output.rb +267 -0
  24. data/lib/adhearsion/call_controller/record.rb +42 -0
  25. data/lib/adhearsion/call_controller/utility.rb +60 -0
  26. data/lib/adhearsion/calls.rb +81 -0
  27. data/lib/adhearsion/cli.rb +1 -3
  28. data/lib/adhearsion/cli_commands.rb +142 -0
  29. data/lib/adhearsion/configuration.rb +149 -0
  30. data/lib/adhearsion/console.rb +19 -8
  31. data/lib/adhearsion/dialplan_controller.rb +9 -0
  32. data/lib/adhearsion/events.rb +84 -0
  33. data/lib/adhearsion/foundation/all.rb +0 -7
  34. data/lib/adhearsion/foundation/custom_daemonizer.rb +4 -6
  35. data/lib/adhearsion/foundation/exception_handler.rb +9 -0
  36. data/lib/adhearsion/foundation/object.rb +26 -8
  37. data/lib/adhearsion/foundation/synchronized_hash.rb +3 -6
  38. data/lib/adhearsion/foundation/thread_safety.rb +17 -1
  39. data/lib/adhearsion/generators/app/app_generator.rb +4 -13
  40. data/lib/adhearsion/generators/app/templates/Gemfile +10 -5
  41. data/lib/adhearsion/generators/app/templates/Procfile +1 -0
  42. data/lib/adhearsion/generators/app/templates/README.md +28 -0
  43. data/lib/adhearsion/generators/app/templates/config/adhearsion.rb +41 -0
  44. data/lib/adhearsion/generators/app/templates/{components/simon_game → lib}/simon_game.rb +6 -18
  45. data/lib/adhearsion/generators/app/templates/script/ahn +2 -2
  46. data/lib/adhearsion/initializer.rb +151 -293
  47. data/lib/adhearsion/initializer/logging.rb +33 -0
  48. data/lib/adhearsion/logging.rb +65 -69
  49. data/lib/adhearsion/menu_dsl.rb +15 -0
  50. data/lib/adhearsion/menu_dsl/calculated_match.rb +39 -0
  51. data/lib/adhearsion/menu_dsl/calculated_match_collection.rb +41 -0
  52. data/lib/adhearsion/menu_dsl/fixnum_match_calculator.rb +18 -0
  53. data/lib/adhearsion/menu_dsl/match_calculator.rb +36 -0
  54. data/lib/adhearsion/{voip/menu_state_machine/menu_class.rb → menu_dsl/menu.rb} +38 -40
  55. data/lib/adhearsion/menu_dsl/menu_builder.rb +69 -0
  56. data/lib/adhearsion/menu_dsl/range_match_calculator.rb +55 -0
  57. data/lib/adhearsion/menu_dsl/string_match_calculator.rb +21 -0
  58. data/lib/adhearsion/outbound_call.rb +64 -0
  59. data/lib/adhearsion/plugin.rb +319 -0
  60. data/lib/adhearsion/plugin/collection.rb +19 -0
  61. data/lib/adhearsion/plugin/initializer.rb +37 -0
  62. data/lib/adhearsion/plugin/methods_container.rb +6 -0
  63. data/lib/adhearsion/process.rb +94 -0
  64. data/lib/adhearsion/punchblock_plugin.rb +29 -0
  65. data/lib/adhearsion/punchblock_plugin/initializer.rb +137 -0
  66. data/lib/adhearsion/router.rb +30 -0
  67. data/lib/adhearsion/router/route.rb +42 -0
  68. data/lib/adhearsion/script_ahn_loader.rb +2 -2
  69. data/lib/adhearsion/tasks.rb +14 -9
  70. data/lib/adhearsion/tasks/configuration.rb +26 -0
  71. data/lib/adhearsion/tasks/plugins.rb +17 -0
  72. data/lib/adhearsion/version.rb +8 -14
  73. data/spec/adhearsion/call_controller/dial_spec.rb +138 -0
  74. data/spec/adhearsion/call_controller/input_spec.rb +278 -0
  75. data/spec/adhearsion/call_controller/menu_spec.rb +120 -0
  76. data/spec/adhearsion/call_controller/output_spec.rb +466 -0
  77. data/spec/adhearsion/call_controller/record_spec.rb +125 -0
  78. data/spec/adhearsion/call_controller_spec.rb +395 -0
  79. data/spec/adhearsion/call_spec.rb +438 -0
  80. data/spec/adhearsion/calls_spec.rb +47 -0
  81. data/spec/adhearsion/configuration_spec.rb +308 -0
  82. data/spec/adhearsion/dialplan_controller_spec.rb +26 -0
  83. data/spec/adhearsion/events_spec.rb +112 -0
  84. data/spec/adhearsion/initializer/logging_spec.rb +58 -0
  85. data/spec/adhearsion/initializer_spec.rb +209 -122
  86. data/spec/adhearsion/logging_spec.rb +58 -47
  87. data/spec/adhearsion/menu_dsl/calculated_match_collection_spec.rb +56 -0
  88. data/spec/adhearsion/menu_dsl/calculated_match_spec.rb +57 -0
  89. data/spec/adhearsion/menu_dsl/fixnum_match_calculator_spec.rb +33 -0
  90. data/spec/adhearsion/menu_dsl/match_calculator_spec.rb +13 -0
  91. data/spec/adhearsion/menu_dsl/menu_builder_spec.rb +118 -0
  92. data/spec/adhearsion/menu_dsl/menu_spec.rb +210 -0
  93. data/spec/adhearsion/menu_dsl/range_match_calculator_spec.rb +28 -0
  94. data/spec/adhearsion/menu_dsl/string_match_calculator_spec.rb +36 -0
  95. data/spec/adhearsion/menu_dsl_spec.rb +12 -0
  96. data/spec/adhearsion/outbound_call_spec.rb +174 -0
  97. data/spec/adhearsion/plugin_spec.rb +489 -0
  98. data/spec/adhearsion/process_spec.rb +34 -0
  99. data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +294 -0
  100. data/spec/adhearsion/router/route_spec.rb +99 -0
  101. data/spec/adhearsion/router_spec.rb +106 -0
  102. data/spec/adhearsion_spec.rb +46 -0
  103. data/spec/spec_helper.rb +14 -14
  104. data/spec/support/call_controller_test_helpers.rb +48 -0
  105. data/spec/support/initializer_stubs.rb +8 -13
  106. data/spec/support/punchblock_mocks.rb +6 -0
  107. metadata +255 -253
  108. data/CHANGELOG +0 -174
  109. data/bin/ahnctl +0 -68
  110. data/bin/jahn +0 -43
  111. data/examples/asterisk_manager_interface/standalone.rb +0 -51
  112. data/lib/adhearsion/commands.rb +0 -302
  113. data/lib/adhearsion/component_manager.rb +0 -278
  114. data/lib/adhearsion/component_manager/component_tester.rb +0 -54
  115. data/lib/adhearsion/component_manager/spec_framework.rb +0 -18
  116. data/lib/adhearsion/events_support.rb +0 -65
  117. data/lib/adhearsion/foundation/blank_slate.rb +0 -3
  118. data/lib/adhearsion/foundation/event_socket.rb +0 -205
  119. data/lib/adhearsion/foundation/future_resource.rb +0 -36
  120. data/lib/adhearsion/foundation/metaprogramming.rb +0 -17
  121. data/lib/adhearsion/foundation/numeric.rb +0 -13
  122. data/lib/adhearsion/foundation/pseudo_guid.rb +0 -10
  123. data/lib/adhearsion/foundation/relationship_properties.rb +0 -42
  124. data/lib/adhearsion/foundation/string.rb +0 -26
  125. data/lib/adhearsion/generators/app/templates/.ahnrc +0 -34
  126. data/lib/adhearsion/generators/app/templates/README +0 -8
  127. data/lib/adhearsion/generators/app/templates/components/ami_remote/ami_remote.rb +0 -15
  128. data/lib/adhearsion/generators/app/templates/components/disabled/HOW_TO_ENABLE +0 -7
  129. data/lib/adhearsion/generators/app/templates/components/disabled/stomp_gateway/README.markdown +0 -47
  130. data/lib/adhearsion/generators/app/templates/components/disabled/stomp_gateway/stomp_gateway.rb +0 -34
  131. data/lib/adhearsion/generators/app/templates/components/disabled/stomp_gateway/stomp_gateway.yml +0 -12
  132. data/lib/adhearsion/generators/app/templates/components/disabled/xmpp_gateway/README.markdown +0 -3
  133. data/lib/adhearsion/generators/app/templates/components/disabled/xmpp_gateway/xmpp_gateway.rb +0 -11
  134. data/lib/adhearsion/generators/app/templates/components/disabled/xmpp_gateway/xmpp_gateway.yml +0 -0
  135. data/lib/adhearsion/generators/app/templates/config/startup.rb +0 -81
  136. data/lib/adhearsion/generators/app/templates/dialplan.rb +0 -3
  137. data/lib/adhearsion/generators/app/templates/events.rb +0 -33
  138. data/lib/adhearsion/host_definitions.rb +0 -67
  139. data/lib/adhearsion/initializer/asterisk.rb +0 -86
  140. data/lib/adhearsion/initializer/configuration.rb +0 -324
  141. data/lib/adhearsion/initializer/database.rb +0 -60
  142. data/lib/adhearsion/initializer/drb.rb +0 -31
  143. data/lib/adhearsion/initializer/freeswitch.rb +0 -22
  144. data/lib/adhearsion/initializer/ldap.rb +0 -57
  145. data/lib/adhearsion/initializer/rails.rb +0 -41
  146. data/lib/adhearsion/initializer/xmpp.rb +0 -42
  147. data/lib/adhearsion/tasks/components.rb +0 -32
  148. data/lib/adhearsion/tasks/database.rb +0 -5
  149. data/lib/adhearsion/tasks/deprecations.rb +0 -59
  150. data/lib/adhearsion/tasks/generating.rb +0 -20
  151. data/lib/adhearsion/tasks/lint.rb +0 -4
  152. data/lib/adhearsion/voip/asterisk.rb +0 -4
  153. data/lib/adhearsion/voip/asterisk/agi_server.rb +0 -121
  154. data/lib/adhearsion/voip/asterisk/commands.rb +0 -1966
  155. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +0 -140
  156. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +0 -102
  157. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +0 -250
  158. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +0 -240
  159. data/lib/adhearsion/voip/asterisk/config_manager.rb +0 -64
  160. data/lib/adhearsion/voip/asterisk/manager_interface.rb +0 -697
  161. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +0 -1681
  162. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +0 -341
  163. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +0 -78
  164. data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +0 -87
  165. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +0 -80
  166. data/lib/adhearsion/voip/call.rb +0 -521
  167. data/lib/adhearsion/voip/call_routing.rb +0 -64
  168. data/lib/adhearsion/voip/commands.rb +0 -17
  169. data/lib/adhearsion/voip/constants.rb +0 -39
  170. data/lib/adhearsion/voip/conveniences.rb +0 -18
  171. data/lib/adhearsion/voip/dial_plan.rb +0 -252
  172. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +0 -151
  173. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +0 -37
  174. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +0 -27
  175. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +0 -124
  176. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +0 -69
  177. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +0 -16
  178. data/lib/adhearsion/voip/dsl/numerical_string.rb +0 -128
  179. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +0 -48
  180. data/lib/adhearsion/voip/freeswitch/event_handler.rb +0 -58
  181. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +0 -129
  182. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +0 -38
  183. data/lib/adhearsion/voip/freeswitch/oes_server.rb +0 -195
  184. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +0 -80
  185. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +0 -123
  186. data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +0 -57
  187. data/lib/adhearsion/xmpp/connection.rb +0 -61
  188. data/lib/theatre.rb +0 -147
  189. data/lib/theatre/README.markdown +0 -64
  190. data/lib/theatre/callback_definition_loader.rb +0 -86
  191. data/lib/theatre/guid.rb +0 -23
  192. data/lib/theatre/invocation.rb +0 -131
  193. data/lib/theatre/namespace_manager.rb +0 -153
  194. data/lib/theatre/version.rb +0 -2
  195. data/spec/adhearsion/cli_spec.rb +0 -306
  196. data/spec/adhearsion/component_manager_spec.rb +0 -292
  197. data/spec/adhearsion/constants_spec.rb +0 -8
  198. data/spec/adhearsion/drb_spec.rb +0 -65
  199. data/spec/adhearsion/fixtures/dialplan.rb +0 -3
  200. data/spec/adhearsion/foundation/event_socket_spec.rb +0 -168
  201. data/spec/adhearsion/host_definitions_spec.rb +0 -79
  202. data/spec/adhearsion/initializer/configuration_spec.rb +0 -291
  203. data/spec/adhearsion/initializer/loading_spec.rb +0 -154
  204. data/spec/adhearsion/initializer/paths_spec.rb +0 -74
  205. data/spec/adhearsion/relationship_properties_spec.rb +0 -54
  206. data/spec/adhearsion/voip/asterisk/agi_server_spec.rb +0 -473
  207. data/spec/adhearsion/voip/asterisk/ami/ami_spec.rb +0 -550
  208. data/spec/adhearsion/voip/asterisk/ami/lexer/ami_fixtures.yml +0 -30
  209. data/spec/adhearsion/voip/asterisk/ami/lexer/lexer_story +0 -291
  210. data/spec/adhearsion/voip/asterisk/ami/lexer/lexer_story.rb +0 -241
  211. data/spec/adhearsion/voip/asterisk/ami/lexer/story_helper.rb +0 -124
  212. data/spec/adhearsion/voip/asterisk/commands_spec.rb +0 -3241
  213. data/spec/adhearsion/voip/asterisk/config_file_generators/agents_spec.rb +0 -251
  214. data/spec/adhearsion/voip/asterisk/config_file_generators/queues_spec.rb +0 -323
  215. data/spec/adhearsion/voip/asterisk/config_file_generators/voicemail_spec.rb +0 -306
  216. data/spec/adhearsion/voip/asterisk/config_manager_spec.rb +0 -127
  217. data/spec/adhearsion/voip/asterisk/menu_command/calculated_match_spec.rb +0 -109
  218. data/spec/adhearsion/voip/asterisk/menu_command/matchers_spec.rb +0 -97
  219. data/spec/adhearsion/voip/call_routing_spec.rb +0 -125
  220. data/spec/adhearsion/voip/dialplan_manager_spec.rb +0 -468
  221. data/spec/adhearsion/voip/dsl/dialing_dsl_spec.rb +0 -270
  222. data/spec/adhearsion/voip/dsl/dispatcher_spec.rb +0 -82
  223. data/spec/adhearsion/voip/dsl/dispatcher_spec_helper.rb +0 -45
  224. data/spec/adhearsion/voip/dsl/parser_spec.rb +0 -69
  225. data/spec/adhearsion/voip/freeswitch/basic_connection_manager_spec.rb +0 -39
  226. data/spec/adhearsion/voip/freeswitch/inbound_connection_manager_spec.rb +0 -39
  227. data/spec/adhearsion/voip/freeswitch/oes_server_spec.rb +0 -9
  228. data/spec/adhearsion/voip/numerical_string_spec.rb +0 -61
  229. data/spec/adhearsion/voip/phone_number_spec.rb +0 -45
  230. data/spec/support/the_following_code.rb +0 -3
  231. data/spec/theatre/dsl_examples/simple_before_call.rb +0 -7
  232. data/spec/theatre/dsl_spec.rb +0 -69
  233. data/spec/theatre/invocation_spec.rb +0 -182
  234. data/spec/theatre/namespace_spec.rb +0 -125
  235. data/spec/theatre/spec_helper_spec.rb +0 -28
  236. data/spec/theatre/theatre_class_spec.rb +0 -148
@@ -1,240 +0,0 @@
1
- require File.join(File.dirname(__FILE__), 'config_generator')
2
-
3
- module Adhearsion
4
- module VoIP
5
- module Asterisk
6
- module ConfigFileGenerators
7
- class Voicemail < AsteriskConfigGenerator
8
-
9
- DEFAULT_GENERAL_SECTION = {
10
- :format => :wav
11
- }
12
-
13
- # Don't worry. These will be overridable soon.
14
- STATIC_ZONEMESSAGES_CONTEXT = %{
15
- [zonemessages]
16
- eastern=America/New_York|'vm-received' Q 'digits/at' IMp
17
- central=America/Chicago|'vm-received' Q 'digits/at' IMp
18
- central24=America/Chicago|'vm-received' q 'digits/at' H N 'hours'
19
- military=Zulu|'vm-received' q 'digits/at' H N 'hours' 'phonetic/z_p'
20
- european=Europe/Copenhagen|'vm-received' a d b 'digits/at' HM
21
- }.unindent
22
-
23
- attr_reader :properties, :context_definitions
24
- def initialize
25
- @properties = DEFAULT_GENERAL_SECTION.clone
26
- @mailboxes = {}
27
- @context_definitions = []
28
- super
29
- end
30
-
31
- def context(name)
32
- raise ArgumentError, "Name cannot be 'general'!" if name.to_s.downcase == 'general'
33
- raise ArgumentError, "A name can only be characters, numbers, and underscores!" if name.to_s !~ /^[\w_]+$/
34
-
35
- ContextDefinition.new(name).tap do |context_definition|
36
- yield context_definition
37
- context_definitions << context_definition
38
- end
39
- end
40
-
41
- def greeting_maximum(seconds)
42
- int "maxgreet" => seconds
43
- end
44
-
45
- def execute_on_pin_change(command)
46
- string "externpass" => command
47
- end
48
-
49
- def recordings
50
- @recordings ||= RecordingDefinition.new
51
- yield @recordings if block_given?
52
- @recordings
53
- end
54
-
55
- def emails
56
- @emails ||= EmailDefinition.new
57
- if block_given?
58
- yield @emails
59
- else
60
- @emails
61
- end
62
- end
63
-
64
- def to_s
65
- email_properties = @emails ? @emails.properties : {}
66
- AsteriskConfigGenerator.warning_message +
67
- "[general]\n" +
68
- properties.merge(email_properties).map { |(key,value)| "#{key}=#{value}" }.sort.join("\n") + "\n\n" +
69
- STATIC_ZONEMESSAGES_CONTEXT +
70
- context_definitions.map(&:to_s).join("\n\n")
71
- end
72
-
73
- private
74
-
75
- class ContextDefinition < AsteriskConfigGenerator
76
-
77
- attr_reader :mailboxes
78
- def initialize(name)
79
- @name = name
80
- @mailboxes = []
81
- super()
82
- end
83
-
84
- # TODO: This will hold a lot of the methods from the [general] section!
85
-
86
- def to_s
87
- (%W[[#@name]] + mailboxes.map(&:to_s)).join "\n"
88
- end
89
-
90
- def mailbox(mailbox_number)
91
- box = MailboxDefinition.new(mailbox_number)
92
- yield box
93
- mailboxes << box
94
- end
95
-
96
- private
97
-
98
- def mailbox_entry(options)
99
- MailboxDefinition.new.tap do |mailbox|
100
- yield mailbox if block_given?
101
- mailboxes << definition
102
- end
103
- end
104
-
105
- class MailboxDefinition
106
-
107
- attr_reader :mailbox_number
108
- def initialize(mailbox_number)
109
- check_numeric mailbox_number
110
- @mailbox_number = mailbox_number
111
- @definition = {}
112
- super()
113
- end
114
-
115
- def pin_number(number)
116
- check_numeric number
117
- @definition[:pin_number] = number
118
- end
119
-
120
- def name(str)
121
- @definition[:name] = str
122
- end
123
-
124
- def email(str)
125
- @definition[:email] = str
126
- end
127
-
128
- def to_hash
129
- @definition
130
- end
131
-
132
- def to_s
133
- %(#{mailbox_number} => #{@definition[:pin_number]},#{@definition[:name]},#{@definition[:email]})[/^(.+?),*$/,1]
134
- end
135
-
136
- private
137
-
138
- def check_numeric(number)
139
- raise ArgumentError, number.inspect + " is not numeric!" unless number.to_s =~ /^\d+$/
140
- end
141
-
142
- end
143
- end
144
-
145
- class EmailDefinition < AsteriskConfigGenerator
146
- EMAIL_VARIABLE_CONVENIENCES = {
147
- :name => '${VM_NAME}',
148
- :duration => '${VM_DUR}',
149
- :message_number => '${VM_MSGNUM}',
150
- :mailbox => '${VM_MAILBOX}',
151
- :caller_id => '${VM_CALLERID}',
152
- :date => '${VM_DATE}',
153
- :caller_id_number => '${VM_CIDNUM}',
154
- :caller_id_name => '${VM_CIDNAME}'
155
- }
156
-
157
- attr_reader :properties
158
- def initialize
159
- @properties = {}
160
- super
161
- end
162
-
163
- def [](email_variable)
164
- if EMAIL_VARIABLE_CONVENIENCES.has_key? email_variable
165
- EMAIL_VARIABLE_CONVENIENCES[email_variable]
166
- else
167
- raise ArgumentError, "Unrecognized variable #{email_variable.inspect}"
168
- end
169
- end
170
-
171
- def disable!
172
- raise NotImpementedError
173
- end
174
-
175
- def from(options)
176
- name, email = options.values_at :name, :email
177
- string :serveremail => email
178
- string :fromstring => name
179
- end
180
-
181
- def attach_recordings(true_or_false)
182
- boolean :attach => true_or_false
183
- end
184
-
185
- def attach_recordings?
186
- properties[:attach] == 'yes'
187
- end
188
-
189
- def body(str)
190
- str = str.gsub("\r", '').gsub("\n", '\n')
191
- if str.length > 512
192
- raise ArgumentError, "Asterisk has an email body limit of 512 characters! Your body is too long!\n" +
193
- ("-" * 10) + "\n" + str
194
- end
195
- string :emailbody => str
196
- end
197
-
198
- def subject(str)
199
- string :emailsubject => str
200
- end
201
-
202
- def command(cmd)
203
- string :mailcmd => cmd
204
- end
205
-
206
- end
207
-
208
- class RecordingDefinition < AsteriskConfigGenerator
209
-
210
- attr_reader :properties
211
- def initialize
212
- @properties = {}
213
- super
214
- end
215
-
216
- def format(symbol)
217
- one_of [:gsm, :wav49, :wav], :format => symbol
218
- end
219
-
220
- def allowed_length(seconds)
221
- case seconds
222
- when Fixnum
223
- int :maxmessage => "value"
224
- when Range
225
- int :minmessage => seconds.first
226
- int :maxmessage => seconds.last
227
- else
228
- raise ArgumentError, "Argument must be a Fixnum or Range!"
229
- end
230
- end
231
-
232
- def maximum_silence(seconds)
233
- int :maxsilence => seconds
234
- end
235
- end
236
- end
237
- end
238
- end
239
- end
240
- end
@@ -1,64 +0,0 @@
1
- require 'enumerator'
2
- module Adhearsion
3
- module VoIP
4
- module Asterisk
5
- class ConfigurationManager
6
-
7
- class << self
8
- def normalize_configuration(file_contents)
9
- # cat sip.conf | sed -e 's/\s*;.*$//g' | sed -e '/^;.*$/d' | sed -e '/^\s*$/d'
10
- file_contents.split(/\n+/).map do |line|
11
- line.sub(/;.+$/, '').strip
12
- end.join("\n").squeeze("\n")
13
- end
14
- end
15
-
16
- attr_reader :filename
17
-
18
- def initialize(filename)
19
- @filename = filename
20
- end
21
-
22
- def sections
23
- @sections ||= read_configuration
24
- end
25
-
26
- def [](section_name)
27
- result = sections.find { |(name, *rest)| section_name == name }
28
- result.last if result
29
- end
30
-
31
- def delete_section(section_name)
32
- sections.reject! { |(name, *rest)| section_name == name }
33
- end
34
-
35
- def new_section(name, properties={})
36
- sections << [name, properties]
37
- end
38
-
39
- private
40
-
41
- def read_configuration
42
- normalized_file = self.class.normalize_configuration File.open(@filename, 'r'){|f| f.read}
43
- sections = normalized_file.split(/^\[([-_\w]+)\]$/)[1..-1]
44
- return [] if sections.nil?
45
- sections.each_slice(2).map do |(name,properties)|
46
- [name, hash_from_properties(properties)]
47
- end
48
- end
49
-
50
- def hash_from_properties(properties)
51
- properties.split(/\n+/).inject({}) do |property_hash,property|
52
- all, name, value = *property.match(/^\s*([^=]+?)\s*=\s*(.+)\s*$/)
53
- next property_hash unless name && value
54
- property_hash[name] = value
55
- property_hash
56
- end
57
- end
58
- end
59
- end
60
- end
61
- end
62
-
63
- # Read a file: cat a file
64
- # Parse a file: separate into a two dimensional hash
@@ -1,697 +0,0 @@
1
- require 'adhearsion/voip/asterisk/manager_interface/ami_lexer'
2
-
3
- module Adhearsion
4
- module VoIP
5
- module Asterisk
6
-
7
- ##
8
- # Sorry, this AMI class has been deprecated. Please see http://docs.adhearsion.com/Asterisk_Manager_Interface for
9
- # documentation on the new way of handling AMI. This new version is much better and should not require an enormous
10
- # migration on your part.
11
- #
12
- class AMI
13
- def initialize
14
- raise "Sorry, this AMI class has been deprecated. Please see http://docs.adhearsion.com/display/adhearsion/Asterisk+Manager+Interface for documentation on the new way of handling AMI. This new version is much better and should not require an enormous migration on your part."
15
- end
16
- end
17
-
18
- mattr_accessor :manager_interface
19
-
20
- module Manager
21
-
22
- ##
23
- # This class abstracts a connection to the Asterisk Manager Interface. Its purpose is, first and foremost, to make
24
- # the protocol consistent. Though the classes employed to assist this class (ManagerInterfaceAction,
25
- # ManagerInterfaceResponse, ManagerInterfaceError, etc.) are relatively user-friendly, they're designed to be a
26
- # building block on which to build higher-level abstractions of the Asterisk Manager Interface.
27
- #
28
- class ManagerInterface
29
-
30
- CAUSAL_EVENT_NAMES = %w[queuestatus sippeers iaxpeers parkedcalls
31
- dahdishowchannels coreshowchannels dbget
32
- status agents konferencelist] unless defined? CAUSAL_EVENT_NAMES
33
-
34
- RETRY_SLEEP = 5
35
-
36
- class << self
37
-
38
- def connect(*args)
39
- new(*args).tap do |connection|
40
- connection.connect!
41
- end
42
- end
43
-
44
- def replies_with_action_id?(name, headers={})
45
- name = name.to_s.downcase
46
- !UnsupportedActionName::UNSUPPORTED_ACTION_NAMES.include? name
47
- end
48
-
49
- ##
50
- # When sending an action with "causal events" (i.e. events which must be collected to form a proper
51
- # response), AMI should send a particular event which instructs us that no more events will be sent.
52
- # This event is called the "causal event terminator".
53
- #
54
- # Note: you must supply both the name of the event and any headers because it's possible that some uses of an
55
- # action (i.e. same name, different headers) have causal events while other uses don't.
56
- #
57
- # @param [String] name the name of the event
58
- # @param [Hash] the headers associated with this event
59
- # @return [String] the downcase()'d name of the event name for which to wait
60
- #
61
- def has_causal_events?(name, headers={})
62
- CAUSAL_EVENT_NAMES.include? name.to_s.downcase
63
- end
64
-
65
- ##
66
- # Used to determine the event name for an action which has causal events.
67
- #
68
- # @param [String] action_name
69
- # @return [String] The corresponding event name which signals the completion of the causal event sequence.
70
- #
71
- def causal_event_terminator_name_for(action_name)
72
- return nil unless has_causal_events?(action_name)
73
- action_name = action_name.to_s.downcase
74
- case action_name
75
- when "sippeers", "iaxpeers"
76
- "peerlistcomplete"
77
- when "dbget"
78
- "dbgetresponse"
79
- when "konferencelist"
80
- "conferencelistcomplete"
81
- else
82
- action_name + "complete"
83
- end
84
- end
85
-
86
- end
87
-
88
- DEFAULT_SETTINGS = {
89
- :host => "localhost",
90
- :port => 5038,
91
- :username => "admin",
92
- :password => "secret",
93
- :events => true,
94
- :auto_reconnect => true,
95
- :event_callback => proc { |event| Events.trigger(%w[asterisk manager_interface], event) }
96
- }.freeze unless defined? DEFAULT_SETTINGS
97
-
98
- attr_reader *DEFAULT_SETTINGS.keys
99
-
100
- ##
101
- # Creates a new Asterisk Manager Interface connection and exposes certain methods to control it. The constructor
102
- # takes named parameters as Symbols. Note: if the :events option is given, this library will establish a separate
103
- # socket for just events. Two sockets are used because some actions actually respond with events, making it very
104
- # complicated to differentiate between response-type events and normal events.
105
- #
106
- # @param [Hash] options Available options are :host, :port, :username, :password, and :events
107
- #
108
- def initialize(options={})
109
- options = parse_options options
110
- @host = options[:host]
111
- @username = options[:username]
112
- @password = options[:password]
113
- @port = options[:port]
114
- @events = options[:events]
115
- @auto_reconnect = options[:auto_reconnect]
116
- @event_callback = options[:event_callback]
117
-
118
- @sent_messages = {}
119
- @sent_messages_lock = Mutex.new
120
-
121
- @actions_lexer = DelegatingAsteriskManagerInterfaceLexer.new self, \
122
- :message_received => :action_message_received,
123
- :error_received => :action_error_received
124
-
125
- @write_queue = Queue.new
126
-
127
- if @events
128
- @events_lexer = DelegatingAsteriskManagerInterfaceLexer.new self, \
129
- :message_received => :event_message_received,
130
- :error_received => :event_error_received
131
- end
132
- end
133
-
134
- def action_message_received(message)
135
- if message.kind_of? Manager::ManagerInterfaceEvent
136
- # Trigger the return value of the waiting action id...
137
- corresponding_action = @current_action_with_causal_events
138
- event_collection = @event_collection_for_current_action
139
-
140
- if corresponding_action
141
-
142
- # The "DBGet" command is causal, meaning it has an separate
143
- # event that contains the data for command's response. However,
144
- # unlike other causal commands, AMI does not send a
145
- # "DBGetComplete" action indicating the causal event is
146
- # finished. This is fixed starting in Asterisk 1.8.
147
- if message.name.downcase == "dbgetresponse"
148
- event_collection << message
149
- end
150
-
151
- # If this is the meta-event which signals no more events will follow and the response is complete.
152
- if message.name.downcase == corresponding_action.causal_event_terminator_name
153
- # Wake up the waiting Thread
154
- corresponding_action.future_resource.resource = event_collection.freeze
155
-
156
- # Clear the stored action and event collection
157
- @current_action_with_causal_events = nil
158
- @event_collection_for_current_action = nil
159
- else
160
- event_collection << message
161
- # We have more causal events coming.
162
- end
163
- else
164
- ahn_log.ami.error "Got an unexpected event on actions socket! This AMI command may have a multi-message response. Try making Adhearsion treat it as CAUSAL_EVENT #{message.inspect}"
165
- end
166
-
167
- elsif message["ActionID"].nil?
168
- # No ActionID! Release the write lock and wake up the waiter
169
- else
170
- action_id = message["ActionID"]
171
- corresponding_action = data_for_message_received_with_action_id action_id
172
- if corresponding_action
173
- message.action = corresponding_action
174
-
175
- if corresponding_action.has_causal_events?
176
- # By this point the write loop will already have started blocking by calling the response() method on the
177
- # action. Because we must collect more events before we wake the write loop up again, let's create these
178
- # instance variable which will needed when the subsequent causal events come in.
179
- @current_action_with_causal_events = corresponding_action
180
- @event_collection_for_current_action = []
181
- else
182
- # Wake any Threads waiting on the response.
183
- corresponding_action.future_resource.resource = message
184
- end
185
- else
186
- ahn_log.ami.error "Received an AMI message with an unrecognized ActionID!! This may be an bug! #{message.inspect}"
187
- end
188
- end
189
- end
190
-
191
- def action_error_received(ami_error)
192
- action_id = ami_error["ActionID"]
193
-
194
- corresponding_action = data_for_message_received_with_action_id action_id
195
-
196
- if corresponding_action
197
- corresponding_action.future_resource.resource = ami_error
198
- else
199
- ahn_log.ami.error "Received an AMI error with an unrecognized ActionID!! This may be an bug! #{ami_error.inspect}"
200
- end
201
- end
202
-
203
- ##
204
- # Called only when this ManagerInterface is instantiated with events enabled.
205
- #
206
- def event_message_received(event)
207
- return if event.kind_of?(ManagerInterfaceResponse) && event["Message"] == "Authentication accepted"
208
- # TODO: convert the event name to a certain namespace.
209
- @event_callback.call(event)
210
- end
211
-
212
- def event_error_received(message)
213
- # Does this ever even occur?
214
- ahn_log.ami.error "Hmmm, got an error on the AMI events-only socket! This must be a bug! #{message.inspect}"
215
- end
216
-
217
- ##
218
- # Called when our Ragel parser encounters some unexpected syntax from Asterisk. Anytime this is called, it should
219
- # be considered a bug in Adhearsion. Note: this same method is called regardless of whether the syntax error
220
- # happened on the actions socket or on the events socket.
221
- #
222
- def syntax_error_encountered(ignored_chunk)
223
- ahn_log.ami.error "ADHEARSION'S AMI PARSER ENCOUNTERED A SYNTAX ERROR! " +
224
- "PLEASE REPORT THIS ON http://bugs.adhearsion.com! OFFENDING TEXT:\n#{ignored_chunk.inspect}"
225
- end
226
-
227
- ##
228
- # Must be called after instantiation. Also see ManagerInterface::connect().
229
- #
230
- # @raise [AuthenticationFailedException] if username or password are rejected
231
- #
232
- def connect!
233
- establish_actions_connection
234
- establish_events_connection if @events
235
- self
236
- end
237
-
238
- def actions_connection_established
239
- @actions_state = :connected
240
- start_actions_writer_loop
241
- end
242
-
243
- def actions_connection_disconnected
244
- @actions_state = :disconnected
245
- ahn_log.ami.error "AMI connection for ACTION disconnected !!!"
246
- clear_actions_connection
247
- establish_actions_connection if @auto_reconnect
248
- end
249
-
250
- def events_connection_established
251
- @events_state = :connected
252
- end
253
-
254
- def events_connection_disconnected
255
- @events_state = :disconnected
256
- ahn_log.ami.error "AMI connection for EVENT disconnected !!!"
257
- clear_events_connection
258
- establish_events_connection if @auto_reconnect
259
- end
260
-
261
- def clear_actions_connection
262
- stop_actions_writer_loop
263
- clear_actions_connection_resources
264
- disconnect_actions_connection if @actions_state.equal? :connected
265
- end
266
-
267
- def clear_events_connection
268
- disconnect_events_connection if @events_state.equal? :connected
269
- end
270
-
271
- def disconnect!
272
- clear_actions_connection
273
- clear_events_connection
274
- end
275
-
276
- def dynamic
277
- # TODO: Return an object which responds to method_missing
278
- end
279
-
280
- ##
281
- # Used to directly send a new action to Asterisk. Note: NEVER supply an ActionID; these are handled internally.
282
- #
283
- # @param [String, Symbol] action_name The name of the action (e.g. Originate)
284
- # @param [Hash] headers Other key/value pairs to send in this action. Note: don't provide an ActionID
285
- # @return [FutureResource] Call resource() on this object if you wish to access the response (optional). Note: if the response has not come in yet, your Thread will wait until it does.
286
- # @deprecated Async AMI is deprecated
287
- #
288
- def send_action_asynchronously(action_name, headers={})
289
- ahn_log.ami.deprecation.warn <<WARN
290
- \n
291
- Asterisk only supports one outstanding action ID at a time, making the
292
- asynchronous AMI interface dangerous to use. This interface is now
293
- deprecated and will disappear in future versions of Adhearsion. If you need
294
- to do background processing while Asterisk processes a long-running action
295
- (such as a synchronous Originate) then you will need to handle this manually
296
- with Ruby threads. Note that Originate specifically has an :Async header
297
- which tells Asterisk to process the Originate event asynchronously.
298
- WARN
299
- _send_action_asynchronously(action_name, headers)
300
- end
301
-
302
- ##
303
- # Sends an action over the AMI connection and blocks your Thread until the response comes in. If there was an error
304
- # for some reason, the error will be raised as an ManagerInterfaceError.
305
- #
306
- # @param [String, Symbol] action_name The name of the action (e.g. Originate)
307
- # @param [Hash] headers Other key/value pairs to send in this action. Note: don't provide an ActionID
308
- # @raise [ManagerInterfaceError] When Asterisk can't execute this action, it sends back an Error which is converted into an ManagerInterfaceError object and raised. Access ManagerInterfaceError#message for the reported message from Asterisk.
309
- # @return [ManagerInterfaceResponse, ImmediateResponse] Contains the response from Asterisk and all headers
310
- #
311
- def send_action_synchronously(*args)
312
- _send_action_asynchronously(*args).response.tap do |response|
313
- raise response if response.kind_of?(ManagerInterfaceError)
314
- end
315
- end
316
-
317
- alias send_action send_action_synchronously
318
-
319
- # ping sends an action to the Asterisk Manager Interface that returns a pong
320
- # more details here: http://www.voip-info.org/wiki/index.php?page=Asterisk+Manager+API+Action+Ping
321
- def ping
322
- send_action "Ping"
323
- true
324
- end
325
-
326
- # The originate method launches a call to Asterisk, full details here:
327
- # http://www.voip-info.org/tiki-index.php?page=Asterisk+Manager+API+Action+Originate
328
- # Takes these arguments as a hash:
329
- #
330
- # Channel: Channel on which to originate the call (The same as you specify in the Dial application command)
331
- # Context: Context to use on connect (must use Exten & Priority with it)
332
- # Exten: Extension to use on connect (must use Context & Priority with it)
333
- # Priority: Priority to use on connect (must use Context & Exten with it)
334
- # Timeout: Timeout (in milliseconds) for the originating connection to happen(defaults to 30000 milliseconds)
335
- # CallerID: CallerID to use for the call
336
- # Variable: Channels variables to set (max 32). Variables will be set for both channels (local and connected).
337
- # Account: Account code for the call
338
- # Application: Application to use on connect (use Data for parameters)
339
- # Data : Data if Application parameter is used
340
- # Async: For the origination to be asynchronous (allows multiple calls to be generated without waiting for a response)
341
- # ActionID: The request identifier. It allows you to identify the response to this request.
342
- # You may use a number or a string. Useful when you make several simultaneous requests.
343
- #
344
- # For example:
345
- # originate { :channel => 'SIP/1000@sipnetworks.com',
346
- # :context => 'my_context',
347
- # :exten => 's',
348
- # :priority => '1' }
349
- def originate(options={})
350
- options = options.clone
351
- options[:callerid] = options.delete :caller_id if options.has_key? :caller_id
352
- options[:exten] = options.delete :extension if options.has_key? :extension
353
- if options.has_key?(:variables) && options[:variables].kind_of?(Hash)
354
- options[:variable] = options.delete(:variables).map {|pair| pair.join('=')}.join(@coreSettings["ArgumentDelimiter"])
355
- end
356
- send_action "Originate", options
357
- end
358
-
359
- # An introduction connects two endpoints together. The first argument is
360
- # the first person the PBX will call. When she's picked up, Asterisk will
361
- # play ringing while the second person is being dialed.
362
- #
363
- # The first argument is the person called first. Pass this as a canonical
364
- # IAX2/server/user type argument. Destination takes the same format, but
365
- # comma-separated Dial() arguments can be optionally passed after the
366
- # technology.
367
- #
368
- # TODO: Provide an example when this works.
369
- #
370
- def introduce(caller, callee, opts={})
371
- dial_args = callee
372
- dial_args += "|#{opts[:options]}" if opts[:options]
373
- call_and_exec caller, "Dial", :args => dial_args, :caller_id => opts[:caller_id]
374
- end
375
-
376
- # hangup terminates a call accepts a channel as the argument
377
- # full details here: http://www.voip-info.org/wiki/index.php?page=Asterisk+Manager+API+Action+Hangup
378
- def hangup(channel)
379
- send_action "Hangup", :channel => channel
380
- end
381
-
382
- # call_and_exec allows you to make a call to a channel and then execute an Astersik application
383
- # on that call
384
- def call_and_exec(channel, app, opts={})
385
- args = { :channel => channel, :application => app }
386
- args[:caller_id] = opts[:caller_id] if opts[:caller_id]
387
- args[:data] = opts[:args] if opts[:args]
388
- args[:variables] = opts[:variables] if opts[:variables]
389
- originate args
390
- end
391
-
392
- # call_into_context is syntactic sugar for the Asterisk originate command that allows you to
393
- # launch a call into a particular context. For example:
394
- #
395
- # call_into_context('SIP/1000@sipnetworks.com', 'my_context', { :variables => { :session_guid => new_guid }})
396
- def call_into_context(channel, context, options={})
397
- args = {:channel => channel, :context => context}
398
- args[:priority] = options[:priority] || 1
399
- args[:exten] = options[:extension] if options[:extension]
400
- args[:caller_id] = options[:caller_id] if options[:caller_id]
401
- args[:variables] = options[:variables] if options[:variables]
402
- originate args
403
- end
404
-
405
- private
406
- def _send_action_asynchronously(action_name, headers={})
407
- check_action_name action_name
408
- action = ManagerInterfaceAction.new(action_name, headers)
409
- if action.replies_with_action_id?
410
- @write_queue << action
411
- action
412
- else
413
- raise NotImplementedError
414
- end
415
- end
416
-
417
-
418
-
419
- protected
420
-
421
- ##
422
- # This class will be removed once this AMI library fully supports all known protocol anomalies.
423
- #
424
- class UnsupportedActionName < ArgumentError
425
- UNSUPPORTED_ACTION_NAMES = %w[
426
- queues
427
- ] unless defined? UNSUPPORTED_ACTION_NAMES
428
-
429
- # Blacklist some actions depends on the Asterisk version
430
- def self.preinitialize(version)
431
- if version < 1.8
432
- %w[iaxpeers muteaudio mixmonitormute aocmessage].each do |action|
433
- UNSUPPORTED_ACTION_NAMES << action
434
- end
435
- end
436
-
437
- if version < 1.6
438
- %w[skinnydevices skinnyshowdevice skinnylines skinnyshowline coreshowchannels
439
- sipshowregistry getconfigjson bridge listallvoicemailusers dbdel dbdeltree
440
- insert jitterbufstats atxfer iaxregistry queuereload queuereset].each do |action|
441
- UNSUPPORTED_ACTION_NAMES << action
442
- end
443
- end
444
- end
445
-
446
- def initialize(name)
447
- super "At the moment this AMI library doesn't support the #{name.inspect} action because it causes a protocol anomaly. Support for it will be coming shortly."
448
- end
449
-
450
- end
451
-
452
- def check_action_name(name)
453
- name = name.to_s.downcase
454
- raise UnsupportedActionName.new(name) if UnsupportedActionName::UNSUPPORTED_ACTION_NAMES.include? name
455
- true
456
- end
457
-
458
- def start_actions_writer_loop
459
- @actions_writer_thread = Thread.new do
460
- begin
461
- actions_writer_loop
462
- rescue => e
463
- Events.trigger(['exception'], e)
464
- end
465
- end
466
- end
467
-
468
- def stop_actions_writer_loop
469
- if @actions_writer_thread
470
- @write_queue << :STOP!
471
- @actions_writer_thread.join
472
- @actions_writer_thread = nil
473
- end
474
- end
475
-
476
- def actions_writer_loop
477
- loop do
478
- begin
479
- next_action = @write_queue.shift
480
- return :stopped if next_action.equal? :STOP!
481
- register_action_with_metadata next_action
482
-
483
- ahn_log.ami.debug "Sending AMI action: #{"\n>>> " + next_action.to_s.gsub(/(\r\n)+/, "\n>>> ")}"
484
- @actions_connection.send_data next_action.to_s
485
- # If it's "causal event" action, we must wait here until it's fully responded
486
- next_action.response if next_action.has_causal_events?
487
- rescue Object => e
488
- ahn_log.ami.debug "Error in AMI writer loop: #{e.class}: #{e.message}\n\t#{e.backtrace.join("\n\t")}"
489
- end
490
- end
491
- end
492
-
493
- ##
494
- # When we send out an AMI action, we need to track the ActionID and have the other Thread handling the socket IO
495
- # notify the sending Thread that a response has been received. This method instantiates a new FutureResource and
496
- # keeps it around in a synchronized Hash for the IO-handling Thread to notify when a response with a matching
497
- # ActionID is seen again. See also data_for_message_received_with_action_id() which is how the IO-handling Thread
498
- # gets the metadata registered in the method back later.
499
- #
500
- # @param [ManagerInterfaceAction] action The ManagerInterfaceAction to send
501
- # @param [Hash] headers The other key/value pairs being sent with this message
502
- #
503
- def register_action_with_metadata(action)
504
- raise ArgumentError, "Must supply an action!" if action.nil?
505
- @sent_messages_lock.synchronize do
506
- @sent_messages[action.action_id] = action
507
- end
508
- end
509
-
510
- def data_for_message_received_with_action_id(action_id)
511
- @sent_messages_lock.synchronize do
512
- @sent_messages.delete action_id
513
- end
514
- end
515
-
516
- # Give an error response to any outstanding messages -- they
517
- # won't be completed now
518
- def clear_actions_connection_resources
519
- # Fail all outstanding messages and reset the message list
520
- @sent_messages_lock.synchronize do
521
- @sent_messages.each do |action_id, action|
522
- error = ManagerInterfaceError.new
523
- error.message = "Connection terminated to AMI server"
524
-
525
- action.future_resource.resource = error
526
- end
527
-
528
- @sent_messages = {}
529
- end
530
- end
531
-
532
- ##
533
- # Instantiates a new ManagerInterfaceActionsConnection and assigns it to @actions_connection.
534
- #
535
- # @return [EventSocket]
536
- #
537
- def establish_actions_connection
538
- @actions_connection = EventSocket.connect(@host, @port) do |handler|
539
- handler.receive_data { |data| @actions_lexer << data }
540
- handler.connected { actions_connection_established }
541
- handler.disconnected { actions_connection_disconnected }
542
- end
543
- login_actions
544
- rescue Errno::ECONNREFUSED => e
545
- ahn_log.ami.warn "ACTIONS thread connection refused! Retrying in #{RETRY_SLEEP} seconds..."
546
- sleep RETRY_SLEEP
547
- retry
548
- end
549
-
550
- def disconnect_actions_connection
551
- # Clean up the EventSocket we may have
552
- if @actions_connection
553
- @actions_connection.disconnect!
554
- @actions_connection.join
555
- @actions_connection = nil
556
- end
557
- end
558
-
559
- ##
560
- # Instantiates a new ManagerInterfaceEventsConnection and assigns it to @events_connection.
561
- #
562
- # @return [EventSocket]
563
- #
564
- def establish_events_connection
565
-
566
- # Note: the @events_connection instance variable is set in login()
567
- @events_connection = EventSocket.connect(@host, @port) do |handler|
568
- handler.receive_data { |data| @events_lexer << data }
569
- handler.connected { events_connection_established }
570
- handler.disconnected { events_connection_disconnected }
571
- end
572
- login_events
573
- ahn_log.ami "Successful AMI events-only connection into #{@username}@#{@host}"
574
- rescue Errno::ECONNREFUSED => e
575
- ahn_log.ami.warn "EVENTS thread connection refused! Retrying in #{RETRY_SLEEP} seconds..."
576
- sleep RETRY_SLEEP
577
- retry
578
- end
579
-
580
- def login_actions
581
- response = send_action "Login", "Username" => @username, "Secret" => @password, "Events" => "Off"
582
- ahn_log.ami "Successful AMI actions-only connection into #{@username}@#{@host}"
583
- if @actions_lexer.ami_version < 1.1
584
- @coreSettings = Hash.new
585
- @coreSettings["AsteriskVersion"] = "1.4.0"
586
- @coreSettings["AMIversion"] = "1.0"
587
- @coreSettings["ArgumentDelimiter"] = "|"
588
- else
589
- @coreSettings = send_action_synchronously("CoreSettings").headers
590
- @coreSettings["ArgumentDelimiter"] = ","
591
- end
592
- UnsupportedActionName::preinitialize(@coreSettings["AsteriskVersion"].to_f)
593
- response
594
- rescue ManagerInterfaceError => e
595
- raise AuthenticationFailedException, "Incorrect username and password! #{e.message}"
596
- end
597
-
598
- def disconnect_events_connection
599
- # Clean up the EventSocket we may have
600
- if @events_connection
601
- @events_connection.disconnect!
602
- @events_connection.join
603
- @events_connection = nil
604
- end
605
- end
606
-
607
- ##
608
- # Since this method is always called after the login_actions method, an AuthenticationFailedException would have already
609
- # been raised if the username/password were off. Because this is the only action we ever need to send on this socket,
610
- # it goes straight to the EventSocket connection (bypassing the @write_queue).
611
- #
612
- def login_events
613
- login_action = ManagerInterfaceAction.new "Login", "Username" => @username, "Secret" => @password, "Events" => "On"
614
- @events_connection.send_data login_action.to_s
615
- end
616
-
617
- def parse_options(options)
618
- unrecognized_keys = options.keys.map { |key| key.to_sym } - DEFAULT_SETTINGS.keys
619
- if unrecognized_keys.any?
620
- raise ArgumentError, "Unrecognized named argument(s): #{unrecognized_keys.to_sentence}"
621
- end
622
- DEFAULT_SETTINGS.merge options
623
- end
624
-
625
- ##
626
- # Raised when calling ManagerInterface#connect!() and the server responds with an error after logging in.
627
- #
628
- class AuthenticationFailedException < StandardError; end
629
-
630
- class NotConnectedError < StandardError; end
631
-
632
- ##
633
- # Each time ManagerInterface#send_action is invoked, a new ManagerInterfaceAction is instantiated.
634
- #
635
- class ManagerInterfaceAction
636
-
637
- attr_reader :name, :headers, :future_resource, :action_id, :causal_event_terminator_name
638
- def initialize(name, headers={})
639
- @name = name.to_s.downcase.freeze
640
- @headers = headers.stringify_keys.freeze
641
- @action_id = new_action_id.freeze
642
- @future_resource = FutureResource.new
643
- @causal_event_terminator_name = ManagerInterface.causal_event_terminator_name_for name
644
- end
645
-
646
- ##
647
- # Used internally by ManagerInterface for the actions in AMI which break the protocol's definition and do not
648
- # reply with an ActionID.
649
- #
650
- def replies_with_action_id?
651
- ManagerInterface.replies_with_action_id?(@name, @headers)
652
- end
653
-
654
- ##
655
- # Some AMI actions effectively respond with many events which collectively constitute the actual response. These
656
- # Must be handled specially by the protocol parser, so this method helps inform the parser.
657
- #
658
- def has_causal_events?
659
- ManagerInterface.has_causal_events?(@name, @headers)
660
- end
661
-
662
- ##
663
- # Abstracts the generation of new ActionIDs. This could be implemented virutally any way, provided each
664
- # invocation returns something unique, so this will generate a GUID and return it.
665
- #
666
- # @return [String] characters in GUID format (e.g. "4C5F4E1C-A0F1-4D13-8751-C62F2F783062")
667
- #
668
- def new_action_id
669
- new_guid # Implemented in lib/adhearsion/foundation/pseudo_guid.rb
670
- end
671
-
672
- ##
673
- # Converts this action into a protocol-valid String, ready to be sent over a socket.
674
- #
675
- def to_s
676
- @textual_representation ||= (
677
- "Action: #{@name}\r\nActionID: #{@action_id}\r\n" +
678
- @headers.map { |(key,value)| "#{key}: #{value}" }.join("\r\n") +
679
- (@headers.any? ? "\r\n\r\n" : "\r\n")
680
- )
681
- end
682
-
683
- ##
684
- # If the response has simply not been received yet from Asterisk, the calling Thread will block until it comes
685
- # in. Once the response comes in, subsequent calls immediately return a reference to the ManagerInterfaceResponse
686
- # object.
687
- #
688
- def response
689
- future_resource.resource
690
- end
691
-
692
- end
693
- end
694
- end
695
- end
696
- end
697
- end