From 2336af5525533c9201b8da9162e2a7e7ea4b87fe Mon Sep 17 00:00:00 2001 From: Jeremy Fincher Date: Fri, 30 Jul 2004 06:52:21 +0000 Subject: [PATCH] Updated to allow multiple servers. --- RELNOTES | 5 ++- plugins/Relay.py | 4 +- scripts/supybot | 11 +----- src/asyncoreDrivers.py | 31 +++++++++++---- src/conf.py | 33 +++++++++++++--- src/drivers.py | 9 ++--- src/socketDrivers.py | 88 ++++++++++++++++++++++-------------------- src/twistedDrivers.py | 23 +++++++++-- 8 files changed, 126 insertions(+), 78 deletions(-) diff --git a/RELNOTES b/RELNOTES index cdab036e6..655011056 100644 --- a/RELNOTES +++ b/RELNOTES @@ -9,12 +9,13 @@ configuration variable won't work. To fix this, add these lines to your configuration: supybot.networks.default: - supybot.networks..server: + supybot.networks..servers: supybot.networks..password: Where is the name of the network you're connecting to, is the old supybot.server, and is the old -supybot.password. +supybot.password. Also note the "servers" rather than "server": we +now allow the configuration of multiple servers per network. Since we support multiple networks and multiple servers now, the --server option has been removed from scripts/supybot, as has the diff --git a/plugins/Relay.py b/plugins/Relay.py index d8713fec7..ef75ac037 100644 --- a/plugins/Relay.py +++ b/plugins/Relay.py @@ -183,13 +183,13 @@ class Relay(callbacks.Privmsg): if serverPort is None: raise ValueError, '_connect requires a (server, port) if ' \ 'the network is not registered.' - conf.registerNetwork(network, server=('%s:%s' % serverPort)) + conf.registerNetwork(network, servers=('%s:%s' % serverPort,)) if makeNew: self.log.info('Creating new Irc for relaying to %s.', network) newIrc = irclib.Irc(network) newIrc.state.history = realIrc.state.history newIrc.callbacks = realIrc.callbacks - driver = drivers.newDriver(serverPort, newIrc) + driver = drivers.newDriver(newIrc) else: newIrc = realIrc self._addIrc(newIrc) diff --git a/scripts/supybot b/scripts/supybot index 8ac7949eb..83bc36dcb 100755 --- a/scripts/supybot +++ b/scripts/supybot @@ -109,7 +109,6 @@ if __name__ == '__main__': # -p (profiling) # -O (optimizing) # -n, --nick (nick) - # -s, --server (server) # --startup (commands to run onStart) # --connect (commands to run afterConnect) # --config (configuration values) @@ -216,14 +215,6 @@ if __name__ == '__main__': sys.stderr.write('default when the bot starts.') sys.exit(-1) - server = network.server() - if ':' in server: - serverAndPort = server.split(':', 1) - serverAndPort[1] = int(serverAndPort[1]) - server = tuple(serverAndPort) - else: - server = (server, 6667) - if options.optimize: # This doesn't work anymore. __builtins__.__debug__ = False @@ -262,7 +253,7 @@ if __name__ == '__main__': irc = irclib.Irc(network=defaultNetwork) callback = Owner.Class() irc.addCallback(callback) - driver = drivers.newDriver(server, irc) + driver = drivers.newDriver(irc) if options.debug: for (name, module) in sys.modules.iteritems(): diff --git a/src/asyncoreDrivers.py b/src/asyncoreDrivers.py index fd561ec45..2b95fa408 100644 --- a/src/asyncoreDrivers.py +++ b/src/asyncoreDrivers.py @@ -63,28 +63,45 @@ class AsyncoreRunnerDriver(drivers.IrcDriver): class AsyncoreDriver(asynchat.async_chat, object): - def __init__(self, (server, port), irc): + def __init__(self, irc): asynchat.async_chat.__init__(self) - self.server = (server, port) self.irc = irc - self.irc.driver = self self.buffer = '' + self.servers = () + self.networkGroup = conf.supybot.networks.get(self.irc.network) self.set_terminator('\n') + # XXX: Use utils.getSocket. self.create_socket(socket.AF_INET, socket.SOCK_STREAM) try: - self.connect(self.server) + self.connect(self._getNextServer()) except socket.error, e: - log.warning('Error connecting to %s: %s', self.server[0], e) + log.warning('Error connecting to %s: %s', self.currentServer, e) self.reconnect(wait=True) + def _getServers(self): + # We do this, rather than itertools.cycle the servers in __init__, + # because otherwise registry updates given as setValues or sets + # wouldn't be visible until a restart. + return self.networkGroup.servers()[:] # Be sure to copy! + + def _getNextServer(self): + if not self.servers: + self.servers = self._getServers() + assert self.servers, 'Servers value for %s is empty.' % \ + self.networkGroup.name + server = self.servers.pop(0) + self.currentServer = '%s:%s' % server + return server + def _scheduleReconnect(self, at=60): when = time.time() + at if not world.dying: whenS = log.timestamp(when) - log.info('Scheduling reconnect to %s at %s', self.server[0], whenS) + log.info('Scheduling reconnect to %s at %s', + self.currentServer, whenS) def makeNewDriver(): self.irc.reset() - driver = self.__class__(self.server, self.irc) + driver = self.__class__(self.irc) schedule.addEvent(makeNewDriver, when) def writable(self): diff --git a/src/conf.py b/src/conf.py index d205c2c24..024582ca2 100644 --- a/src/conf.py +++ b/src/conf.py @@ -163,20 +163,41 @@ registerGlobalValue(supybot, 'user', registry.String('Supybot %s' % version, """Determines the user the bot sends to the server.""")) -# TODO: Make this check for validity. registerGroup(supybot, 'networks') -registerGlobalValue(supybot.networks, 'default', registry.String('', - """Determines what the default network joined by the bot will be.""")) +registerGlobalValue(supybot.networks, 'default', + registry.String('', """Determines what the default network joined by the + bot will be.""")) -def registerNetwork(name, password='', server=''): +class Servers(registry.SpaceSeparatedListOfStrings): + def normalize(self, s): + if ':' not in s: + s += ':6667' + return s + + def convert(self, s): + s = self.normalize(s) + (server, port) = s.split(':') + port = int(port) + return (server, port) + + def __call__(self): + L = registry.SpaceSeparatedListOfStrings.__call__(self) + return map(self.convert, L) + + def __str__(self): + return ' '.join(registry.SpaceSeparatedListOfStrings.__call__(self)) + +def registerNetwork(name, password='', servers=()): name = intern(name) network = registerGroup(supybot.networks, name) registerGlobalValue(network, 'password', registry.String(password, """Determines what password will be used on %s. Yes, we know that technically passwords are server-specific and not network-specific, but this is the best we can do right now.""" % name)) - registerGlobalValue(network, 'server', registry.String(server, - """Determines what server the bot will connect to for %s.""" % name)) + registerGlobalValue(network, 'servers', Servers(servers, + """Determines what servers the bot will connect to for %s. Each will + be tried in order, wrapping back to the first when the cycle is + completed.""" % name)) return network # Let's fill our networks. diff --git a/src/drivers.py b/src/drivers.py index 9ae2f8c08..a48a50342 100644 --- a/src/drivers.py +++ b/src/drivers.py @@ -54,8 +54,6 @@ class IrcDriver(object): """Base class for drivers.""" def __init__(self): add(self.name(), self) - if not hasattr(self, 'irc'): - self.irc = None # This is to satisfy PyChecker. def run(self): raise NotImplementedError @@ -113,7 +111,7 @@ def run(): del _drivers[name] _drivers[name] = driver -def newDriver(server, irc, moduleName=None): +def newDriver(irc, moduleName=None): """Returns a new driver for the given server using the irc given and using conf.supybot.driverModule to determine what driver to pick.""" if moduleName is None: @@ -128,9 +126,8 @@ def newDriver(server, irc, moduleName=None): elif not moduleName.startswith('supybot.'): moduleName = 'supybot.' + moduleName driverModule = __import__(moduleName, {}, {}, ['not empty']) - log.debug('Creating new driver for %s:%s (%s)', - server[0], server[1], moduleName) - driver = driverModule.Driver(server, irc) + log.debug('Creating new driver for %s.', irc) + driver = driverModule.Driver(irc) irc.driver = driver return driver diff --git a/src/socketDrivers.py b/src/socketDrivers.py index b0c50693c..7fbcace00 100644 --- a/src/socketDrivers.py +++ b/src/socketDrivers.py @@ -53,30 +53,47 @@ import supybot.drivers as drivers import supybot.ircmsgs as ircmsgs import supybot.schedule as schedule -instances = 0 -originalPoll = conf.supybot.drivers.poll() -def resetPoll(): - log.info('Resetting supybot.drivers.poll to %s', originalPoll) - conf.supybot.drivers.poll.setValue(originalPoll) -atexit.register(resetPoll) - +reconnectWaits = (0, 60, 300) class SocketDriver(drivers.IrcDriver): - def __init__(self, (server, port), irc, reconnectWaits=(0, 60, 300)): - global instances - instances += 1 - conf.supybot.drivers.poll.setValue(originalPoll / instances) - self.server = (server, port) - drivers.IrcDriver.__init__(self) # Must come after server is set. + def __init__(self, irc): self.irc = irc - self.irc.driver = self + drivers.IrcDriver.__init__(self) # Must come after setting irc. + self.networkGroup = conf.supybot.networks.get(self.irc.network) + self.servers = () + self.eagains = 0 self.inbuffer = '' self.outbuffer = '' self.connected = False - self.eagains = 0 self.reconnectWaitsIndex = 0 self.reconnectWaits = reconnectWaits self.connect() + def _getServers(self): + # We do this, rather than itertools.cycle the servers in __init__, + # because otherwise registry updates given as setValues or sets + # wouldn't be visible until a restart. + return self.networkGroup.servers()[:] # Be sure to copy! + + def _getNextServer(self): + if not self.servers: + self.servers = self._getServers() + assert self.servers, 'Servers value for %s is empty.' % \ + self.networkGroup.name + server = self.servers.pop(0) + self.currentServer = '%s:%s' % server + return server + + def _handleSocketError(self, e): + # (11, 'Resource temporarily unavailable') raised if connect + # hasn't finished yet. We'll keep track of how many we get. + if e.args[0] != 11 and self.eagains > 120: + log.warning('Disconnect from %s: %s.', + self.currentServer, e.args[1]) + self.reconnect(wait=True) + else: + log.debug('Got EAGAIN, current count: %s.', self.eagains) + self.eagains += 1 + def _sendIfMsgs(self): msgs = [self.irc.takeMsg()] while msgs[-1] is not None: @@ -89,15 +106,7 @@ class SocketDriver(drivers.IrcDriver): self.outbuffer = self.outbuffer[sent:] self.eagains = 0 except socket.error, e: - # (11, 'Resource temporarily unavailable') raised if connect - # hasn't finished yet. - if e.args[0] != 11 and self.eagains > 120: - server = '%s:%s' % self.server - log.warning('Disconnect from %s: %s', server, e.args[1]) - self.reconnect(wait=True) - else: - log.debug('Got EAGAIN, current count: %s', self.eagains) - self.eagains += 1 + self._handleSocketError(e) def run(self): if not self.connected: @@ -119,13 +128,7 @@ class SocketDriver(drivers.IrcDriver): except socket.timeout: pass except socket.error, e: - # Same as with _sendIfMsgs. - if e.args[0] != 11 or self.eagains > 120: - log.warning('Disconnect from %s: %s', self.server, e) - self.reconnect(wait=True) - else: - log.debug('Got EAGAIN, current count: %s', self.eagains) - self.eagains += 1 + self._handleSocketError(e) return self._sendIfMsgs() @@ -133,7 +136,7 @@ class SocketDriver(drivers.IrcDriver): self.reconnect(reset=False, **kwargs) def reconnect(self, wait=False, reset=True): - server = '%s:%s' % self.server + server = self._getNextServer() if reset: log.debug('Resetting %s.', self.irc) self.irc.reset() @@ -143,16 +146,17 @@ class SocketDriver(drivers.IrcDriver): log.info('Reconnect called on driver for %s.', self.irc) self.conn.close() elif not wait: - log.info('Connecting to %s.', server) + log.info('Connecting to %s.', self.currentServer) self.connected = False if wait: - log.info('Reconnect to %s waiting.', server) + log.info('Reconnect to %s waiting.', self.currentServer) self._scheduleReconnect() return try: - self.conn = utils.getSocket(self.server[0]) + self.conn = utils.getSocket(server[0]) except socket.error, e: - log.warning('Error connecting to %s: %s', server, e.args[1]) + log.warning('Error connecting to %s: %s', + self.currentServer, e.args[1]) self.reconnect(wait=True) return # We allow more time for the connect here, since it might take longer. @@ -161,7 +165,7 @@ class SocketDriver(drivers.IrcDriver): if self.reconnectWaitsIndex < len(self.reconnectWaits)-1: self.reconnectWaitsIndex += 1 try: - self.conn.connect(self.server) + self.conn.connect(server) self.conn.settimeout(conf.supybot.drivers.poll()) except socket.error, e: if e.args[0] == 115: @@ -172,7 +176,7 @@ class SocketDriver(drivers.IrcDriver): 'check for %s', whenS) schedule.addEvent(self._checkAndWriteOrReconnect, when) else: - log.warning('Error connecting to %s: %s', self.server[0], e) + log.warning('Error connecting to %s: %s', self.currentServer,e) self.reconnect(wait=True) return self.connected = True @@ -186,15 +190,15 @@ class SocketDriver(drivers.IrcDriver): self.connected = True self.reconnectWaitPeriodsIndex = 0 else: - log.warning('Error connecting to %s: Timed out.', self.server[0]) + log.warning('Error connecting to %s: Timed out.',self.currentServer) self.reconnect() def _scheduleReconnect(self): when = time.time() + self.reconnectWaits[self.reconnectWaitsIndex] if not world.dying: whenS = log.timestamp(when) - server = '%s:%s' % self.server - log.info('Scheduling reconnect to %s at %s', server, whenS) + log.info('Scheduling reconnect to %s at %s', + self.irc.network, whenS) schedule.addEvent(self.reconnect, when) def die(self): @@ -203,7 +207,7 @@ class SocketDriver(drivers.IrcDriver): # self.irc.die() Kill off the ircs yourself, jerk! def name(self): - return '%s%s' % (self.__class__.__name__, self.server) + return '%s(%s)' % (self.__class__.__name__, self.irc) Driver = SocketDriver diff --git a/src/twistedDrivers.py b/src/twistedDrivers.py index 675ee1c47..865decebc 100644 --- a/src/twistedDrivers.py +++ b/src/twistedDrivers.py @@ -92,12 +92,29 @@ class SupyIrcProtocol(LineReceiver): class SupyReconnectingFactory(ReconnectingClientFactory): maxDelay = 300 protocol = SupyIrcProtocol - def __init__(self, (server, port), irc): + def __init__(self, irc): self.irc = irc - self.server = (server, port) - reactor.connectTCP(server, port, self) + self.networkGroup = conf.supybot.networks.get(self.irc.network) + self.servers = () + reactor.connectTCP('', 0, self) + def _getServers(self): + # We do this, rather than itertools.cycle the servers in __init__, + # because otherwise registry updates given as setValues or sets + # wouldn't be visible until a restart. + return self.networkGroup.servers()[:] # Be sure to copy! + + def _getNextServer(self): + if not self.servers: + self.servers = self._getServers() + assert self.servers, 'Servers value for %s is empty.' % \ + self.networkGroup.name + server = self.servers.pop(0) + self.currentServer = '%s:%s' % server + return server + def buildProtocol(self, addr): + addr = self._getNextServer() protocol = ReconnectingClientFactory.buildProtocol(self, addr) protocol.irc = self.irc return protocol