adhearsion 0.8.3 → 0.8.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/CHANGELOG +11 -2
  2. data/EVENTS +1 -1
  3. data/Rakefile +4 -4
  4. data/adhearsion.gemspec +8 -4
  5. data/app_generators/ahn/USAGE +3 -3
  6. data/app_generators/ahn/ahn_generator.rb +11 -11
  7. data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +3 -3
  8. data/app_generators/ahn/templates/components/disabled/restful_rpc/example-client.rb +6 -6
  9. data/app_generators/ahn/templates/components/disabled/restful_rpc/restful_rpc.rb +16 -16
  10. data/app_generators/ahn/templates/components/disabled/restful_rpc/spec/restful_rpc_spec.rb +42 -42
  11. data/app_generators/ahn/templates/components/disabled/sandbox/sandbox.rb +11 -11
  12. data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +1 -1
  13. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +6 -6
  14. data/app_generators/ahn/templates/components/simon_game/simon_game.rb +4 -4
  15. data/app_generators/ahn/templates/config/startup.rb +31 -16
  16. data/bin/ahn +3 -3
  17. data/bin/ahnctl +8 -8
  18. data/bin/jahn +3 -3
  19. data/examples/asterisk_manager_interface/standalone.rb +2 -2
  20. data/lib/adhearsion.rb +4 -2
  21. data/lib/adhearsion/cli.rb +31 -31
  22. data/lib/adhearsion/component_manager.rb +39 -39
  23. data/lib/adhearsion/component_manager/component_tester.rb +14 -14
  24. data/lib/adhearsion/component_manager/spec_framework.rb +1 -1
  25. data/lib/adhearsion/events_support.rb +12 -12
  26. data/lib/adhearsion/foundation/blank_slate.rb +1 -1
  27. data/lib/adhearsion/foundation/custom_daemonizer.rb +2 -2
  28. data/lib/adhearsion/foundation/event_socket.rb +26 -26
  29. data/lib/adhearsion/foundation/future_resource.rb +6 -6
  30. data/lib/adhearsion/foundation/metaprogramming.rb +2 -2
  31. data/lib/adhearsion/foundation/numeric.rb +3 -3
  32. data/lib/adhearsion/foundation/relationship_properties.rb +7 -7
  33. data/lib/adhearsion/foundation/string.rb +8 -8
  34. data/lib/adhearsion/foundation/synchronized_hash.rb +8 -8
  35. data/lib/adhearsion/host_definitions.rb +16 -16
  36. data/lib/adhearsion/initializer.rb +74 -65
  37. data/lib/adhearsion/initializer/asterisk.rb +15 -9
  38. data/lib/adhearsion/initializer/configuration.rb +54 -39
  39. data/lib/adhearsion/initializer/database.rb +4 -4
  40. data/lib/adhearsion/initializer/drb.rb +6 -6
  41. data/lib/adhearsion/initializer/freeswitch.rb +1 -1
  42. data/lib/adhearsion/initializer/ldap.rb +51 -0
  43. data/lib/adhearsion/initializer/rails.rb +8 -8
  44. data/lib/adhearsion/logging.rb +16 -16
  45. data/lib/adhearsion/tasks/deprecations.rb +12 -12
  46. data/lib/adhearsion/tasks/generating.rb +2 -2
  47. data/lib/adhearsion/tasks/testing.rb +7 -7
  48. data/lib/adhearsion/version.rb +1 -1
  49. data/lib/adhearsion/voip/asterisk/agi_server.rb +44 -14
  50. data/lib/adhearsion/voip/asterisk/commands.rb +281 -237
  51. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +8 -8
  52. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +14 -14
  53. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +16 -16
  54. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +39 -39
  55. data/lib/adhearsion/voip/asterisk/config_manager.rb +13 -13
  56. data/lib/adhearsion/voip/asterisk/manager_interface.rb +91 -87
  57. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +739 -739
  58. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +60 -60
  59. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +16 -16
  60. data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +1 -1
  61. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +13 -13
  62. data/lib/adhearsion/voip/asterisk/super_manager.rb +3 -3
  63. data/lib/adhearsion/voip/call.rb +101 -64
  64. data/lib/adhearsion/voip/call_routing.rb +9 -9
  65. data/lib/adhearsion/voip/constants.rb +7 -7
  66. data/lib/adhearsion/voip/conveniences.rb +1 -1
  67. data/lib/adhearsion/voip/dial_plan.rb +42 -40
  68. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +27 -27
  69. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +1 -1
  70. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +6 -6
  71. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +17 -17
  72. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +7 -7
  73. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +2 -2
  74. data/lib/adhearsion/voip/dsl/numerical_string.rb +3 -3
  75. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +7 -7
  76. data/lib/adhearsion/voip/freeswitch/event_handler.rb +1 -1
  77. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +20 -20
  78. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +5 -5
  79. data/lib/adhearsion/voip/freeswitch/oes_server.rb +33 -33
  80. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +1 -1
  81. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +2 -2
  82. data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +9 -9
  83. data/lib/theatre.rb +18 -18
  84. data/lib/theatre/callback_definition_loader.rb +17 -17
  85. data/lib/theatre/guid.rb +6 -6
  86. data/lib/theatre/invocation.rb +19 -19
  87. data/lib/theatre/namespace_manager.rb +28 -28
  88. metadata +55 -14
