# -*- test-case-name: twisted.test.test_popsicle -*-
# 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
#
from twisted.internet import defer
import os
import weakref
from twisted.python import log
from twisted.persisted.styles import instance,instancemethod
try:
True, False
except NameError:
True, False = 1, 0
class Freezer:
DELETE = 0
SAVE = 1
def __init__(self):
self.persistentObjects = weakref.WeakKeyDictionary()
self.dirtySet = {}
self.cleaning = False
self.pendingCall = None
def addSaver(self, obj, saver):
"""Add an interested saver to a particular object.
"""
ent = self.persistentObjects.get(obj)
if not ent:
ent = [None, []]
self.persistentObjects[obj] = ent
ent[1].append(saver)
def removeSaver(self, obj, saver):
ent = self.persistentObjects.get(obj)
if not ent:
return
ent[1].remove(saver)
def setPersistentReference(self, obj, pref):
ent = self.persistentObjects.get(obj)
if not ent:
ent = [pref, []]
self.persistentObjects[obj] = ent
else:
ent[0] = pref
def getPersistentReference(self, obj, repo=None):
ent = self.persistentObjects.get(obj)
if not ent:
pr = PersistentReference(None, None, obj)
self.setPersistentReference(obj, pr)
ent = self.persistentObjects[obj]
if repo is not None:
pr.acquireOID(repo)
return ent[0]
def dirty(self, obj, dirt=SAVE):
if self.cleaning:
raise RuntimeError("Can't flag an object as dirty while cleaning.")
self._dirty(obj)
from twisted.internet import reactor
if not self.pendingCall:
self.pendingCall = reactor.callLater(1.0, self._cleanTime)
def _dirty(self, obj, dirt=SAVE):
self.dirtySet[obj] = dirt
def register(self, obj, repo):
pr = self.getPersistentReference(obj, repo)
self.dirty(obj)
return pr
def delete(self, obj):
return self.dirty(obj, Freezer.DELETE)
def _cleanTime(self):
self.pendingCall = None
self.clean()
def clean(self):
"""Persistence tick. Clean out the fridge, persist everything that
wants to be saved.
"""
#log.msg("cleaning popsicle")
if self.pendingCall:
self.pendingCall.cancel()
self.pendingCall = None
self.cleaning = True
savers = {}
try:
while self.dirtySet:
l = self.dirtySet.items()
self.dirtySet.clear()
for obj, doSave in l:
ent = self.persistentObjects.get(obj)
if not ent:
continue
for saver in ent[1]:
savers[saver] = 1
try:
if doSave:
# print 'saving',obj
saver.save(obj)
else:
# print 'deleting',obj
saver.delete(obj)
except:
# TODO: more error handling: backout transactions?
# restore state of objects? what else?
log.deferr()
finally:
self.cleaning = False
for saver in savers.keys():
try:
saver.cleaned()
except:
log.deferr()
try:
theFreezer
except NameError:
# Create the public interface singleton.
theFreezer = Freezer()
ref = theFreezer.getPersistentReference
clean = theFreezer.clean
dirty = theFreezer.dirty
register = theFreezer.register
class ISaver:
def save(self, object):
"""Save (and optionally index) the given object.
"""
def delete(self, object):
"""Remove the given object.
"""
class PersistentReference:
"""I am a reference to a persistent object.
The most interesting external interface is __call__: instances of this
class should generally be treated as zero-argument functions which return
Deferreds.
"""
# Maintenance notes: there are 2 states for instances of this class.
# either self.oid and self.repo are set to non-None values, in which case
# this represents a stored object, or self.obj is set, in which case this
# represents a value that *wants* to be stored. In state 2, the object is
# mutable, so care is taken to cache such objects (see
# Freezer.getPersistentReference). However, in state 1, prefs are
# effectively immutable, so multiple may be instantiated with the same
# OID/repo pair.
def __init__(self, oid, repo, obj):
self.oid = oid
self.repo = repo
self.obj = obj
def __call__(self):
"""Load my object into memory, and return a Deferred which will fire
it."""
if self.obj is not None:
return defer.succeed(self.obj)
return self.repo.loadRef(self)
def acquireOID(self, repo=None):
"""Take a PersistentReference that isn't really persistent yet, and
associate it with a repository, give it an OID, and set it dirty.
This should happen *synchronously during persistence*.
"""
# TODO: better name for this method!!!
if self.obj is not None:
assert self.oid is None, "inconsistent state of p-ref: %s" % self.oid
if repo is None:
assert theFreezer._savingRepo is not None, "Calling acquireOID without repo outside of save!"
repo = theFreezer._savingRepo
o = self.obj
self.repo = repo
self.oid = repo.generateOID(o)
self.obj = None
theFreezer.addSaver(o, repo)
# use internal API to avoid warning: we *really really* want to dirty
theFreezer._dirty(o)
repo.cache(self.oid, o, finished=0)
return self.oid
elif ((self.repo is repo) or
repo is None and self.repo is theFreezer._savingRepo):
assert self.oid is not None, "inconsistent state of p-ref"
return self.oid
else:
# I don't belong to this repo, but I _do_ belong to someone else.
return None
### assert False, "wrong repository!"
|