Code

OTK generation was busted (thanks Stuart D. Gathman)
[roundup.git] / roundup / backends / sessions_dbm.py
1 #$Id: sessions_dbm.py,v 1.5 2004-03-31 23:08:38 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 exists(self, infoid):
26         db = self.opendb('c')
27         try:
28             return db.has_key(infoid)
29         finally:
30             db.close()
32     def clear(self):
33         path = os.path.join(self.dir, self.name)
34         if os.path.exists(path):
35             os.remove(path)
36         elif os.path.exists(path+'.db'):    # dbm appends .db
37             os.remove(path+'.db')
39     def cache_db_type(self, path):
40         ''' determine which DB wrote the class file, and cache it as an
41             attribute of __class__ (to allow for subclassed DBs to be
42             different sorts)
43         '''
44         db_type = ''
45         if os.path.exists(path):
46             db_type = whichdb.whichdb(path)
47             if not db_type:
48                 raise hyperdb.DatabaseError, "Couldn't identify database type"
49         elif os.path.exists(path+'.db'):
50             # if the path ends in '.db', it's a dbm database, whether
51             # anydbm says it's dbhash or not!
52             db_type = 'dbm'
53         self.__class__._db_type = db_type
55     _marker = []
56     def get(self, infoid, value, default=_marker):
57         db = self.opendb('c')
58         try:
59             if db.has_key(infoid):
60                 values = marshal.loads(db[infoid])
61             else:
62                 if default != self._marker:
63                     return default
64                 raise KeyError, 'No such %s "%s"'%(self.name, infoid)
65             return values.get(value, None)
66         finally:
67             db.close()
69     def getall(self, infoid):
70         db = self.opendb('c')
71         try:
72             try:
73                 d = marshal.loads(db[infoid])
74                 del d['__timestamp']
75                 return d
76             except KeyError:
77                 raise KeyError, 'No such %s "%s"'%(self.name, infoid)
78         finally:
79             db.close()
81     def set(self, infoid, **newvalues):
82         db = self.opendb('c')
83         try:
84             if db.has_key(infoid):
85                 values = marshal.loads(db[infoid])
86             else:
87                 values = {'__timestamp': time.time()}
88             values.update(newvalues)
89             db[infoid] = marshal.dumps(values)
90         finally:
91             db.close()
93     def list(self):
94         db = self.opendb('r')
95         try:
96             return db.keys()
97         finally:
98             db.close()
100     def destroy(self, infoid):
101         db = self.opendb('c')
102         try:
103             if db.has_key(infoid):
104                 del db[infoid]
105         finally:
106             db.close()
108     def opendb(self, mode):
109         '''Low-level database opener that gets around anydbm/dbm
110            eccentricities.
111         '''
112         # figure the class db type
113         path = os.path.join(os.getcwd(), self.dir, self.name)
114         if self._db_type is None:
115             self.cache_db_type(path)
117         db_type = self._db_type
119         # new database? let anydbm pick the best dbm
120         if not db_type:
121             return anydbm.open(path, 'c')
123         # open the database with the correct module
124         dbm = __import__(db_type)
125         return dbm.open(path, mode)
127     def commit(self):
128         pass
130     def close(self):
131         pass
133     def updateTimestamp(self, sessid):
134         self.set(sessid, __timestamp=time.time())
136     def clean(self, now):
137         """Age sessions, remove when they haven't been used for a week.
138         """
139         week = 60*60*24*7
140         for sessid in self.list():
141             sess = self.get(sessid, '__timestamp', None)
142             if sess is None:
143                 sess=time.time()
144                 self.updateTimestamp(sessid)
145             interval = now - sess
146             if interval > week:
147                 self.destroy(sessid)
149 class Sessions(BasicDatabase):
150     name = 'sessions'
152 class OneTimeKeys(BasicDatabase):
153     name = 'otks'