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.
Files changed (107) hide show
  1. data/CHANGELOG +6 -0
  2. data/EVENTS +11 -0
  3. data/LICENSE +456 -0
  4. data/README.txt +5 -0
  5. data/Rakefile +120 -0
  6. data/adhearsion.gemspec +146 -0
  7. data/app_generators/ahn/USAGE +5 -0
  8. data/app_generators/ahn/ahn_generator.rb +87 -0
  9. data/app_generators/ahn/templates/.ahnrc +34 -0
  10. data/app_generators/ahn/templates/README +8 -0
  11. data/app_generators/ahn/templates/Rakefile +23 -0
  12. data/app_generators/ahn/templates/components/ami_remote/ami_remote.rb +15 -0
  13. data/app_generators/ahn/templates/components/disabled/HOW_TO_ENABLE +7 -0
  14. data/app_generators/ahn/templates/components/disabled/stomp_gateway/README.markdown +47 -0
  15. data/app_generators/ahn/templates/components/disabled/stomp_gateway/config.yml +12 -0
  16. data/app_generators/ahn/templates/components/disabled/stomp_gateway/stomp_gateway.rb +34 -0
  17. data/app_generators/ahn/templates/components/restful_rpc/README.markdown +11 -0
  18. data/app_generators/ahn/templates/components/restful_rpc/config.yml +34 -0
  19. data/app_generators/ahn/templates/components/restful_rpc/example-client.rb +48 -0
  20. data/app_generators/ahn/templates/components/restful_rpc/restful_rpc.rb +87 -0
  21. data/app_generators/ahn/templates/components/simon_game/simon_game.rb +56 -0
  22. data/app_generators/ahn/templates/config/startup.rb +53 -0
  23. data/app_generators/ahn/templates/dialplan.rb +3 -0
  24. data/app_generators/ahn/templates/events.rb +32 -0
  25. data/bin/ahn +28 -0
  26. data/bin/ahnctl +68 -0
  27. data/bin/jahn +42 -0
  28. data/examples/asterisk_manager_interface/standalone.rb +51 -0
  29. data/lib/adhearsion/cli.rb +223 -0
  30. data/lib/adhearsion/component_manager/spec_framework.rb +24 -0
  31. data/lib/adhearsion/component_manager.rb +208 -0
  32. data/lib/adhearsion/events_support.rb +84 -0
  33. data/lib/adhearsion/foundation/all.rb +9 -0
  34. data/lib/adhearsion/foundation/blank_slate.rb +5 -0
  35. data/lib/adhearsion/foundation/custom_daemonizer.rb +45 -0
  36. data/lib/adhearsion/foundation/event_socket.rb +203 -0
  37. data/lib/adhearsion/foundation/future_resource.rb +36 -0
  38. data/lib/adhearsion/foundation/global.rb +1 -0
  39. data/lib/adhearsion/foundation/metaprogramming.rb +17 -0
  40. data/lib/adhearsion/foundation/numeric.rb +13 -0
  41. data/lib/adhearsion/foundation/pseudo_guid.rb +10 -0
  42. data/lib/adhearsion/foundation/relationship_properties.rb +42 -0
  43. data/lib/adhearsion/foundation/string.rb +26 -0
  44. data/lib/adhearsion/foundation/synchronized_hash.rb +96 -0
  45. data/lib/adhearsion/foundation/thread_safety.rb +7 -0
  46. data/lib/adhearsion/host_definitions.rb +67 -0
  47. data/lib/adhearsion/initializer/asterisk.rb +81 -0
  48. data/lib/adhearsion/initializer/configuration.rb +254 -0
  49. data/lib/adhearsion/initializer/database.rb +49 -0
  50. data/lib/adhearsion/initializer/drb.rb +31 -0
  51. data/lib/adhearsion/initializer/freeswitch.rb +22 -0
  52. data/lib/adhearsion/initializer/rails.rb +40 -0
  53. data/lib/adhearsion/initializer.rb +373 -0
  54. data/lib/adhearsion/logging.rb +92 -0
  55. data/lib/adhearsion/tasks/database.rb +5 -0
  56. data/lib/adhearsion/tasks/deprecations.rb +59 -0
  57. data/lib/adhearsion/tasks/generating.rb +20 -0
  58. data/lib/adhearsion/tasks/lint.rb +4 -0
  59. data/lib/adhearsion/tasks/testing.rb +37 -0
  60. data/lib/adhearsion/tasks.rb +16 -0
  61. data/lib/adhearsion/version.rb +9 -0
  62. data/lib/adhearsion/voip/asterisk/agi_server.rb +81 -0
  63. data/lib/adhearsion/voip/asterisk/commands.rb +1284 -0
  64. data/lib/adhearsion/voip/asterisk/config_generators/agents.conf.rb +140 -0
  65. data/lib/adhearsion/voip/asterisk/config_generators/config_generator.rb +101 -0
  66. data/lib/adhearsion/voip/asterisk/config_generators/queues.conf.rb +250 -0
  67. data/lib/adhearsion/voip/asterisk/config_generators/voicemail.conf.rb +240 -0
  68. data/lib/adhearsion/voip/asterisk/config_manager.rb +71 -0
  69. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rb +1754 -0
  70. data/lib/adhearsion/voip/asterisk/manager_interface/ami_lexer.rl.rb +286 -0
  71. data/lib/adhearsion/voip/asterisk/manager_interface/ami_messages.rb +78 -0
  72. data/lib/adhearsion/voip/asterisk/manager_interface/ami_protocol_lexer_machine.rl +87 -0
  73. data/lib/adhearsion/voip/asterisk/manager_interface.rb +562 -0
  74. data/lib/adhearsion/voip/asterisk/special_dial_plan_managers.rb +80 -0
  75. data/lib/adhearsion/voip/asterisk/super_manager.rb +19 -0
  76. data/lib/adhearsion/voip/asterisk.rb +4 -0
  77. data/lib/adhearsion/voip/call.rb +440 -0
  78. data/lib/adhearsion/voip/call_routing.rb +64 -0
  79. data/lib/adhearsion/voip/commands.rb +9 -0
  80. data/lib/adhearsion/voip/constants.rb +39 -0
  81. data/lib/adhearsion/voip/conveniences.rb +18 -0
  82. data/lib/adhearsion/voip/dial_plan.rb +218 -0
  83. data/lib/adhearsion/voip/dsl/dialing_dsl/dialing_dsl_monkey_patches.rb +37 -0
  84. data/lib/adhearsion/voip/dsl/dialing_dsl.rb +151 -0
  85. data/lib/adhearsion/voip/dsl/dialplan/control_passing_exception.rb +27 -0
  86. data/lib/adhearsion/voip/dsl/dialplan/dispatcher.rb +124 -0
  87. data/lib/adhearsion/voip/dsl/dialplan/parser.rb +71 -0
  88. data/lib/adhearsion/voip/dsl/dialplan/thread_mixin.rb +16 -0
  89. data/lib/adhearsion/voip/dsl/numerical_string.rb +117 -0
  90. data/lib/adhearsion/voip/freeswitch/basic_connection_manager.rb +48 -0
  91. data/lib/adhearsion/voip/freeswitch/event_handler.rb +58 -0
  92. data/lib/adhearsion/voip/freeswitch/freeswitch_dialplan_command_factory.rb +129 -0
  93. data/lib/adhearsion/voip/freeswitch/inbound_connection_manager.rb +38 -0
  94. data/lib/adhearsion/voip/freeswitch/oes_server.rb +195 -0
  95. data/lib/adhearsion/voip/menu_state_machine/calculated_match.rb +80 -0
  96. data/lib/adhearsion/voip/menu_state_machine/matchers.rb +123 -0
  97. data/lib/adhearsion/voip/menu_state_machine/menu_builder.rb +58 -0
  98. data/lib/adhearsion/voip/menu_state_machine/menu_class.rb +149 -0
  99. data/lib/adhearsion.rb +37 -0
  100. data/lib/theatre/README.markdown +64 -0
  101. data/lib/theatre/callback_definition_loader.rb +84 -0
  102. data/lib/theatre/guid.rb +23 -0
  103. data/lib/theatre/invocation.rb +121 -0
  104. data/lib/theatre/namespace_manager.rb +153 -0
  105. data/lib/theatre/version.rb +2 -0
  106. data/lib/theatre.rb +151 -0
  107. 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,9 @@
1
+ require 'English'
2
+ require 'tmpdir'
3
+ require 'tempfile'
4
+ require 'active_support'
5
+
6
+ # Require all other files here.
7
+ Dir.glob File.join(File.dirname(__FILE__), "*rb") do |file|
8
+ require file
9
+ end
@@ -0,0 +1,5 @@
1
+ class BlankSlate
2
+ instance_methods.each do |method|
3
+ undef_method method unless method =~ /^__/ || method == 'instance_eval'
4
+ end
5
+ 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,17 @@
1
+ class Object
2
+ def metaclass
3
+ class << self
4
+ self
5
+ end
6
+ end
7
+
8
+ def meta_eval(&block)
9
+ metaclass.instance_eval &block
10
+ end
11
+
12
+ def meta_def(name, &block)
13
+ meta_eval do
14
+ define_method name, &block
15
+ end
16
+ end
17
+ end
@@ -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