pngbot.py:IrcBot.connect(): set socket non-blocking, ignore BlockingIOError, and select() for writability & optionally specified timeout.

pngbot.py:IrcBot.readline(): replace settimeout()/socket.timeout-based logic w/ select() for readability & optionally specified timeout.
pngbot.py:IrcBot.sendline(): select() for writability & optionally specified timeout and send() full buffer until failure.
pngbot.py:IrcMiRCARTBot.dispatch(): dispatch expired timers prior to calling readline().
pngbot.py:main(): default (maximum) connect timeout to 15 seconds.
This commit is contained in:
Lucio Andrés Illanes Albornoz 2018-01-03 01:38:41 +01:00
parent 09a7995a50
commit 3a207fc53c

View File

@ -24,22 +24,29 @@
from itertools import chain from itertools import chain
import base64 import base64
import errno, os, select, socket, sys, time
import json import json
import mirc2png import mirc2png
import os, socket, sys, time import requests, urllib.request
import requests
import urllib.request
class IrcBot: class IrcBot:
"""Blocking abstraction over the IRC protocol""" """Non-blocking abstraction over the IRC protocol"""
serverHname = serverPort = None; serverHname = serverPort = None;
clientNick = clientIdent = clientGecos = None; clientNick = clientIdent = clientGecos = None;
clientSocket = clientSocketFile = None; clientSocket = clientSocketFile = None;
# {{{ connect(): Connect to server and register # {{{ connect(): Connect to server and register w/ optional timeout
def connect(self): def connect(self, timeout=None):
self.clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.clientSocket.setblocking(0)
try:
self.clientSocket.connect((self.serverHname, int(self.serverPort))) self.clientSocket.connect((self.serverHname, int(self.serverPort)))
except BlockingIOError:
pass
if timeout:
select.select([], [self.clientSocket.fileno()], [], timeout)
else:
select.select([], [self.clientSocket.fileno()], [])
self.clientSocketFile = self.clientSocket.makefile() self.clientSocketFile = self.clientSocket.makefile()
self.sendline("NICK", self.clientNick) self.sendline("NICK", self.clientNick)
self.sendline("USER", self.clientIdent, "0", "0", self.clientGecos) self.sendline("USER", self.clientIdent, "0", "0", self.clientGecos)
@ -51,13 +58,12 @@ class IrcBot:
self.clientSocket = self.clientSocketFile = None; self.clientSocket = self.clientSocketFile = None;
# }}} # }}}
# {{{ readline(): Read and parse single line from server into canonicalised list w/ optional timeout # {{{ readline(): Read and parse single line from server into canonicalised list w/ optional timeout
def readline(self, timeout=0): def readline(self, timeout=None):
if timeout: if timeout:
self.clientSocket.settimeout(timeout) select.select([self.clientSocket.fileno()], [], [], timeout)
try: else:
select.select([self.clientSocket.fileno()], [], [])
msg = self.clientSocketFile.readline() msg = self.clientSocketFile.readline()
except socket.timeout:
return "TIMEOUT"
if len(msg): if len(msg):
msg = msg.rstrip("\r\n") msg = msg.rstrip("\r\n")
else: else:
@ -73,15 +79,24 @@ class IrcBot:
msg = [""] + msg[0:] msg = [""] + msg[0:]
return msg return msg
# }}} # }}}
# {{{ sendline(): Parse and send single line to server from list # {{{ sendline(): Parse and send single line to server from list w/ optional timeout
def sendline(self, *args): def sendline(self, *args, timeout=None):
msg = ""; argNumMax = len(args); msg = ""; argNumMax = len(args);
for argNum in range(0, argNumMax): for argNum in range(0, argNumMax):
if argNum == (argNumMax - 1): if argNum == (argNumMax - 1):
msg += ":" + args[argNum] msg += ":" + args[argNum]
else: else:
msg += args[argNum] + " " msg += args[argNum] + " "
return self.clientSocket.send((msg + "\r\n").encode()) msg = (msg + "\r\n").encode(); msgLen = len(msg); msgBytesSent = 0;
while msgBytesSent < msgLen:
if timeout:
timeLast = time.time()
select.select([], [self.clientSocket.fileno()], [], timeout)
timeNow = time.time(); timeout -= timeNow - timeLast;
else:
select.select([], [self.clientSocket.fileno()], [])
msgBytesSent = self.clientSocket.send(msg)
msg = msg[msgBytesSent:]; msgLen -= msgBytesSent;
# }}} # }}}
# {{{ Initialisation method # {{{ Initialisation method
def __init__(self, serverHname, serverPort, clientNick, clientIdent, clientGecos): def __init__(self, serverHname, serverPort, clientNick, clientIdent, clientGecos):
@ -94,10 +109,10 @@ class IrcMiRCARTBot(IrcBot):
clientChannelLastMessage = clientChannelOps = clientChannel = None clientChannelLastMessage = clientChannelOps = clientChannel = None
clientChannelRejoinTimerNext = clientChannelRejoin = None clientChannelRejoinTimerNext = clientChannelRejoin = None
# {{{ connect(): Connect to server and (re)initialise # {{{ connect(): Connect to server and (re)initialise w/ optional timeout
def connect(self): def connect(self, timeout=None):
print("Connecting to {}:{}...".format(self.serverHname, self.serverPort)) print("Connecting to {}:{}...".format(self.serverHname, self.serverPort))
super().connect() super().connect(timeout)
print("Connected to {}:{}.".format(self.serverHname, self.serverPort)) print("Connected to {}:{}.".format(self.serverHname, self.serverPort))
print("Registering on {}:{} as {}, {}, {}...".format(self.serverHname, self.serverPort, self.clientNick, self.clientIdent, self.clientGecos)) print("Registering on {}:{} as {}, {}, {}...".format(self.serverHname, self.serverPort, self.clientNick, self.clientIdent, self.clientGecos))
self.clientLastMessage = 0; self.clientChannelOps = []; self.clientLastMessage = 0; self.clientChannelOps = [];
@ -216,11 +231,13 @@ class IrcMiRCARTBot(IrcBot):
timeNow = time.time() timeNow = time.time()
if self.clientChannelRejoinTimerNext > timeNow: if self.clientChannelRejoinTimerNext > timeNow:
clientTimerNextDelta = self.clientChannelRejoinTimerNext - timeNow clientTimerNextDelta = self.clientChannelRejoinTimerNext - timeNow
if clientTimerNextDelta:
timeNow = time.time()
if self.clientChannelRejoinTimerNext <= timeNow:
self.dispatchTimer()
serverMessage = self.readline(clientTimerNextDelta) serverMessage = self.readline(clientTimerNextDelta)
if serverMessage == None: if serverMessage == None:
self.dispatchNone(); break; self.dispatchNone(); break;
elif serverMessage == "TIMEOUT":
self.dispatchTimer()
elif serverMessage[1] == "001": elif serverMessage[1] == "001":
self.dispatch001(serverMessage) self.dispatch001(serverMessage)
elif serverMessage[1] == "353": elif serverMessage[1] == "353":
@ -265,7 +282,7 @@ class IrcMiRCARTBot(IrcBot):
def main(*argv): def main(*argv):
_IrcMiRCARTBot = IrcMiRCARTBot(*argv[1:]) _IrcMiRCARTBot = IrcMiRCARTBot(*argv[1:])
while True: while True:
_IrcMiRCARTBot.connect() _IrcMiRCARTBot.connect(15)
_IrcMiRCARTBot.dispatch() _IrcMiRCARTBot.dispatch()
_IrcMiRCARTBot.close() _IrcMiRCARTBot.close()