1 #!/usr/bin/env python 2 3 """ 4 Session helper functions. 5 6 Copyright (C) 2004, 2005 Paul Boddie <paul@boddie.org.uk> 7 8 This library is free software; you can redistribute it and/or 9 modify it under the terms of the GNU Lesser General Public 10 License as published by the Free Software Foundation; either 11 version 2.1 of the License, or (at your option) any later version. 12 13 This library is distributed in the hope that it will be useful, 14 but WITHOUT ANY WARRANTY; without even the implied warranty of 15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 Lesser General Public License for more details. 17 18 You should have received a copy of the GNU Lesser General Public 19 License along with this library; if not, write to the Free Software 20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 21 """ 22 23 import shelve 24 import os 25 import glob 26 import time 27 import random 28 import sys 29 30 class SessionStore: 31 32 "A class representing a session store." 33 34 def __init__(self, trans, session_directory, session_cookie_name="SID", concurrent=1, delay=1): 35 36 """ 37 Initialise the session store, specifying the transaction 'trans' within 38 which all session access will occur, a base 'session_directory', the 39 optional 'session_cookie_name' where the session identifier is held for 40 each user, and specifying using the optional 'concurrent' parameter 41 whether concurrent access within the framework might occur (1) or 42 whether the framework queues accesses at some other level (0). The 43 optional 'delay' argument specifies the time in seconds between each 44 poll of the session file when that file is found to be locked for 45 editing. 46 """ 47 48 self.trans = trans 49 self.session_directory = session_directory 50 self.session_cookie_name = session_cookie_name 51 self.concurrent = concurrent 52 self.delay = delay 53 54 # Internal state. 55 56 self.store = None 57 self.store_filename, self.edit_filename = None, None 58 self.to_expire = None 59 60 def close(self): 61 62 "Close the store, tidying up files and filenames." 63 64 if self.store is not None: 65 self.store.close() 66 self.store = None 67 if self.edit_filename is not None: 68 try: 69 os.rename(self.edit_filename, self.store_filename) 70 except OSError: 71 pass 72 self.edit_filename, self.store_filename = None, None 73 74 # Handle expiry appropriately. 75 76 if self.to_expire is not None: 77 self._expire_session(self.to_expire) 78 self.trans.delete_cookie(self.session_cookie_name) 79 80 def expire_session(self): 81 82 """ 83 Expire the session in the given transaction. 84 """ 85 86 # Perform expiry. 87 88 cookie = self.trans.get_cookie(self.session_cookie_name) 89 if cookie: 90 self.to_expire = cookie.value 91 92 def _expire_session(self, session_id): 93 94 """ 95 Expire the session with the given 'session_id'. Note that in concurrent 96 session stores, this operation will block if another execution context 97 is editing the session. 98 """ 99 100 filename = os.path.join(self.session_directory, session_id) 101 if self.concurrent: 102 while 1: 103 try: 104 os.unlink(filename) 105 except OSError: 106 time.sleep(self.delay) 107 else: 108 break 109 else: 110 try: 111 os.unlink(filename) 112 except OSError: 113 pass 114 115 def get_session(self, create): 116 117 """ 118 Get the session for the given transaction, creating a new session if 119 'create' is set to 1 (rather than 0). Where new sessions are created, an 120 appropriate session identifier cookie will be created. 121 Returns a session object or None if no session exists and none is then 122 created. 123 """ 124 125 cookie = self.trans.get_cookie(self.session_cookie_name) 126 if cookie: 127 return self._get_session(cookie.value, create) 128 elif create: 129 session_id = self._get_session_identifier() 130 self.trans.set_cookie_value(self.session_cookie_name, session_id) 131 return self._get_session(session_id, create) 132 else: 133 return None 134 135 def _get_session(self, session_id, create): 136 137 """ 138 Get a session with the given 'session_id' and whether new sessions 139 should be created ('create' set to 1). 140 Returns a dictionary-like object representing the session. 141 """ 142 143 filename = os.path.join(self.session_directory, session_id) 144 145 # Enforce locking. 146 147 if self.concurrent: 148 149 # Where the session is present (possibly being edited)... 150 151 if glob.glob(filename + "*"): 152 while 1: 153 try: 154 os.rename(filename, filename + ".edit") 155 except OSError: 156 time.sleep(self.delay) 157 else: 158 break 159 160 # Where no session is present and none should be created, return. 161 162 elif not create: 163 return None 164 165 self.store_filename = filename 166 filename = filename + ".edit" 167 self.edit_filename = filename 168 169 # For non-concurrent situations, return if no session exists and none 170 # should be created. 171 172 elif not os.path.exists(filename) and not create: 173 return None 174 175 self.store = shelve.open(filename) 176 return Wrapper(self.store) 177 178 def _get_session_identifier(self): 179 180 "Return a session identifier as a string." 181 182 g = random.Random() 183 return str(g.randint(0, sys.maxint - 1)) 184 185 class Wrapper: 186 187 "A wrapper around shelf objects." 188 189 def __init__(self, store): 190 self.store = store 191 192 def __getattr__(self, name): 193 if hasattr(self.store, name): 194 return getattr(self.store, name) 195 else: 196 raise AttributeError, name 197 198 def __getitem__(self, name): 199 # Convert to UTF-8 to avoid bsddb limitations. 200 return self.store[name.encode("utf-8")] 201 202 def __delitem__(self, name): 203 # Convert to UTF-8 to avoid bsddb limitations. 204 del self.store[name.encode("utf-8")] 205 206 def __setitem__(self, name, value): 207 # Convert to UTF-8 to avoid bsddb limitations. 208 self.store[name.encode("utf-8")] = value 209 210 def keys(self): 211 l = [] 212 for key in self.store.keys(): 213 # Convert from UTF-8 to avoid bsddb limitations. 214 l.append(unicode(key, "utf-8")) 215 return l 216 217 def items(self): 218 l = [] 219 for key in self.keys(): 220 l.append((key, self[key])) 221 return l 222 223 # vim: tabstop=4 expandtab shiftwidth=4