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.
- data/bin/marvin +22 -156
- data/handlers/keiki_thwopper.rb +21 -0
- data/handlers/tweet_tweet.rb +1 -3
- data/lib/marvin/abstract_client.rb +75 -189
- data/lib/marvin/abstract_parser.rb +9 -11
- data/lib/marvin/base.rb +134 -101
- data/lib/marvin/client/actions.rb +104 -0
- data/lib/marvin/client/default_handlers.rb +97 -0
- data/lib/marvin/command_handler.rb +60 -49
- data/lib/marvin/console.rb +4 -31
- data/lib/marvin/core_commands.rb +30 -12
- data/lib/marvin/distributed/client.rb +225 -0
- data/lib/marvin/distributed/handler.rb +85 -0
- data/lib/marvin/distributed/protocol.rb +88 -0
- data/lib/marvin/distributed/server.rb +154 -0
- data/lib/marvin/distributed.rb +4 -10
- data/lib/marvin/dsl.rb +103 -0
- data/lib/marvin/exception_tracker.rb +7 -4
- data/lib/marvin/irc/client.rb +127 -99
- data/lib/marvin/irc/event.rb +14 -10
- data/lib/marvin/irc.rb +0 -1
- data/lib/marvin/middle_man.rb +1 -1
- data/lib/marvin/parsers/command.rb +10 -8
- data/lib/marvin/parsers/prefixes/host_mask.rb +12 -7
- data/lib/marvin/parsers/prefixes/server.rb +1 -1
- data/lib/marvin/parsers/ragel_parser.rb +59 -52
- data/lib/marvin/parsers/ragel_parser.rl +6 -7
- data/lib/marvin/parsers/simple_parser.rb +4 -9
- data/lib/marvin/parsers.rb +1 -2
- data/lib/marvin/settings.rb +29 -79
- data/lib/marvin/test_client.rb +20 -26
- data/lib/marvin/util.rb +10 -3
- data/lib/marvin.rb +42 -39
- data/templates/boot.erb +3 -0
- data/templates/connections.yml.erb +10 -0
- data/templates/debug_handler.erb +5 -0
- data/templates/hello_world.erb +10 -0
- data/templates/rakefile.erb +15 -0
- data/templates/settings.yml.erb +8 -0
- data/{config/setup.rb → templates/setup.erb} +8 -10
- data/templates/test_helper.erb +17 -0
- data/test/abstract_client_test.rb +63 -0
- data/test/parser_comparison.rb +2 -2
- data/test/parser_test.rb +3 -3
- data/test/test_helper.rb +58 -6
- metadata +51 -83
- data/README.textile +0 -105
- data/TUTORIAL.textile +0 -54
- data/VERSION.yml +0 -4
- data/config/boot.rb +0 -14
- data/config/connections.yml.sample +0 -5
- data/config/settings.yml.sample +0 -13
- data/handlers/logging_handler.rb +0 -89
- data/lib/marvin/core_ext.rb +0 -11
- data/lib/marvin/daemon.rb +0 -71
- data/lib/marvin/data_store.rb +0 -73
- data/lib/marvin/dispatchable.rb +0 -99
- data/lib/marvin/distributed/dispatch_handler.rb +0 -83
- data/lib/marvin/distributed/drb_client.rb +0 -78
- data/lib/marvin/distributed/ring_server.rb +0 -41
- data/lib/marvin/handler.rb +0 -12
- data/lib/marvin/irc/server/abstract_connection.rb +0 -84
- data/lib/marvin/irc/server/base_connection.rb +0 -66
- data/lib/marvin/irc/server/channel.rb +0 -115
- data/lib/marvin/irc/server/named_store.rb +0 -14
- data/lib/marvin/irc/server/remote_interface.rb +0 -77
- data/lib/marvin/irc/server/user/handle_mixin.rb +0 -140
- data/lib/marvin/irc/server/user.rb +0 -5
- data/lib/marvin/irc/server/user_connection.rb +0 -134
- data/lib/marvin/irc/server/virtual_user_connection.rb +0 -80
- data/lib/marvin/irc/server.rb +0 -71
- data/lib/marvin/loader.rb +0 -149
- data/lib/marvin/logger.rb +0 -86
- data/lib/marvin/options.rb +0 -42
- data/lib/marvin/parsers/regexp_parser.rb +0 -93
- data/lib/marvin/status.rb +0 -72
- data/script/client +0 -3
- data/script/console +0 -3
- data/script/distributed_client +0 -3
- data/script/install +0 -1
- data/script/ring_server +0 -4
- data/script/server +0 -4
- data/script/status +0 -3
- data/spec/marvin/abstract_client_test.rb +0 -38
- data/spec/spec_helper.rb +0 -14
data/lib/marvin/console.rb
CHANGED
@@ -5,7 +5,7 @@ module Marvin
|
|
5
5
|
|
6
6
|
module BaseExtensions
|
7
7
|
def parse(line)
|
8
|
-
Marvin::Settings.
|
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.
|
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
|
-
|
41
|
+
ARGV.replace []
|
42
|
+
IRB.start
|
70
43
|
end
|
71
44
|
|
72
45
|
def self.run
|
data/lib/marvin/core_commands.rb
CHANGED
@@ -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(
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
say "
|
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
|
-
|
16
|
-
|
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
|
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
|
data/lib/marvin/distributed.rb
CHANGED
@@ -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 :
|
11
|
-
autoload :
|
12
|
-
autoload :
|
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
|