Skip to content

Commit f1ca8ef

Browse files
author
bspkrs
committed
attempt to refactor IRC reconnect
1 parent 9f48440 commit f1ca8ef

File tree

4 files changed

+141
-159
lines changed

4 files changed

+141
-159
lines changed

AsyncSocket.py

-3
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,6 @@ def handle_connect(self):
5555
def handle_close(self):
5656
self.logger.info("Closing Socket")
5757
self.close()
58-
# if not self.bot.isTerminating:
59-
# self.logger.info('Throwing sys.exit(408)')
60-
# sys.exit(408)
6158

6259
def handle_read(self):
6360
self.recvBuffer = self.recvBuffer + self.recv(8192)

BotBase.py

+123-108
Original file line numberDiff line numberDiff line change
@@ -13,61 +13,67 @@
1313
from IRCHandler import CmdHandler,CmdGenerator,Sender,Color, EOL
1414
from ConfigHandler import AdvConfigParser
1515

16-
# TODO: Refactor the bothandler to be per-bot and have it handle reconnect.
17-
# TODO: There should be a new class BotManager that maintains a list of BotHandlers that are running.
16+
1817
class BotHandler(object):
19-
botList = {}
20-
21-
@classmethod
22-
def addBot(cls, botname, bot):
23-
if not botname in cls.botList or not cls.botList[botname]:
24-
cls.botList[botname] = bot
25-
26-
@classmethod
27-
def remBot(cls, botname):
28-
if botname in cls.botList and cls.botList[botname]:
29-
retval = cls.botList[botname]
30-
cls.botList.__delitem__(botname)
31-
retval.onShuttingDown()
32-
return retval
33-
return None
34-
35-
@classmethod
36-
def runAll(cls):
37-
cls.startAll()
38-
cls.loop()
39-
40-
@classmethod
41-
def setAllKilled(cls):
42-
for bot in cls.botList.values():
43-
bot.isTerminating = True
44-
45-
@classmethod
46-
def stopAll(cls):
47-
for bot in cls.botList.values():
48-
bot.onShuttingDown()
49-
50-
@classmethod
51-
def loop(cls):
52-
try:
53-
asyncore.loop()
54-
except KeyboardInterrupt as e:
55-
print("Keyboard Interrupt: Shutting down.")
56-
cls.setAllKilled()
57-
cls.stopAll()
58-
except:
59-
print('Other Exception: Shutting down.')
60-
cls.stopAll()
61-
raise
6218

63-
@classmethod
64-
def startAll(cls):
65-
for bot in cls.botList.values():
66-
bot.onStartUp()
67-
bot.connect()
19+
def __init__(self, bot, reconnect_wait=15, reset_attempt_secs=300, max_reconnects=10):
20+
self.bot = bot
21+
self.reconnect_wait = reconnect_wait
22+
self.reset_attempt_secs = reset_attempt_secs
23+
self.max_reconnects = max_reconnects
24+
25+
def start(self):
26+
self.bot.onStartUp()
27+
self.bot.connect()
28+
return self
29+
30+
def stop(self):
31+
self.bot.onShuttingDown()
32+
return self
33+
34+
def setKilled(self):
35+
self.bot.isTerminating = True
36+
return self
37+
38+
def run(self):
39+
restart = True
40+
last_start = 0
41+
reconnect_attempts = 0
42+
43+
while restart:
44+
if last_start != 0 and reconnect_attempts != 0:
45+
self.bot.logger.warning('Attempting IRC reconnection in %d seconds...' % self.reconnect_wait)
46+
time.sleep(self.reconnect_wait)
47+
48+
last_start = time.time()
49+
50+
if not self.bot.isRunning:
51+
self.bot.resetSockets()
52+
self.start()
53+
54+
try:
55+
asyncore.loop()
56+
except KeyboardInterrupt as e:
57+
print("Keyboard Interrupt: Shutting down.")
58+
self.stop()
59+
restart = False
60+
except:
61+
print('Other Exception: Shutting down.')
62+
self.stop()
63+
raise
64+
65+
if not self.bot.isTerminating:
66+
self.bot.logger.warning('IRC connection was lost.')
67+
68+
if time.time() - last_start > self.reset_attempt_secs:
69+
reconnect_attempts = 0
70+
71+
reconnect_attempts += 1
72+
restart = reconnect_attempts <= self.max_reconnects
73+
6874

6975
class BotBase(object):
70-
def __init__(self, configfile = None, nspass = None, backupcfg=False):
76+
def __init__(self, configfile=None, nspass=None, backupcfg=False):
7177
self.configfile = configfile if configfile else 'bot.cfg'
7278

7379
if backupcfg and os.path.exists(self.configfile):
@@ -146,20 +152,17 @@ def __init__(self, configfile = None, nspass = None, backupcfg=False):
146152
self.isIdentified = False #Turn to true when nick/ident commands are sent
147153
self.isReady = False #Turn to true after RPL_ENDOFMOTD. Every join/nick etc commands should be sent once this is True.
148154
self.isTerminating = False #This is set to true by the stop command to bypass restarting
149-
self.isRunning = True
155+
self.isRunning = False
150156

