diff --git a/plugins/MoobotFactoids.py b/plugins/MoobotFactoids.py index 812802deb..96f195da1 100644 --- a/plugins/MoobotFactoids.py +++ b/plugins/MoobotFactoids.py @@ -59,7 +59,7 @@ import callbacks import Owner -dbfilename = os.path.join(conf.dataDir, 'MoobotFactoids.db') +dbfilename = os.path.join(conf.dataDir, 'MoobotFactoids') def configure(onStart, afterConnect, advanced): # This will be called by setup.py to configure this module. onStart and @@ -118,41 +118,46 @@ class OptionList(object): def pickOptions(s): return OptionList().tokenize(s) + +class MoobotDBHandler(plugins.DBHandler): + def makeDb(self, filename): + """create MoobotFactoids database and tables""" + if os.path.exists(filename): + db = sqlite.connect(filename) + else: + db = sqlite.connect(filename, converters={'bool': bool}) + cursor = db.cursor() + cursor.execute("""CREATE TABLE factoids ( + key TEXT PRIMARY KEY, + created_by INTEGER, + created_at TIMESTAMP, + modified_by INTEGER, + modified_at TIMESTAMP, + locked_at TIMESTAMP, + locked_by INTEGER, + last_requested_by TEXT, + last_requested_at TIMESTAMP, + fact TEXT, + requested_count INTEGER + )""") + db.commit() + return db + + class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): priority = 98 addressedRegexps = ['changeFactoid', 'augmentFactoid', 'replaceFactoid', 'addFactoid'] def __init__(self): callbacks.PrivmsgCommandAndRegexp.__init__(self) - self.makeDb(dbfilename) - - def makeDb(self, filename): - """create MoobotFactoids database and tables""" - if os.path.exists(filename): - self.db = sqlite.connect(filename) - return - self.db = sqlite.connect(filename, converters={'bool': bool}) - cursor = self.db.cursor() - cursor.execute("""CREATE TABLE factoids ( - key TEXT PRIMARY KEY, - created_by INTEGER, - created_at TIMESTAMP, - modified_by INTEGER, - modified_at TIMESTAMP, - locked_at TIMESTAMP, - locked_by INTEGER, - last_requested_by TEXT, - last_requested_at TIMESTAMP, - fact TEXT, - requested_count INTEGER - )""") - self.db.commit() + self.dbHandler = MoobotDBHandler(dbfilename) def die(self): # Handle DB stuff - self.db.commit() - self.db.close() - del self.db + db = self.dbHandler.getDb() + db.commit() + db.close() + del db def parseFactoid(self, irc, msg, fact): type = "define" # Default is to just spit the factoid back as a @@ -171,14 +176,15 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): def updateFactoidRequest(self, key, hostmask): """Updates the last_requested_* fields of a factoid row""" - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""UPDATE factoids SET last_requested_by = %s, last_requested_at = %s, requested_count = requested_count +1 WHERE key = %s""", hostmask, int(time.time()), key) - self.db.commit() + db.commit() def invalidCommand(self, irc, msg, tokens): key = ' '.join(tokens) @@ -186,7 +192,8 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): if key.startswith('\x01'): return # Check the factoid db for an appropriate reply - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""SELECT fact FROM factoids WHERE key LIKE %s""", key) if cursor.rowcount == 0: return False @@ -210,7 +217,8 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): def addFactoid(self, irc, msg, match): r"^(.+?)\s+(?:is|_is_)\s+(.+)" # First, check and see if the entire message matches a factoid key - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""SELECT * FROM factoids WHERE key LIKE %s""", match.group().rstrip('?! ')) if cursor.rowcount != 0: @@ -240,7 +248,7 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): (%s, %s, %s, NULL, NULL, NULL, NULL, NULL, NULL, %s, 0)""", key, id, int(time.time()), fact) - self.db.commit() + db.commit() irc.reply(msg, conf.replySuccess) def changeFactoid(self, irc, msg, match): @@ -252,7 +260,8 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): irc.error(msg, conf.replyNotRegistered) return key, regexp = match.groups() - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() # Check and make sure it's in the DB cursor.execute("""SELECT locked_at, fact FROM factoids WHERE key LIKE %s""", key) @@ -275,7 +284,7 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): SET fact = %s, modified_by = %s, modified_at = %s WHERE key = %s""", new_fact, id, int(time.time()), key) - self.db.commit() + db.commit() irc.reply(msg, conf.replySuccess) def augmentFactoid(self, irc, msg, match): @@ -287,7 +296,8 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): irc.error(msg, conf.replyNotRegistered) return key, new_text = match.groups() - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() # Check and make sure it's in the DB cursor.execute("""SELECT locked_at, fact FROM factoids WHERE key LIKE %s""", key) @@ -305,7 +315,7 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): SET fact = %s, modified_by = %s, modified_at = %s WHERE key = %s""", new_fact, id, int(time.time()), key) - self.db.commit() + db.commit() irc.reply(msg, conf.replySuccess) def replaceFactoid(self, irc, msg, match): @@ -322,7 +332,8 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): if '_is_' in match.group(): key, new_fact = imap(str.strip, match.group().split('_is_', 1)) key = key.split(' ', 1)[1] # Take out everything to first space - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() # Check and make sure it's in the DB cursor.execute("""SELECT locked_at, fact FROM factoids WHERE key LIKE %s""", key) @@ -343,7 +354,7 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): locked_at = NULL WHERE key = %s""", new_fact, id, int(time.time()), key) - self.db.commit() + db.commit() irc.reply(msg, conf.replySuccess) def literal(self, irc, msg, args): @@ -353,7 +364,8 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): the factoid value is done as it is with normal retrieval. """ key = privmsgs.getArgs(args, required=1) - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""SELECT fact FROM factoids WHERE key LIKE %s""", key) if cursor.rowcount == 0: irc.error(msg, "No such factoid: %r" % key) @@ -371,7 +383,8 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): # Start building the response string s = key + ": " # Next, get all the info and build the response piece by piece - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""SELECT created_by, created_at, modified_by, modified_at, last_requested_by, last_requested_at, requested_count, locked_by, locked_at FROM @@ -418,7 +431,8 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): irc.error(msg, conf.replyNotRegistered) return key = privmsgs.getArgs(args, required=1) - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""SELECT created_by, locked_by FROM factoids WHERE key LIKE %s""", key) if cursor.rowcount == 0: @@ -450,7 +464,7 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): id = None cursor.execute("""UPDATE factoids SET locked_at = %s, locked_by = %s WHERE key = %s""", locked_at, id, key) - self.db.commit() + db.commit() irc.reply(msg, conf.replySuccess) def lock(self, irc, msg, args): @@ -484,7 +498,8 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): method = getattr(self, '_most%s' % arg, None) if method is None: raise callbacks.ArgumentError - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""SELECT COUNT(*) FROM factoids""") if int(cursor.fetchone()[0]) == 0: irc.error(msg, 'I don\'t have any factoids in my database!') @@ -535,7 +550,8 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): except KeyError: irc.error(msg, "No such user: %r" % author) return - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""SELECT key FROM factoids WHERE created_by = %s ORDER BY key""", id) @@ -554,7 +570,8 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): """ search = privmsgs.getArgs(args, required=1) glob = '%' + search + '%' - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""SELECT key FROM factoids WHERE key LIKE %s ORDER BY key""", @@ -574,7 +591,8 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): """ search = privmsgs.getArgs(args, required=1) glob = '%' + search + '%' - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""SELECT key FROM factoids WHERE fact LIKE %s ORDER BY key""", @@ -599,7 +617,8 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): irc.error(msg, conf.replyNotRegistered) return key = privmsgs.getArgs(args, required=1) - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""SELECT key, locked_at FROM factoids WHERE key LIKE %s""", key) if cursor.rowcount == 0: @@ -610,7 +629,7 @@ class MoobotFactoids(callbacks.PrivmsgCommandAndRegexp): irc.error(msg, "Factoid is locked, cannot remove.") return cursor.execute("""DELETE FROM factoids WHERE key = %s""", key) - self.db.commit() + db.commit() irc.reply(msg, conf.replySuccess) Class = MoobotFactoids diff --git a/plugins/Note.py b/plugins/Note.py index a54c65944..24de9e937 100644 --- a/plugins/Note.py +++ b/plugins/Note.py @@ -47,54 +47,62 @@ import debug import utils import ircdb import ircmsgs +import plugins import privmsgs import ircutils import callbacks dbfilename = os.path.join(conf.dataDir, 'Notes.db') +class NoteDb(plugins.DBHandler): + def makeDb(self, filename): + "create Notes database and tables" + if os.path.exists(filename): + db = sqlite.connect(filename) + else: + db = sqlite.connect(filename, converters={'bool': bool}) + cursor = db.cursor() + cursor.execute("""CREATE TABLE notes ( + id INTEGER PRIMARY KEY, + from_id INTEGER, + to_id INTEGER, + added_at TIMESTAMP, + notified BOOLEAN, + read BOOLEAN, + public BOOLEAN, + note TEXT + )""") + db.commit() + return db + + + class Note(callbacks.Privmsg): def __init__(self): callbacks.Privmsg.__init__(self) - self.makeDB(dbfilename) - - def makeDB(self, filename): - "create Notes database and tables" - if os.path.exists(filename): - self.db = sqlite.connect(filename) - return - self.db = sqlite.connect(filename, converters={'bool': bool}) - cursor = self.db.cursor() - cursor.execute("""CREATE TABLE notes ( - id INTEGER PRIMARY KEY, - from_id INTEGER, - to_id INTEGER, - added_at TIMESTAMP, - notified BOOLEAN, - read BOOLEAN, - public BOOLEAN, - note TEXT - )""") - self.db.commit() + self.dbHandler = NoteDb(name=os.path.join(conf.dataDir, 'Notes')) def setAsRead(self, id): - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""UPDATE notes SET read=1, notified=1 WHERE id=%s""", id) - self.db.commit() + db.commit() def die(self): - self.db.commit() - self.db.close() - del self.db + db = self.dbHandler.getDb() + db.commit() + db.close() + del db def doPrivmsg(self, irc, msg): try: id = ircdb.users.getUserId(msg.prefix) except KeyError: return - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""SELECT COUNT(*) FROM notes WHERE notes.to_id=%s AND notified=0""", id) unnotified = int(cursor.fetchone()[0]) @@ -108,7 +116,7 @@ class Note(callbacks.Privmsg): irc.queueMsg(ircmsgs.privmsg(msg.nick, s)) cursor.execute("""UPDATE notes SET notified=1 WHERE notes.to_id=%s""", id) - self.db.commit() + db.commit() def send(self, irc, msg, args): """ @@ -135,12 +143,13 @@ class Note(callbacks.Privmsg): public = 1 else: public = 0 - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() now = int(time.time()) cursor.execute("""INSERT INTO notes VALUES (NULL, %s, %s, %s, 0, 0, %s, %s)""", fromId, toId, now, public, note) - self.db.commit() + db.commit() cursor.execute("""SELECT id FROM notes WHERE from_id=%s AND to_id=%s AND added_at=%s""", fromId, toId, now) @@ -158,7 +167,8 @@ class Note(callbacks.Privmsg): except KeyError: irc.error(msg, conf.replyNotRegistered) return - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""SELECT note, to_id, from_id, added_at, public FROM notes WHERE (to_id=%s OR from_id=%s) AND id=%s""", @@ -202,7 +212,8 @@ class Note(callbacks.Privmsg): except KeyError: irc.error(msg, conf.replyNotRegistered) return - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""SELECT id, from_id, public FROM notes WHERE notes.to_id=%s AND notes.read=0""", id) @@ -224,7 +235,8 @@ class Note(callbacks.Privmsg): except KeyError: irc.error(msg, conf.replyNotRegistered) return - cursor = self.db.cursor() + db = self.dbHandler.getDb() + cursor = db.cursor() cursor.execute("""SELECT id, from_id, public FROM notes WHERE notes.to_id=%s AND notes.read=1""", id) diff --git a/src/plugins.py b/src/plugins.py index ba7f8d37c..776e2ca28 100644 --- a/src/plugins.py +++ b/src/plugins.py @@ -54,11 +54,49 @@ import ircutils import privmsgs import callbacks +try: + import sqlite + Connection = sqlite.Connection + class MyConnection(sqlite.Connection): + def commit(self, *args, **kwargs): + if self.autocommit: + return + else: + Connection.commit(self, *args, **kwargs) + sqlite.Connection = MyConnection +except ImportError: + pass + +class DBHandler(object): + def __init__(self, name=None, suffix='.db'): + if name is None: + self.name = self.__class__.__name__ + else: + self.name = name + if suffix and suffix[0] != '.': + suffix = '.' + suffix + self.suffix = suffix + self.cachedDb = None + + def makeFilename(self): + return self.name + self.suffix + + def makeDb(self, filename): + raise NotImplementedError + + def getDb(self): + if self.cachedDb is None or \ + threading.currentThread() is not world.mainThread: + db = self.makeDb(self.makeFilename()) + else: + db = self.cachedDb + db.autocommit = 1 + return db + class ChannelDBHandler(object): """A class to handle database stuff for individual channels transparently. """ suffix = '.db' - threaded = False def __init__(self, suffix='.db'): self.dbCache = ircutils.IrcDict() suffix = self.suffix @@ -78,16 +116,13 @@ class ChannelDBHandler(object): def getDb(self, channel): """Use this to get a database for a specific channel.""" - try: - if self.threaded: - return self.makeDb(self.makeFilename(channel)) - else: - return self.dbCache[channel] - except KeyError: + if channel not in self.dbCache or \ + threading.currentThread() is not world.mainThread: db = self.makeDb(self.makeFilename(channel)) - if not self.threaded: - self.dbCache[channel] = db - return db + else: + db = self.dbCache[channel] + db.autocommit = 1 + return db def die(self): for db in self.dbCache.itervalues(): diff --git a/src/world.py b/src/world.py index 4c1e62b8e..639cb995b 100644 --- a/src/world.py +++ b/src/world.py @@ -41,16 +41,15 @@ import sre import time import types import atexit -try: - import msvcrt -except ImportError: - pass +import threading import conf import debug startedAt = 0.0 +mainThread = threading.currentThread() + threadsSpawned = 1 # Starts at one for the initial "thread." commandsProcessed = 0 ### @@ -75,7 +74,10 @@ def upkeep(): # Function to be run on occasion to do upkeep stuff. collected = gc.collect() if os.name == 'nt': try: + import msvcrt msvcrt.heapmin() + except ImportError: + pass except IOError: # Win98 sux0rs! pass if gc.garbage: