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