# Twisted, the Framework of Your Internet # Copyright (C) 2001-2002 Matthew W. Lefkowitz # # This library is free software; you can redistribute it and/or # modify it under the terms of version 2.1 of the GNU Lesser General Public # License as published by the Free Software Foundation. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # -*- test-case-name: twisted.test.test_popsicle -*- """Abstract Repository classes for Popsicle. """ # System Imports import os import weakref # Twisted Imports from twisted.internet import defer from twisted.persisted.styles import instance # Sibling Imports from twisted.popsicle.freezer import theFreezer, ISaver, PersistentReference, ref class OIDNotFound(Exception): pass class Repository: """A data storage that can be loaded from. A repository of objects. I am an abstract class. Subclass me and implement 'loadOID' and 'saveOID' Note that 'Repository' implementations are really open-ended. This implementation strategy is suggested, though, and it is strongly suggested that authors of new repositories implement at least the caching shown here. However, facilities such as ad-hoc querying are omitted from this interface, and should be provided by e.g. an SQL Repository implementation. A repository is both a saver and a collection of PersistentReferences. """ __implements__ = ISaver _lastOID = 0 def saveOID(self, oid, obj): """ Return a Deferred which will fire True when the object is saved. """ raise NotImplementedError() def __init__(self): """Initialize me (set up cache). """ self._cache = weakref.WeakValueDictionary() self._revCache = weakref.WeakKeyDictionary() self._pRefs = weakref.WeakValueDictionary() def load(self, oid): """Load an object from cache or by OID. Return a Deferred. This method should be called by external objects looking for a 'starting point' into the repository. """ oid = str(oid) if self._pRefs.has_key(oid): pRef = self._pRefs[oid] else: pRef = PersistentReference(str(oid), self, None) return pRef() def loadNow(self, oid): """External API for synchronously loading stuff. This should ONLY BE USED by code that is doing the actual loading/saving of structured objects. Application code should always make calls through PersistentReference.__call__, otherwise it will not work on some back-ends. """ oid = str(oid) if self._cache.get(oid): return self._cache[oid] # this code is copied and subtly changed from _cbLoadedOID. I wish I # could have found a better way to do it, but -- expect bugs here! pRef = PersistentReference(oid, self, None) pRef.deferred = defer.Deferred() try: try: obj = self.loadOIDNow(oid) except: pRef.deferred.errback() raise else: theFreezer.setPersistentReference(obj, pRef) theFreezer.addSaver(obj, self) pRef.deferred.callback(obj) self.cache(oid, obj) return obj finally: del pRef.deferred def loadOIDNow(self, oid): """ Implement me if you want to implement synchronous loading. """ raise NotImplementedError() def loadOID(self, oid): """Implement me to return a Deferred if you want to implement asynchronous loading. """ return defer.execute(self.loadOIDNow, oid) def createOID(self, oid, klass): """Create an instance with an oid and cache it. This is useful during loading. """ i = instance(klass) self.cache(oid, i, 0) return i def cleaned(self): """The freezer finished cleaning, and some of my objects were cleaned. """ def loadRef(self, pRef): """ Synonymous with ref.__call__(). """ oid = pRef.oid obj = self._cache.get(oid) if obj is not None: return defer.succeed(obj) elif self._pRefs.has_key(oid): # have a persistent ref, but no object return pRef.deferred else: # have no persistent ref d = defer.Deferred() self._pRefs[oid] = pRef pRef.deferred = d d2 = self.loadOID(oid) d2.addCallback(self._cbLoadedOID, oid, pRef) return d def _cbLoadedOID(self, result, oid, pref): theFreezer.setPersistentReference(result, pref) theFreezer.addSaver(result, self) self.cache(oid, result) pref.deferred.callback(result) del pref.deferred return result def generateOID(self, obj): """Generate an OID synchronously. Necessary for some types of persistence, but """ self._lastOID += 1 return self._lastOID def cache(self, oid, obj, finished=1): """Weakly cache an object for the given OID. This means I own it, so also register it with the Freezer as such. """ self._cache[oid] = obj self._revCache[obj] = oid def getOID(self, obj): if self._revCache.has_key(obj): return self._revCache[obj] else: # TODO: if OID generation really needs to be async... return ref(obj).acquireOID(self) def save(self, obj): """ Save an object... If this is the first time I am saving this particular object, I need to locate a new unique ID for it. """ theFreezer._savingRepo = self try: oid = self.getOID(obj) val = self.saveOID(oid, obj) self.cache(oid, obj) return val finally: theFreezer._savingRepo = None class DirectoryRepository(Repository): def __init__(self, dirname): if not os.path.isdir(dirname): os.mkdir(dirname) fn = os.path.join(dirname,".popsiqnum") Repository.__init__(self) self.dirname = dirname def generateOID(self, obj): fn = os.path.join(self.dirname,".popsiqnum") try: seq = str(int(open(fn).read()) + 1) except IOError: seq = '0' open(fn,'w').write(seq) return seq