Update News to use commands.wrap and switch from an SQLite db to a flatfile

mapping.
This commit is contained in:
James Vega 2004-10-06 21:56:06 +00:00
parent edc698e48b
commit 0242a8619d
2 changed files with 162 additions and 216 deletions

View File

@ -34,8 +34,6 @@ dates.
__revision__ = "$Id$" __revision__ = "$Id$"
import supybot.plugins as plugins
import os import os
import time import time
@ -43,148 +41,94 @@ import supybot.dbi as dbi
import supybot.conf as conf import supybot.conf as conf
import supybot.ircdb as ircdb import supybot.ircdb as ircdb
import supybot.utils as utils import supybot.utils as utils
import supybot.plugins as plugins
from supybot.commands import wrap
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
import supybot.privmsgs as privmsgs import supybot.privmsgs as privmsgs
import supybot.callbacks as callbacks import supybot.callbacks as callbacks
class NewsRecord(dbi.Record): class DbiNewsDB(plugins.DbiChannelDB):
__fields__ = [ class DB(dbi.DB):
'subject', class Record(dbi.Record):
'text', __fields__ = [
'at', 'subject',
'expires', 'text',
'by' 'at',
] 'expires',
def __str__(self): 'by',
format = conf.supybot.humanTimestampFormat() ]
try:
user = ircdb.users.getUser(int(self.by)).name
except ValueError:
user = self.by
except KeyError:
user = 'a user that is no longer registered'
if int(self.expires) == 0:
s = '%s (Subject: "%s", added by %s on %s)' % \
(self.text, self.subject, self.by,
time.strftime(format, time.localtime(int(self.at))))
else:
s = '%s (Subject: "%s", added by %s on %s, expires at %s)' % \
(self.text, self.subject, self.by,
time.strftime(format, time.localtime(int(self.at))),
time.strftime(format, time.localtime(int(self.expires))))
return s
class SqliteNewsDB(object): def __str__(self):
def __init__(self, filename): format = conf.supybot.humanTimestampFormat()
self.dbs = ircutils.IrcDict() try:
self.filename = filename user = ircdb.users.getUser(int(self.by)).name
except ValueError:
user = self.by
except KeyError:
user = 'a user that is no longer registered'
if int(self.expires) == 0:
s = '%s (Subject: "%s", added by %s on %s)' % \
(self.text, self.subject, self.by,
time.strftime(format, time.localtime(int(self.at))))
else:
s = '%s (Subject: "%s", added by %s on %s, expires at %s)'
s = s % (self.text, self.subject, user,
time.strftime(format, time.localtime(int(self.at))),
time.strftime(format, time.localtime(int(self.expires))))
return s
def close(self): def __init__(self, filename):
for db in self.dbs.itervalues(): # We use self.__class__ here because apparently DB isn't in our
db.close() # scope. python--
self.__parent = super(self.__class__, self)
self.__parent.__init__(filename)
def _getDb(self, channel): def add(self, subject, text, at, expires, by):
try: return self.__parent.add(self.Record(at=at, by=by, text=text,
import sqlite subject=subject, expires=expires))
except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to ' \
'use this plugin. Download it at ' \
'<http://pysqlite.sf.net/>'
filename = plugins.makeChannelFilename(self.filename, channel)
if filename in self.dbs:
return self.dbs[filename]
if os.path.exists(filename):
self.dbs[filename] = sqlite.connect(filename)
return self.dbs[filename]
db = sqlite.connect(filename)
self.dbs[filename] = db
cursor = db.cursor()
cursor.execute("""CREATE TABLE news (
id INTEGER PRIMARY KEY,
subject TEXT,
item TEXT,
added_at TIMESTAMP,
expires_at TIMESTAMP,
added_by TEXT
)""")
db.commit()
return db
def add(self, channel, subject, text, added_at, expires, by): def getOld(self, id=None):
db = self._getDb(channel) now = time.time()
cursor = db.cursor() if id:
cursor.execute("INSERT INTO news VALUES (NULL, %s, %s, %s, %s, %s)", return self.get(id)
subject, text, added_at, expires, by)
db.commit()
# XXX This should change only to support id, and the old functionality
# should move out to another method.
def get(self, channel, id=None, old=False):
db = self._getDb(channel)
cursor = db.cursor()
if id:
cursor.execute("""SELECT item, subject, added_at,
expires_at, added_by
FROM news
WHERE id=%s""", id)
if cursor.rowcount == 0:
raise dbi.NoRecordError, id
(text, subject, at, expires, by) = cursor.fetchone()
return NewsRecord(id, text=text, subject=subject, at=int(at),
expires=int(expires), by=by)
else:
if old:
cursor.execute("""SELECT id, subject
FROM news
WHERE expires_at <> 0 AND expires_at < %s
ORDER BY id DESC""", int(time.time()))
else: else:
cursor.execute("""SELECT id, subject L = [R for R in self if R.expires < now and R.expires != 0]
FROM news if not L:
WHERE expires_at > %s OR expires_at=0""", raise dbi.NoRecordError
int(time.time())) else:
if cursor.rowcount == 0: return L
raise dbi.NoRecordError
def get(self, id=None):
now = time.time()
if id:
return self.__parent.get(id)
else: else:
return cursor.fetchall() L = [R for R in self if R.expires >= now or R.expires == 0]
if not L:
raise dbi.NoRecordError
return L
def remove(self, channel, id): def change(self, id, f):
db = self._getDb(channel) news = self.get(id)
cursor = db.cursor() s = '%s: %s' % (news.subject, news.text)
cursor.execute("""DELETE FROM news WHERE id = %s""", id) s = f(s)
db.commit() (news.subject, news.text) = s.split(': ', 1)
if cursor.rowcount == 0: self.set(id, news)
raise dbi.NoRecordError, id
def change(self, channel, id, replacer): NewsDB = plugins.DB('News', {'flat': DbiNewsDB})
db = self._getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT subject, item FROM news WHERE id=%s""", id)
if cursor.rowcount == 0:
raise dbi.NoRecordError, id
(subject, item) = cursor.fetchone()
s = '%s: %s' % (subject, item)
s = replacer(s)
(newSubject, newItem) = s.split(': ', 1)
cursor.execute("""UPDATE news SET subject=%s, item=%s WHERE id=%s""",
newSubject, newItem, id)
NewsDB = plugins.DB('News',
{'sqlite': SqliteNewsDB})
class News(callbacks.Privmsg): class News(callbacks.Privmsg):
def __init__(self): def __init__(self):
self.__parent = super(News, self) self.__parent = super(News, self)
self.__parent.__init__() self.__parent.__init__()
self.db = NewsDB() self.db = NewsDB()
self.removeOld = False
def die(self): def die(self):
self.__parent.die() self.__parent.die()
self.db.close() self.db.close()
def add(self, irc, msg, args, channel): def add(self, irc, msg, args, channel, user, at, expires, news):
"""[<channel>] <expires> <subject>: <text> """[<channel>] <expires> <subject>: <text>
Adds a given news item of <text> to a channel with the given <subject>. Adds a given news item of <text> to a channel with the given <subject>.
@ -192,24 +136,15 @@ class News(callbacks.Privmsg):
now. <channel> is only necessary if the message isn't sent in the now. <channel> is only necessary if the message isn't sent in the
channel itself. channel itself.
""" """
(expire_interval, news) = privmsgs.getArgs(args, required=2)
try: try:
expire_interval = int(expire_interval)
(subject, text) = news.split(': ', 1) (subject, text) = news.split(': ', 1)
except ValueError: except ValueError:
raise callbacks.ArgumentError raise callbacks.ArgumentError
added_at = int(time.time()) id = self.db.add(channel, subject, text, at, expires, user.id)
expires = expire_interval and (added_at + expire_interval) irc.replySuccess('(News item #%s added)' % id)
# Set the other stuff needed for the insert. add = wrap(add, ['channeldb', 'user', 'now', 'expiry', 'text'])
try:
by = ircdb.users.getUserId(msg.prefix)
except KeyError:
by = msg.nick
self.db.add(channel, subject, text, added_at, expires, by)
irc.replySuccess()
add = privmsgs.checkChannelCapability(add, 'news')
def news(self, irc, msg, args): def news(self, irc, msg, args, channel, id):
"""[<channel>] [<id>] """[<channel>] [<id>]
Display the news items for <channel> in the format of '(#id) subject'. Display the news items for <channel> in the format of '(#id) subject'.
@ -217,81 +152,92 @@ class News(callbacks.Privmsg):
news items. <channel> is only necessary if the message isn't sent in news items. <channel> is only necessary if the message isn't sent in
the channel itself. the channel itself.
""" """
channel = privmsgs.getChannel(msg, args) if not id:
id = privmsgs.getArgs(args, required=0, optional=1)
if id:
try:
record = self.db.get(channel, int(id))
irc.reply(str(record))
except dbi.NoRecordError, id:
irc.errorInvalid('news item id', id)
else:
try: try:
records = self.db.get(channel) records = self.db.get(channel)
items = ['(#%s) %s' % (id, s) for (id, s) in records] items = ['(#%s) %s' % (R.id, R.subject) for R in records]
s = 'News for %s: %s' % (channel, '; '.join(items)) s = 'News for %s: %s' % (channel, '; '.join(items))
irc.reply(s) irc.reply(s)
except dbi.NoRecordError: except dbi.NoRecordError:
irc.reply('No news for %s.' % channel) irc.reply('No news for %s.' % channel)
else:
try:
if id < 1:
raise ValueError
record = self.db.get(channel, id)
irc.reply(str(record))
except dbi.NoRecordError, id:
irc.errorInvalid('news item id', id)
except ValueError:
irc.errorInvalid('news item id', id,
'<id> must be a positive integer.')
news = wrap(news, ['channeldb', ('int?', None)])
def remove(self, irc, msg, args, channel): def remove(self, irc, msg, args, channel, id):
"""[<channel>] <number> """[<channel>] <id>
Removes the news item with id <number> from <channel>. <channel> is Removes the news item with <id> from <channel>. <channel> is only
only necessary if the message isn't sent in the channel itself. necessary if the message isn't sent in the channel itself.
""" """
id = privmsgs.getArgs(args)
try: try:
if id < 1:
raise ValueError
self.db.remove(channel, id) self.db.remove(channel, id)
irc.replySuccess() irc.replySuccess()
except dbi.NoRecordError: except dbi.NoRecordError:
irc.errorInvalid('news item id', id) irc.errorInvalid('news item id', id)
remove = privmsgs.checkChannelCapability(remove, 'news') except ValueError:
irc.errorInvalid('news item id', id,
'<id> must be a positive integer.')
remove = wrap(remove, ['channeldb', 'int'])
def change(self, irc, msg, args, channel): def change(self, irc, msg, args, channel, id, replacer):
"""[<channel>] <number> <regexp> """[<channel>] <id> <regexp>
Changes the news item with id <number> from <channel> according to the Changes the news item with <id> from <channel> according to the
regular expression <regexp>. <regexp> should be of the form regular expression <regexp>. <regexp> should be of the form
s/text/replacement/flags. <channel> is only necessary if the message s/text/replacement/flags. <channel> is only necessary if the message
isn't sent on the channel itself. isn't sent on the channel itself.
""" """
(id, regexp) = privmsgs.getArgs(args, required=2)
try:
replacer = utils.perlReToReplacer(regexp)
except ValueError, e:
irc.error(str(e))
return
try: try:
if id < 1:
raise ValueError
self.db.change(channel, id, replacer) self.db.change(channel, id, replacer)
irc.replySuccess() irc.replySuccess()
except dbi.NoRecordError: except dbi.NoRecordError:
irc.errorInvalid('news item id', id) irc.errorInvalid('news item id', id)
change = privmsgs.checkChannelCapability(change, 'news') except ValueError:
irc.errorInvalid('news item id', id,
'<id> must be a positive integer.')
change = wrap(change, ['channeldb', 'int', 'regexpReplacer'])
def old(self, irc, msg, args): def old(self, irc, msg, args, channel, id):
"""[<channel>] [<number>] """[<channel>] [<id>]
Returns the old news item for <channel> with id <number>. If no number Returns the old news item for <channel> with <id>. If no number is
is given, returns all the old news items in reverse order. <channel> given, returns all the old news items in reverse order. <channel> is
is only necessary if the message isn't sent in the channel itself. only necessary if the message isn't sent in the channel itself.
""" """
channel = privmsgs.getChannel(msg, args)
id = privmsgs.getArgs(args, required=0, optional=1)
if id: if id:
try: try:
record = self.db.get(channel, id, old=True) if id < 1:
raise ValueError
record = self.db.getOld(channel, id)
irc.reply(str(record)) irc.reply(str(record))
except dbi.NoRecordError, id: except dbi.NoRecordError, id:
irc.errorInvalid('news item id', id) irc.errorInvalid('news item id', id)
except ValueError:
irc.errorInvalid('news item id', id,
'<id> must be a positive integer.')
else: else:
try: try:
records = self.db.get(channel, old=True) records = self.db.getOld(channel)
items = ['(#%s) %s' % (id, s) for (id, s) in records] items = ['(#%s) %s' % (R.id, R.subject) for R in records]
s = 'Old news for %s: %s' % (channel, '; '.join(items)) s = 'Old news for %s: %s' % (channel, '; '.join(items))
irc.reply(s) irc.reply(s)
except dbi.NoRecordError: except dbi.NoRecordError:
irc.reply('No old news for %s.' % channel) irc.reply('No old news for %s.' % channel)
old = wrap(old, ['channeldb', ('int?', None)])
Class = News Class = News

View File

@ -33,57 +33,57 @@ import time
import supybot.utils as utils import supybot.utils as utils
try: class NewsTestCase(ChannelPluginTestCase):
import sqlite plugins = ('News','User')
except ImportError: def setUp(self):
sqlite = None ChannelPluginTestCase.setUp(self)
# Create a valid user to use
self.prefix = 'news!bar@baz'
self.irc.feedMsg(ircmsgs.privmsg(self.nick, 'register tester moo',
prefix=self.prefix))
m = self.irc.takeMsg() # Response to register.
def testAddnews(self):
self.assertNotError('add 0 subject: foo')
self.assertRegexp('news', 'subject')
self.assertNotError('add 0 subject2: foo2')
self.assertRegexp('news', 'subject.*subject2')
self.assertNotError('add 5 subject3: foo3')
self.assertRegexp('news', 'subject3')
print
print 'Sleeping to expire the news item (testAddnews)'
time.sleep(6)
print 'Done sleeping.'
self.assertNotRegexp('news', 'subject3')
if sqlite is not None: def testNews(self):
class NewsTestCase(ChannelPluginTestCase): # These should both fail first, as they will have nothing in the DB
plugins = ('News',) self.assertRegexp('news', 'no news')
def testAddnews(self): self.assertRegexp('news #channel', 'no news')
self.assertNotError('add 0 subject: foo') # Now we'll add news and make sure listnews doesn't fail
self.assertRegexp('news', 'subject') self.assertNotError('add #channel 0 subject: foo')
self.assertNotError('add 0 subject2: foo2') self.assertNotError('news #channel')
self.assertRegexp('news', 'subject.*subject2') self.assertNotError('add 0 subject: foo')
self.assertNotError('add 5 subject3: foo3') self.assertRegexp('news', '#1')
self.assertRegexp('news', 'subject3') self.assertNotError('news 1')
print
print 'Sleeping to expire the news item (testAddnews)'
time.sleep(6)
print 'Done sleeping.'
self.assertNotRegexp('news', 'subject3')
def testNews(self): def testChangenews(self):
# These should both fail first, as they will have nothing in the DB self.assertNotError('add 0 Foo: bar')
self.assertRegexp('news', 'no news') self.assertNotError('change 1 s/bar/baz/')
self.assertRegexp('news #channel', 'no news') self.assertNotRegexp('news 1', 'bar')
# Now we'll add news and make sure listnews doesn't fail self.assertRegexp('news 1', 'baz')
self.assertNotError('add #channel 0 subject: foo')
self.assertNotError('news #channel')
self.assertNotError('add 0 subject: foo')
self.assertRegexp('news', '#1')
self.assertNotError('news 1')
def testChangenews(self):
self.assertNotError('add 0 Foo: bar')
self.assertNotError('change 1 s/bar/baz/')
self.assertNotRegexp('news 1', 'bar')
self.assertRegexp('news 1', 'baz')
def testOldnews(self):
self.assertRegexp('old', 'No old news')
self.assertNotError('add 0 a: b')
self.assertRegexp('old', 'No old news')
self.assertNotError('add 5 foo: bar')
self.assertRegexp('old', 'No old news')
print
print 'Sleeping to expire the news item (testOldnews)'
time.sleep(6)
print 'Done sleeping.'
self.assertNotError('old')
def testOldnews(self):
self.assertRegexp('old', 'No old news')
self.assertNotError('add 0 a: b')
self.assertRegexp('old', 'No old news')
self.assertNotError('add 5 foo: bar')
self.assertRegexp('old', 'No old news')
print
print 'Sleeping to expire the news item (testOldnews)'
time.sleep(6)
print 'Done sleeping.'
self.assertNotError('old')
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: