gyorgy@0: """ gyorgy@0: DiractorySession.py gyorgy@0: gyorgy@0: Created by George Fazekas, QMUL on 2009-06-03. gyorgy@0: Copyright (c) 2009 QMUL. All rights reserved. gyorgy@0: gyorgy@0: This Cherrypy extension implements a hybrid session type, gyorgy@0: where data is held in memory, while files are written to gyorgy@0: a directory created for each session. gyorgy@0: gyorgy@0: To use it as a builtin session type, we use: gyorgy@0: cherrypy.lib.sessions.DirectorySession = DirectorySession gyorgy@0: """ gyorgy@0: from __future__ import with_statement gyorgy@0: import os,sys,time,shutil,datetime,logging,threading,cherrypy gyorgy@0: from cherrypy.lib.sessions import Session gyorgy@0: from cherrypy.lib import static gyorgy@0: gyorgy@0: from sawautil import convertFilename gyorgy@0: gyorgy@0: class DirectorySession(Session): gyorgy@0: gyorgy@0: cache = {} gyorgy@0: locks = {} gyorgy@0: instanceList = [] gyorgy@0: gyorgy@0: def __init__(self, id=None, **kwargs): gyorgy@0: # for i in self.instanceList : gyorgy@0: # print 'previous-sessions: ', i.id gyorgy@0: # if (i.id == id) : gyorgy@0: # print 'got instance already!' gyorgy@0: # self.instanceList.append(self) gyorgy@0: # print 'instance initialised:' + str(len(self.instanceList)) gyorgy@0: # kwargs['SESSIONS_PATH'] = os.path.abspath(kwargs['SESSIONS_PATH']) gyorgy@0: gyorgy@0: if not id in self.cache: gyorgy@0: new_session_signal = True gyorgy@0: else : gyorgy@0: new_session_signal = False gyorgy@0: gyorgy@0: Session.__init__(self, id=id, **kwargs) gyorgy@0: gyorgy@0: if new_session_signal or not self.has_key('name'): gyorgy@0: active_sessions = str(self.__len__()) gyorgy@0: self.name = "Session %s.%s: " %(active_sessions, str(self.id)[-2:]) gyorgy@0: self['name'] = self.name gyorgy@0: self['log_count'] = 0 gyorgy@0: try : gyorgy@0: # try to write initial session data into the log file gyorgy@0: log_msg = "%sCherrypy::DirectorySession:New Session Created: ID = %s active sessions: %s \n" %(self.name,str(self.id),active_sessions) gyorgy@0: log_msg += '\n'.join(map ( lambda x: " %s: %s " %x , cherrypy.request.headers.items() )) gyorgy@0: cherrypy.log(log_msg) gyorgy@0: except : gyorgy@0: print "An error has occured while writing initial session data to the log file." gyorgy@0: gyorgy@0: def setup(cls,**kwargs): gyorgy@0: print 'Setting up storage class... Nothing to do here yet.' gyorgy@0: for k, v in kwargs.iteritems(): gyorgy@0: setattr(cls, k, v) gyorgy@0: print k,v gyorgy@0: # cls.SESSIONS_PATH = kwargs['SESSIONS_PATH'] we could setup a cleanup thread here for the dirs too. kwargs['sessions_path'] = os.path.abspath(kwargs['sessions_path']) gyorgy@0: gyorgy@0: # NOTE: This is now passed in via tools.config: gyorgy@0: # tools.sessions.SESSIONS_PATH gyorgy@0: gyorgy@0: # DELETION COMMENTED OUT gyorgy@0: #if (os.path.exists(cls.SESSIONS_PATH)) : gyorgy@0: # shutil.rmtree (cls.SESSIONS_PATH, ignore_errors=True, onerror=None) gyorgy@0: # cherrypy.log('Cherrypy::DirectorySession:setup (classmethod) Existing session directory removed.') gyorgy@0: if not (os.path.exists(cls.SESSIONS_PATH)) : gyorgy@0: os.mkdir(cls.SESSIONS_PATH) gyorgy@0: cherrypy.log('Cherrypy::DirectorySession:setup (classmethod) New sessions path created: %s' %str(cls.SESSIONS_PATH)) gyorgy@0: gyorgy@0: gyorgy@0: setup = classmethod(setup) gyorgy@0: gyorgy@0: def get_session_name(self): gyorgy@0: """ Return the human readable name of the session. """ gyorgy@0: if self.has_key('name') : gyorgy@0: return self['name'] gyorgy@0: else : gyorgy@0: return 'Session None: ' gyorgy@0: gyorgy@0: def log(self,msg,context='',severity=logging.DEBUG,traceback=True): gyorgy@0: """ Write into cherrypy.log using the session prefix.""" gyorgy@0: if not self.has_key('log_count') : self['log_count'] = 0 gyorgy@0: log_msg = self.get_session_name() + "m%03i. %s" %(self['log_count'],msg) gyorgy@0: self['log_count'] += 1 gyorgy@0: cherrypy.log(log_msg,context,severity,traceback) gyorgy@0: gyorgy@0: def get_session_path(self): gyorgy@0: """ Return a valid path for this session """ gyorgy@0: if self.has_key('session_path') : gyorgy@0: return self.get('session_path',None) gyorgy@0: else : gyorgy@0: if not self.id : gyorgy@0: raise AssertionError("Can not create directory for uninitialised session.") gyorgy@0: return None gyorgy@0: sessionPath = os.path.join(self.SESSIONS_PATH,str(self.id)) gyorgy@0: if os.path.exists(sessionPath) : gyorgy@0: # clash of session IDs (unprobable), flush data gyorgy@0: shutil.rmtree (sessionPath, ignore_errors=True, onerror=None) gyorgy@0: print "flushed data in %s" % sessionPath gyorgy@0: try : gyorgy@0: os.makedirs(sessionPath) gyorgy@0: except: gyorgy@0: raise IOError("Session directory for id %r not created." % self.id) gyorgy@0: return None gyorgy@0: if os.path.exists(sessionPath) : gyorgy@0: # create an entry in self so that the clean_up thread knows about this session gyorgy@0: # setattr(self._data,'session_path',sessionPath) gyorgy@0: self['session_path'] = sessionPath gyorgy@0: return sessionPath gyorgy@0: gyorgy@0: gyorgy@0: # TODO: the other use case: write the output of a pipe gyorgy@0: def write_file(self,fileObj,fileName,filetype = None): gyorgy@0: pass gyorgy@0: gyorgy@0: def write_data(self,data,filename,filetype = None): gyorgy@0: data = str(data) gyorgy@0: sessionPath = self.get_session_path() gyorgy@0: filePath = os.path.join(sessionPath,filename) gyorgy@0: # print 'writing file to: ',filePath gyorgy@0: with open(filePath,'wb') as f: gyorgy@0: f.writelines(data) gyorgy@0: gyorgy@0: # check file and save filetype key if given gyorgy@0: if not os.path.exists(filePath) \ gyorgy@0: or not os.stat(filePath).st_size > 0 : gyorgy@0: cherrypy.log("Session x.xx: mxxx. Cherrypy::DirectorySession:write_data: Warning: Could not verify file: %s " %filePath) gyorgy@0: return False gyorgy@0: elif not filetype : gyorgy@0: return True gyorgy@0: elif self.has_key(filetype) : gyorgy@0: ol = self.get(filetype,None) gyorgy@0: ol.append(os.path.basename(filePath)) gyorgy@0: self[filetype] = list(ol) gyorgy@0: else : gyorgy@0: self[filetype]= list([os.path.basename(filePath)]) gyorgy@0: self.save() gyorgy@0: return True gyorgy@0: gyorgy@0: gyorgy@0: def write_fsfile(self,fsFile,filetype = None): gyorgy@0: '''Write file to session given as CGI field-storage object.''' gyorgy@0: #fsFile is a cgi.FieldStorage object : cherrypy._cpcgifs.FieldStorage() gyorgy@0: if hasattr (fsFile,'filename') and hasattr(fsFile,'file'): gyorgy@0: return self._write_file(fsFile.file,fsFile.filename,filetype) gyorgy@0: gyorgy@0: def write_fsfile_as(self,fsFile,filename,filetype = None): gyorgy@0: '''Write file to session given as CGI field-storage object.''' gyorgy@0: #fsFile is a cgi.FieldStorage object : cherrypy._cpcgifs.FieldStorage() gyorgy@0: if hasattr (fsFile,'filename') and hasattr(fsFile,'file'): gyorgy@0: return self._write_file(fsFile.file,filename,filetype) gyorgy@0: gyorgy@0: def _write_file(self,file,filename,filetype): gyorgy@0: '''Write file to session given as python file object.''' gyorgy@0: filename = convertFilename(filename) gyorgy@0: sessionPath = self.get_session_path() gyorgy@0: filePath = os.path.join(sessionPath,filename) gyorgy@0: # print 'writing file to: ',filePath gyorgy@0: gyorgy@0: file.seek(0) gyorgy@0: newFile = open(filePath,'wb') gyorgy@0: try : gyorgy@0: shutil.copyfileobj(file,newFile) gyorgy@0: except : gyorgy@0: try : gyorgy@0: while True: gyorgy@0: data = file.read(8192) gyorgy@0: if not data: break gyorgy@0: newFile.write(data) gyorgy@0: except: gyorgy@0: # newFile.close() gyorgy@0: cherrypy.log("Session x.xx: mxxx. Cherrypy::DirectorySession: Could not write file to session: %i " %self.id) gyorgy@0: raise IOError("Cherrypy::DirectorySession: Could not write file to session %r " %self.id) gyorgy@0: finally: gyorgy@0: newFile.close() gyorgy@0: gyorgy@0: if not os.path.exists(filePath) \ gyorgy@0: or not os.stat(filePath).st_size > 0 : gyorgy@0: cherrypy.log("Session x.xx: mxxx. Cherrypy::DirectorySession:_write_file: Warning: Failed to verify file at: %s " %filePath) gyorgy@0: return False gyorgy@0: elif not filetype : gyorgy@0: return True gyorgy@0: elif self.has_key(filetype) : gyorgy@0: ol = self.get(filetype,None) gyorgy@0: ol.append(os.path.basename(filePath)) gyorgy@0: self[filetype] = list(ol) gyorgy@0: else : gyorgy@0: self[filetype]= list([os.path.basename(filePath)]) gyorgy@0: self.save() gyorgy@0: return True gyorgy@0: gyorgy@0: def getFiles(self,filelist): gyorgy@0: pass gyorgy@0: gyorgy@0: def clean_up(self): gyorgy@0: """Clean up expired sessions.""" gyorgy@0: now = datetime.datetime.now() gyorgy@0: log_msg = "Session x.xx: mxxx. Cherrypy::DirectorySession: Cleaning up session data at: %s Length of cache = %i " %(str(now), len(self.cache)) gyorgy@0: print '\nCeaning up session data at %s: \n===========================' % now gyorgy@0: print 'Length of cache = ', len(self.cache) gyorgy@0: print 'cache members: ', self.cache.keys() gyorgy@0: cherrypy.log(log_msg) gyorgy@0: gyorgy@0: #clean up temp if server not in use gyorgy@0: temp_path='/Users/Shared/george/sawa/sonic-annotator-webapp/static/temp/' gyorgy@0: if len(self.cache.keys()) == 0 and os.path.exists(temp_path): gyorgy@0: try : gyorgy@0: for fname in os.listdir(temp_path): gyorgy@0: os.remove(os.path.join(temp_path,fname)) gyorgy@0: print "Note: Cleaned temp directory." gyorgy@0: except: gyorgy@0: print "Warning: could not clear temp directory." gyorgy@0: pass gyorgy@0: gyorgy@0: for id, (data, expiration_time) in self.cache.items(): gyorgy@0: print 'Id: ', id gyorgy@0: print 'expiration time: ', expiration_time gyorgy@0: print 'Length of data: ', len(data) gyorgy@0: print 'data members: ', data.keys() gyorgy@0: print 'SESSIONS_PATH: ', self.SESSIONS_PATH gyorgy@0: gyorgy@0: if expiration_time < now: gyorgy@0: gyorgy@0: path = os.path.join(self.SESSIONS_PATH,str(id)) gyorgy@0: if os.path.exists(path) : gyorgy@0: for fname in os.listdir(path): gyorgy@0: os.remove(os.path.join(path,fname)) gyorgy@0: # os.removedirs(path) -> removes sessions too gyorgy@0: os.rmdir(path) gyorgy@0: try: gyorgy@0: del self.cache[id] gyorgy@0: except KeyError: gyorgy@0: pass gyorgy@0: try: gyorgy@0: del self.locks[id] gyorgy@0: except KeyError: gyorgy@0: pass gyorgy@0: gyorgy@0: def _exists(self): gyorgy@0: return self.id in self.cache gyorgy@0: gyorgy@0: def _load(self): gyorgy@0: return self.cache.get(self.id) gyorgy@0: gyorgy@0: #this is always called on an on_end_request hook (by cherrypy.session.save) gyorgy@0: def _save(self, expiration_time): gyorgy@0: print 'Saving Session ', self.id gyorgy@0: self.cache[self.id] = (self._data, expiration_time) gyorgy@0: gyorgy@0: def _delete(self): gyorgy@0: del self.cache[self.id] gyorgy@0: gyorgy@0: def acquire_lock(self): gyorgy@0: """Acquire an exclusive lock on the currently-loaded session data.""" gyorgy@0: self.locked = True gyorgy@0: self.locks.setdefault(self.id, threading.RLock()).acquire() gyorgy@0: gyorgy@0: def release_lock(self): gyorgy@0: """Release the lock on the currently-loaded session data.""" gyorgy@0: self.locks[self.id].release() gyorgy@0: self.locked = False gyorgy@0: gyorgy@0: def __len__(self): gyorgy@0: """Return the number of active sessions.""" gyorgy@0: return len(self.cache) gyorgy@0: gyorgy@0: cherrypy.lib.sessions.DirectorySession = DirectorySession