@@ -1,24 +1,24 @@
1
1
  module Adhearsion
2
2
  module Components
3
-
3
+
4
4
  mattr_accessor :component_manager
5
-
5
+
6
6
  class ConfigurationError < Exception; end
7
-
7
+
8
8
  class ComponentManager
9
-
9
+
10
10
  class << self
11
-
11
+
12
12
  def scopes_valid?(*scopes)
13
13
  unrecognized_scopes = (scopes.flatten - SCOPE_NAMES).map(&:inspect)
14
14
  raise ArgumentError, "Unrecognized scopes #{unrecognized_scopes.to_sentence}" if unrecognized_scopes.any?
15
15
  true
16
16
  end
17
-
17
+
18
18
  end
19
-
19
+
20
20
  SCOPE_NAMES = [:dialplan, :events, :generators, :rpc, :global]
21
-
21
+
22
22
  attr_reader :scopes, :lazy_config_loader
23
23
  def initialize(path_to_container_directory)
24
24
  @path_to_container_directory = path_to_container_directory
@@ -28,14 +28,14 @@ module Adhearsion
28
28
  end
29
29
  @lazy_config_loader = LazyConfigLoader.new(self)
30
30
  end
31
-
31
+
32
32
  ##
33
33
  # Includes the anonymous Module created for the :global scope in Object, making its methods globally accessible.
34
34
  #
35
35
  def globalize_global_scope!
36
36
  Object.send :include, @scopes[:global]
37
37
  end
38
-
38
+
39
39
  def load_components
40
40
  components = Dir.glob(File.join(@path_to_container_directory + "/*")).select do |path|
41
41
  File.directory?(path)
@@ -50,9 +50,9 @@ module Adhearsion
50
50
  ahn_log.warn "Component directory does not contain a matching .rb file! Was expecting #{component_file.inspect}"
51
51
  end
52
52
  end
53
-
53
+
54
54
  end
55
-
55
+
56
56
  ##
57
57
  # Loads the configuration file for a given component name.
58
58
  #
@@ -67,12 +67,12 @@ module Adhearsion
67
67
  return {}
68
68
  end
69
69
  end
70
-
70
+
71
71
  def extend_object_with(object, *scopes)
72
72
  raise ArgumentError, "Must supply at least one scope!" if scopes.empty?
73
-
73
+
74
74
  self.class.scopes_valid? scopes
75
-
75
+
76
76
  scopes.each do |scope|
77
77
  methods = @scopes[scope]
78
78
  if object.kind_of?(Module)
@@ -83,7 +83,7 @@ module Adhearsion
83
83
  end
