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'