Code

Finished implementation of session and one-time-key stores for RDBMS
[roundup.git] / roundup / backends / sessions_dbm.py
1 #$Id: sessions_dbm.py,v 1.1 2004-03-18 01:58:45 richard Exp $
2 """This module defines a very basic store that's used by the CGI interface
3 to store session and one-time-key information.
5 Yes, it's called "sessions" - because originally it only defined a session
6 class. It's now also used for One Time Key handling too.
7 """
8 __docformat__ = 'restructuredtext'
10 import anydbm, whichdb, os, marshal, time
12 class BasicDatabase:
13     ''' Provide a nice encapsulation of an anydbm store.
15         Keys are id strings, values are automatically marshalled data.
16     '''
17     _db_type = None
19     def __init__(self, db):
20         self.config = db.config
21         self.dir = db.config.DATABASE
22         # ensure files are group readable and writable
23         os.umask(0002)
25     def clear(self):
26         path = os.path.join(self.dir, self.name)
27         if os.path.exists(path):
28             os.remove(path)
29         elif os.path.exists(path+'.db'):    # dbm appends .db
30             os.remove(path+'.db')
32     def cache_db_type(self, path):
33         ''' determine which DB wrote the class file, and cache it as an
34             attribute of __class__ (to allow for subclassed DBs to be
35             different sorts)
36         '''
37         db_type = ''
38         if os.path.exists(path):
39             db_type = whichdb.whichdb(path)
40             if not db_type:
41                 raise hyperdb.DatabaseError, "Couldn't identify database type"
42         elif os.path.exists(path+'.db'):
43             # if the path ends in '.db', it's a dbm database, whether
44             # anydbm says it's dbhash or not!
45             db_type = 'dbm'
46         self.__class__._db_type = db_type
48     _marker = []
49     def get(self, infoid, value, default=_marker):
50         db = self.opendb('c')
51         try:
52             if db.has_key(infoid):
53                 values = marshal.loads(db[infoid])
54             else:
55                 if default != self._marker:
56                     return default
57                 raise KeyError, 'No such %s "%s"'%(self.name, infoid)
58             return values.get(value, None)
59         finally:
60             db.close()
62     def getall(self, infoid):
63         db = self.opendb('c')
64         try:
65             try:
66                 return marshal.loads(db[infoid])
67             except KeyError:
68                 raise KeyError, 'No such %s "%s"'%(self.name, infoid)
69         finally:
70             db.close()
72     def set(self, infoid, **newvalues):
73         db = self.opendb('c')
74         try:
75             if db.has_key(infoid):
76                 values = marshal.loads(db[infoid])
77             else:
78                 values = {'__timestamp': time.time()}
79             values.update(newvalues)
80             db[infoid] = marshal.dumps(values)
81         finally:
82             db.close()
84     def list(self):
85         db = self.opendb('r')
86         try:
87             return db.keys()
88         finally:
89             db.close()
91     def destroy(self, infoid):
92         db = self.opendb('c')
93         try:
94             if db.has_key(infoid):
95                 del db[infoid]
96         finally:
97             db.close()
99     def opendb(self, mode):
100         '''Low-level database opener that gets around anydbm/dbm
101            eccentricities.
102         '''
103         # figure the class db type
104         path = os.path.join(os.getcwd(), self.dir, self.name)
105         if self._db_type is None:
106             self.cache_db_type(path)
108         db_type = self._db_type
110         # new database? let anydbm pick the best dbm
111         if not db_type:
112             return anydbm.open(path, 'c')
114         # open the database with the correct module
115         dbm = __import__(db_type)
116         return dbm.open(path, mode)
118     def commit(self):
119         pass
121     def close(self):
122         pass
124     def updateTimestamp(self, sessid):
125         self.set(sessid, __timestamp=time.time())
127     def clean(self, now):
128         """Age sessions, remove when they haven't been used for a week.
129         """
130         week = 60*60*24*7
131         for sessid in self.list():
132             interval = now - self.get(sessid, '__timestamp')
133             if interval > week:
134                 self.destroy(sessid)
136 class Sessions(BasicDatabase):
137     name = 'sessions'
139 class OneTimeKeys(BasicDatabase):
140     name = 'otks'