"""mems.lib.persist_session Provides a few subclasses of Quixote's session classes, which add persistence inside a ZODB database. """ # created 2000/07/26, AMK # moved from mems.ui.lib to mems.lib 2000/12/11, GPW __revision__ = "$Id: persist_session.py,v 1.29 2001/10/23 23:16:45 nascheme Exp $" import sys from time import strftime, localtime from mems.lib import base from quixote.session import \ SessionManager, Session, ApplicationState, \ register_app_state_class from ZODB.POSException import ConflictError from quixote import get_publisher class MXSessionManager (base.MXPersistent, SessionManager): """ Instance attributes: sessions : OOBTree { session_id:string : MXSession } (same as in SessionManager, just changed to a BTree for scalability) """ __SessionManager_init = SessionManager.__init__ __SessionManager_get_session = SessionManager.get_session def __init__ (self): self.__SessionManager_init() self.sessions = base.new_persistent_map(big=1) # Kludge to howl if we're being created in a context where # the vfab.conf file has not been loaded. Icky, but beats # getting key configuration information wrong. if get_publisher() is not None: expected = "mx_session" cookie_name = get_publisher().config.cookie_name if cookie_name != expected: sys.stderr.write("WARNING: cookie name is '%s', expected '%s':\n" " has vfab.conf been loaded?\n" % (cookie_name, expected)) def sorted_keys (self): return self.sessions.keys() # sorted for free because it's # a BTree def abort (self): # XXX needs to only abort for the session connection! or not? get_transaction().abort() def commit (self): # XXX needs to only commit for the session connection! or not? get_transaction().commit() def update_cache (self): """Syncronize the ZODB cache. This is only necessary when using ZEO. Regarding cache invalidation Jim Fulton says: This is only an issue when running "synchronously", out of the control of an asyncore main loop. It arises from the fact that there is no asyncore main loop running and, therefore, no way to get asynchronous cache invalidation messages. We don't get communication from the server unless we contact it for some other reason, such as a read or write. I'm going to need to add some additional API to allow this. In the mean time, to hack my way around this, I make commit meaningless modification to force an update. """ self._p_changed = 1 try: get_transaction().commit() except ConflictError, exc: print "Caught conflict error, oid: %s" % `str(exc)` def new_session(self, request, id): "Return a new instance of the Session class." # Subclasses can override this to return an instance of a different # class. return MXSession(request, id) def get_session(self, request): #self.update_cache() return self.__SessionManager_get_session(request) # class MXSessionManager class MXSession (base.MXPersistent, Session): """ Instance attributes: app_state : PersistentMapping { string : MXApplicationState } app_creation_time : PersistentMapping { string : float } app_access_time : PersistentMapping { string : float } form_tokens : PersistentList [string] (These are all the same as in Session, but changed to PersistentMapping for easier persistence.) """ __Session_init = Session.__init__ def __init__ (self, request, id): self.__Session_init(request, id) # Replace various dictionary-valued attributes with persistent maps self.app_state = base.new_persistent_map() self.app_creation_time = base.new_persistent_map() self.app_access_time = base.new_persistent_map() self.form_tokens = base.new_persistent_list() def dump (self, file=None, header=1, deep=1): file = self._startdump(file, header) file.write("user: %s (actual user: %s)\n" % (`self.user`, `self.actual_user`)) file.write("time of...\n") ctime = self.creation_time atime = self.access_time file.write(" creation: %.2f (%s)\n" % (ctime, format_time(ctime))) file.write(" last access: %.2f (%s)\n" % (atime, format_time(atime))) self._dump_attrs(file, "form_tokens") if self.app_state: if deep: file.write("application states:\n") file.indent_more() for app in self.app_state.keys(): head = ("%s (created %s, accessed %s)" % (app, format_time(self.app_creation_time[app]), format_time(self.app_access_time[app]))) self._subdump(file, value=self.app_state[app], head=head) file.indent_less() else: file.write("applications running: " + ", ".join(self.app_state.keys())) else: file.write("no applications running\n") # class MXSession class MXApplicationState (base.MXPersistent, ApplicationState): """MEMS Exchange application state classes should inherit from this class in order to get all the benefits of being an MXPersistent class. Instance attributes: none """ # class MXApplicationState # so applications don't have to import this from Quixote register_app_state_class = register_app_state_class def format_time (time): return strftime("%Y-%m-%d %H:%M:%S", localtime(time))