adhearsion 1.2.6 → 2.0.0.alpha1

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 (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