Sutto-marvin 0.4.0 → 0.8.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|