151-
self.socket = AsyncSocket.AsyncSocket(self, self.host, self.port, self.floodLimit)
152-
self.dccSocketv4 = DCCSocket.DCCSocket(self, False)
153-
if socket.has_ipv6:
154-
try:
155-
self.dccSocketv6 = DCCSocket.DCCSocket(self, True)
156-
except socket.error, e:
157-
if e.message.find('A socket operation was attempted to an unreachable network') != -1:
158-
self.dccSocketv6 = None
159-
self.cmdHandler = CmdHandler(self, self.socket)
157+
self.socket = None
158+
self.dccSocketv4 = None
159+
self.dccSocketv6 = None
160+
self.resetSockets()
161+
self.cmdHandler = CmdHandler(self)
160162

161-
self.registerCommand('dcc', self.requestDCC, ['any'], 0, 0, "", "Requests a DCC connection to the bot.")
162-
self.registerCommand('more', self.sendMore, ['any'], 0, 1, "[clear]", 'Gets the next %d queued command results. Commands that can queue results will tell you so.' % self.moreCount)
163+
self.registerCommand('dcc', self.requestDCC, ['any'], 0, 0, "", "Requests a DCC connection to the bot.")
164+
#self.registerCommand('pub', self.pubCmd, ['any'], 1, 999, "<command>", "Executes a command and sends the results to the destination channel.")
165+
self.registerCommand('more', self.sendMore, ['any'], 0, 1, "[clear]", 'Gets the next %d queued command results. Commands that can queue results will tell you so.' % self.moreCount)
163166

164167
self.registerCommand('useradd', self.useradd, ['admin'], 2, 2, "<user> <group>","Adds user to group.")
165168
self.registerCommand('userrm', self.userrm, ['admin'], 2, 2, "<user> <group>","Removes user from group.")
@@ -171,7 +174,6 @@ def __init__(self, configfile = None, nspass = None, backupcfg=False):
171174
self.registerCommand('groupget', self.groupget, ['admin'], 0, 0, "", "Returns a list of groups and commands.")
172175
self.registerCommand('groupmeta', self.groupmeta, ['admin'], 3, 999, "<group> <key> <value>", "Add a value to a group key")
173176

174-
175177
self.registerCommand('banadd', self.banadd, ['admin'], 2, 2, "<user|host> <cmd>", "Bans the user from using the specified command.")
176178
self.registerCommand('banrm', self.banrm, ['admin'], 2, 2, "<user|host> <cmd>", "Remove a ban on an user.")
177179
self.registerCommand('banget', self.banget, ['admin'], 1, 1, "<user|host>", "Returns the ban list for the given user.")
@@ -184,6 +186,62 @@ def __init__(self, configfile = None, nspass = None, backupcfg=False):
184186
if self.about_msg and self.about_msg != '':
185187
self.registerCommand('about', self.aboutcmd, ['any'], 0, 0, '', 'About this bot.')
186188

189+
def resetSockets(self):
190+
self.socket = AsyncSocket.AsyncSocket(self, self.host, self.port, self.floodLimit)
191+
self.dccSocketv4 = DCCSocket.DCCSocket(self, False)
192+
if socket.has_ipv6:
193+
try:
194+
self.dccSocketv6 = DCCSocket.DCCSocket(self, True)
195+
except socket.error, e:
196+
if e.message.find('A socket operation was attempted to an unreachable network') != -1:
197+
self.dccSocketv6 = None
198+
199+
def run(self):
200+
if self.host == "":
201+
self.logger.error("Please set an IRC server in the config file.")
202+
return
203+
204+
self.onStartUp()
205+
self.connect()
206+
try:
207+
asyncore.loop()
208+
except KeyboardInterrupt as e:
209+
self.logger.info("Shutting down.")
210+
if not self.isTerminating:
211+
self.isTerminating = True
212+
self.onShuttingDown()
213+
except:
214+
self.onShuttingDown()
215+
raise
216+
217+
def connect(self):
218+
self.isRunning = True
219+
self.socket.doConnect()
220+
221+
def onStartUp(self):
222+
pass
223+
224+
def onShuttingDown(self):
225+
if self.isRunning:
226+
self.logger.info('Shutting down bot')
227+
if self.cmdHandler.monitor_thread:
228+
self.cmdHandler.monitor_thread.cancel()
229+
for user in self.users.values():
230+
if user.dccSocket:
231+
user.dccSocket.handle_close()
232+
233+
self.isRunning = False
234+
self.isReady = False
235+
self.isIdentified = False
236+
self.dccSocketv4.handle_close()
237+
if self.dccSocketv6:
238+
self.dccSocketv6.handle_close()
239+
self.socket.handle_close()
240+
241+
def killSelf(self, bot, sender, dest, cmd, args):
242+
self.logger.info("Killing self.")
243+
self.isTerminating = True
244+
187245
# User handling commands
188246
def useradd(self, bot, sender, dest, cmd, args):
189247
user = args[0].lower()
@@ -348,7 +406,7 @@ def helpcmd(self, bot, sender, dest, cmd, args):
348406
maxcmdlen = len(max(self.cmdHandler.commands.keys(), key=len))
349407
maxargslen = len(max([i['descargs'] for i in self.cmdHandler.commands.values()], key=len))
350408

