Sutto-marvin 0.4.0 → 0.8.0.0

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 (85) hide show
  1. data/bin/marvin +22 -156
  2. data/handlers/keiki_thwopper.rb +21 -0
  3. data/handlers/tweet_tweet.rb +1 -3
  4. data/lib/marvin/abstract_client.rb +75 -189
  5. data/lib/marvin/abstract_parser.rb +9 -11
  6. data/lib/marvin/base.rb +134 -101
  7. data/lib/marvin/client/actions.rb +104 -0
  8. data/lib/marvin/client/default_handlers.rb +97 -0
  9. data/lib/marvin/command_handler.rb +60 -49
  10. data/lib/marvin/console.rb +4 -31
  11. data/lib/marvin/core_commands.rb +30 -12
  12. data/lib/marvin/distributed/client.rb +225 -0
  13. data/lib/marvin/distributed/handler.rb +85 -0
  14. data/lib/marvin/distributed/protocol.rb +88 -0
  15. data/lib/marvin/distributed/server.rb +154 -0
  16. data/lib/marvin/distributed.rb +4 -10
  17. data/lib/marvin/dsl.rb +103 -0
  18. data/lib/marvin/exception_tracker.rb +7 -4
  19. data/lib/marvin/irc/client.rb +127 -99
  20. data/lib/marvin/irc/event.rb +14 -10
  21. data/lib/marvin/irc.rb +0 -1
  22. data/lib/marvin/middle_man.rb +1 -1
  23. data/lib/marvin/parsers/command.rb +10 -8
  24. data/lib/marvin/parsers/prefixes/host_mask.rb +12 -7
  25. data/lib/marvin/parsers/prefixes/server.rb +1 -1
  26. data/lib/marvin/parsers/ragel_parser.rb +59 -52
  27. data/lib/marvin/parsers/ragel_parser.rl +6 -7
  28. data/lib/marvin/parsers/simple_parser.rb +4 -9
  29. data/lib/marvin/parsers.rb +1 -2
  30. data/lib/marvin/settings.rb +29 -79
  31. data/lib/marvin/test_client.rb +20 -26
  32. data/lib/marvin/util.rb +10 -3
  33. data/lib/marvin.rb +42 -39
  34. data/templates/boot.erb +3 -0
  35. data/templates/connections.yml.erb +10 -0
  36. data/templates/debug_handler.erb +5 -0
  37. data/templates/hello_world.erb +10 -0
  38. data/templates/rakefile.erb +15 -0
  39. data/templates/settings.yml.erb +8 -0
  40. data/{config/setup.rb → templates/setup.erb} +8 -10
  41. data/templates/test_helper.erb +17 -0
  42. data/test/abstract_client_test.rb +63 -0
  43. data/test/parser_comparison.rb +2 -2
  44. data/test/parser_test.rb +3 -3
  45. data/test/test_helper.rb +58 -6
  46. metadata +51 -83
  47. data/README.textile +0 -105
  48. data/TUTORIAL.textile +0 -54
  49. data/VERSION.yml +0 -4
  50. data/config/boot.rb +0 -14
  51. data/config/connections.yml.sample +0 -5
  52. data/config/settings.yml.sample +0 -13
  53. data/handlers/logging_handler.rb +0 -89
  54. data/lib/marvin/core_ext.rb +0 -11
  55. data/lib/marvin/daemon.rb +0 -71
  56. data/lib/marvin/data_store.rb +0 -73
  57. data/lib/marvin/dispatchable.rb +0 -99
  58. data/lib/marvin/distributed/dispatch_handler.rb +0 -83
  59. data/lib/marvin/distributed/drb_client.rb +0 -78
  60. data/lib/marvin/distributed/ring_server.rb +0 -41
  61. data/lib/marvin/handler.rb +0 -12
  62. data/lib/marvin/irc/server/abstract_connection.rb +0 -84
  63. data/lib/marvin/irc/server/base_connection.rb +0 -66
  64. data/lib/marvin/irc/server/channel.rb +0 -115
  65. data/lib/marvin/irc/server/named_store.rb +0 -14
  66. data/lib/marvin/irc/server/remote_interface.rb +0 -77
  67. data/lib/marvin/irc/server/user/handle_mixin.rb +0 -140
  68. data/lib/marvin/irc/server/user.rb +0 -5
  69. data/lib/marvin/irc/server/user_connection.rb +0 -134
  70. data/lib/marvin/irc/server/virtual_user_connection.rb +0 -80
  71. data/lib/marvin/irc/server.rb +0 -71
  72. data/lib/marvin/loader.rb +0 -149
  73. data/lib/marvin/logger.rb +0 -86
  74. data/lib/marvin/options.rb +0 -42
  75. data/lib/marvin/parsers/regexp_parser.rb +0 -93
  76. data/lib/marvin/status.rb +0 -72
  77. data/script/client +0 -3
  78. data/script/console +0 -3
  79. data/script/distributed_client +0 -3
  80. data/script/install +0 -1
  81. data/script/ring_server +0 -4
  82. data/script/server +0 -4
  83. data/script/status +0 -3
  84. data/spec/marvin/abstract_client_test.rb +0 -38
  85. data/spec/spec_helper.rb +0 -14
