Code

fix
[roundup.git] / roundup / backends / sessions_dbm.py
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 :