marvin 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 +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
|