diff roundup/backends/sessions_dbm.py @ 2082:c091cacdc505

Finished implementation of session and one-time-key stores for RDBMS backends. Refactored the API of sessions and their interaction with the backend database a fair bit too. Added some session tests. Nothing testing ageing yet, 'cos that's a pain inna ass to test :) Note: metakit backend still uses the *dbm implementation. It might want to implement its own session store some day, as it'll be faster than the *dbm one.
author Richard Jones <richard@users.sourceforge.net>
date Thu, 18 Mar 2004 01:58:46 +0000
parents
children 93f03c6714d8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/roundup/backends/sessions_dbm.py	Thu Mar 18 01:58:46 2004 +0000
@@ -0,0 +1,141 @@
+#$Id: sessions_dbm.py,v 1.1 2004-03-18 01:58:45 richard Exp $
+"""This module defines a very basic store that's used by the CGI interface
+to store session and one-time-key information.
+
+Yes, it's called "sessions" - because originally it only defined a session
+class. It's now also used for One Time Key handling too.
+"""
+__docformat__ = 'restructuredtext'
+
+import anydbm, whichdb, os, marshal, time
+
+class BasicDatabase:
+    ''' Provide a nice encapsulation of an anydbm store.
+
+        Keys are id strings, values are automatically marshalled data.
+    '''
+    _db_type = None
+
+    def __init__(self, db):
+        self.config = db.config
+        self.dir = db.config.DATABASE
+        # ensure files are group readable and writable
+        os.umask(0002)
+
+    def clear(self):
+        path = os.path.join(self.dir, self.name)
+        if os.path.exists(path):
+            os.remove(path)
+        elif os.path.exists(path+'.db'):    # dbm appends .db
+            os.remove(path+'.db')
+
+    def cache_db_type(self, path):
+        ''' determine which DB wrote the class file, and cache it as an
+            attribute of __class__ (to allow for subclassed DBs to be
+            different sorts)
+        '''
+        db_type = ''
+        if os.path.exists(path):
+            db_type = whichdb.whichdb(path)
+            if not db_type:
+                raise hyperdb.DatabaseError, "Couldn't identify database type"
+        elif os.path.exists(path+'.db'):
+            # if the path ends in '.db', it's a dbm database, whether
+            # anydbm says it's dbhash or not!
+            db_type = 'dbm'
+        self.__class__._db_type = db_type
+
+    _marker = []
+    def get(self, infoid, value, default=_marker):
+        db = self.opendb('c')
+        try:
+            if db.has_key(infoid):
+                values = marshal.loads(db[infoid])
+            else:
+                if default != self._marker:
+                    return default
+                raise KeyError, 'No such %s "%s"'%(self.name, infoid)
+            return values.get(value, None)
+        finally:
+            db.close()
+
+    def getall(self, infoid):
+        db = self.opendb('c')
+        try:
+            try:
+                return marshal.loads(db[infoid])
+            except KeyError:
+                raise KeyError, 'No such %s "%s"'%(self.name, infoid)
+        finally:
+            db.close()
+
+    def set(self, infoid, **newvalues):
+        db = self.opendb('c')
+        try:
+            if db.has_key(infoid):
+                values = marshal.loads(db[infoid])
+            else:
+                values = {'__timestamp': time.time()}
+            values.update(newvalues)
+            db[infoid] = marshal.dumps(values)
+        finally:
+            db.close()
+
+    def list(self):
+        db = self.opendb('r')
+        try:
+            return db.keys()
+        finally:
+            db.close()
+
+    def destroy(self, infoid):
+        db = self.opendb('c')
+        try:
+            if db.has_key(infoid):
+                del db[infoid]
+        finally:
+            db.close()
+
+    def opendb(self, mode):
+        '''Low-level database opener that gets around anydbm/dbm
+           eccentricities.
+        '''
+        # figure the class db type
+        path = os.path.join(os.getcwd(), self.dir, self.name)
+        if self._db_type is None:
+            self.cache_db_type(path)
+
+        db_type = self._db_type
+
+        # new database? let anydbm pick the best dbm
+        if not db_type:
+            return anydbm.open(path, 'c')
+
+        # open the database with the correct module
+        dbm = __import__(db_type)
+        return dbm.open(path, mode)
+
+    def commit(self):
+        pass
+
+    def close(self):
+        pass
+
+    def updateTimestamp(self, sessid):
+        self.set(sessid, __timestamp=time.time())
+
+    def clean(self, now):
+        """Age sessions, remove when they haven't been used for a week.
+        """
+        week = 60*60*24*7
+        for sessid in self.list():
+            interval = now - self.get(sessid, '__timestamp')
+            if interval > week:
+                self.destroy(sessid)
+
+class Sessions(BasicDatabase):
+    name = 'sessions'
+
+class OneTimeKeys(BasicDatabase):
+    name = 'otks'
+

Roundup Issue Tracker: http://roundup-tracker.org/