84
84
  object
85
85
  end
86
-
86
+
87
87
  def load_code(code)
88
88
  load_container ComponentDefinitionContainer.load_code(code)
89
89
  end
@@ -91,9 +91,9 @@ module Adhearsion
91
91
  def load_file(filename)
92
92
  load_container ComponentDefinitionContainer.load_file(filename)
93
93
  end
94
-
94
+
95
95
  protected
96
-
96
+
97
97
  def load_container(container)
98
98
  container.constants.each do |constant_name|
99
99
  constant_value = container.const_get(constant_name)
@@ -101,9 +101,9 @@ module Adhearsion
101
101
  end
102
102
  metadata = container.metaclass.send(:instance_variable_get, :@metadata)
103
103
  metadata[:initialization_block].call if metadata[:initialization_block]
104
-
104
+
105
105
  self.class.scopes_valid? metadata[:scopes].keys
106
-
106
+
107
107
  metadata[:scopes].each_pair do |scope, method_definition_blocks|
108
108
  method_definition_blocks.each do |method_definition_block|
109
109
  @scopes[scope].module_eval(&method_definition_block)
@@ -113,45 +113,45 @@ module Adhearsion
113
113
  end
114
114
 
115
115
  class ComponentDefinitionContainer < Module
116
-
116
+
117
117
  class << self
118
118
  def load_code(code)
119
119
  returning(new) do |instance|
120
120
  instance.module_eval code
121
121
  end
122
122
  end
123
-
123
+
124
124
  def load_file(filename)
125
125
  returning(new) do |instance|
126
126
  instance.module_eval File.read(filename), filename
127
127
  end
128
128
  end
129
129
  end
130
-
130
+
131
131
  def initialize(&block)
132
132
  # Hide our instance variables in the singleton class
133
133
  metadata = {}
134
134
  metaclass.send(:instance_variable_set, :@metadata, metadata)
135
-
135
+
136
136
  metadata[:scopes] = ComponentManager::SCOPE_NAMES.inject({}) do |scopes, name|
137
137
  scopes[name] = []
138
138
  scopes
139
139
  end
140
-
140
+
141
141
  super
142
-
142
+
143
143
  meta_def(:initialize) { raise "This object has already been instantiated. Are you sure you didn't mean initialization()?" }
144
144
  end
145
-
145
+
146
146
  def methods_for(*scopes, &block)
147
147
  raise ArgumentError if scopes.empty?
148
-
148
+
149
149
  ComponentManager.scopes_valid? scopes
150
-
150
+
151
151
  metadata = metaclass.send(:instance_variable_get, :@metadata)
152
152
  scopes.each { |scope| metadata[:scopes][scope] << block }
153
153
  end
154
-
154
+
155
155
  def initialization(&block)
156
156
  # Raise an exception if the initialization block has already been set
157
157
  metadata = metaclass.send(:instance_variable_get, :@metadata)
@@ -162,18 +162,18 @@ module Adhearsion
162
162
  end
163
163
  end
164
164
  alias initialisation initialization
165
-
165
+
166
166
  protected
167
-
167
+
168
168
  class << self
169
169
  def self.method_added(method_name)
170
170
  @methods ||= []
171
171
  @methods << method_name
172
172
  end
173
173
  end
174
-
174
+
175
175
  end
176
-
176
+
177
177
  class ComponentMethodDefinitionContainer < Module
178
178
  class << self
179
179
  def method_added(method_name)
@@ -181,27 +181,27 @@ module Adhearsion
181
181
  @methods << method_name
182
182
  end
183
183
  end
184
-
184
+
185
185
  attr_reader :scopes
186
186
  def initialize(*scopes, &block)
187
187
  @scopes = []
188
188
  super(&block)
189
189
  end
190
-
190
+
191
191
  end
192
-
192
+
193
193
  class LazyConfigLoader
194
194
  def initialize(component_manager)
195
195
  @component_manager = component_manager
196
196
  end