@@ -5,7 +5,7 @@ module Marvin
5
5
 
6
6
  module BaseExtensions
7
7
  def parse(line)
8
- Marvin::Settings.default_parser.parse(line)
8
+ Marvin::Settings.parser.parse(line)
9
9
  end
10
10
 
11
11
  def logger
@@ -13,35 +13,7 @@ module Marvin
13
13
  end
14
14
 
15
15
  def client
16
- $client ||= Marvin::Settings.default_client.new(:port => 6667, :server => "irc.freenode.net")
17
- end
18
-
19
- class ServerMock < Marvin::IRC::Server::BaseConnection
20
- def send_line(line)
21
- puts ">> #{line}"
22
- end
23
- def kill_connection!
24
- puts "Killing connection"
25
- end
26
-
27
- def get_peername
28
- # Localhost, HTTP
29
- "\034\036\000P\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\001\000\000\000\000"
30
- end
31
-
32
- def host
33
- "localhost"
34
- end
35
-
36
- def port
37
- 6667
38
- end
39
-
40
- end
41
-
42
- def server(reset = false)
43
- $server = ServerMock.new(:port => 6667, :host => "localhost") if $server.blank? || reset
44
- return $server
16
+ $client ||= Marvin::Settings.client.new(:port => 6667, :server => "irc.freenode.net")
45
17
  end
46
18
 
47
19
  def user(reset = false)
@@ -66,7 +38,8 @@ module Marvin
66
38
  end
67
39
 
68
40
  def run
69
- IRB.start(@file)
41
+ ARGV.replace []
42
+ IRB.start
70
43
  end
71
44
 
72
45
  def self.run
@@ -1,20 +1,38 @@
1
1
  module Marvin
2
2
  class CoreCommands < CommandHandler
3
3
 
4
+ # Returns a hash of doccumented method names
5
+ def self.method_documentation
6
+ documented = Hash.new { |h,k| h[k] = [] }
7
+ @@method_descriptions.each_key do |klass|
8
+ next unless klass.registered?
9
+ @@exposed_method_mapping[klass].each do |m|
10
+ desc = @@method_descriptions[klass][m]
11
+ documented[m.to_s] << desc if desc.present?
12
+ end
13
+ end
14
+ return documented
15
+ end
16
+
17
+ def registered_and_exposed_handlers
18
+ end
19
+
4
20
  exposes :help
5
21
  desc "Generates this usage statement"
6
- def help(methods)
7
- method_names = exposed_method_names.map { |n| n.to_s }
8
- documented_names = descriptions.keys.map { |k| k.to_s } & method_names
9
- if methods.empty?
10
- width = documented_names.map { |s| s.length }.max
11
- say "Hello there, I know the following commands:"
12
- documented_names.each { |name| say "#{name.ljust(width)} - #{descriptions[name.to_sym]}" }
13
- say "As well as the following undescribed commands: #{(method_names - documented_names).sort.join(", ")}"
22
+ def help(method)
23
+ method = method.strip
24
+ documentation = self.class.method_documentation
25
+ names = documentation.keys.sort
26
+ if method.blank?
27
+ display_names = names.map { |n| exposed_name(n) }
28
+ width = display_names.map { |d| d.length }.max
29
+ say "Hello there, I know the following documented commands:"
30
+ names.each_with_index do |name, index|
31
+ say "#{display_names[index].ljust(width)} - #{documentation[name].join("; ")}"
32
+ end
14
33
  else
