marvin 0.8.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/marvin +33 -0
- data/handlers/debug_handler.rb +5 -0
- data/handlers/hello_world.rb +9 -0
- data/handlers/keiki_thwopper.rb +21 -0
- data/handlers/simple_logger.rb +24 -0
- data/handlers/tweet_tweet.rb +19 -0
- data/lib/marvin.rb +56 -0
- data/lib/marvin/abstract_client.rb +146 -0
- data/lib/marvin/abstract_parser.rb +29 -0
- data/lib/marvin/base.rb +195 -0
- data/lib/marvin/client/actions.rb +104 -0
- data/lib/marvin/client/default_handlers.rb +97 -0
- data/lib/marvin/command_handler.rb +91 -0
- data/lib/marvin/console.rb +50 -0
- data/lib/marvin/core_commands.rb +49 -0
- data/lib/marvin/distributed.rb +8 -0
- 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/dsl.rb +103 -0
- data/lib/marvin/exception_tracker.rb +19 -0
- data/lib/marvin/exceptions.rb +11 -0
- data/lib/marvin/irc.rb +7 -0
- data/lib/marvin/irc/client.rb +168 -0
- data/lib/marvin/irc/event.rb +39 -0
- data/lib/marvin/irc/replies.rb +154 -0
- data/lib/marvin/logging_handler.rb +76 -0
- data/lib/marvin/middle_man.rb +103 -0
- data/lib/marvin/parsers.rb +9 -0
- data/lib/marvin/parsers/command.rb +107 -0
- data/lib/marvin/parsers/prefixes.rb +8 -0
- data/lib/marvin/parsers/prefixes/host_mask.rb +35 -0
- data/lib/marvin/parsers/prefixes/server.rb +24 -0
- data/lib/marvin/parsers/ragel_parser.rb +720 -0
- data/lib/marvin/parsers/ragel_parser.rl +143 -0
- data/lib/marvin/parsers/simple_parser.rb +35 -0
- data/lib/marvin/settings.rb +31 -0
- data/lib/marvin/test_client.rb +58 -0
- data/lib/marvin/util.rb +54 -0
- 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/templates/setup.erb +31 -0
- data/templates/test_helper.erb +17 -0
- data/test/abstract_client_test.rb +63 -0
- data/test/parser_comparison.rb +62 -0
- data/test/parser_test.rb +266 -0
- data/test/test_helper.rb +62 -0
- data/test/util_test.rb +57 -0
- metadata +136 -0
@@ -0,0 +1,104 @@
|
|
1
|
+
module Marvin
|
2
|
+
class AbstractClient
|
3
|
+
|
4
|
+
## General IRC Functions / Actions
|
5
|
+
|
6
|
+
# Sends a specified command to the server.
|
7
|
+
# Takes name (e.g. :privmsg) and all of the args.
|
8
|
+
# Very simply formats them as a string correctly
|
9
|
+
# and calls send_data with the results.
|
10
|
+
def command(name, *args)
|
11
|
+
# First, get the appropriate command
|
12
|
+
name = name.to_s.upcase
|
13
|
+
args = args.flatten
|
14
|
+
args << util.last_param(args.pop)
|
15
|
+
send_line "#{name} #{args.compact.join(" ").strip}\r\n"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Join one or more channels on the current server
|
19
|
+
# e.g.
|
20
|
+
# client.join "#marvin-testing"
|
21
|
+
# client.join ["#marvin-testing", "#rubyonrails"]
|
22
|
+
# client.join "#marvin-testing", "#rubyonrails"
|
23
|
+
def join(*channels_to_join)
|
24
|
+
channels_to_join = channels_to_join.flatten.map { |c| util.channel_name(c) }
|
25
|
+
# If you're joining multiple channels at once, we join them together
|
26
|
+
command :JOIN, channels_to_join.join(",")
|
27
|
+
channels_to_join.each { |channel| dispatch :outgoing_join, :target => channel }
|
28
|
+
logger.info "Sent JOIN for channels #{channels_to_join.join(", ")}"
|
29
|
+
end
|
30
|
+
|
31
|
+
# Parts a channel, with an optional reason
|
32
|
+
# e.g.
|
33
|
+
# part "#marvin-testing"
|
34
|
+
# part "#marvin-testing", "Ninjas stole by felafel"
|
35
|
+
def part(channel, reason = nil)
|
36
|
+
channel = util.channel_name(channel)
|
37
|
+
# Send the command anyway, even if we're not a
|
38
|
+
# a recorded member something might of happened.
|
39
|
+
command :part, channel, reason
|
40
|
+
if channels.include?(channel)
|
41
|
+
dispatch :outgoing_part, :target => channel, :reason => reason
|
42
|
+
logger.info "Parted channel #{channel} - #{reason.present? ? reason : "Non given"}"
|
43
|
+
else
|
44
|
+
logger.warn "Parted channel #{channel} but wasn't recorded as member of channel"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Quites from a server, first parting all channels if a second
|
49
|
+
# argument is passed as true
|
50
|
+
# e.g.
|
51
|
+
# quit
|
52
|
+
# quit "Going to grab some z's"
|
53
|
+
def quit(reason = nil, part_before_quit = false)
|
54
|
+
@disconnect_expected = true
|
55
|
+
# If the user wants to part before quitting, they should
|
56
|
+
# pass a second, true, parameter
|
57
|
+
if part_before_quit
|
58
|
+
logger.info "Preparing to part from channels before quitting"
|
59
|
+
channels.to_a.each { |chan| part(chan, reason) }
|
60
|
+
logger.info "Parted from all channels, quitting"
|
61
|
+
end
|
62
|
+
command :quit, reason
|
63
|
+
dispatch :outgoing_quit
|
64
|
+
# Remove the connections from the pool
|
65
|
+
connections.delete(self)
|
66
|
+
logger.info "Quit from #{host_with_port}"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Sends a message to a target (either a channel or a user)
|
70
|
+
# e.g.
|
71
|
+
# msg "#marvin-testing", "Hello there!"
|
72
|
+
# msg "SuttoL", "Hey, I'm playing with marvin!"
|
73
|
+
def msg(target, message)
|
74
|
+
command :privmsg, target, message
|
75
|
+
dispatch :outgoing_message, :target => target, :message => message
|
76
|
+
logger.info "Message #{target} - #{message}"
|
77
|
+
end
|
78
|
+
|
79
|
+
# Does a CTCP action in a channel (equiv. to doing /me in most IRC clients)
|
80
|
+
# e.g.
|
81
|
+
# action "#marvin-testing", "is about to sleep"
|
82
|
+
# action "SuttoL", "is about to sleep"
|
83
|
+
def action(target, message)
|
84
|
+
command :privmsg, target, "\01ACTION #{message.strip}\01"
|
85
|
+
dispatch :outgoing_action, :target => target, :message => message
|
86
|
+
logger.info "Action sent to #{target} - #{message}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def pong(data)
|
90
|
+
command :pong, data
|
91
|
+
dispatch :outgoing_pong
|
92
|
+
logger.info "PONG sent to #{host_with_port} w/ data - #{data}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def nick(new_nick)
|
96
|
+
logger.info "Changing nick to #{new_nick}"
|
97
|
+
command :nick, new_nick
|
98
|
+
@nickname = new_nick
|
99
|
+
dispatch :outgoing_nick, :new_nick => new_nick
|
100
|
+
logger.info "Nick changed to #{new_nick}"
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Marvin
|
2
|
+
class AbstractClient
|
3
|
+
|
4
|
+
# Default handlers
|
5
|
+
|
6
|
+
# The default handler for all things initialization-related
|
7
|
+
# on the client. Usually, this will send the user command,
|
8
|
+
# set out nick, join all of the channels / rooms we wish
|
9
|
+
# to be in and if a password is specified in the configuration,
|
10
|
+
# it will also attempt to identify us.
|
11
|
+
def handle_client_connected(opts = {})
|
12
|
+
logger.info "About to handle client connected"
|
13
|
+
# If the pass is set
|
14
|
+
unless pass.blank?
|
15
|
+
logger.info "Sending pass for connection"
|
16
|
+
command :pass, pass
|
17
|
+
end
|
18
|
+
# IRC Connection is establish so we send all the required commands to the server.
|
19
|
+
logger.info "Setting default nickname"
|
20
|
+
nick nicks.shift
|
21
|
+
logger.info "Sending user command"
|
22
|
+
command :user, configuration.user, "0", "*", configuration.name
|
23
|
+
rescue Exception => e
|
24
|
+
Marvin::ExceptionTracker.log(e)
|
25
|
+
end
|
26
|
+
|
27
|
+
# handle a bunch of default events that happen at a connection
|
28
|
+
# level instead of a per-app level.
|
29
|
+
|
30
|
+
# The default response for PING's - it simply replies
|
31
|
+
# with a PONG.
|
32
|
+
def handle_incoming_ping(opts = {})
|
33
|
+
logger.info "Received Incoming Ping - Handling with a PONG"
|
34
|
+
pong(opts[:data])
|
35
|
+
end
|
36
|
+
|
37
|
+
# TODO: Get the correct mapping for a given
|
38
|
+
# Code.
|
39
|
+
def handle_incoming_numeric(opts = {})
|
40
|
+
case opts[:code]
|
41
|
+
when Marvin::IRC::Replies[:RPL_WELCOME]
|
42
|
+
handle_welcome
|
43
|
+
when Marvin::IRC::Replies[:ERR_NICKNAMEINUSE]
|
44
|
+
handle_nick_taken
|
45
|
+
when Marvin::IRC::Replies[:RPL_TOPIC]
|
46
|
+
handle_channel_topic
|
47
|
+
end
|
48
|
+
code = opts[:code].to_i
|
49
|
+
args = Marvin::Util.arguments(opts[:data])
|
50
|
+
dispatch :incoming_numeric_processed, :code => code, :data => args
|
51
|
+
end
|
52
|
+
|
53
|
+
def handle_welcome
|
54
|
+
logger.info "Welcome received from server"
|
55
|
+
# If a password is specified, we will attempt to message
|
56
|
+
# NickServ to identify ourselves.
|
57
|
+
say ":IDENTIFY #{self.configuration.password}", "NickServ" if configuration.password.present?
|
58
|
+
# Join the default channels IF they're already set
|
59
|
+
# Note that Marvin::IRC::Client.connect will set them AFTER this stuff is run.
|
60
|
+
join default_channels
|
61
|
+
end
|
62
|
+
|
63
|
+
# The default handler for when a users nickname is taken on
|
64
|
+
# on the server. It will attempt to get the nicknickname from
|
65
|
+
# the nicknames part of the configuration (if available) and
|
66
|
+
# will then call #nick to change the nickname.
|
67
|
+
def handle_nick_taken
|
68
|
+
logger.info "Nickname '#{nickname}' on #{server} taken, trying next."
|
69
|
+
logger.info "Available Nicknames: #{nicks.empty? ? "None" : nicks.join(", ")}"
|
70
|
+
if !nicks.empty?
|
71
|
+
logger.info "Getting next nickname to switch"
|
72
|
+
next_nick = nicks.shift # Get the next nickname
|
73
|
+
logger.info "Attemping to set nickname to '#{next_nick}'"
|
74
|
+
nick next_nick
|
75
|
+
else
|
76
|
+
logger.fatal "No Nicknames available - QUITTING"
|
77
|
+
quit
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Only record joins when you've successfully joined the channel.
|
82
|
+
def handle_incoming_join(opts = {})
|
83
|
+
if opts[:nick] == @nickname
|
84
|
+
channels << opts[:target]
|
85
|
+
logger.info "Successfully joined channel #{opts[:target]}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Make sure we show user server errors
|
90
|
+
def handle_incoming_error(opts = {})
|
91
|
+
if opts[:message].present?
|
92
|
+
logger.info "Got ERROR Message: #{opts[:message]}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Marvin
|
4
|
+
class CommandHandler < Base
|
5
|
+
|
6
|
+
class_inheritable_accessor :command_prefix
|
7
|
+
self.command_prefix = ""
|
8
|
+
|
9
|
+
@@exposed_method_mapping = Hash.new { |h,k| h[k] = [] }
|
10
|
+
@@method_descriptions = Hash.new { |h,k| h[k] = {} }
|
11
|
+
@@registered_classes = Set.new
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def command(name, method_desc = nil, &blk)
|
16
|
+
exposes name
|
17
|
+
desc method_desc unless method_desc.blank?
|
18
|
+
define_method(name, &blk)
|
19
|
+
end
|
20
|
+
|
21
|
+
def prefix_is(p)
|
22
|
+
self.command_prefix = p
|
23
|
+
end
|
24
|
+
|
25
|
+
def exposes(*args)
|
26
|
+
args.each { |name| @@exposed_method_mapping[self] << name.to_sym }
|
27
|
+
end
|
28
|
+
|
29
|
+
def exposed_methods
|
30
|
+
methods = []
|
31
|
+
klass = self
|
32
|
+
while klass != Object
|
33
|
+
methods += @@exposed_method_mapping[klass]
|
34
|
+
klass = klass.superclass
|
35
|
+
end
|
36
|
+
return methods.uniq.compact
|
37
|
+
end
|
38
|
+
|
39
|
+
def prefix_regexp
|
40
|
+
/^#{command_prefix}/
|
41
|
+
end
|
42
|
+
|
43
|
+
def desc(description)
|
44
|
+
@last_description = description
|
45
|
+
end
|
46
|
+
|
47
|
+
def exposed_name(method)
|
48
|
+
"#{command_prefix}#{method}"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
on_event :incoming_message, :check_for_commands
|
54
|
+
|
55
|
+
def check_for_commands
|
56
|
+
message = options.message.to_s.strip
|
57
|
+
data, command = nil, nil
|
58
|
+
if from_channel?
|
59
|
+
name, command, data = message.split(/\s+/, 2)
|
60
|
+
return if name !~ /^#{client.nickname}:/i
|
61
|
+
else
|
62
|
+
command, data = message.splt(/\s+/, 2)
|
63
|
+
end
|
64
|
+
data ||= ""
|
65
|
+
if (command_name = extract_command_name(command)).present?
|
66
|
+
logger.info "Processing command '#{command_name}' for #{from}"
|
67
|
+
send(command_name, data.to_s) if respond_to?(command_name)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def extract_command_name(command)
|
72
|
+
re = self.class.prefix_regexp
|
73
|
+
if command =~ re
|
74
|
+
method_name = command.gsub(re, "").underscore.to_sym
|
75
|
+
return method_name if self.class.exposed_methods.include?(method_name)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def exposed_name(name)
|
80
|
+
self.class.exposed_name(name)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.method_added(name)
|
84
|
+
if @last_description.present?
|
85
|
+
@@method_descriptions[self][name.to_sym] = @last_description
|
86
|
+
@last_description = nil
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'irb'
|
2
|
+
|
3
|
+
module Marvin
|
4
|
+
class Console
|
5
|
+
|
6
|
+
module BaseExtensions
|
7
|
+
def parse(line)
|
8
|
+
Marvin::Settings.parser.parse(line)
|
9
|
+
end
|
10
|
+
|
11
|
+
def logger
|
12
|
+
Marvin::Logger.logger
|
13
|
+
end
|
14
|
+
|
15
|
+
def client
|
16
|
+
$client ||= Marvin::Settings.client.new(:port => 6667, :server => "irc.freenode.net")
|
17
|
+
end
|
18
|
+
|
19
|
+
def user(reset = false)
|
20
|
+
unless @user_created || reset
|
21
|
+
server.receive_line "NICK SuttoL"
|
22
|
+
server.receive_line "USER SuttoL 0 * :SuttoL"
|
23
|
+
@user_created = true
|
24
|
+
end
|
25
|
+
return server.connection_implementation
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(file = $0)
|
30
|
+
@file = file
|
31
|
+
setup_irb
|
32
|
+
end
|
33
|
+
|
34
|
+
def setup_irb
|
35
|
+
# This is a bit hacky, surely there is a better way?
|
36
|
+
# e.g. some way to specify which scope irb runs in.
|
37
|
+
eval("include Marvin::Console::BaseExtensions", TOPLEVEL_BINDING)
|
38
|
+
end
|
39
|
+
|
40
|
+
def run
|
41
|
+
ARGV.replace []
|
42
|
+
IRB.start
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.run
|
46
|
+
self.new.run
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Marvin
|
2
|
+
class CoreCommands < CommandHandler
|
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
|
+
|
20
|
+
exposes :help
|
21
|
+
desc "Generates this usage statement"
|
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
|
33
|
+
else
|
34
|
+
if names.include? method
|
35
|
+
reply "#{exposed_name(method)} - #{documentation[method].join("; ")}"
|
36
|
+
else
|
37
|
+
reply "I'm sorry, I can't help with #{m} - it seems to be undocumented."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
exposes :about
|
43
|
+
desc "Displays the current marvin and ruby versions."
|
44
|
+
def about(*args)
|
45
|
+
reply "Marvin v#{Marvin::VERSION} running on Ruby #{RUBY_VERSION} (#{RUBY_PLATFORM})"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
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
|