1087967b6096a6d8d0e8e311238c19c378a488e0
1 #$Id: sessions_dbm.py,v 1.10 2008-08-18 05:04:01 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
11 from roundup import hyperdb
12 from roundup.i18n import _
14 class BasicDatabase:
15 ''' Provide a nice encapsulation of an anydbm store.
17 Keys are id strings, values are automatically marshalled data.
18 '''
19 _db_type = None
21 def __init__(self, db):
22 self.config = db.config
23 self.dir = db.config.DATABASE
24 os.umask(db.config.UMASK)
26 def exists(self, infoid):
27 db = self.opendb('c')
28 try:
29 return db.has_key(infoid)
30 finally:
31 db.close()
33 def clear(self):
34 path = os.path.join(self.dir, self.name)
35 if os.path.exists(path):
36 os.remove(path)
37 elif os.path.exists(path+'.db'): # dbm appends .db
38 os.remove(path+'.db')
40 def cache_db_type(self, path):
41 ''' determine which DB wrote the class file, and cache it as an
42 attribute of __class__ (to allow for subclassed DBs to be
43 different sorts)
44 '''
45 db_type = ''
46 if os.path.exists(path):
47 db_type = whichdb.whichdb(path)
48 if not db_type:
49 raise hyperdb.DatabaseError, \
50 _("Couldn't identify database type")
51 elif os.path.exists(path+'.db'):
52 # if the path ends in '.db', it's a dbm database, whether
53 # anydbm says it's dbhash or not!
54 db_type = 'dbm'
55 self.__class__._db_type = db_type
57 _marker = []
58 def get(self, infoid, value, default=_marker):
59 db = self.opendb('c')
60 try:
61 if db.has_key(infoid):
62 values = marshal.loads(db[infoid])
63 else:
64 if default != self._marker:
65 return default
66 raise KeyError, 'No such %s "%s"'%(self.name, infoid)
67 return values.get(value, None)
68 finally:
69 db.close()
71 def getall(self, infoid):
72 db = self.opendb('c')
73 try:
74 try:
75 d = marshal.loads(db[infoid])
76 del d['__timestamp']
77 return d
78 except KeyError:
79 raise KeyError, 'No such %s "%s"'%(self.name, infoid)
80 finally:
81 db.close()
83 def set(self, infoid, **newvalues):
84 db = self.opendb('c')
85 try:
86 if db.has_key(infoid):
87 values = marshal.loads(db[infoid])
88 else:
89 values = {'__timestamp': time.time()}
90 values.update(newvalues)
91 db[infoid] = marshal.dumps(values)
92 finally:
93 db.close()
95 def list(self):
96 db = self.opendb('r')
97 try:
98 return db.keys()
99 finally:
100 db.close()
102 def destroy(self, infoid):
103 db = self.opendb('c')
104 try:
105 if db.has_key(infoid):
106 del db[infoid]
107 finally:
108 db.close()
110 def opendb(self, mode):
111 '''Low-level database opener that gets around anydbm/dbm
112 eccentricities.
113 '''
114 # figure the class db type
115 path = os.path.join(os.getcwd(), self.dir, self.name)
116 if self._db_type is None:
117 self.cache_db_type(path)
119 db_type = self._db_type
121 # new database? let anydbm pick the best dbm
122 if not db_type:
123 return anydbm.open(path, 'c')
125 # open the database with the correct module
126 dbm = __import__(db_type)
127 return dbm.open(path, mode)
129 def commit(self):
130 pass
132 def close(self):
133 pass
135 def updateTimestamp(self, sessid):
136 ''' don't update every hit - once a minute should be OK '''
137 sess = self.get(sessid, '__timestamp', None)
138 now = time.time()
139 if sess is None or now > sess + 60:
140 self.set(sessid, __timestamp=now)
142 def clean(self):
143 ''' Remove session records that haven't been used for a week. '''
144 now = time.time()
145 week = 60*60*24*7
146 for sessid in self.list():
147 sess = self.get(sessid, '__timestamp', None)
148 if sess is None:
149 self.updateTimestamp(sessid)
150 continue
151 interval = now - sess
152 if interval > week:
153 self.destroy(sessid)
155 class Sessions(BasicDatabase):
156 name = 'sessions'
158 class OneTimeKeys(BasicDatabase):
159 name = 'otks'
161 # vim: set sts ts=4 sw=4 et si :