15
- m = methods.first
16
- if documented_names.include? m.to_s
17
- reply "#{m}: #{descriptions[m.to_sym]}"
34
+ if names.include? method
35
+ reply "#{exposed_name(method)} - #{documentation[method].join("; ")}"
18
36
  else
19
37
  reply "I'm sorry, I can't help with #{m} - it seems to be undocumented."
20
38
  end
@@ -24,7 +42,7 @@ module Marvin
24
42
  exposes :about
25
43
  desc "Displays the current marvin and ruby versions."
26
44
  def about(*args)
27
- reply "Marvin v#{Marvin::VERSION::STRING} running on Ruby #{RUBY_VERSION} (#{RUBY_PLATFORM})"
45
+ reply "Marvin v#{Marvin::VERSION} running on Ruby #{RUBY_VERSION} (#{RUBY_PLATFORM})"
28
46
  end
29
47
 
30
48
  end
@@ -0,0 +1,225 @@
1
+ require 'json'
2
+ require 'digest/sha2'
3
+ require 'eventmachine'
4
+ require 'socket'
5
+
6
+ module Marvin
7
+ module Distributed
8
+ class Client < Marvin::AbstractClient
9
+
10
+ attr_accessor :em_connection, :remote_client_host, :remote_client_nick
11
+
12
+ class RemoteClientProxy
13
+ is :loggable
14
+
15
+ def initialize(conn, host_with_port, nickname)
16
+ @connection = conn
17
+ @host_with_port = host_with_port
18
+ @nickname = nickname
19
+ end
20
+
21
+ def nickname
22
+ @nickname
23
+ end
24
+
25
+ def host_with_port
26
+ @host_with_port
27
+ end
28
+
29
+ def method_missing(name, *args)
30
+ logger.debug "Proxying #{name}(#{args.inspect[1..-2]}) to #{@host_with_port}"
31
+ @connection.send_message(:action, {
32
+ "action" => name.to_s,
33
+ "arguments" => args,
34
+ "client-host" => @host_with_port
35
+ })
36
+ end
37
+
38
+ end
39
+
40
+ class EMConnection < Marvin::Distributed::Protocol
41
+
42
+ register_handler_method :event
43
+ register_handler_method :authentication_failed
44
+ register_handler_method :authenticated
45
+ register_handler_method :unauthorized
46
+
47
+ cattr_accessor :stopping
48
+ self.stopping = false
49
+
50
+ attr_accessor :client, :port, :connection_host, :connection_port, :configuration
51
+
52
+ def initialize(*args)
53
+ @configuration = args.last.is_a?(Marvin::Nash) ? args.pop : Marvin::Nash.new
54
+ super(*args)
55
+ @callbacks = {}
56
+ @client = Marvin::Distributed::Client.new(self)
57
+ @authenticated = false
58
+ end
59
+
60
+ def post_init
61
+ super
62
+ logger.info "Connected to distributed server"
63
+ if should_use_tls?
64
+ logger.info "Attempting to initialize tls"
65
+ start_tls
66
+ else
67
+ process_authentication
68
+ end
69
+ end
70
+
71
+ def ssl_handshake_completed
72
+ logger.info "tls handshake completed"
73
+ process_authentication if should_use_tls?
74
+ end
75
+
76
+
77
+ def unbind
78
+ if self.stopping
79
+ logger.info "Stopping distributed client"
80
+ else
81
+ logger.info "Lost connection to distributed client - Scheduling reconnect"
82
+ EventMachine.add_timer(15) { EMConnection.connect(connection_host, connection_port, @configuration) }
83
+ end
84
+ super
85
+ end
86
+
87
+ def process_authentication
88
+ if configuration.token?
89
+ logger.info "Attempting to authenticate..."
90
+ send_message(:authenticate, {:token => configuration.token})
91
+ end
92
+ end
93
+
94
+ def handle_event(options = {})
95
+ event = options["event-name"]
96
+ client_host = options["client-host"]
97
+ client_nick = options["client-nick"]
98
+ options = options["event-options"]
99
+ options = {} unless options.is_a?(Hash)
100
+ return if event.blank?
101
+ begin
102
+ logger.debug "Handling #{event}"
103
+ @client.remote_client_host = client_host
104
+ @client.remote_client_nick = client_nick
105
+ @client.setup_handlers
106
+ @client.dispatch(event.to_sym, options)
107
+ rescue Exception => e
108
+ logger.warn "Got Exception - Forwarding to Remote"
109
+ Marvin::ExceptionTracker.log(e)
110
+ send_message(:exception, {
111
+ "name" => e.class.name,
112
+ "message" => e.message,
113
+ "backtrace" => e.backtrace
114
+ })
115
+ ensure
116
+ logger.debug "Sending completed message"
117
+ send_message(:completed)
118
+ @client.reset!
119
+ end
120
+ end
121
+
122
+ def handle_unauthorized(options = {})
123
+ logger.warn "Attempted action when unauthorized. Stopping client."
124
+ Marvin::Distributed::Client.stop
125
+ end
126
+
127
+ def handle_authenticated(options = {})
128
+ @authenticated = true
129
+ logger.info "Successfully authenticated with #{host_with_port}"
130
+ end
131
+
132
+ def handle_authentication_failed(options = {})
133
+ logger.info "Authentication with #{host_with_port} failed. Stopping."
134
+ Marvin::Distributed::Client.stop
135
+ end
136
+
137
+ def self.connect(host, port, config = Marvin::Nash.new)
138
+ logger.info "Attempting to connect to #{host}:#{port}"
139
+ EventMachine.connect(host, port, self, config) do |c|
140
+ c.connection_host = host
141
+ c.connection_port = port
142
+ end
143
+ end
144
+
145
+ protected
146
+
147
+ def options_for_callback(blk)
148
+ return {} if blk.blank?
149
+ cb_id = "callback-#{seld.object_id}-#{Time.now.to_f}"
150
+ count = 0
151
+ count += 1 while @callbacks.has_key?(Digest::SHA256.hexdigest("#{cb_id}-#{count}"))
152
+ final_id = Digest::SHA256.hexdigest("#{cb_id}-#{count}")
153
+ @callbacks[final_id] = blk
154
+ {"callback-id" => final_id}
155
+ end
156
+
157
+ def process_callback(hash)
158
+ if hash.is_a?(Hash) && hash.has_key?("callback-id")
159
+ callback = @callbacks.delete(hash["callback-id"])
160
+ callback.call(self, hash)
161
+ end
162
+ end
163
+
164
+ def host_with_port
165
+ @host_with_port ||= begin
166
+ port, ip = Socket.unpack_sockaddr_in(get_peername)
167
+ "#{ip}:#{port}"
168
+ end
169
+ end
170
+
171
+ def should_use_tls?
172
+ @using_tls ||= configuration.encrypted?
173
+ end
174
+
175
+ end
176
+
177
+ def initialize(em_connection)
178
+ @em_connection = em_connection
179
+ end
180
+
181
+ def remote_client
182
+ @remote_client ||= RemoteClientProxy.new(@em_connection, @remote_client_host, @remote_client_nick)
183
+ end
184
+
185
+ def reset!
186
+ @remote_client = nil
187
+ @remote_client_nick = nil
188
+ @remote_client_host = nil
189
+ reset_handlers
190
+ end
191
+
192
+ def setup_handlers
193
+ self.class.handlers.each { |h| h.client = remote_client if h.respond_to?(:client=) }
194
+ end
195
+
196
+ def reset_handlers
197
+ self.class.handlers.each { |h| h.client = nil if h.respond_to?(:client=) }
198
+ end
199
+
200
+ class << self
201
+
202
+ def run
203
+ logger.info "Preparing to start distributed client"
204
+ EventMachine.kqueue
205
+ EventMachine.epoll
206
+ EventMachine.run do
207
+ opts = Marvin::Settings.distributed || Marvin::Nash.new
208
+ opts = opts.client || Marvin::Nash.new
209
+ host = opts.host || "0.0.0.0"
210
+ port = (opts.port || 8943).to_i
211
+ EMConnection.connect(host, port, opts)
212
+ end
213
+ end
214
+
215
+ def stop
216
+ logger.info "Stopping distributed client..."
217
+ EMConnection.stopping = true
218
+ EventMachine.stop_event_loop
219
+ end
220
+
221
+ end
222
+
223
+ end
224
+ end
225
+ end
@@ -0,0 +1,85 @@
1
+ module Marvin
2
+ module Distributed
3
+ class Handler < Marvin::Base
4
+
5
+
6
+ EVENT_WHITELIST = [:incoming_message, :incoming_action]
7
+ QUEUE_PROCESSING_SPACING = 3
8
+
9
+ attr_accessor :message_queue
10
+
11
+ def initialize
12
+ super
13
+ @message_queue = []
14
+ end
15
+
16
+ def handle(message, options)
17
+ return unless EVENT_WHITELIST.include?(message)
18
+ super(message, options)
19
+ dispatch(message, options)
20
+ end
21
+
22
+ def dispatch(name, options, client = self.client)
23
+ return if client.blank?
24
+ server = Marvin::Distributed::Server.next
25
+ if server.blank?
26
+ logger.debug "Distributed handler is currently busy - adding to queue"
27
+ # TODO: Add to queued messages, wait
28
+ @message_queue << [name, options, client]
29
+ run! unless running?
30
+ else
31
+ server.dispatch(client, name, options)
32
+ end
33
+ rescue Exception => e
34
+ logger.warn "Error dispatching #{name}"
35
+ Marvin::ExceptionTracker.log(e)
36
+ end
37
+
38
+ def process_queue
39
+ count = [@message_queue.size, Server.free_connections.size].min
40
+ logger.debug "Processing #{count} item(s) from the message queue"
41
+ count.times { |item| dispatch(*@message_queue.shift) }
42
+ if @message_queue.empty?
43
+ logger.debug "The message queue is now empty"
44
+ else
45
+ logger.debug "The message queue still has #{count} item(s)"
46
+ end
47
+ check_queue_progress
48
+ end
49
+
50
+ def running?
51
+ @running_timer.present?
52
+ end
53
+
54
+ def run!
55
+ @running_timer = EventMachine::PeriodicTimer.new(QUEUE_PROCESSING_SPACING) { process_queue }
56
+ end
57
+
58
+ def check_queue_progress
59
+ if @message_queue.blank? && running?
60
+ @running_timer.cancel
61
+ @running_timer = nil
62
+ elsif @message_queue.present? && !running?
63
+ run!
64
+ end
65
+ end
66
+
67
+ class << self
68
+
69
+ def whitelist_event(name)
70
+ EVENT_WHITELIST << name.to_sym
71
+ EVENT_WHITELIST.uniq!
72
+ end
73
+
74
+ def register!(*args)
75
+ # DO NOT register if this is not a normal client.
76
+ return unless Marvin::Loader.client?
77
+ logger.info "Registering distributed handler on #{Marvin::Settings.client}"
78
+ super
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,88 @@
1
+ module Marvin
2
+ module Distributed
3
+ class Protocol < EventMachine::Protocols::LineAndTextProtocol
4
+ is :loggable
5
+
6
+ class_inheritable_accessor :handler_methods
7
+ self.handler_methods = {}
8
+
9
+ attr_accessor :callbacks
10
+
11
+ def receive_line(line)
12
+ line.strip!
13
+ logger.debug "<< #{line}"
14
+ response = JSON.parse(line)
15
+ handle_response(response)
16
+ rescue JSON::ParserError
17
+ logger.debug "JSON parsing error for #{line.inspect}"
18
+ rescue Exception => e
19
+ Marvin::ExceptionTracker.log(e)
20
+ end
21
+
22
+ def send_message(name, arguments = {}, &callback)
23
+ logger.debug "Sending #{name.inspect} to #{self.host_with_port}"
24
+ payload = {
25
+ "message" => name.to_s,
26
+ "options" => arguments,
27
+ "sent-at" => Time.now
28
+ }
29
+ payload.merge!(options_for_callback(callback))
30
+ payload = JSON.dump(payload)
31
+ logger.debug ">> #{payload}"
32
+ send_data "#{payload}\n"
33
+ end
34
+
35
+ def handle_response(response)
36
+ logger.debug "Handling response in distributed protocol (response => #{response.inspect})"
37
+ return unless response.is_a?(Hash) && response.has_key?("message")
38
+ options = response["options"] || {}
39
+ process_response_message(response["message"], options)
40
+ end
41
+
42
+ def host_with_port
43
+ @host_with_port ||= begin
44
+ port, ip = Socket.unpack_sockaddr_in(get_peername)
45
+ "#{ip}:#{port}"
46
+ end
47
+ end
48
+
49
+ protected
50
+
51
+ def options_for_callback(blk)
52
+ return {} if blk.blank?
53
+ cb_id = "callback-#{seld.object_id}-#{Time.now.to_f}"
54
+ count = 0
55
+ count += 1 while @callbacks.has_key?(Digest::SHA256.hexdigest("#{cb_id}-#{count}"))
56
+ final_id = Digest::SHA256.hexdigest("#{cb_id}-#{count}")
57
+ @callbacks ||= {}
58
+ @callbacks[final_id] = blk
59
+ {"callback-id" => final_id}
60
+ end
61
+
62
+ def process_callback(hash)
63
+ @callbacks ||= {}
64
+ if hash.is_a?(Hash) && hash.has_key?("callback-id")
65
+ callback = @callbacks.delete(hash["callback-id"])
66
+ callback.call(self, hash)
67
+ end
68
+ end
69
+
70
+ def process_response_message(message, options)
71
+ method = self.handler_methods[message.to_s]
72
+ if method.present? && respond_to?(method)
73
+ logger.debug "Dispatching #{message} to #{method}"
74
+ send(method, options)
75
+ else
76
+ logger.warn "Got unknown message (#{message}) with options: #{options.inspect}"
77
+ end
78
+ end
79
+
80
+ def self.register_handler_method(name, method = nil)
81
+ name = name.to_s
82
+ method ||= "handle_#{name}".to_sym
83
+ self.handler_methods[name] = method
84
+ end
85
+
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,154 @@
1
+ require 'json'
2
+ require 'digest/sha2'
3
+ require 'eventmachine'
4
+ require 'socket'
5
+
6
+ module Marvin
7
+ module Distributed
8
+ class Server < Protocol
9
+
10
+ register_handler_method :completed
11
+ register_handler_method :exception
12
+ register_handler_method :action
13
+ register_handler_method :authenticate
14
+
15
+ cattr_accessor :free_connections, :action_whitelist
16
+ self.free_connections = []
17
+ self.action_whitelist = [:nick, :pong, :action, :msg, :quit, :part, :join, :command]
18
+
19
+ attr_accessor :processing, :configuration, :using_tls
20
+
21
+ def initialize(*args)
22
+ @configuration = args.last.is_a?(Marvin::Nash) ? args.pop : Marvin::nash.new
23
+ super(*args)
24
+ end
25
+
26
+ def post_init
27
+ super
28
+ @callbacks = {}
29
+ logger.info "Got distributed client connection with #{self.host_with_port}"
30
+ if should_use_tls?
31
+ start_tls
32
+ else
33
+ complete_processing
34
+ end
35
+ end
36
+
37
+ def ssl_handshake_completed
38
+ complete_processing if should_use_tls?
39
+ end
40
+
41
+ def unbind
42
+ logger.info "Lost distributed client connection with #{self.host_with_port}"
43
+ @@free_connections.delete(self)
44
+ super
45
+ end
46
+
47
+ def dispatch(client, name, options)
48
+ @processing = true
49
+ send_message(:event, {
50
+ "event-name" => name.to_s,
51
+ "event-options" => options,
52
+ "client-host" => client.host_with_port,
53
+ "client-nick" => client.nickname
54
+ })
55
+ end
56
+
57
+ def handle_authenticate(options = {})
58
+ return unless requires_auth?
59
+ logger.info "Attempting authentication for distributed client"
60
+ if options["token"].present? && options["token"] == configuration.token
61
+ @authenticated = true
62
+ send_message(:authenticated)
63
+ else
64
+ send_message(:authentication_failed)
65
+ end
66
+ end
67
+
68
+ def handle_completed(options = {})
69
+ return if fails_auth!
70
+ logger.debug "Completed message from #{self.host_with_port}"
71
+ complete_processing
72
+ end
73
+
74
+ def handle_exception(options = {})
75
+ return if fails_auth!
76
+ logger.info "Handling exception on #{self.host_with_port}"
77
+ name = options["name"]
78
+ message = options["message"]
79
+ backtrace = options["backtrace"]
80
+ logger.warn "Error in remote client - #{name}: #{message}"
81
+ [*backtrace].each { |line| logger.warn "--> #{line}" } if backtrace.present?
82
+ end
83
+
84
+ def handle_action(options = {})
85
+ return if fails_auth!
86
+ logger.debug "Handling action from on #{self.host_with_port}"
87
+ server = lookup_client_for(options["client-host"])
88
+ action = options["action"]
89
+ arguments = [*options["arguments"]]
90
+ return if server.blank? || action.blank?
91
+ begin
92
+ a = action.to_sym
93
+ if self.action_whitelist.include?(a)
94
+ server.send(a, *arguments) if server.respond_to?(a)
95
+ else
96
+ logger.warn "Client attempted invalid action #{a.inspect}"
97
+ end
98
+ rescue Exception => e
99
+ Marvin::ExceptionTracker.log(e)
100
+ end
101
+ end
102
+
103
+ def complete_processing
104
+ @@free_connections << self
105
+ @processing = false
106
+ end
107
+
108
+ def start_processing
109
+ @processing = true
110
+ end
111
+
112
+ def lookup_client_for(key)
113
+ Marvin::IRC::Client.connections.detect do |c|
114
+ c.host_with_port == key
115
+ end
116
+ end
117
+
118
+ def requires_auth?
119
+ configuration.token? && !authenticated?
120
+ end
121
+
122
+ def authenticated?
123
+ @authenticated ||= false
124
+ end
125
+
126
+ def should_use_tls?
127
+ @using_tls ||= configuration.encrypted?
128
+ end
129
+
130
+ def fails_auth!
131
+ if requires_auth?
132
+ logger.debug "Authentication missing for distributed client"
133
+ send_message(:unauthorized)
134
+ close_connection_after_writing
135
+ return true
136
+ end
137
+ end
138
+
139
+ def self.start
140
+ opts = Marvin::Settings.distributed || Marvin::Nash.new
141
+ opts = opts.server || Marvin::Nash.new
142
+ host = opts.host || "0.0.0.0"
143
+ port = (opts.port || 8943).to_i
144
+ logger.info "Starting distributed server on #{host}:#{port} (requires authentication = #{opts.token?})"
145
+ EventMachine.start_server(host, port, self, opts)
146
+ end
147
+
148
+ def self.next
149
+ @@free_connections.shift
150
+ end
151
+
152
+ end
153
+ end
154
+ end
@@ -1,14 +1,8 @@
1
- require 'drb'
2
- require 'rinda/ring'
3
-
4
-
5
1
  module Marvin
6
- # Distributed tools for Marvin instances.
7
- # Uses a tuple space etc + DRb to provide
8
- # IRC Processing across the network.
9
2
  module Distributed
10
- autoload :RingServer, 'marvin/distributed/ring_server'
11
- autoload :DispatchHandler, 'marvin/distributed/dispatch_handler'
12
- autoload :DRbClient, 'marvin/distributed/drb_client'
3
+ autoload :Protocol, 'marvin/distributed/protocol'
4
+ autoload :Server, 'marvin/distributed/server'
5
+ autoload :Handler, 'marvin/distributed/handler'
6
+ autoload :Client, 'marvin/distributed/client'
13
7
  end
14
8
  end