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.
- data/.gitignore +17 -10
- data/CHANGELOG.md +273 -0
- data/Gemfile +1 -1
- data/Guardfile +17 -0
- data/README.markdown +61 -9
- data/Rakefile +16 -48
- data/adhearsion.gemspec +21 -7
- data/bin/ahn +3 -1
- data/cucumber.yml +4 -0
- data/features/app_generator.feature +42 -0
- data/features/cli.feature +108 -0
- data/features/step_definitions/app_generator_steps.rb +6 -0
- data/features/step_definitions/cli_steps.rb +74 -0
- data/features/support/aruba_helper.rb +22 -0
- data/features/support/env.rb +37 -0
- data/features/support/utils.rb +8 -0
- data/lib/adhearsion.rb +85 -41
- data/lib/adhearsion/call.rb +176 -0
- data/lib/adhearsion/call_controller.rb +134 -0
- data/lib/adhearsion/call_controller/dial.rb +70 -0
- data/lib/adhearsion/call_controller/input.rb +173 -0
- data/lib/adhearsion/call_controller/menu.rb +124 -0
- data/lib/adhearsion/call_controller/output.rb +267 -0
- data/lib/adhearsion/call_controller/record.rb +42 -0
- data/lib/adhearsion/call_controller/utility.rb +60 -0
- data/lib/adhearsion/calls.rb +81 -0
- data/lib/adhearsion/cli.rb +1 -3
- data/lib/adhearsion/cli_commands.rb +142 -0
- data/lib/adhearsion/configuration.rb +149 -0
- data/lib/adhearsion/console.rb +19 -8
- data/lib/adhearsion/dialplan_controller.rb +9 -0
- data/lib/adhearsion/events.rb +84 -0
- data/lib/adhearsion/foundation/all.rb +0 -7
- data/lib/adhearsion/foundation/custom_daemonizer.rb +4 -6
- data/lib/adhearsion/foundation/exception_handler.rb +9 -0
- data/lib/adhearsion/foundation/object.rb +26 -8
- data/lib/adhearsion/foundation/synchronized_hash.rb +3 -6
- data/lib/adhearsion/foundation/thread_safety.rb +17 -1
- data/lib/adhearsion/generators/app/app_generator.rb +4 -13
- data/lib/adhearsion/generators/app/templates/Gemfile +10 -5
- data/lib/adhearsion/generators/app/templates/Procfile +1 -0
- data/lib/adhearsion/generators/app/templates/README.md +28 -0
- data/lib/adhearsion/generators/app/templates/config/adhearsion.rb +41 -0
- data/lib/adhearsion/generators/app/templates/{components/simon_game → lib}/simon_game.rb +6 -18
- data/lib/adhearsion/generators/app/templates/script/ahn +2 -2
- data/lib/adhearsion/initializer.rb +151 -293
- data/lib/adhearsion/initializer/logging.rb +33 -0
- data/lib/adhearsion/logging.rb +65 -69
- data/lib/adhearsion/menu_dsl.rb +15 -0
- data/lib/adhearsion/menu_dsl/calculated_match.rb +39 -0
- data/lib/adhearsion/menu_dsl/calculated_match_collection.rb +41 -0
- data/lib/adhearsion/menu_dsl/fixnum_match_calculator.rb +18 -0
- data/lib/adhearsion/menu_dsl/match_calculator.rb +36 -0
- data/lib/adhearsion/{voip/menu_state_machine/menu_class.rb → menu_dsl/menu.rb} +38 -40
- data/lib/adhearsion/menu_dsl/menu_builder.rb +69 -0
- data/lib/adhearsion/menu_dsl/range_match_calculator.rb +55 -0
- data/lib/adhearsion/menu_dsl/string_match_calculator.rb +21 -0
- data/lib/adhearsion/outbound_call.rb +64 -0
- data/lib/adhearsion/plugin.rb +319 -0
- data/lib/adhearsion/plugin/collection.rb +19 -0
- data/lib/adhearsion/plugin/initializer.rb +37 -0
- data/lib/adhearsion/plugin/methods_container.rb +6 -0
- data/lib/adhearsion/process.rb +94 -0
- data/lib/adhearsion/punchblock_plugin.rb +29 -0
- data/lib/adhearsion/punchblock_plugin/initializer.rb +137 -0
- data/lib/adhearsion/router.rb +30 -0
- data/lib/adhearsion/router/route.rb +42 -0
- data/lib/adhearsion/script_ahn_loader.rb +2 -2
- data/lib/adhearsion/tasks.rb +14 -9
- data/lib/adhearsion/tasks/configuration.rb +26 -0
- data/lib/adhearsion/tasks/plugins.rb +17 -0
- data/lib/adhearsion/version.rb +8 -14
- data/spec/adhearsion/call_controller/dial_spec.rb +138 -0
- data/spec/adhearsion/call_controller/input_spec.rb +278 -0
- data/spec/adhearsion/call_controller/menu_spec.rb +120 -0
- data/spec/adhearsion/call_controller/output_spec.rb +466 -0
- data/spec/adhearsion/call_controller/record_spec.rb +125 -0
- data/spec/adhearsion/call_controller_spec.rb +395 -0
- data/spec/adhearsion/call_spec.rb +438 -0
- data/spec/adhearsion/calls_spec.rb +47 -0
- data/spec/adhearsion/configuration_spec.rb +308 -0
- data/spec/adhearsion/dialplan_controller_spec.rb +26 -0
- data/spec/adhearsion/events_spec.rb +112 -0
- data/spec/adhearsion/initializer/logging_spec.rb +58 -0
- data/spec/adhearsion/initializer_spec.rb +209 -122
- data/spec/adhearsion/logging_spec.rb +58 -47
- data/spec/adhearsion/menu_dsl/calculated_match_collection_spec.rb +56 -0
- data/spec/adhearsion/menu_dsl/calculated_match_spec.rb +57 -0
- data/spec/adhearsion/menu_dsl/fixnum_match_calculator_spec.rb +33 -0
- data/spec/adhearsion/menu_dsl/match_calculator_spec.rb +13 -0
- data/spec/adhearsion/menu_dsl/menu_builder_spec.rb +118 -0
- data/spec/adhearsion/menu_dsl/menu_spec.rb +210 -0
- data/spec/adhearsion/menu_dsl/range_match_calculator_spec.rb +28 -0
- data/spec/adhearsion/menu_dsl/string_match_calculator_spec.rb +36 -0
- data/spec/adhearsion/menu_dsl_spec.rb +12 -0
- data/spec/adhearsion/outbound_call_spec.rb +174 -0
- data/spec/adhearsion/plugin_spec.rb +489 -0
- data/spec/adhearsion/process_spec.rb +34 -0
- data/spec/adhearsion/punchblock_plugin/initializer_spec.rb +294 -0
- data/spec/adhearsion/router/route_spec.rb +99 -0
- data/spec/adhearsion/router_spec.rb +106 -0
- data/spec/adhearsion_spec.rb +46 -0
- data/spec/spec_helper.rb +14 -14
- data/spec/support/call_controller_test_helpers.rb +48 -0
- data/spec/support/initializer_stubs.rb +8 -13
- data/spec/support/punchblock_mocks.rb +6 -0
- metadata +255 -253
- data/CHANGELOG +0 -174
- data/bin/ahnctl +0 -68
- data/bin/jahn +0 -43
- data/examples/asterisk_manager_interface/standalone.rb +0 -51
- data/lib/adhearsion/commands.rb +0 -302
- data/lib/adhearsion/component_manager.rb +0 -278
- data/lib/adhearsion/component_manager/component_tester.rb +0 -54
- data/lib/adhearsion/component_manager/spec_framework.rb +0 -18
- data/lib/adhearsion/events_support.rb +0 -65
- data/lib/adhearsion/foundation/blank_slate.rb +0 -3
- data/lib/adhearsion/foundation/event_socket.rb +0 -205
- data/lib/adhearsion/foundation/future_resource.rb +0 -36
- data/lib/adhearsion/foundation/metaprogramming.rb +0 -17
- data/lib/adhearsion/foundation/numeric.rb +0 -13
- data/lib/adhearsion/foundation/pseudo_guid.rb +0 -10
- data/lib/adhearsion/foundation/relationship_properties.rb +0 -42
- data/lib/adhearsion/foundation/string.rb +0 -26
- data/lib/adhearsion/generators/app/templates/.ahnrc +0 -34
- data/lib/adhearsion/generators/app/templates/README +0 -8
- data/lib/adhearsion/generators/app/templates/components/ami_remote/ami_remote.rb +0 -15
- data/lib/adhearsion/generators/app/templates/components/disabled/HOW_TO_ENABLE +0 -7
- data/lib/adhearsion/generators/app/templates/components/disabled/stomp_gateway/README.markdown +0 -47
- data/lib/adhearsion/generators/app/templates/components/disabled/stomp_gateway/stomp_gateway.rb +0 -34
- data/lib/adhearsion/generators/app/templates/components/disabled/stomp_gateway/stomp_gateway.yml +0 -12
- data/lib/adhearsion/generators/app/templates/components/disabled/xmpp_gateway/README.markdown +0 -3
- data/lib/adhearsion/generators/app/templates/components/disabled/xmpp_gateway/xmpp_gateway.rb +0 -11
- data/lib/adhearsion/generators/app/templates/components/disabled/xmpp_gateway/xmpp_gateway.yml +0 -0
- data/lib/adhearsion/generators/app/templates/config/startup.rb +0 -81
- data/lib/adhearsion/generators/app/templates/dialplan.rb +0 -3
- data/lib/adhearsion/generators/app/templates/events.rb +0 -33
- data/lib/adhearsion/host_definitions.rb +0 -67
- data/lib/adhearsion/initializer/asterisk.rb +0 -86
- data/lib/adhearsion/initializer/configuration.rb +0 -324
- data/lib/adhearsion/initializer/database.rb +0 -60
- data/lib/adhearsion/initializer/drb.rb +0 -31
- data/lib/adhearsion/initializer/freeswitch.rb +0 -22
- data/lib/adhearsion/initializer/ldap.rb +0 -57
- data/lib/adhearsion/initializer/rails.rb +0 -41
- data/lib/adhearsion/initializer/xmpp.rb +0 -42
- data/lib/adhearsion/tasks/components.rb +0 -32
- data/lib/adhearsion/tasks/database.rb +0 -5
- data/lib/adhearsion/tasks/deprecations.rb +0 -59
- data/lib/adhearsion/tasks/generating.rb +0 -20
- data/lib/adhearsion/tasks/lint.rb +0 -4
- data/lib/adhearsion/voip/asterisk.rb +0 -4
- data/lib/adhearsion/voip/asterisk/agi_server.rb +0 -121
- data/lib/adhearsion/voip/asterisk/commands.rb +0 -1966
- data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +0 -140
- data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +0 -102
- data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +0 -250
- data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +0 -240
- data/lib/adhearsion/voip/asterisk/config_manager.rb +0 -64
- data/lib/adhearsion/voip/asterisk/manager_interface.rb +0 -697
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +0 -1681
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +0 -341
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +0 -78
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +0 -87
- data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +0 -80
- data/lib/adhearsion/voip/call.rb +0 -521
- data/lib/adhearsion/voip/call_routing.rb +0 -64
- data/lib/adhearsion/voip/commands.rb +0 -17
- data/lib/adhearsion/voip/constants.rb +0 -39
- data/lib/adhearsion/voip/conveniences.rb +0 -18
- data/lib/adhearsion/voip/dial_plan.rb +0 -252
- data/lib/adhearsion/voip/dsl/dialing_dsl.rb +0 -151
- data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +0 -37
- data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +0 -27
- data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +0 -124
- data/lib/adhearsion/voip/dsl/dialplan/parser.rb +0 -69
- data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +0 -16
- data/lib/adhearsion/voip/dsl/numerical_string.rb +0 -128
- data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +0 -48
- data/lib/adhearsion/voip/freeswitch/event_handler.rb +0 -58
- data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +0 -129
- data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +0 -38
- data/lib/adhearsion/voip/freeswitch/oes_server.rb +0 -195
- data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +0 -80
- data/lib/adhearsion/voip/menu_state_machine/matchers.rb +0 -123
- data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +0 -57
- data/lib/adhearsion/xmpp/connection.rb +0 -61
- data/lib/theatre.rb +0 -147
- data/lib/theatre/README.markdown +0 -64
- data/lib/theatre/callback_definition_loader.rb +0 -86
- data/lib/theatre/guid.rb +0 -23
- data/lib/theatre/invocation.rb +0 -131
- data/lib/theatre/namespace_manager.rb +0 -153
- data/lib/theatre/version.rb +0 -2
- data/spec/adhearsion/cli_spec.rb +0 -306
- data/spec/adhearsion/component_manager_spec.rb +0 -292
- data/spec/adhearsion/constants_spec.rb +0 -8
- data/spec/adhearsion/drb_spec.rb +0 -65
- data/spec/adhearsion/fixtures/dialplan.rb +0 -3
- data/spec/adhearsion/foundation/event_socket_spec.rb +0 -168
- data/spec/adhearsion/host_definitions_spec.rb +0 -79
- data/spec/adhearsion/initializer/configuration_spec.rb +0 -291
- data/spec/adhearsion/initializer/loading_spec.rb +0 -154
- data/spec/adhearsion/initializer/paths_spec.rb +0 -74
- data/spec/adhearsion/relationship_properties_spec.rb +0 -54
- data/spec/adhearsion/voip/asterisk/agi_server_spec.rb +0 -473
- data/spec/adhearsion/voip/asterisk/ami/ami_spec.rb +0 -550
- data/spec/adhearsion/voip/asterisk/ami/lexer/ami_fixtures.yml +0 -30
- data/spec/adhearsion/voip/asterisk/ami/lexer/lexer_story +0 -291
- data/spec/adhearsion/voip/asterisk/ami/lexer/lexer_story.rb +0 -241
- data/spec/adhearsion/voip/asterisk/ami/lexer/story_helper.rb +0 -124
- data/spec/adhearsion/voip/asterisk/commands_spec.rb +0 -3241
- data/spec/adhearsion/voip/asterisk/config_file_generators/agents_spec.rb +0 -251
- data/spec/adhearsion/voip/asterisk/config_file_generators/queues_spec.rb +0 -323
- data/spec/adhearsion/voip/asterisk/config_file_generators/voicemail_spec.rb +0 -306
- data/spec/adhearsion/voip/asterisk/config_manager_spec.rb +0 -127
- data/spec/adhearsion/voip/asterisk/menu_command/calculated_match_spec.rb +0 -109
- data/spec/adhearsion/voip/asterisk/menu_command/matchers_spec.rb +0 -97
- data/spec/adhearsion/voip/call_routing_spec.rb +0 -125
- data/spec/adhearsion/voip/dialplan_manager_spec.rb +0 -468
- data/spec/adhearsion/voip/dsl/dialing_dsl_spec.rb +0 -270
- data/spec/adhearsion/voip/dsl/dispatcher_spec.rb +0 -82
- data/spec/adhearsion/voip/dsl/dispatcher_spec_helper.rb +0 -45
- data/spec/adhearsion/voip/dsl/parser_spec.rb +0 -69
- data/spec/adhearsion/voip/freeswitch/basic_connection_manager_spec.rb +0 -39
- data/spec/adhearsion/voip/freeswitch/inbound_connection_manager_spec.rb +0 -39
- data/spec/adhearsion/voip/freeswitch/oes_server_spec.rb +0 -9
- data/spec/adhearsion/voip/numerical_string_spec.rb +0 -61
- data/spec/adhearsion/voip/phone_number_spec.rb +0 -45
- data/spec/support/the_following_code.rb +0 -3
- data/spec/theatre/dsl_examples/simple_before_call.rb +0 -7
- data/spec/theatre/dsl_spec.rb +0 -69
- data/spec/theatre/invocation_spec.rb +0 -182
- data/spec/theatre/namespace_spec.rb +0 -125
- data/spec/theatre/spec_helper_spec.rb +0 -28
- 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
|