nxy/bot/tell.py
jkhsjdhjs e03f5d0a43 add auto reconnect for postgres
This only works on the second database interaction, since psycopg2 only notices that
the connection is gone, when a query is executed.

So in the common case reconnect works as follows:
- some bot method calls a cursor function like .execute(), .fetchone(), etc.
  - this raises an error if the connection is broken
  - if following code then requests a new cursor, this will also fail since psycopg2
    now knows that the connection is gone
  - the error is caught in storage.DBConn.cursor(), a new connection will be set up
    of which a new cursor is yielded
If the error happens in connection.commit() or .rollback() instead we can instantly
reconnect since these methods are wrapped.

So why not wrap the cursor methods as well?
Consider the following example:
A query is the last thing that was executed on a cursor.
The database connection is lost.
Now .fetchone() is called on the cursor.
We could wrap .fetchone() and reconnect, but we'd have to use a new cursor since
cursors are linked to connections. And on this new cursor .fetchone() wouldn't
make any sense, since we haven't executed a query on this cursor.
2020-03-16 21:51:32 +00:00

86 lines
2.6 KiB
Python

# -*- coding: utf-8 -*-
from datetime import datetime
import irc3
from docopt import Dict
from irc3.plugins.command import command
from irc3.utils import IrcString
from psycopg2 import Error
from . import DatabasePlugin, Bot
class Tell(DatabasePlugin):
def __init__(self, bot: Bot):
super().__init__(bot)
self.tell_queue = {}
with self.con.cursor() as cur:
cur.execute('''
SELECT
to_nick, from_nick, message, created_at
FROM
tells
''')
for res in cur.fetchall():
nick = res['to_nick'].lower()
if nick not in self.tell_queue:
self.tell_queue[nick] = []
self.tell_queue[nick].append(res[1:])
@command
def tell(self, mask: IrcString, target: IrcString, args: Dict):
"""Saves a message for nick to forward on activity
%%tell <nick> <message>...
"""
nick = args['<nick>'].lower()
tell = [nick, mask.nick, ' '.join(args['<message>']).strip(), datetime.utcnow()]
if nick not in self.tell_queue:
self.tell_queue[nick] = []
self.tell_queue[nick].append(tell[1:])
try:
with self.con.cursor() as cur:
cur.execute('''
INSERT INTO
tells (to_nick, from_nick, message, created_at)
VALUES
(%s, %s, %s, %s)
''', tell)
self.con.commit()
self.bot.notice(mask.nick, "I will tell that to {} when I see them.".format(nick))
except Error as ex:
self.log.error(ex)
self.con.rollback()
self.bot.notice(mask.nick, "database error")
@irc3.event(r'(?i)^:(?P<mask>.*) PRIVMSG .* :.*')
def on_message(self, mask: str):
"""If activ nick has tells, forward and delete them."""
nick = IrcString(mask).nick.lower()
if nick in self.tell_queue:
# Forward all tells for nick
for tell in self.tell_queue[nick]:
self.bot.privmsg(nick, '\x02[Tell]\x02 Message from {nick} at {time}: {message}'.format(
nick=tell[0],
time=tell[1], # TODO: format time
message=tell[2],
))
del self.tell_queue[nick]
with self.con.cursor() as cur:
cur.execute('''
DELETE FROM
tells
WHERE
to_nick = %s
''', [nick])
self.con.commit()