| 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. |
| 4 | |
| 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' |
| 9 | |
| 10 | import os, marshal, time |
| 11 | |
| 12 | from roundup import hyperdb |
| 13 | from roundup.i18n import _ |
| 14 | from roundup.anypy.dbm_ import anydbm, whichdb |
| 15 | |
| 16 | class BasicDatabase: |
| 17 | ''' Provide a nice encapsulation of an anydbm store. |
| 18 | |
| 19 | Keys are id strings, values are automatically marshalled data. |
| 20 | ''' |
| 21 | _db_type = None |
| 22 | |
| 23 | def __init__(self, db): |
| 24 | self.config = db.config |
| 25 | self.dir = db.config.DATABASE |
| 26 | os.umask(db.config.UMASK) |
| 27 | |
| 28 | def exists(self, infoid): |
| 29 | db = self.opendb('c') |
| 30 | try: |
| 31 | return infoid in db |
| 32 | finally: |
| 33 | db.close() |
| 34 | |
| 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') |
| 41 | |
| 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 |
| 58 | |
| 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() |
| 72 | |
| 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() |
| 84 | |
| 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() |
| 96 | |
| 97 | def list(self): |
| 98 | db = self.opendb('r') |
| 99 | try: |
| 100 | return list(db) |
| 101 | finally: |
| 102 | db.close() |
| 103 | |
| 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() |
| 111 | |
| 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) |
| 120 | |
| 121 | db_type = self._db_type |
| 122 | |
| 123 | # new database? let anydbm pick the best dbm |
| 124 | if not db_type: |
| 125 | return anydbm.open(path, 'c') |
| 126 | |
| 127 | # open the database with the correct module |
| 128 | dbm = __import__(db_type) |
| 129 | return dbm.open(path, mode) |
| 130 | |
| 131 | def commit(self): |
| 132 | pass |
| 133 | |
| 134 | def close(self): |
| 135 | pass |
| 136 | |
| 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) |
| 143 | |
| 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) |
| 156 | |
| 157 | class Sessions(BasicDatabase): |
| 158 | name = 'sessions' |
| 159 | |
| 160 | class OneTimeKeys(BasicDatabase): |
| 161 | name = 'otks' |
| 162 | |
| 163 | # vim: set sts ts=4 sw=4 et si : |