197
-
197
+
198
198
  def method_missing(component_name)
199
199
  config = @component_manager.configuration_for_component_named(component_name.to_s)
200
200
  (class << self; self; end).send(:define_method, component_name) { config }
201
201
  config
202
202
  end
203
203
  end
204
-
204
+
205
205
  end
206
206
  end
207
207
  end
@@ -1,7 +1,7 @@
1
1
  module ComponentTester
2
-
2
+
3
3
  class << self
4
-
4
+
5
5
  ##
6
6
  #
7
7
  #
@@ -10,46 +10,46 @@ module ComponentTester
10
10
  def new(component_name, component_directory)
11
11
  component_directory = File.expand_path component_directory
12
12
  main_file = component_directory + "/#{component_name}/#{component_name}.rb"
13
-
13
+
14
14
  component_manager = Adhearsion::Components::ComponentManager.new(component_directory)
15
15
  component_module = Adhearsion::Components::ComponentManager::ComponentDefinitionContainer.load_file main_file
16
-
16
+
17
17
  Module.new do
18
-
18
+
19
19
  extend ComponentTester
20
-
20
+
21
21
  (class << self; self; end).send(:define_method, :component_manager) { component_manager }
22
22
  (class << self; self; end).send(:define_method, :component_name) { component_name }
23
23
  (class << self; self; end).send(:define_method, :component_module) { component_module }
24
24
  (class << self; self; end).send(:define_method, :component_directory) { component_directory }
25
-
26
-
25
+
26
+
27
27
  define_method(:component_manager) { component_manager }
28
28
  define_method(:component_name) { component_name }
29
29
  define_method(:component_module) { component_module }
30
30
  define_method(:component_directory) { component_directory }
31
-
31
+
32
32
  def self.const_missing(name)
33
33
  component_module.const_get name
34
34
  end
35
-
35
+
36
36
  end
37
37
  end
38
38
  end
39
-
39
+
40
40
  def helper_method(name)
41
41
  Object.new.extend(component_module).method(name)
42
42
  end
43
-
43
+
44
44
  def config
45
45
  component_manager.configuration_for_component_named component_name
46
46
  end
47
-
47
+
48
48
  def initialize!
49
49
  metadata = component_module.metaclass.send(:instance_variable_get, :@metadata)
50
50
  if metadata && metadata[:initialization_block].kind_of?(Proc)
51
51
  metadata[:initialization_block].call
52
52
  end
53
53
  end
54
-
54
+
55
55
  end
@@ -8,7 +8,7 @@ end
8
8
  begin
9
9
  require 'rr'
10
10
  rescue LoadError
11
- abort 'You do not have the "rr" gem installed! You must install it to continue.\n\nsudo gem install rr\n\n'
11
+ abort 'You do not have the "rr" gem installed! You must install it to continue.\n\nsudo gem install rr\n\n'
12
12
  end
13
13
 
14
14
  module ComponentConfigurationSpecHelper
@@ -22,7 +22,7 @@ require 'theatre'
22
22
 
23
23
  module Adhearsion
24
24
  module Events
25
-
25
+
26
26
  DEFAULT_FRAMEWORK_EVENT_NAMESPACES = %w[
27
27
  /after_initialized
28
28
  /shutdown
@@ -32,21 +32,21 @@ module Adhearsion
32
32
  /asterisk/hungup_call
33
33
  /asterisk/failed_call
34
34
  ]
35
-
35
+
36
36
  class << self
37
-
37
+
38
38
  def framework_theatre
39
39
  defined?(@@framework_theatre) ? @@framework_theatre : reinitialize_theatre!
40
40
  end
41
-
41
+
42
42
  def trigger(*args)
43
43
  framework_theatre.trigger(*args)
44
44
  end
45
-
45
+
46
46
  def trigger_immediately(*args)
47
47
  framework_theatre.trigger_immediately(*args)
48
48
  end
49
-
49
+
50
50
  def reinitialize_theatre!