351-
formatstr = "§B%%%ds %%%ds§N : %%s"%(maxcmdlen * -1, maxargslen * -1)
409+
formatstr = "§B%%%ds %%%ds§N : %%s" %(maxcmdlen * -1, maxargslen * -1)
352410
showall = len(args) > 0 and args[0] == '*'
353411
allowedcmds = []
354412

@@ -507,49 +565,6 @@ def updateConfig(self):
507565

508566
self.config.write(fp)
509567

510-
def run(self):
511-
if self.host == "":
512-
self.logger.error("Please set an IRC server in the config file.")
513-
return
514-
515-
self.onStartUp()
516-
self.connect()
517-
try:
518-
asyncore.loop()
519-
except KeyboardInterrupt as e:
520-
self.logger.info("Shutting down.")
521-
if not self.isTerminating:
522-
self.isTerminating = True
523-
self.onShuttingDown()
524-
except:
525-
self.onShuttingDown()
526-
raise
527-
528-
def connect(self):
529-
self.socket.doConnect()
530-
531-
def onStartUp(self):
532-
pass
533-
534-
def onShuttingDown(self):
535-
if self.isRunning:
536-
self.logger.info('Shutting down bot')
537-
if self.cmdHandler.monitor_thread:
538-
self.cmdHandler.monitor_thread.cancel()
539-
for user in self.users.values():
540-
if user.dccSocket:
541-
user.dccSocket.handle_close()
542-
543-
self.isRunning = False
544-
self.dccSocketv4.handle_close()
545-
if self.dccSocketv6:
546-
self.dccSocketv6.handle_close()
547-
self.socket.handle_close()
548-
549-
def killSelf(self, bot, sender, dest, cmd, args):
550-
self.logger.info("Killing self.")
551-
self.isTerminating = True
552-
553568
#IRC COMMANDS QUICK ACCESS
554569
def sendRaw(self, msg):
555570
for line in [line.strip('\r\n') for line in msg.split(EOL) if line.strip('\r\n') != '']:

IRCHandler.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,8 @@ def getDurationStr(timeint):
197197
return time.strftime(formatstr, time.gmtime(timeint))
198198

199199
class CmdHandler(object):
200-
def __init__(self, bot, socket):
200+
def __init__(self, bot):
201201
self.bot = bot
202-
self.socket = socket
203202
self.sender = ""
204203
self.cmd = ""
205204
self.params = []
@@ -508,22 +507,22 @@ def onCOMMAND(self, sender, dest, command):
508507

509508
if len(args) < cmd['minarg'] or len(args) > cmd['maxarg']:
510509
if cmd['descargs']:
511-
self.bot.sendNotice(sender.nick, "Wrong syntax : %s"%cmd['descargs'])
510+
self.bot.sendNotice(sender.nick, "Wrong syntax : %s" % cmd['descargs'])
512511
return
513512
else:
514-
self.bot.sendNotice(sender.nick, "Wrong number of arguments : min = %s, max = %s"%(cmd['minarg'], cmd['maxarg']))
513+
self.bot.sendNotice(sender.nick, "Wrong number of arguments : min = %s, max = %s" % (cmd['minarg'], cmd['maxarg']))
515514
return
516515

517516
#We already know this sender
518517
if sender.nick in self.bot.users and sender == self.bot.users[sender.nick]:
519-
self.logger.info("Found user %s in current users"%sender.nick)
518+
self.logger.info("Found user %s in current users" % sender.nick)
520519

521520
if -1 < self.bot.authtimeout < time.time() - self.bot.users[sender.nick].lastAuth:
522521
self.bot.users[sender.nick].unauthenticate()
523522

524523
# If current auth is not 3, we retry to authenticate the account but resending the request and resetting the flags
525524
if self.bot.users[sender.nick].auth != 3:
526-
self.logger.info("Reauthenticating user %s"%sender.nick)
525+
self.logger.info("Reauthenticating user %s" % sender.nick)
527526
self.bot.users[sender.nick].unauthenticate()
528527
self.bot.sendRaw(self.bot.nickAuth.format(nickserv=self.bot.nickserv, nick=sender.nick) + EOL)
529528
self.bot.sendRaw(CmdGenerator.getWHOIS(sender.nick))
@@ -532,7 +531,7 @@ def onCOMMAND(self, sender, dest, command):
532531
cmdThread.start()
533532

534533
else:
535-
self.logger.info("Adding and authenticating user %s"%sender.nick)
534+
self.logger.info("Adding and authenticating user %s" % sender.nick)
536535
self.bot.users[sender.nick] = sender
537536
self.bot.sendRaw(self.bot.nickAuth.format(nickserv=self.bot.nickserv, nick=sender.nick) + EOL)
538537
self.bot.sendRaw(CmdGenerator.getWHOIS(sender.nick))

0 commit comments

Comments
 (0)