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