51
51
  @@framework_theatre.gracefully_stop! if defined? @@framework_theatre
52
52
  rescue
@@ -59,26 +59,26 @@ module Adhearsion
59
59
  end
60
60
  return @@framework_theatre
61
61
  end
62
-
62
+
63
63
  def register_namespace_name(name)
64
64
  framework_theatre.register_namespace_name name
65
65
  end
66
-
66
+
67
67
  def stop!
68
68
  Events.trigger :shutdown
69
69
  framework_theatre.graceful_stop!
70
70
  framework_theatre.join
71
71
  end
72
-
72
+
73
73
  def register_callback(namespace, block_arg=nil, &method_block)
74
74
  raise ArgumentError, "Cannot supply two blocks!" if block_arg && block_given?
75
75
  block = method_block || block_arg
76
76
  raise ArgumentError, "Must supply a callback!" unless block
77
-
77
+
78
78
  framework_theatre.register_callback_at_namespace(namespace, block)
79
79
  end
80
-
80
+
81
81
  end
82
-
82
+
83
83
  end
84
84
  end
@@ -1,5 +1,5 @@
1
1
  class BlankSlate
2
- instance_methods.each do |method|
2
+ instance_methods.each do |method|
3
3
  undef_method method unless method =~ /^__/ || method == 'instance_eval'
4
4
  end
5
5
  end
@@ -3,7 +3,7 @@
3
3
  # meet Adhearsion's quality standards.
4
4
  module Adhearsion
5
5
  module CustomDaemonizer
6
-
6
+
7
7
  # Try to fork if at all possible retrying every 5 sec if the
8
8
  # maximum process limit for the system has been reached
9
9
  def safefork
@@ -38,7 +38,7 @@ module Adhearsion
38
38
 
39
39
  STDIN.reopen "/dev/null"
40
40
  STDOUT.reopen '/dev/null', "a"
41
- STDERR.reopen log_file
41
+ STDERR.reopen log_file, "a"
42
42
  return oldmode ? sess_id : 0
43
43
  end
44
44
  end
@@ -1,9 +1,9 @@
1
1
  ##
2
2
  # EventSocket is a small abstraction of TCPSocket which causes it to behave much like an EventMachine Connection object for
3
- # the sake of better testability. The EventMachine Connection paradigm (as well as other networking libraries such as the
4
- # Objective-C HTTP library) uses callbacks to signal different stages of a socket's lifecycle.
3
+ # the sake of better testability. The EventMachine Connection paradigm (as well as other networking libraries such as the
4
+ # Objective-C HTTP library) uses callbacks to signal different stages of a socket's lifecycle.
5
5
  #
6
- # A handler can be registered in one of two ways: through registrations on an object yielded by the constructor or
6
+ # A handler can be registered in one of two ways: through registrations on an object yielded by the constructor or
7
7
  # pre-defined on the object given as a constructor parameter. Below is an example definition which uses the block way:
8
8
  #
9
9
  # EventSocket.new do |handler|
@@ -35,7 +35,7 @@
35
35
  # end
36
36
  # EventSocket.new(MyCallbackHandler.new)
37
37
  #
38
- # If you wish to ask the EventSocket what state it is in, you can call the Thread-safe EventSocket#state method. The
38
+ # If you wish to ask the EventSocket what state it is in, you can call the Thread-safe EventSocket#state method. The
39
39
  # supported states are:
40
40
  #
41
41
  # - :new
@@ -43,10 +43,10 @@
43
43
  # - :stopped
44
44
  # - :connection_dropped
45
45
  #
46
- # Note: the EventSocket's state will be changed before these callbacks are executed. For example, if your "connected"
46
+ # Note: the EventSocket's state will be changed before these callbacks are executed. For example, if your "connected"
47
47
  # callback queried its own EventSocket for its state, it will have already transitioned to the connected() state.
48
48
  #
