marvin 0.8.1 → 0.8.2
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/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
|