jicksta-adhearsion 0.7.999
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.
- data/CHANGELOG +6 -0
- data/EVENTS +11 -0
- data/LICENSE +456 -0
- data/README.txt +5 -0
- data/Rakefile +120 -0
- data/adhearsion.gemspec +146 -0
- data/app_generators/ahn/USAGE +5 -0
- data/app_generators/ahn/ahn_generator.rb +87 -0
- data/app_generators/ahn/templates/.ahnrc +34 -0
- data/app_generators/ahn/templates/README +8 -0
- data/app_generators/ahn/templates/Rakefile +23 -0
- data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
- data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/config.yml +12 -0
- data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
- data/app_generators/ahn/templates/components/restful_rpc/README.markdown +11 -0
- data/app_generators/ahn/templates/components/restful_rpc/config.yml +34 -0
- data/app_generators/ahn/templates/components/restful_rpc/example-client.rb +48 -0
- data/app_generators/ahn/templates/components/restful_rpc/restful_rpc.rb +87 -0
- data/app_generators/ahn/templates/components/simon_game/simon_game.rb +56 -0
- data/app_generators/ahn/templates/config/startup.rb +53 -0
- data/app_generators/ahn/templates/dialplan.rb +3 -0
- data/app_generators/ahn/templates/events.rb +32 -0
- data/bin/ahn +28 -0
- data/bin/ahnctl +68 -0
- data/bin/jahn +42 -0
- data/examples/asterisk_manager_interface/standalone.rb +51 -0
- data/lib/adhearsion/cli.rb +223 -0
- data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
- data/lib/adhearsion/component_manager.rb +208 -0
- data/lib/adhearsion/events_support.rb +84 -0
- data/lib/adhearsion/foundation/all.rb +9 -0
- data/lib/adhearsion/foundation/blank_slate.rb +5 -0
- data/lib/adhearsion/foundation/custom_daemonizer.rb +45 -0
- data/lib/adhearsion/foundation/event_socket.rb +203 -0
- data/lib/adhearsion/foundation/future_resource.rb +36 -0
- data/lib/adhearsion/foundation/global.rb +1 -0
- data/lib/adhearsion/foundation/metaprogramming.rb +17 -0
- data/lib/adhearsion/foundation/numeric.rb +13 -0
- data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
- data/lib/adhearsion/foundation/relationship_properties.rb +42 -0
- data/lib/adhearsion/foundation/string.rb +26 -0
- data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
- data/lib/adhearsion/foundation/thread_safety.rb +7 -0
- data/lib/adhearsion/host_definitions.rb +67 -0
- data/lib/adhearsion/initializer/asterisk.rb +81 -0
- data/lib/adhearsion/initializer/configuration.rb +254 -0
- data/lib/adhearsion/initializer/database.rb +49 -0
- data/lib/adhearsion/initializer/drb.rb +31 -0
- data/lib/adhearsion/initializer/freeswitch.rb +22 -0
- data/lib/adhearsion/initializer/rails.rb +40 -0
- data/lib/adhearsion/initializer.rb +373 -0
- data/lib/adhearsion/logging.rb +92 -0
- data/lib/adhearsion/tasks/database.rb +5 -0
- data/lib/adhearsion/tasks/deprecations.rb +59 -0
- data/lib/adhearsion/tasks/generating.rb +20 -0
- data/lib/adhearsion/tasks/lint.rb +4 -0
- data/lib/adhearsion/tasks/testing.rb +37 -0
- data/lib/adhearsion/tasks.rb +16 -0
- data/lib/adhearsion/version.rb +9 -0
- data/lib/adhearsion/voip/asterisk/agi_server.rb +81 -0
- data/lib/adhearsion/voip/asterisk/commands.rb +1284 -0
- data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
- data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
- data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
- data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
- data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1754 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +286 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
- data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
- data/lib/adhearsion/voip/asterisk/manager_interface.rb +562 -0
- data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
- data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
- data/lib/adhearsion/voip/asterisk.rb +4 -0
- data/lib/adhearsion/voip/call.rb +440 -0
- data/lib/adhearsion/voip/call_routing.rb +64 -0
- data/lib/adhearsion/voip/commands.rb +9 -0
- data/lib/adhearsion/voip/constants.rb +39 -0
- data/lib/adhearsion/voip/conveniences.rb +18 -0
- data/lib/adhearsion/voip/dial_plan.rb +218 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
- data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
- data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
- data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
- data/lib/adhearsion/voip/dsl/dialplan/parser.rb +71 -0
- data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
- data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
- data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
- data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
- data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
- data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
- data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
- data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
- data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
- data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
- data/lib/adhearsion.rb +37 -0
- data/lib/theatre/README.markdown +64 -0
- data/lib/theatre/callback_definition_loader.rb +84 -0
- data/lib/theatre/guid.rb +23 -0
- data/lib/theatre/invocation.rb +121 -0
- data/lib/theatre/namespace_manager.rb +153 -0
- data/lib/theatre/version.rb +2 -0
- data/lib/theatre.rb +151 -0
- metadata +177 -0
@@ -0,0 +1,208 @@
|
|
1
|
+
module Adhearsion
|
2
|
+
module Components
|
3
|
+
|
4
|
+
mattr_accessor :component_manager
|
5
|
+
|
6
|
+
class ConfigurationError < Exception; end
|
7
|
+
|
8
|
+
class ComponentManager
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
def scopes_valid?(*scopes)
|
13
|
+
unrecognized_scopes = (scopes.flatten - SCOPE_NAMES).map(&:inspect)
|
14
|
+
raise ArgumentError, "Unrecognized scopes #{unrecognized_scopes.to_sentence}" if unrecognized_scopes.any?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
SCOPE_NAMES = [:dialplan, :events, :generators, :rpc, :global]
|
21
|
+
DEFAULT_CONFIG_NAME = "config.yml"
|
22
|
+
|
23
|
+
attr_reader :scopes, :lazy_config_loader
|
24
|
+
def initialize(path_to_container_directory)
|
25
|
+
@path_to_container_directory = path_to_container_directory
|
26
|
+
@scopes = SCOPE_NAMES.inject({}) do |scopes, name|
|
27
|
+
scopes[name] = Module.new
|
28
|
+
scopes
|
29
|
+
end
|
30
|
+
@lazy_config_loader = LazyConfigLoader.new(self)
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Includes the anonymous Module created for the :global scope in Object, making its methods globally accessible.
|
35
|
+
#
|
36
|
+
def globalize_global_scope!
|
37
|
+
Object.send :include, @scopes[:global]
|
38
|
+
end
|
39
|
+
|
40
|
+
def load_components
|
41
|
+
components = Dir.glob(File.join(@path_to_container_directory + "/*")).select do |path|
|
42
|
+
File.directory?(path)
|
43
|
+
end
|
44
|
+
components.map! { |path| File.basename path }
|
45
|
+
components.each do |component|
|
46
|
+
next if component == "disabled"
|
47
|
+
component_file = File.join(@path_to_container_directory, component, component + ".rb")
|
48
|
+
if File.exists? component_file
|
49
|
+
load_file component_file
|
50
|
+
else
|
51
|
+
ahn_log.warn "Component directory does not contain a matching .rb file! Was expecting #{component_file.inspect}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Loads the configuration file for a given component name.
|
59
|
+
#
|
60
|
+
# @return [Hash] The loaded YAML for the given component name. An empty Hash if no YAML file exists.
|
61
|
+
#
|
62
|
+
def configuration_for_component_named(component_name)
|
63
|
+
component_dir = File.join(@path_to_container_directory, component_name)
|
64
|
+
config_file = File.join component_dir, DEFAULT_CONFIG_NAME
|
65
|
+
if File.exists?(config_file)
|
66
|
+
YAML.load_file config_file
|
67
|
+
else
|
68
|
+
return {}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def extend_object_with(object, *scopes)
|
73
|
+
raise ArgumentError, "Must supply at least one scope!" if scopes.empty?
|
74
|
+
|
75
|
+
self.class.scopes_valid? scopes
|
76
|
+
|
77
|
+
scopes.each do |scope|
|
78
|
+
methods = @scopes[scope]
|
79
|
+
if object.kind_of?(Module)
|
80
|
+
object.send :include, methods
|
81
|
+
else
|
82
|
+
object.extend methods
|
83
|
+
end
|
84
|
+
end
|
85
|
+
object
|
86
|
+
end
|
87
|
+
|
88
|
+
def load_code(code)
|
89
|
+
load_container ComponentDefinitionContainer.load_code(code)
|
90
|
+
end
|
91
|
+
|
92
|
+
def load_file(filename)
|
93
|
+
load_container ComponentDefinitionContainer.load_file(filename)
|
94
|
+
end
|
95
|
+
|
96
|
+
protected
|
97
|
+
|
98
|
+
def load_container(container)
|
99
|
+
container.constants.each do |constant_name|
|
100
|
+
constant_value = container.const_get(constant_name)
|
101
|
+
Object.const_set(constant_name, constant_value)
|
102
|
+
end
|
103
|
+
metadata = container.metaclass.send(:instance_variable_get, :@metadata)
|
104
|
+
metadata[:initialization_block].call if metadata[:initialization_block]
|
105
|
+
|
106
|
+
self.class.scopes_valid? metadata[:scopes].keys
|
107
|
+
|
108
|
+
metadata[:scopes].each_pair do |scope, method_definition_blocks|
|
109
|
+
method_definition_blocks.each do |method_definition_block|
|
110
|
+
@scopes[scope].module_eval(&method_definition_block)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
container
|
114
|
+
end
|
115
|
+
|
116
|
+
class ComponentDefinitionContainer < Module
|
117
|
+
|
118
|
+
class << self
|
119
|
+
def load_code(code)
|
120
|
+
returning(new) do |instance|
|
121
|
+
instance.module_eval code
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def load_file(filename)
|
126
|
+
returning(new) do |instance|
|
127
|
+
instance.module_eval File.read(filename), filename
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def initialize(&block)
|
133
|
+
# Hide our instance variables in the singleton class
|
134
|
+
metadata = {}
|
135
|
+
metaclass.send(:instance_variable_set, :@metadata, metadata)
|
136
|
+
|
137
|
+
metadata[:scopes] = ComponentManager::SCOPE_NAMES.inject({}) do |scopes, name|
|
138
|
+
scopes[name] = []
|
139
|
+
scopes
|
140
|
+
end
|
141
|
+
|
142
|
+
super
|
143
|
+
|
144
|
+
meta_def(:initialize) { raise "This object has already been instantiated. Are you sure you didn't mean initialization()?" }
|
145
|
+
end
|
146
|
+
|
147
|
+
def methods_for(*scopes, &block)
|
148
|
+
raise ArgumentError if scopes.empty?
|
149
|
+
|
150
|
+
ComponentManager.scopes_valid? scopes
|
151
|
+
|
152
|
+
metadata = metaclass.send(:instance_variable_get, :@metadata)
|
153
|
+
scopes.each { |scope| metadata[:scopes][scope] << block }
|
154
|
+
end
|
155
|
+
|
156
|
+
def initialization(&block)
|
157
|
+
# Raise an exception if the initialization block has already been set
|
158
|
+
metadata = metaclass.send(:instance_variable_get, :@metadata)
|
159
|
+
if metadata[:initialization_block]
|
160
|
+
raise "You should only have one initialization() block!"
|
161
|
+
else
|
162
|
+
metadata[:initialization_block] = block
|
163
|
+
end
|
164
|
+
end
|
165
|
+
alias initialisation initialization
|
166
|
+
|
167
|
+
protected
|
168
|
+
|
169
|
+
class << self
|
170
|
+
def self.method_added(method_name)
|
171
|
+
@methods ||= []
|
172
|
+
@methods << method_name
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
class ComponentMethodDefinitionContainer < Module
|
179
|
+
class << self
|
180
|
+
def method_added(method_name)
|
181
|
+
@methods ||= []
|
182
|
+
@methods << method_name
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
attr_reader :scopes
|
187
|
+
def initialize(*scopes, &block)
|
188
|
+
@scopes = []
|
189
|
+
super(&block)
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
class LazyConfigLoader
|
195
|
+
def initialize(component_manager)
|
196
|
+
@component_manager = component_manager
|
197
|
+
end
|
198
|
+
|
199
|
+
def method_missing(component_name)
|
200
|
+
config = @component_manager.configuration_for_component_named(component_name.to_s)
|
201
|
+
(class << self; self; end).send(:define_method, component_name) { config }
|
202
|
+
config
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
%w[theatre jicksta-theatre].each do |theatre_gem_name|
|
2
|
+
# Some older versions of the master branch required the theatre gem be installed. This is deprecation logic to inform the
|
3
|
+
# user to uninstall the gem if they happened to have used theatre in the past.
|
4
|
+
begin
|
5
|
+
gem theatre_gem_name
|
6
|
+
rescue LoadError
|
7
|
+
# Good. It should not be installed.
|
8
|
+
else
|
9
|
+
abort <<-MESSAGE
|
10
|
+
It seems you have the "#{theatre_gem_name}" gem installed. As of Dec. 7th, 2008 Theatre has been rolled into Adhearsion
|
11
|
+
and will be distributed with it.
|
12
|
+
|
13
|
+
Please uninstall the gem by doing "sudo gem uninstall #{theatre_gem_name}".
|
14
|
+
|
15
|
+
You will not need to install it again in the future. Sorry for the inconvenience.
|
16
|
+
MESSAGE
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'theatre'
|
21
|
+
|
22
|
+
|
23
|
+
module Adhearsion
|
24
|
+
module Events
|
25
|
+
|
26
|
+
DEFAULT_FRAMEWORK_EVENT_NAMESPACES = %w[
|
27
|
+
/after_initialized
|
28
|
+
/shutdown
|
29
|
+
/asterisk/manager_interface
|
30
|
+
/asterisk/before_call
|
31
|
+
/asterisk/after_call
|
32
|
+
/asterisk/hungup_call
|
33
|
+
/asterisk/failed_call
|
34
|
+
]
|
35
|
+
|
36
|
+
class << self
|
37
|
+
|
38
|
+
def framework_theatre
|
39
|
+
defined?(@@framework_theatre) ? @@framework_theatre : reinitialize_theatre!
|
40
|
+
end
|
41
|
+
|
42
|
+
def trigger(*args)
|
43
|
+
framework_theatre.trigger(*args)
|
44
|
+
end
|
45
|
+
|
46
|
+
def trigger_immediately(*args)
|
47
|
+
framework_theatre.trigger_immediately(*args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def reinitialize_theatre!
|
51
|
+
@@framework_theatre.gracefully_stop! if defined? @@framework_theatre
|
52
|
+
rescue
|
53
|
+
# Recover and reinitalize
|
54
|
+
ensure
|
55
|
+
# TODO: Extract number of threads to use from AHN_CONFIG
|
56
|
+
@@framework_theatre = Theatre::Theatre.new
|
57
|
+
DEFAULT_FRAMEWORK_EVENT_NAMESPACES.each do |namespace|
|
58
|
+
register_namespace_name namespace
|
59
|
+
end
|
60
|
+
return @@framework_theatre
|
61
|
+
end
|
62
|
+
|
63
|
+
def register_namespace_name(name)
|
64
|
+
framework_theatre.register_namespace_name name
|
65
|
+
end
|
66
|
+
|
67
|
+
def stop!
|
68
|
+
Events.trigger :shutdown
|
69
|
+
framework_theatre.graceful_stop!
|
70
|
+
framework_theatre.join
|
71
|
+
end
|
72
|
+
|
73
|
+
def register_callback(namespace, block_arg=nil, &method_block)
|
74
|
+
raise ArgumentError, "Cannot supply two blocks!" if block_arg && block_given?
|
75
|
+
block = method_block || block_arg
|
76
|
+
raise ArgumentError, "Must supply a callback!" unless block
|
77
|
+
|
78
|
+
framework_theatre.register_callback_at_namespace(namespace, block)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# This is largely based on the Daemonize library by Travis Whitton and
|
2
|
+
# Judson Lester. http://grub.ath.cx/daemonize. I cleaned it up a bit to
|
3
|
+
# meet Adhearsion's quality standards.
|
4
|
+
module Adhearsion
|
5
|
+
module CustomDaemonizer
|
6
|
+
|
7
|
+
# Try to fork if at all possible retrying every 5 sec if the
|
8
|
+
# maximum process limit for the system has been reached
|
9
|
+
def safefork
|
10
|
+
begin
|
11
|
+
pid = fork
|
12
|
+
return pid if pid
|
13
|
+
rescue Errno::EWOULDBLOCK
|
14
|
+
sleep 5
|
15
|
+
retry
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# This method causes the current running process to become a daemon
|
20
|
+
def daemonize(log_file='/dev/null')
|
21
|
+
oldmode = 0
|
22
|
+
srand # Split rand streams between spawning and daemonized process
|
23
|
+
safefork and exit # Fork and exit from the parent
|
24
|
+
|
25
|
+
# Detach from the controlling terminal
|
26
|
+
unless sess_id = Process.setsid
|
27
|
+
raise 'Cannot detach from controlled terminal'
|
28
|
+
end
|
29
|
+
|
30
|
+
# Prevent the possibility of acquiring a controlling terminal
|
31
|
+
if oldmode.zero?
|
32
|
+
trap 'SIGHUP', 'IGNORE'
|
33
|
+
exit if pid = safefork
|
34
|
+
end
|
35
|
+
|
36
|
+
Dir.chdir "/" # Release old working directory
|
37
|
+
File.umask 0000 # Ensure sensible umask
|
38
|
+
|
39
|
+
STDIN.reopen "/dev/null"
|
40
|
+
STDOUT.reopen '/dev/null', "a"
|
41
|
+
STDERR.reopen log_file
|
42
|
+
return oldmode ? sess_id : 0
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
##
|
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.
|
5
|
+
#
|
6
|
+
# A handler can be registered in one of two ways: through registrations on an object yielded by the constructor or
|
7
|
+
# pre-defined on the object given as a constructor parameter. Below is an example definition which uses the block way:
|
8
|
+
#
|
9
|
+
# EventSocket.new do |handler|
|
10
|
+
# def handler.receive_data(data)
|
11
|
+
# # Do something here
|
12
|
+
# end
|
13
|
+
# def handler.disconnected
|
14
|
+
# # Do something here
|
15
|
+
# end
|
16
|
+
# def handler.connected
|
17
|
+
# # Do something here
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# Note: this is also a valid way of defining block callbacks:
|
22
|
+
#
|
23
|
+
# EventSocket.new do |handler|
|
24
|
+
# handler.receive_data { |data| do_something }
|
25
|
+
# handler.disconnected { do_something }
|
26
|
+
# handler.connected { do_something }
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# and here is an example of using a handler object:
|
30
|
+
#
|
31
|
+
# class MyCallbackHandler
|
32
|
+
# def receive_data(data) end
|
33
|
+
# def connected() end
|
34
|
+
# def disconnected() end
|
35
|
+
# end
|
36
|
+
# EventSocket.new(MyCallbackHandler.new)
|
37
|
+
#
|
38
|
+
# If you wish to ask the EventSocket what state it is in, you can call the Thread-safe EventSocket#state method. The
|
39
|
+
# supported states are:
|
40
|
+
#
|
41
|
+
# - :new
|
42
|
+
# - :connected
|
43
|
+
# - :stopped
|
44
|
+
# - :connection_dropped
|
45
|
+
#
|
46
|
+
# Note: the EventSocket's state will be changed before these callbacks are executed. For example, if your "connected"
|
47
|
+
# callback queried its own EventSocket for its state, it will have already transitioned to the connected() state.
|
48
|
+
#
|
49
|
+
# Warning: If an exception occurs in your EventSocket callbacks, they will be "eaten" and never bubbled up the call stack.
|
50
|
+
# You should always wrap your callbacks in a begin/rescue clause and handle exceptions explicitly.
|
51
|
+
#
|
52
|
+
require "thread"
|
53
|
+
require "socket"
|
54
|
+
|
55
|
+
class EventSocket
|
56
|
+
|
57
|
+
class << self
|
58
|
+
|
59
|
+
##
|
60
|
+
# Creates and returns a connected EventSocket instance.
|
61
|
+
#
|
62
|
+
def connect(*args, &block)
|
63
|
+
instance = new(*args, &block)
|
64
|
+
instance.connect!
|
65
|
+
instance
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
MAX_CHUNK_SIZE = 256 * 1024
|
70
|
+
|
71
|
+
def initialize(host, port, handler=nil, &block)
|
72
|
+
raise ArgumentError, "Cannot supply both a handler object and a block" if handler && block_given?
|
73
|
+
raise ArgumentError, "Must supply either a handler object or a block" if !handler && !block_given?
|
74
|
+
|
75
|
+
@state_lock = Mutex.new
|
76
|
+
@host = host
|
77
|
+
@port = port
|
78
|
+
|
79
|
+
@state = :new
|
80
|
+
@handler = handler || new_handler_from_block(&block)
|
81
|
+
end
|
82
|
+
|
83
|
+
def state
|
84
|
+
@state_lock.synchronize { @state }
|
85
|
+
end
|
86
|
+
|
87
|
+
def connect!
|
88
|
+
@state_lock.synchronize do
|
89
|
+
if @state.equal? :connected
|
90
|
+
raise ConnectionError, "Already connected!"
|
91
|
+
else
|
92
|
+
@socket = TCPSocket.new(@host, @port)
|
93
|
+
@state = :connected
|
94
|
+
end
|
95
|
+
end
|
96
|
+
@handler.connected rescue nil
|
97
|
+
@reader_thread = spawn_reader_thread
|
98
|
+
self
|
99
|
+
rescue => error
|
100
|
+
@state = :failed
|
101
|
+
raise error
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Thread-safe implementation of write.
|
106
|
+
#
|
107
|
+
# @param [String] data Data to write
|
108
|
+
#
|
109
|
+
def send_data(data)
|
110
|
+
# Note: TCPSocket#write is intrinsically Thread-safe
|
111
|
+
@socket.write data
|
112
|
+
rescue
|
113
|
+
connection_dropped!
|
114
|
+
end
|
115
|
+
##
|
116
|
+
# Disconnects this EventSocket and sets the state to :stopped
|
117
|
+
#
|
118
|
+
def disconnect!
|
119
|
+
@state_lock.synchronize do
|
120
|
+
@socket.close rescue nil
|
121
|
+
@state = :stopped
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
##
|
126
|
+
# Joins the Thread which reads data off the socket.
|
127
|
+
#
|
128
|
+
def join
|
129
|
+
@state_lock.synchronize do
|
130
|
+
if @state.equal? :connected
|
131
|
+
@reader_thread.join
|
132
|
+
else
|
133
|
+
nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def receive_data(data)
|
139
|
+
@handler.receive_data(data)
|
140
|
+
end
|
141
|
+
|
142
|
+
protected
|
143
|
+
|
144
|
+
def connection_dropped!
|
145
|
+
@state_lock.synchronize do
|
146
|
+
unless @state.equal? :connection_dropped
|
147
|
+
@state = :connection_dropped
|
148
|
+
@handler.disconnected
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def spawn_reader_thread
|
154
|
+
Thread.new(&method(:reader_loop))
|
155
|
+
end
|
156
|
+
|
157
|
+
def reader_loop
|
158
|
+
until state.equal? :stopped
|
159
|
+
data = @socket.readpartial(MAX_CHUNK_SIZE)
|
160
|
+
@handler.receive_data data
|
161
|
+
end
|
162
|
+
rescue EOFError
|
163
|
+
connection_dropped!
|
164
|
+
end
|
165
|
+
|
166
|
+
def new_handler_from_block(&handler_block)
|
167
|
+
handler = Object.new
|
168
|
+
handler.metaclass.send :attr_accessor, :set_callbacks
|
169
|
+
handler.set_callbacks = {:receive_data => false, :disconnected => false, :connected => false }
|
170
|
+
|
171
|
+
def handler.receive_data(&block)
|
172
|
+
self.metaclass.send(:remove_method, :receive_data)
|
173
|
+
self.metaclass.send(:define_method, :receive_data) { |data| block.call data }
|
174
|
+
set_callbacks[:receive_data] = true
|
175
|
+
end
|
176
|
+
def handler.connected(&block)
|
177
|
+
self.metaclass.send(:remove_method, :connected)
|
178
|
+
self.metaclass.send(:define_method, :connected) { block.call }
|
179
|
+
set_callbacks[:connected] = true
|
180
|
+
end
|
181
|
+
def handler.disconnected(&block)
|
182
|
+
self.metaclass.send(:remove_method, :disconnected)
|
183
|
+
self.metaclass.send(:define_method, :disconnected) { block.call }
|
184
|
+
set_callbacks[:disconnected] = true
|
185
|
+
end
|
186
|
+
|
187
|
+
def handler.singleton_method_added(name)
|
188
|
+
set_callbacks[name.to_sym] = true
|
189
|
+
end
|
190
|
+
|
191
|
+
yield handler
|
192
|
+
|
193
|
+
handler.set_callbacks.each_pair do |callback_name,was_set|
|
194
|
+
handler.send(callback_name) {} unless was_set
|
195
|
+
end
|
196
|
+
|
197
|
+
handler
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
class ConnectionError < Exception; end
|
202
|
+
|
203
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
class FutureResource
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@resource_lock = Monitor.new
|
7
|
+
@resource_value_blocker = @resource_lock.new_cond
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_yet?
|
11
|
+
@resource_lock.synchronize { defined? @resource }
|
12
|
+
end
|
13
|
+
|
14
|
+
def resource
|
15
|
+
@resource_lock.synchronize do
|
16
|
+
@resource_value_blocker.wait unless defined? @resource
|
17
|
+
@resource
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def resource=(resource)
|
22
|
+
@resource_lock.synchronize do
|
23
|
+
raise ResourceAlreadySetException if defined? @resource
|
24
|
+
@resource = resource
|
25
|
+
@resource_value_blocker.broadcast
|
26
|
+
@resource_value_blocker = nil # Don't really need it anymore.
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class ResourceAlreadySetException < Exception
|
31
|
+
def initialize
|
32
|
+
super "Cannot set this resource twice!"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
Infinity = 1.0/0.0
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Numeric
|
2
|
+
|
3
|
+
def digit()
|
4
|
+
ahn_log.deprecation 'Please do not use Fixnum#digit() and Fixnum#digits() in the future! These will be deprecated soon'
|
5
|
+
self
|
6
|
+
end
|
7
|
+
|
8
|
+
def digits()
|
9
|
+
ahn_log.deprecation 'Please do not use Fixnum#digit() and Fixnum#digits() in the future! These will be deprecated soon'
|
10
|
+
self
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module PseudoGuidGenerator
|
2
|
+
##
|
3
|
+
# Generates a new 128-bit Globally Unique Identifier. It is a "pseudo" in that it does not adhere to the RFC which mandates
|
4
|
+
# that a certain section be reserved for a fragment of the NIC MAC address.
|
5
|
+
def new_guid(separator="-")
|
6
|
+
[8,4,4,4,12].map { |segment_length| String.random(segment_length) }.join(separator)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
include PseudoGuidGenerator
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class Module
|
2
|
+
|
3
|
+
##
|
4
|
+
# In OOP, relationships between classes should be treated as *properties* of those classes. Often, in a complex OO
|
5
|
+
# architecture, you'll end up with many relationships that intermingle in monolithic ways, blunting the effectiveness of
|
6
|
+
# subclassing.
|
7
|
+
#
|
8
|
+
# For example, say you have an Automobile class which, in its constructor, instantiates a new Battery class and performs
|
9
|
+
# some operations on it such as calling an install() method. Let's also assume the Automobile class exposes a repair()
|
10
|
+
# method which uses a class-level method of Battery to diagnose your own instance of Battery. If the result of the
|
11
|
+
# diagnosis shows that the Battery is bad, the Automobile will instantiate a new Battery object and replace the old battery
|
12
|
+
# with the new one.
|
13
|
+
#
|
14
|
+
# Now, what if you wish to create a new Automobile derived from existing technology: a HybridAutomobile subclass. For this
|
15
|
+
# particular HybridAutomobile class, let's simply say the only difference between it and its parent is which kind of
|
16
|
+
# Battery it uses -- it requires its own special subclass of Battery. With Automobile's current implementation, its
|
17
|
+
# references to which Battery it instantiates and uses are embedded in the immutable method defintions. This
|
18
|
+
# HybridAutomobile needs to override which Battery its superclass' methods use and nothing else.
|
19
|
+
#
|
20
|
+
# For this reason, the Battery class which Automobile uses is semantically a property which others may want to override.
|
21
|
+
# In OOP theory, we define overridable properties in the form of methods and override those methods in the subclasses.
|
22
|
+
#
|
23
|
+
# This method exposes one method which creates human-readable semantics to defining these relationships as properties. It's
|
24
|
+
# used as follows:
|
25
|
+
#
|
26
|
+
# class Automobile
|
27
|
+
# relationship :battery => Battery
|
28
|
+
# relationship :chassis => Chassis
|
29
|
+
# # Other properties and instance methods here....
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# class HybridAutomobile < Automobile
|
33
|
+
# relationship :battery => HybridBattery
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
def relationships(relationship_mapping)
|
37
|
+
relationship_mapping.each_pair do |class_name, class_object|
|
38
|
+
define_method(class_name) { class_object }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|