49
- # Warning: If an exception occurs in your EventSocket callbacks, they will be "eaten" and never bubbled up the call stack.
49
+ # Warning: If an exception occurs in your EventSocket callbacks, they will be "eaten" and never bubbled up the call stack.
50
50
  # You should always wrap your callbacks in a begin/rescue clause and handle exceptions explicitly.
51
51
  #
52
52
  require "thread"
@@ -55,7 +55,7 @@ require "socket"
55
55
  class EventSocket
56
56
 
57
57
  class << self
58
-
58
+
59
59
  ##
60
60
  # Creates and returns a connected EventSocket instance.
61
61
  #
@@ -67,23 +67,23 @@ class EventSocket
67
67
  end
68
68
 
69
69
  MAX_CHUNK_SIZE = 256 * 1024
70
-
70
+
71
71
  def initialize(host, port, handler=nil, &block)
72
72
  raise ArgumentError, "Cannot supply both a handler object and a block" if handler && block_given?
73
73
  raise ArgumentError, "Must supply either a handler object or a block" if !handler && !block_given?
74
-
74
+
75
75
  @state_lock = Mutex.new
76
76
  @host = host
77
77
  @port = port
78
-
78
+
79
79
  @state = :new
80
80
  @handler = handler || new_handler_from_block(&block)
81
81
  end
82
-
82
+
83
83
  def state
84
84
  @state_lock.synchronize { @state }
85
85
  end
86
-
86
+
87
87
  def connect!
88
88
  @state_lock.synchronize do
89
89
  if @state.equal? :connected
@@ -100,7 +100,7 @@ class EventSocket
100
100
  @state = :failed
101
101
  raise error
102
102
  end
103
-
103
+
104
104
  ##
105
105
  # Thread-safe implementation of write.
106
106
  #
@@ -121,7 +121,7 @@ class EventSocket
121
121
  @state = :stopped
122
122
  end
123
123
  end
124
-
124
+
125
125
  ##
126
126
  # Joins the Thread which reads data off the socket.
127
127
  #
@@ -134,13 +134,13 @@ class EventSocket
134
134
  end
135
135
  end
136
136
  end
137
-
137
+
138
138
  def receive_data(data)
139
139
  @handler.receive_data(data)
140
140
  end
141
-
141
+
142
142
  protected
143
-
143
+
144
144
  def connection_dropped!
145
145
  @state_lock.synchronize do
146
146
  unless @state.equal? :connection_dropped
@@ -149,11 +149,11 @@ class EventSocket
149
149
  end
150
150
  end
151
151
  end
152
-
152
+
153
153
  def spawn_reader_thread
154
154
  Thread.new(&method(:reader_loop))
155
155
  end
156
-
156
+
157
157
  def reader_loop
158
158
  until state.equal? :stopped
159
159
  data = @socket.readpartial(MAX_CHUNK_SIZE)
@@ -167,7 +167,7 @@ class EventSocket
167
167
  handler = Object.new
168
168
  handler.metaclass.send :attr_accessor, :set_callbacks
169
169
  handler.set_callbacks = {:receive_data => false, :disconnected => false, :connected => false }
170
-
170
+
171
171
  def handler.receive_data(&block)
172
172
  self.metaclass.send(:remove_method, :receive_data)
173
173
  self.metaclass.send(:define_method, :receive_data) { |data| block.call data }
@@ -183,21 +183,21 @@ class EventSocket
183
183
  self.metaclass.send(:define_method, :disconnected) { block.call }
184
184
  set_callbacks[:disconnected] = true
185
185
  end
186
-
186
+
187
187
  def handler.singleton_method_added(name)
188
188
  set_callbacks[name.to_sym] = true
189
189
  end
190
-
190
+
191
191
  yield handler
192
-
192
+
193
193
  handler.set_callbacks.each_pair do |callback_name,was_set|
194
194
  handler.send(callback_name) {} unless was_set
195
195
  end
196
-
196
+
197
197
  handler
198
-
198
+
199
199
  end
200
200
 
201
201
  class ConnectionError < Exception; end
202
-
202
+
203
203
  end