marvin 0.8.1 → 0.8.2
Sign up to get free protection for your applications and to get access to all the features.
- data/handlers/raw_connection_logger.rb +53 -0
- data/lib/marvin.rb +7 -3
- data/lib/marvin/abstract_client.rb +55 -8
- data/lib/marvin/abstract_parser.rb +3 -0
- data/lib/marvin/client/default_handlers.rb +2 -4
- data/lib/marvin/command_handler.rb +1 -1
- data/lib/marvin/distributed/server.rb +1 -1
- data/lib/marvin/irc/client.rb +7 -2
- data/lib/marvin/irc/event.rb +1 -1
- data/lib/marvin/middle_man.rb +22 -77
- data/test/abstract_client_test.rb +1 -1
- metadata +4 -3
@@ -0,0 +1,53 @@
|
|
1
|
+
class RawConnectionLogger < Marvin::Base
|
2
|
+
|
3
|
+
on_event :client_connected, :setup_logging
|
4
|
+
on_event :reloaded, :setup_logging
|
5
|
+
|
6
|
+
on_event :client_disconnected, :teardown_logging
|
7
|
+
on_event :reloaded, :teardown_logging
|
8
|
+
|
9
|
+
@@files = {}
|
10
|
+
|
11
|
+
def setup_logging
|
12
|
+
logger.info "Setting up logging"
|
13
|
+
@@files[client.host_with_port] = nil
|
14
|
+
self.file # Access the ivar to load it
|
15
|
+
end
|
16
|
+
|
17
|
+
def teardown_logging
|
18
|
+
logger.info "Stopping logging"
|
19
|
+
if @@files[client.host_with_port].present?
|
20
|
+
@@files[client.host_with_port].puts ""
|
21
|
+
@@files[client.host_with_port].close
|
22
|
+
@@files.delete client.host_with_port
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
on_event :incoming_line do
|
27
|
+
log_line true
|
28
|
+
end
|
29
|
+
|
30
|
+
on_event :outgoing_line do
|
31
|
+
log_line false
|
32
|
+
end
|
33
|
+
|
34
|
+
def log_line(incoming = true)
|
35
|
+
color = incoming ? :green : :blue
|
36
|
+
prefix = incoming ? "<< " : ">> "
|
37
|
+
file.puts Marvin::ANSIFormatter.format(color, "#{prefix}#{options.line}".strip)
|
38
|
+
end
|
39
|
+
|
40
|
+
def file
|
41
|
+
@@files[client.host_with_port] ||= begin
|
42
|
+
logger.info "Loading Logger..."
|
43
|
+
uri = URI.parse("irc://#{client.host_with_port}")
|
44
|
+
log_path = File.join(Marvin::Settings.root, "log", "connections", "#{uri.host}-#{uri.port}-#{Time.now.to_i}.log")
|
45
|
+
logger.info "Logging to #{log_path}"
|
46
|
+
FileUtils.mkdir_p(File.dirname(log_path))
|
47
|
+
file = File.open(log_path, "a+")
|
48
|
+
file.sync = true if file.respond_to?(:sync=)
|
49
|
+
file
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
data/lib/marvin.rb
CHANGED
@@ -5,7 +5,7 @@ require 'perennial'
|
|
5
5
|
module Marvin
|
6
6
|
include Perennial
|
7
7
|
|
8
|
-
VERSION = [0, 8,
|
8
|
+
VERSION = [0, 8, 2, 0]
|
9
9
|
|
10
10
|
# Client
|
11
11
|
autoload :TestClient, 'marvin/test_client'
|
@@ -39,8 +39,12 @@ module Marvin
|
|
39
39
|
|
40
40
|
end
|
41
41
|
|
42
|
-
|
43
|
-
|
42
|
+
# Returns a string of the current version,
|
43
|
+
# optionally including a build number.
|
44
|
+
# @param [Boolean] include_build include a build version in the string
|
45
|
+
def self.version(include_build = nil)
|
46
|
+
include_build = VERSION[3].to_i > 0 if include_build.nil?
|
47
|
+
VERSION[0, (include_build ? 4 : 3)].join(".")
|
44
48
|
end
|
45
49
|
|
46
50
|
has_library :util, :abstract_client, :abstract_parser, :irc, :exception_tracker
|
@@ -1,6 +1,14 @@
|
|
1
1
|
require "marvin/irc/event"
|
2
2
|
|
3
3
|
module Marvin
|
4
|
+
# An abstract class implementing (and mixing in) a lot of
|
5
|
+
# the default functionality involved on handling an irc
|
6
|
+
# connection.
|
7
|
+
#
|
8
|
+
# To provide an implementation, you must subclass and implement
|
9
|
+
# #send_line, .add_reconnect, and any other loader etc methods.
|
10
|
+
#
|
11
|
+
# @see Marvin::IRC::Client
|
4
12
|
class AbstractClient
|
5
13
|
|
6
14
|
is :dispatchable, :loggable
|
@@ -29,6 +37,9 @@ module Marvin
|
|
29
37
|
# current connection, dispatching a :client_connected event
|
30
38
|
# once it has finished. During this process, it will
|
31
39
|
# call #client= on each handler if they respond to it.
|
40
|
+
#
|
41
|
+
# @see Marvin::AbstractClient.setup
|
42
|
+
# @see Marvin::Base#client=
|
32
43
|
def process_connect
|
33
44
|
self.class.setup
|
34
45
|
logger.info "Initializing the current instance"
|
@@ -38,48 +49,71 @@ module Marvin
|
|
38
49
|
dispatch :client_connected
|
39
50
|
end
|
40
51
|
|
52
|
+
# Handles a lost connection / disconnect, stopping the loader if it's the
|
53
|
+
# last connection (purposely terminated) otherwise scheduling a reconnection
|
54
|
+
#
|
55
|
+
# @see Marvin::Loader.stop!
|
41
56
|
def process_disconnect
|
42
57
|
logger.info "Handling disconnect for #{host_with_port}"
|
43
58
|
connections.delete(self)
|
44
59
|
dispatch :client_disconnected
|
45
|
-
|
60
|
+
if @disconnect_expected
|
61
|
+
Marvin::Loader.stop! if connections.blank?
|
62
|
+
else
|
46
63
|
logger.warn "Unexpectly lost connection to server; adding reconnect"
|
47
64
|
self.class.add_reconnect @connection_config
|
48
|
-
else
|
49
|
-
Marvin::Loader.stop! if connections.blank?
|
50
65
|
end
|
51
66
|
end
|
52
67
|
|
68
|
+
# Iterates over handles and calls client= when defined. Used before dispatching
|
69
|
+
# in order to ensure each hander has the correct client. Note that this needs
|
70
|
+
# to be improved soon.
|
53
71
|
def setup_handlers
|
54
72
|
handlers.each { |h| h.client = self if h.respond_to?(:client=) }
|
55
73
|
end
|
56
74
|
|
75
|
+
# If @@development is true, We'll attempt to reload any changed files (namely,
|
76
|
+
# handlers).s
|
57
77
|
def process_development
|
58
78
|
Marvin::Reloading.reload! if @@development
|
59
79
|
end
|
60
80
|
|
81
|
+
# Before dispatching, check if we need to reload and setup handlers correctly.
|
61
82
|
def pre_dispatching
|
62
83
|
process_development
|
63
84
|
setup_handlers
|
64
85
|
end
|
65
86
|
|
66
|
-
# Sets the current class-wide settings of this IRC Client
|
67
|
-
#
|
68
|
-
#
|
87
|
+
# Sets the current class-wide settings of this IRC Client to
|
88
|
+
# an instance of Marvin::Nash with the properties of a hash
|
89
|
+
# / nash passed in.
|
90
|
+
#
|
91
|
+
# The new configuration will be normalized before use (namely,
|
92
|
+
# it will convert nested items to nashes)
|
93
|
+
#
|
94
|
+
# @param [Marvin::Nash, Hash] config the new configuration
|
69
95
|
def self.configuration=(config)
|
70
96
|
config = Marvin::Nash.new(config.to_hash) unless config.is_a?(Marvin::Nash)
|
71
97
|
@@configuration = config.normalized
|
72
98
|
end
|
73
99
|
|
100
|
+
# Check if if the cient class has been setup yet
|
101
|
+
# @return [Boolean] is the client class setup
|
74
102
|
def self.setup?
|
75
103
|
@setup ||= false
|
76
104
|
end
|
77
105
|
|
106
|
+
# Conditional configure
|
78
107
|
def self.setup
|
79
108
|
return if setup?
|
80
109
|
configure
|
81
110
|
end
|
82
111
|
|
112
|
+
# Configure the class - namely, merge the app-wide configuration
|
113
|
+
# and if given a block, merge the results in.
|
114
|
+
#
|
115
|
+
# @yieldparam [Marvin::Nash] an empty nash
|
116
|
+
# @yieldreturn [Marvin::Nash] any changed settings.
|
83
117
|
def self.configure
|
84
118
|
config = Marvin::Nash.new
|
85
119
|
config.merge! Marvin::Settings.configuration
|
@@ -88,32 +122,45 @@ module Marvin
|
|
88
122
|
config.merge! nash
|
89
123
|
end
|
90
124
|
@@configuration = config
|
91
|
-
# Help is only currently available on an instance running
|
92
|
-
# distributed handler.
|
125
|
+
# Help is only currently available on an instance NOT running the distributed handler.
|
93
126
|
Marvin::CoreCommands.register! unless Marvin::Distributed::Handler.registered?
|
94
127
|
@setup = true
|
95
128
|
end
|
96
129
|
|
97
130
|
## Handling all of the the actual client stuff.
|
98
131
|
|
132
|
+
# Receives a raw line (without EOL characters) and dispatch the
|
133
|
+
# incoming_line event. Once that's done, parse the line and
|
134
|
+
# dispatch an event if present.
|
135
|
+
#
|
136
|
+
# @param [String] line the incoming line
|
99
137
|
def receive_line(line)
|
100
138
|
dispatch :incoming_line, :line => line
|
101
139
|
event = Marvin::Settings.parser.parse(line)
|
102
140
|
dispatch(event.to_incoming_event_name, event.to_hash) unless event.nil?
|
103
141
|
end
|
104
142
|
|
143
|
+
# Returns a list of default channels to join on connect
|
144
|
+
# @return [Array<String>] an array of channel names that are joined on connect
|
105
145
|
def default_channels
|
106
146
|
@default_channels ||= []
|
107
147
|
end
|
108
148
|
|
149
|
+
# Sets the list of default channels to join
|
150
|
+
# @param [Array<String>] channels the array of channel names
|
109
151
|
def default_channels=(channels)
|
110
152
|
@default_channels = channels.to_a.map { |c| c.to_s }
|
111
153
|
end
|
112
154
|
|
155
|
+
# Returns the irc server and port
|
156
|
+
# @return [String] server port in "server:port" format
|
113
157
|
def host_with_port
|
114
158
|
@host_with_port ||= "#{server}:#{port}"
|
115
159
|
end
|
116
160
|
|
161
|
+
# Returns a list available nicks / nicks to try
|
162
|
+
# on connect.
|
163
|
+
# @return [Array<String>] The array of nicks
|
117
164
|
def nicks
|
118
165
|
if @nicks.blank? && !@nicks_loaded
|
119
166
|
logger.info "Setting default nick list"
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module Marvin
|
2
|
+
# Abstract Class for implementing abstract parsers.
|
2
3
|
class AbstractParser
|
3
4
|
|
4
5
|
attr_accessor :line, :command, :event
|
@@ -15,6 +16,8 @@ module Marvin
|
|
15
16
|
@event
|
16
17
|
end
|
17
18
|
|
19
|
+
# Parses a line and return the associated event.
|
20
|
+
# @return [Marvin::IRC:Event] the parsed event
|
18
21
|
def self.parse(line)
|
19
22
|
new(line.strip).to_event
|
20
23
|
end
|
@@ -42,8 +42,6 @@ module Marvin
|
|
42
42
|
handle_welcome
|
43
43
|
when Marvin::IRC::Replies[:ERR_NICKNAMEINUSE]
|
44
44
|
handle_nick_taken
|
45
|
-
when Marvin::IRC::Replies[:RPL_TOPIC]
|
46
|
-
handle_channel_topic
|
47
45
|
end
|
48
46
|
code = opts[:code].to_i
|
49
47
|
args = Marvin::Util.arguments(opts[:data])
|
@@ -73,7 +71,7 @@ module Marvin
|
|
73
71
|
logger.info "Attemping to set nickname to '#{next_nick}'"
|
74
72
|
nick next_nick
|
75
73
|
else
|
76
|
-
logger.
|
74
|
+
logger.error "No Nicknames available - QUITTING"
|
77
75
|
quit
|
78
76
|
end
|
79
77
|
end
|
@@ -89,7 +87,7 @@ module Marvin
|
|
89
87
|
# Make sure we show user server errors
|
90
88
|
def handle_incoming_error(opts = {})
|
91
89
|
if opts[:message].present?
|
92
|
-
logger.
|
90
|
+
logger.error "Server ERROR Message: #{opts[:message]}"
|
93
91
|
end
|
94
92
|
end
|
95
93
|
|
@@ -64,7 +64,7 @@ module Marvin
|
|
64
64
|
name, command, data = message.split(/\s+/, 2)
|
65
65
|
return if name !~ /^#{client.nickname}:/i
|
66
66
|
else
|
67
|
-
command, data = message.
|
67
|
+
command, data = message.split(/\s+/, 2)
|
68
68
|
end
|
69
69
|
data ||= ""
|
70
70
|
if (command_name = extract_command_name(command)).present?
|
@@ -19,7 +19,7 @@ module Marvin
|
|
19
19
|
attr_accessor :processing, :configuration
|
20
20
|
|
21
21
|
def initialize(*args)
|
22
|
-
@configuration = args.last.is_a?(Marvin::Nash) ? args.pop : Marvin::
|
22
|
+
@configuration = args.last.is_a?(Marvin::Nash) ? args.pop : Marvin::Nash.new
|
23
23
|
super(*args)
|
24
24
|
end
|
25
25
|
|
data/lib/marvin/irc/client.rb
CHANGED
@@ -85,7 +85,10 @@ module Marvin::IRC
|
|
85
85
|
## Client specific details
|
86
86
|
|
87
87
|
def send_line(*args)
|
88
|
-
|
88
|
+
args.each do |line|
|
89
|
+
@em_connection.send_line(line)
|
90
|
+
dispatch :outgoing_line, :line => line
|
91
|
+
end
|
89
92
|
end
|
90
93
|
|
91
94
|
class << self
|
@@ -163,7 +166,9 @@ module Marvin::IRC
|
|
163
166
|
|
164
167
|
# Registers a callback handle that will be periodically run.
|
165
168
|
def periodically(timing, event_callback)
|
166
|
-
EventMachine.add_periodic_timer(timing)
|
169
|
+
EventMachine.add_periodic_timer(timing) do
|
170
|
+
dispatch(event_callback.to_sym)
|
171
|
+
end
|
167
172
|
end
|
168
173
|
|
169
174
|
end
|
data/lib/marvin/irc/event.rb
CHANGED
data/lib/marvin/middle_man.rb
CHANGED
@@ -1,29 +1,27 @@
|
|
1
1
|
module Marvin
|
2
|
-
# The middle man is a class you can use to register
|
3
|
-
# other handlers on. e.g. it acts as a way to 'filter'
|
4
|
-
# incoming and outgoing messages. Akin to Rack / WSGI
|
5
|
-
# middleware.
|
6
2
|
class MiddleMan
|
3
|
+
is :loggable, :dispatchable
|
7
4
|
|
8
|
-
# Set the logger cattr to the default marvin logger.
|
9
|
-
cattr_accessor :logger
|
10
|
-
self.logger ||= Marvin::Logger
|
11
|
-
|
12
|
-
# By default, we are *not* setup.
|
13
|
-
@@setup = false
|
14
|
-
|
15
|
-
# Our list of subhandlers. We make sure
|
16
|
-
# the list is unique to our subclass / class.
|
17
|
-
class_inheritable_accessor :subhandlers
|
18
|
-
self.subhandlers = []
|
19
|
-
|
20
|
-
# Finally, the client.
|
21
5
|
attr_reader :client
|
22
6
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
7
|
+
class << self
|
8
|
+
def setup?
|
9
|
+
@@setup ||= false
|
10
|
+
end
|
11
|
+
|
12
|
+
def setup!
|
13
|
+
Marvin::Settings.client.register_handler self.new
|
14
|
+
@@setup = true
|
15
|
+
end
|
16
|
+
|
17
|
+
# Setup iff setup hasn't been done.
|
18
|
+
def setup
|
19
|
+
setup! unless setup?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# When we're told to set the client, not only do we set out own instance
|
24
|
+
# but we also echo the command down to all of our sub-clients.
|
27
25
|
def client=(new_client)
|
28
26
|
@client = new_client
|
29
27
|
setup_subhandler_clients
|
@@ -35,68 +33,15 @@ module Marvin
|
|
35
33
|
|
36
34
|
# Filter incoming events.
|
37
35
|
def handle(message, options)
|
38
|
-
# Process the current event.
|
39
36
|
message, options = process_event(message, options)
|
40
|
-
|
41
|
-
self.send(full_handler_name, opts) if respond_to?(full_handler_name)
|
42
|
-
self.subhandlers.each do |sh|
|
43
|
-
forward_message_to_handler(sh, message, options, full_handler_name)
|
44
|
-
end
|
45
|
-
rescue HaltHandlerProcessing
|
46
|
-
logger.info "Asked to halt the filter processing chain inside a middleman."
|
47
|
-
rescue Exception => e
|
48
|
-
logger.fatal "Exception processing handle #{message}"
|
49
|
-
Marvin::ExceptionTracker.log(e)
|
50
|
-
end
|
51
|
-
|
52
|
-
class << self
|
53
|
-
|
54
|
-
def setup?
|
55
|
-
@@setup
|
56
|
-
end
|
57
|
-
|
58
|
-
# Forcefully do the setup routine.
|
59
|
-
def setup!
|
60
|
-
# Register ourselves as a new handler.
|
61
|
-
Marvin::Settings.client.register_handler self.new
|
62
|
-
@@setup = true
|
63
|
-
end
|
64
|
-
|
65
|
-
# Setup iff setup hasn't been done.
|
66
|
-
def setup
|
67
|
-
return if self.setup?
|
68
|
-
self.setup!
|
69
|
-
end
|
70
|
-
|
71
|
-
# Register a single subhandler.
|
72
|
-
def register_handler(handler, run_setup = true)
|
73
|
-
self.setup if run_setup
|
74
|
-
self.subhandlers << handler unless handler.blank?
|
75
|
-
end
|
76
|
-
|
77
|
-
# Registers a group of subhandlers.
|
78
|
-
def register_handlers(*args)
|
79
|
-
self.setup
|
80
|
-
args.each { |h| self.register_handler(h, false) }
|
81
|
-
end
|
82
|
-
|
37
|
+
dispatch(message, options)
|
83
38
|
end
|
84
39
|
|
85
40
|
private
|
86
41
|
|
87
42
|
def setup_subhandler_clients
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
# This should probably be extracted into some sort of Util's library as
|
92
|
-
# it's shared across a couple of classes but I really can't be bothered
|
93
|
-
# at the moment - I just want to test the concept.
|
94
|
-
def forward_message_to_handler(handler, message, options, full_handler_name)
|
95
|
-
if handler.respond_to?(full_handler_name)
|
96
|
-
handler.send(full_handler_name, options)
|
97
|
-
elsif handler.respond_to?(:handle)
|
98
|
-
handler.handle message, options
|
99
|
-
end
|
43
|
+
current_client = self.client
|
44
|
+
handlers.each { |sh| sh.client = current_client if sh.respond_to?(:client=) }
|
100
45
|
end
|
101
46
|
|
102
47
|
end
|
@@ -23,7 +23,7 @@ class AbstractClientTest < Test::Unit::TestCase
|
|
23
23
|
assert_dispatched :client_connected, 0, {}
|
24
24
|
end
|
25
25
|
|
26
|
-
should "dispatch
|
26
|
+
should "dispatch the correct events" do
|
27
27
|
assert_resets_client
|
28
28
|
client.default_channels = ["#awesome", "#rock"]
|
29
29
|
client.process_connect
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: marvin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Darcy Laycock
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-12-08 00:00:00 +08:00
|
13
13
|
default_executable: marvin
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -43,7 +43,7 @@ dependencies:
|
|
43
43
|
version: "0"
|
44
44
|
version:
|
45
45
|
- !ruby/object:Gem::Dependency
|
46
|
-
name:
|
46
|
+
name: shoulda
|
47
47
|
type: :development
|
48
48
|
version_requirement:
|
49
49
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -81,6 +81,7 @@ files:
|
|
81
81
|
- handlers/debug_handler.rb
|
82
82
|
- handlers/hello_world.rb
|
83
83
|
- handlers/keiki_thwopper.rb
|
84
|
+
- handlers/raw_connection_logger.rb
|
84
85
|
- handlers/simple_logger.rb
|
85
86
|
- handlers/tweet_tweet.rb
|
86
87
|
- lib/marvin.rb
|