New implementation using FlatfileDB.

This commit is contained in:
Jeremy Fincher 2004-08-07 00:41:55 +00:00
parent a59ba2563d
commit ec16cf9622

View File

@ -38,6 +38,7 @@ __revision__ = "$Id$"
import supybot.plugins as plugins import supybot.plugins as plugins
import csv
import sets import sets
import time import time
import getopt import getopt
@ -54,13 +55,6 @@ import supybot.registry as registry
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
import supybot.callbacks as callbacks import supybot.callbacks as callbacks
try:
import sqlite
except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to use this ' \
'plugin. Download it at <http://pysqlite.sf.net/>'
conf.registerPlugin('Note') conf.registerPlugin('Note')
conf.registerGlobalValue(conf.supybot.plugins.Note, 'notifyOnJoin', conf.registerGlobalValue(conf.supybot.plugins.Note, 'notifyOnJoin',
registry.Boolean(False, """Determines whether the bot will notify people of registry.Boolean(False, """Determines whether the bot will notify people of
@ -82,43 +76,93 @@ class Ignores(registry.SpaceSeparatedListOfStrings):
conf.registerUserValue(conf.users.plugins.Note, 'ignores', Ignores([], '')) conf.registerUserValue(conf.users.plugins.Note, 'ignores', Ignores([], ''))
class NoteDb(plugins.DBHandler): class FlatfileNoteDB(plugins.FlatfileDB):
def makeDb(self, filename): class Note(object):
"create Notes database and tables" def __init__(self, L=None, to=None, frm=None, text=None):
if os.path.exists(filename): if L is not None:
db = sqlite.connect(filename) self.frm = int(L[0])
else: self.to = int(L[1])
db = sqlite.connect(filename, converters={'bool': bool}) self.at = float(L[2])
cursor = db.cursor() self.notified = bool(int(L[3]))
cursor.execute("""CREATE TABLE notes ( self.read = bool(int(L[4]))
id INTEGER PRIMARY KEY, self.public = bool(int(L[5]))
from_id INTEGER, self.text = L[6]
to_id INTEGER, else:
added_at TIMESTAMP, self.to = to
notified BOOLEAN, self.frm = frm
read BOOLEAN, self.text = text
public BOOLEAN, self.read = False
note TEXT self.public = True
)""") self.at = time.time()
db.commit() self.notified = False
return db
def __str__(self):
return csv.join(map(str, [self.frm, self.to, self.at,
int(self.notified), int(self.read),
int(self.public), self.text]))
def serialize(self, n):
return str(n)
def deserialize(self, s):
return self.Note(csv.split(s))
def setRead(self, id):
n = self.getRecord(id)
n.read = True
n.notified = True
self.setRecord(id, n)
def setNotified(self, id):
n = self.getRecord(id)
n.notified = True
self.setRecord(id, n)
def getUnnotifiedIds(self, to):
L = []
for (id, note) in self.records():
if note.to == to and not note.notified:
L.append(id)
return L
def getUnreadIds(self, to):
L = []
for (id, note) in self.records():
if note.to == to and not note.read:
L.append(id)
return L
def send(self, frm, to, text):
n = self.Note(frm=frm, to=to, text=text)
return self.addRecord(n)
def get(self, id):
return self.getRecord(id)
def remove(self, id):
n = self.getRecord(id)
assert not n.read
self.delRecord(id)
def notes(self, p):
L = []
for (id, note) in self.records():
if p(note):
L.append((id, note))
return L
def NoteDB():
return FlatfileNoteDB(conf.supybot.directories.data.dirize('Note.db'))
class Note(callbacks.Privmsg): class Note(callbacks.Privmsg):
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
dataDir = conf.supybot.directories.data() self.db = NoteDB()
self.dbHandler = NoteDb(name=os.path.join(dataDir, 'Notes'))
def setAsRead(self, id):
db = self.dbHandler.getDb()
cursor = db.cursor()
cursor.execute("""UPDATE notes
SET read=1, notified=1
WHERE id=%s""", id)
db.commit()
def die(self): def die(self):
self.dbHandler.die() self.db.close()
def doPrivmsg(self, irc, msg): def doPrivmsg(self, irc, msg):
self._notify(irc, msg) self._notify(irc, msg)
@ -130,33 +174,27 @@ class Note(callbacks.Privmsg):
def _notify(self, irc, msg, repeatedly=False): def _notify(self, irc, msg, repeatedly=False):
try: try:
id = ircdb.users.getUserId(msg.prefix) to = ircdb.users.getUserId(msg.prefix)
except KeyError: except KeyError:
return return
db = self.dbHandler.getDb() unnotifiedIds = ['#%s' % nid for nid in self.db.getUnnotifiedIds(to)]
cursor = db.cursor()
cursor.execute("""SELECT id FROM notes
WHERE notes.to_id=%s AND notified=0""", id)
unnotifiedIds = ['#%s' % t[0] for t in cursor.fetchall()]
unnotified = len(unnotifiedIds) unnotified = len(unnotifiedIds)
if unnotified != 0 or repeatedly: if unnotified or repeatedly:
cursor.execute("""SELECT id FROM notes unreadIds = ['#%s' % nid for nid in self.db.getUnreadIds(to)]
WHERE notes.to_id=%s AND read=0""", id)
unreadIds = ['#%s' % t[0] for t in cursor.fetchall()]
unread = len(unreadIds) unread = len(unreadIds)
s = 'You have %s; %s that I haven\'t told you about before now. '\ s = 'You have %s; %s that I haven\'t told you about before now. '\
'%s %s still unread.' % \ '%s %s still unread.' % \
(utils.nItems('note', unread, 'unread'), unnotified, (utils.nItems('note', unread, 'unread'), unnotified,
utils.commaAndify(unreadIds), utils.be(unread)) utils.commaAndify(unreadIds), utils.be(unread))
maker = ircmsgs.privmsg msgmaker = ircmsgs.privmsg
if self.userValue('notifyWithNotice', msg.prefix): if self.userValue('notifyWithNotice', msg.prefix):
maker = ircmsgs.notice msgmaker = ircmsgs.notice
irc.queueMsg(maker(msg.nick, s)) irc.queueMsg(msgmaker(msg.nick, s))
cursor.execute("""UPDATE notes SET notified=1 for nid in unnotifiedIds:
WHERE notes.to_id=%s""", id) id = int(nid[1:])
db.commit() self.db.setNotified(id)
def getUserId(self, irc, name): def _getUserId(self, irc, name):
if ircdb.users.hasUser(name): if ircdb.users.hasUser(name):
return ircdb.users.getUserId(name) return ircdb.users.getUserId(name)
else: else:
@ -166,6 +204,14 @@ class Note(callbacks.Privmsg):
except KeyError: except KeyError:
return None return None
def _validId(self, irc, id):
try:
id = id.lstrip('#')
return int(id)
except ValueError:
irc.error('That\'s not a valid note id.')
return None
def send(self, irc, msg, args): def send(self, irc, msg, args):
"""<recipient>,[<recipient>,[...]] <text> """<recipient>,[<recipient>,[...]] <text>
@ -173,23 +219,18 @@ class Note(callbacks.Privmsg):
specified by separating their names by commas, with *no* spaces specified by separating their names by commas, with *no* spaces
between. between.
""" """
(names, note) = privmsgs.getArgs(args, required=2) (names, text) = privmsgs.getArgs(args, required=2)
# Let's get the from user. # Let's get the from user.
try: try:
fromId = ircdb.users.getUserId(msg.prefix) fromId = ircdb.users.getUserId(msg.prefix)
senderName = ircdb.users.getUser(fromId).name
except KeyError: except KeyError:
irc.errorNotRegistered() irc.errorNotRegistered()
return return
# Let's get the publicitousness. public = ircutils.isChannel(msg.args[0])
if ircutils.isChannel(msg.args[0]):
public = 1
else:
public = 0
names = names.split(',') names = names.split(',')
ids = [self.getUserId(irc, name) for name in names] ids = [self._getUserId(irc, name) for name in names]
badnames = [] badnames = []
# Make sure all targets are registered.
if None in ids: if None in ids:
for (id, name) in zip(ids, names): for (id, name) in zip(ids, names):
if id is None: if id is None:
@ -197,6 +238,8 @@ class Note(callbacks.Privmsg):
irc.errorNoUser(name=utils.commaAndify(badnames, And='or')) irc.errorNoUser(name=utils.commaAndify(badnames, And='or'))
return return
# Make sure the sender isn't being ignored.
senderName = ircdb.users.getUser(fromId).name
for name in names: for name in names:
if senderName in self.userValue('ignores', name): if senderName in self.userValue('ignores', name):
badnames.append(name) badnames.append(name)
@ -204,20 +247,12 @@ class Note(callbacks.Privmsg):
irc.error('%s %s ignoring notes from you.' % \ irc.error('%s %s ignoring notes from you.' % \
(utils.commaAndify(badnames), utils.be(len(badnames)))) (utils.commaAndify(badnames), utils.be(len(badnames))))
return return
db = self.dbHandler.getDb()
cursor = db.cursor()
now = int(time.time())
sent = [] sent = []
for (name, toId) in zip(names, ids): for toId in ids:
cursor.execute("""INSERT INTO notes VALUES id = self.db.send(fromId, toId, text)
(NULL, %s, %s, %s, 0, 0, %s, %s)""", name = ircdb.users.getUser(toId).name
fromId, toId, now, public, note) s = 'note #%s sent to %s' % (id, name)
cursor.execute("""SELECT id FROM notes WHERE
from_id=%s AND to_id=%s AND added_at=%s""",
fromId, toId, now)
s = 'note #%s sent to %s' % (cursor.fetchone()[0], name)
sent.append(s) sent.append(s)
db.commit()
irc.reply(utils.commaAndify(sent).capitalize() + '.') irc.reply(utils.commaAndify(sent).capitalize() + '.')
def unsend(self, irc, msg, args): def unsend(self, irc, msg, args):
@ -226,83 +261,58 @@ class Note(callbacks.Privmsg):
Unsends the note with the id given. You must be the Unsends the note with the id given. You must be the
author of the note, and it must be unread. author of the note, and it must be unread.
""" """
id = privmsgs.getArgs(args)
try: try:
userid = ircdb.users.getUserId(msg.prefix) userid = ircdb.users.getUserId(msg.prefix)
except KeyError: except KeyError:
irc.errorNotRegistered() irc.errorNotRegistered()
return return
db = self.dbHandler.getDb() id = privmsgs.getArgs(args)
cursor = db.cursor() id = self._validId(irc, id)
cursor.execute("""SELECT from_id, read FROM notes WHERE id=%s""", id) if id is None:
if cursor.rowcount < 1:
irc.error('That\'s not a valid note id.')
return return
(from_id, read) = map(int, cursor.fetchone()) note = self.db.get(id)
if from_id == userid: if note.frm == userid:
if not read: if not note.read:
cursor.execute("""DELETE FROM notes WHERE id=%s""", id) self.db.remove(id)
db.commit()
irc.replySuccess() irc.replySuccess()
else: else:
irc.error('That note has been read already.') irc.error('That note has been read already.')
else: else:
irc.error('That note wasn\'t sent by you.') irc.error('That note wasn\'t sent by you.')
def note(self, irc, msg, args): def note(self, irc, msg, args):
"""<note id> """<note id>
Retrieves a single note by its unique note id. Use the 'note list' Retrieves a single note by its unique note id. Use the 'note list'
command to see what unread notes you have. command to see what unread notes you have.
""" """
noteid = privmsgs.getArgs(args)
if noteid.startswith('get'):
irc.error('The Note.get command has changed to be simply "note".')
return
noteid = noteid.lstrip('#') # Some people are just dumb.
try: try:
id = ircdb.users.getUserId(msg.prefix) userid = ircdb.users.getUserId(msg.prefix)
except KeyError: except KeyError:
irc.errorNotRegistered() irc.errorNotRegistered()
return return
try: id = privmsgs.getArgs(args)
noteid = int(noteid) id = self._validId(irc, id)
except ValueError: if id is None:
irc.error('%r is not a valid note id.' % noteid)
return return
db = self.dbHandler.getDb() try:
cursor = db.cursor() note = self.db.get(id)
cursor.execute("""SELECT note, to_id, from_id, added_at, public except KeyError:
FROM notes irc.error('That\'s not a valid note id.')
WHERE (to_id=%s OR from_id=%s) AND id=%s""", return
id, id, noteid) if userid != note.frm and userid != note.to:
if cursor.rowcount < 1:
s = 'You may only retrieve notes you\'ve sent or received.' s = 'You may only retrieve notes you\'ve sent or received.'
irc.error(s) irc.error(s)
return return
(note, toId, fromId, addedAt, public) = cursor.fetchone() elapsed = utils.timeElapsed(time.time() - note.at)
(toId,fromId,addedAt,public) = imap(int, (toId,fromId,addedAt,public)) if note.to == userid:
elapsed = utils.timeElapsed(time.time() - addedAt) author = ircdb.users.getUser(note.frm).name
if toId == id: newnote = '%s (Sent by %s %s ago)' % (note.text, author, elapsed)
author = ircdb.users.getUser(fromId).name elif note.frm == userid:
newnote = '%s (Sent by %s %s ago)' % (note, author, elapsed) recipient = ircdb.users.getUser(note.to).name
elif fromId == id: newnote = '%s (Sent to %s %s ago)' % (note.text, recipient,elapsed)
recipient = ircdb.users.getUser(toId).name irc.reply(newnote, private=(not note.public))
newnote = '%s (Sent to %s %s ago)' % (note, recipient, elapsed) self.db.setRead(id)
irc.reply(newnote, private=(not public))
self.setAsRead(noteid)
def _formatNoteData(self, msg, id, fromId, public, sent=False):
(id, fromId, public) = imap(int, (id, fromId, public))
if public or not ircutils.isChannel(msg.args[0]):
sender = ircdb.users.getUser(fromId).name
if sent:
return '#%s to %s' % (id, sender)
else:
return '#%s from %s' % (id, sender)
else:
return '#%s (private)' % id
def ignore(self, irc, msg, args): def ignore(self, irc, msg, args):
"""[--remove] <user> """[--remove] <user>
@ -330,6 +340,16 @@ class Note(callbacks.Privmsg):
except KeyError: except KeyError:
irc.errorNoUser() irc.errorNoUser()
def _formatNoteId(self, msg, id, frm, public, sent=False):
if public or not ircutils.isChannel(msg.args[0]):
sender = ircdb.users.getUser(frm).name
if sent:
return '#%s to %s' % (id, sender)
else:
return '#%s from %s' % (id, sender)
else:
return '#%s (private)' % id
def list(self, irc, msg, args): def list(self, irc, msg, args):
"""[--{old,sent}] [--{from,to} <user>] """[--{old,sent}] [--{from,to} <user>]
@ -342,14 +362,13 @@ class Note(callbacks.Privmsg):
(optlist, rest) = getopt.getopt(args, '', options) (optlist, rest) = getopt.getopt(args, '', options)
sender, receiver, old, sent = ('', '', False, False) sender, receiver, old, sent = ('', '', False, False)
for (option, arg) in optlist: for (option, arg) in optlist:
option = option.lstrip('-') if option == '--old':
if option == 'old':
old = True old = True
if option == 'sent': if option == '--sent':
sent = True sent = True
if option == 'from': if option == '--from':
sender = arg sender = arg
if option == 'to': if option == '--to':
receiver = arg receiver = arg
sent = True sent = True
if old: if old:
@ -357,28 +376,29 @@ class Note(callbacks.Privmsg):
if sent: if sent:
return self._sentnotes(irc, msg, receiver) return self._sentnotes(irc, msg, receiver)
try: try:
id = ircdb.users.getUserId(msg.prefix) userid = ircdb.users.getUserId(msg.prefix)
except KeyError: except KeyError:
irc.errorNotRegistered() irc.errorNotRegistered()
return return
sql = """SELECT id, from_id, public
FROM notes def p(note):
WHERE notes.to_id=%r AND notes.read=0""" % id return not note.read and note.to == userid
if sender: if sender:
try: try:
sender = ircdb.users.getUserId(sender) sender = ircdb.users.getUserId(sender)
originalP = p
def p(note):
return originalP(note) and note.frm == sender
except KeyError: except KeyError:
irc.error('That user is not in my user database.') irc.errorNoUser()
return return
sql = '%s %s' % (sql, 'AND notes.from_id=%r' % sender) notesAndIds = self.db.notes(p)
db = self.dbHandler.getDb() notesAndIds.sort()
cursor = db.cursor() if not notesAndIds:
cursor.execute(sql)
count = cursor.rowcount
if count < 1:
irc.reply('You have no unread notes.') irc.reply('You have no unread notes.')
else: else:
L = [self._formatNoteData(msg, *t) for t in cursor.fetchall()] L = [self._formatNoteId(msg, id, n.frm, n.public)
for (id, n) in notesAndIds]
L = self._condense(L) L = self._condense(L)
irc.reply(utils.commaAndify(L)) irc.reply(utils.commaAndify(L))
@ -396,68 +416,56 @@ class Note(callbacks.Privmsg):
return notes return notes
def _sentnotes(self, irc, msg, receiver): def _sentnotes(self, irc, msg, receiver):
"""takes no arguments
Returns a list of your most recent old notes.
"""
try: try:
id = ircdb.users.getUserId(msg.prefix) userid = ircdb.users.getUserId(msg.prefix)
except KeyError: except KeyError:
irc.errorNotRegistered() irc.errorNotRegistered()
return return
sql = """SELECT id, to_id, public def p(note):
FROM notes return note.frm == userid
WHERE notes.from_id=%r""" % id
if receiver: if receiver:
try: try:
receiver = ircdb.users.getUserId(receiver) receiver = ircdb.users.getUserId(receiver)
except KeyError: except KeyError:
irc.error('That user is not in my user database.') irc.error('That user is not in my user database.')
return return
sql = '%s %s' % (sql, 'AND notes.to_id=%r' % receiver) originalP = p
sql = '%s ORDER BY id DESC' % sql def p(note):
db = self.dbHandler.getDb() return originalP(note) and note.to == receiver
cursor = db.cursor() notesAndIds = self.db.notes(p)
cursor.execute(sql) notesAndIds.sort()
if cursor.rowcount < 1: if not notesAndIds:
irc.reply('I couldn\'t find any sent notes for your user.') irc.error('I couldn\'t find any sent notes for your user.')
else: else:
ids = [self._formatNoteData(msg, sent=True, *t) for t in ids = [self._formatNoteId(msg, id, note.to, note.public,sent=True)
cursor.fetchall()] for (id, note) in notesAndIds]
ids = self._condense(ids) ids = self._condense(ids)
irc.reply(utils.commaAndify(ids)) irc.reply(utils.commaAndify(ids))
def _oldnotes(self, irc, msg, sender): def _oldnotes(self, irc, msg, sender):
"""takes no arguments
Returns a list of your most recent old notes.
"""
try: try:
id = ircdb.users.getUserId(msg.prefix) userid = ircdb.users.getUserId(msg.prefix)
except KeyError: except KeyError:
irc.errorNotRegistered() irc.errorNotRegistered()
return return
sql = """SELECT id, from_id, public def p(note):
FROM notes return note.to == userid and note.read
WHERE notes.to_id=%r AND notes.read=1""" % id
#self.log.warning(sender)
if sender: if sender:
try: try:
sender = ircdb.users.getUserId(sender) sender = ircdb.users.getUserId(sender)
except KeyError: except KeyError:
irc.error('That user is not in my user database.') irc.error('That user is not in my user database.')
return return
sql = '%s %s' % (sql, 'AND notes.from_id=%r' % sender) originalP = p
sql = '%s ORDER BY id DESC' % sql def p(note):
db = self.dbHandler.getDb() return originalP(note) and note.frm == sender
cursor = db.cursor() notesAndIds = self.db.notes(p)
cursor.execute(sql) notesAndIds.sort()
#self.log.warning(cursor.rowcount) if not notesAndIds:
if cursor.rowcount < 1: irc.reply('I couldn\'t find any matching read notes for your user.')
irc.reply('I couldn\'t find any read notes for your user.')
else: else:
ids = [self._formatNoteData(msg, *t) for t in cursor.fetchall()] ids = [self._formatNoteId(msg, id, note.frm, note.public)
#self.log.warning(ids) for (id, note) in notesAndIds]
ids = self._condense(ids) ids = self._condense(ids)
irc.reply(utils.commaAndify(ids)) irc.reply(utils.commaAndify(ids))