Commit c91c4047 authored by matt@zope.com's avatar matt@zope.com

Updated with more code from the original CoreSessionTracking SessionDataManager

for object expiry.
parent 8eea6b70
...@@ -85,10 +85,10 @@ ...@@ -85,10 +85,10 @@
""" """
Core session tracking SessionData class. Core session tracking SessionData class.
$Id: Transience.py,v 1.2 2001/10/22 16:23:51 matt Exp $ $Id: Transience.py,v 1.3 2001/10/22 18:48:17 matt Exp $
""" """
__version__='$Revision: 1.2 $'[11:-2] __version__='$Revision: 1.3 $'[11:-2]
import Globals import Globals
from Globals import HTMLFile, MessageDialog from Globals import HTMLFile, MessageDialog
...@@ -99,16 +99,20 @@ from OFS.SimpleItem import SimpleItem ...@@ -99,16 +99,20 @@ from OFS.SimpleItem import SimpleItem
from Persistence import Persistent, PersistentMapping from Persistence import Persistent, PersistentMapping
from Acquisition import Implicit, aq_base from Acquisition import Implicit, aq_base
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from BTrees import OOBTree
import os.path import os.path
import math
import time import time
_notfound = [] _notfound = []
_marker = []
# permissions # permissions
ADD_DATAMGR_PERM = 'Add Transient Object Container' ADD_DATAMGR_PERM = 'Add Transient Object Container'
CHANGE_DATAMGR_PERM = 'Change Transient Object Containers' CHANGE_DATAMGR_PERM = 'Change Transient Object Containers'
MGMT_SCREEN_PERM = 'View management screens' MGMT_SCREEN_PERM = 'View management screens'
ACCESS_CONTENTS_PERM = 'Access contents information' ACCESS_CONTENTS_PERM = 'Access contents information'
CREATE_TRANSIENTS_PERM = 'Create Transient Objects'
ACCESS_SESSIONDATA_PERM = 'Access Transient Objects' ACCESS_SESSIONDATA_PERM = 'Access Transient Objects'
MANAGE_CONTAINER_PERM = 'Manage Transient Object Container' MANAGE_CONTAINER_PERM = 'Manage Transient Object Container'
...@@ -165,6 +169,8 @@ class TransientObjectContainer(SimpleItem): ...@@ -165,6 +169,8 @@ class TransientObjectContainer(SimpleItem):
['Manager','Anonymous']) ['Manager','Anonymous'])
security.setPermissionDefault(ACCESS_SESSIONDATA_PERM, security.setPermissionDefault(ACCESS_SESSIONDATA_PERM,
['Manager','Anonymous']) ['Manager','Anonymous'])
security.setPermissionDefault(CREATE_TRANSIENTS_PERM,
['Manager',])
security.declareProtected(MGMT_SCREEN_PERM, 'manage_container') security.declareProtected(MGMT_SCREEN_PERM, 'manage_container')
manage_container = HTMLFile('dtml/manageTransientObjectContainer', manage_container = HTMLFile('dtml/manageTransientObjectContainer',
...@@ -180,17 +186,23 @@ class TransientObjectContainer(SimpleItem): ...@@ -180,17 +186,23 @@ class TransientObjectContainer(SimpleItem):
# #
def __init__(self, id, title='', timeout_mins=20, addNotification=None, def __init__(self, id, title='', timeout_mins=20, addNotification=None,
delNotification=None): delNotification=None, err_margin=.20, ctype=OOBTree.OOBTree):
self.id = id self.id = id
self.title=title self.title=title
self._container = {}
self._ctype = ctype
self._addCallback = None self._addCallback = None
self._delCallback = None self._delCallback = None
self.setTimeoutMinutes(timeout_mins) self._err_margin = err_margin
self._setTimeout(timeout_mins)
self._reset()
self.setAddNotificationTarget(addNotification)
self.setDelNotificationTarget(delNotification) self.setDelNotificationTarget(delNotification)
self.setAddNotificationTarget(addNotification)
# ----------------------------------------------------------------- # -----------------------------------------------------------------
# ItemWithID # ItemWithID
...@@ -204,24 +216,28 @@ class TransientObjectContainer(SimpleItem): ...@@ -204,24 +216,28 @@ class TransientObjectContainer(SimpleItem):
# StringKeyedHomogenousItemContainer # StringKeyedHomogenousItemContainer
# #
security.declareProtected(CREATE_TRANSIENTS_PERM, 'new')
def new(self, key): def new(self, key):
if type(key) is not type(''): if type(key) is not type(''):
raise TypeError, (key, "key is not a string type") raise TypeError, (key, "key is not a string type")
if self._container.has_key(key): if self.has_key(key):
raise KeyError, key # Not allowed to dup keys raise KeyError, key # Not allowed to dup keys
item = TransientObject(key, parent=self) item = TransientObject(key, parent=self)
self._container[key] = item self[key] = item
self.notifyAdd(item)
return item return item
security.declareProtected(CREATE_TRANSIENTS_PERM, 'new_or_existing')
def new_or_existing(self, key): def new_or_existing(self, key):
item = self._container.get(key,_notfound) item = self.get(key,_notfound)
if item is not _notfound: return item if item is not _notfound: return item
return self.new(key) return self.new(key)
...@@ -232,11 +248,15 @@ class TransientObjectContainer(SimpleItem): ...@@ -232,11 +248,15 @@ class TransientObjectContainer(SimpleItem):
security.declareProtected(MANAGE_CONTAINER_PERM, 'setTimeoutMinutes') security.declareProtected(MANAGE_CONTAINER_PERM, 'setTimeoutMinutes')
def setTimeoutMinutes(self, timeout_mins): def setTimeoutMinutes(self, timeout_mins):
self._timeout = timeout_mins """ """
if timeout_mins != self.getTimeoutMinutes():
self._setTimeout(timeout_mins)
self._reset()
security.declareProtected(MGMT_SCREEN_PERM, 'getTimeoutMinutes') security.declareProtected(MGMT_SCREEN_PERM, 'getTimeoutMinutes')
def getTimeoutMinutes(self): def getTimeoutMinutes(self):
return self._timeout """ """
return self._timeout_secs / 60
security.declareProtected(MGMT_SCREEN_PERM, 'getAddNotificationTarget') security.declareProtected(MGMT_SCREEN_PERM, 'getAddNotificationTarget')
def getAddNotificationTarget(self): def getAddNotificationTarget(self):
...@@ -279,16 +299,6 @@ class TransientObjectContainer(SimpleItem): ...@@ -279,16 +299,6 @@ class TransientObjectContainer(SimpleItem):
# #
security.declareProtected(MGMT_SCREEN_PERM, 'getLen')
def getLen(self):
"""
Potentially expensive helper function to figure out how
many items are contained.
"""
return len(self._container)
security.declareProtected(MANAGE_CONTAINER_PERM, security.declareProtected(MANAGE_CONTAINER_PERM,
'manage_changeTransientObjectContainer') 'manage_changeTransientObjectContainer')
def manage_changeTransientObjectContainer(self, title='', def manage_changeTransientObjectContainer(self, title='',
...@@ -318,7 +328,7 @@ class TransientObjectContainer(SimpleItem): ...@@ -318,7 +328,7 @@ class TransientObjectContainer(SimpleItem):
f = os.path.join(Globals.data_dir, "transientobjects.zexp") f = os.path.join(Globals.data_dir, "transientobjects.zexp")
self.c = PersistentMapping() self.c = PersistentMapping()
for k, v in self._container.items(): for k, v in self.items():
self.c[k] = v self.c[k] = v
get_transaction().commit() get_transaction().commit()
...@@ -340,13 +350,218 @@ class TransientObjectContainer(SimpleItem): ...@@ -340,13 +350,218 @@ class TransientObjectContainer(SimpleItem):
conn = self._p_jar conn = self._p_jar
ob = conn.importFile(f) ob = conn.importFile(f)
for k,v in ob.items(): for k,v in ob.items():
self._container[k] = v self[k] = v
if REQUEST is not None: if REQUEST is not None:
return MessageDialog( return MessageDialog(
title="Transient objects imported", title="Transient objects imported",
message="Transient objects imported from %s" % f, message="Transient objects imported from %s" % f,
action="manage_container") action="manage_container")
def _setTimeout(self, timeout_mins):
if type(timeout_mins) is not type(1):
raise TypeError, (timeout_mins, "Must be integer")
self._timeout_secs = timeout_mins * 60
def _reset(self):
t_secs = self._timeout_secs
r_secs = self._resolution_secs = int(t_secs * self._err_margin) or 1
numbuckets = int(math.floor(t_secs/r_secs)) or 1
l = []
i = 0
now = int(time.time())
for x in range(numbuckets):
dump_after = now + i
c = self._ctype()
l.insert(0, [c, dump_after])
i = i + r_secs
index = self._ctype()
self._ring = Ring(l, index)
def _getCurrentBucket(self, get_dump=0):
# no timeout always returns last bucket
if not self._timeout_secs:
b, dump_after = self._ring._data[0]
return b
index = self._ring._index
now = int(time.time())
i = self._timeout_secs
# expire all buckets in the ring which have a dump_after time that
# is before now, turning the ring as many turns as necessary to
# get to a non-expirable bucket.
while 1:
l = b, dump_after = self._ring._data[-1]
if now > dump_after:
self._ring.turn()
# mutate elements in-place in the ring
new_dump_after = now + i
l[1] = new_dump_after
self._clean(b, index)
i = i + self._resolution_secs
else:
break
if get_dump:
return self._ring._data[0], dump_after, now
else:
b, dump_after = self._ring._data[0]
return b
def _clean(self, b, index):
# What is all this?
#for ob in b.values():
# d = last = None
# f = getattr(ob, self._onend, None)
# #
# # HUH?
# #
# getDataMgr = getattr(ob, 'getDataMgr', None)
# if getDataMgr is not None:
# if callable(getDataMgr):
# d = getDataMgr()
# if d != last:
# mgr = self.aq_parent.unrestrictedTraverse(d)
# last = d
# if callable(f): f(mgr)
for k, v in list(index.items()):
if v is b:
self.notifyDestruct(index[k])
del index[k]
b.clear()
def _show(self):
""" debug method """
b,dump,now = self._getCurrentBucket(1)
ringdumps = map(lambda x: `x[1]`[-4], self._ring)
t = (
"now: "+`now`[-4:],
"dump_after: "+`dump`[-4:],
"ring_dumps: "+`ringdumps`,
"ring: " + `self._ring`
)
for x in t:
print x
def __setitem__(self, k, v):
current = self._getCurrentBucket()
index = self._ring._index
b = index.get(k)
if b is None:
# this is a new key
index[k] = current
elif b is not current:
# this is an old key that isn't in the current bucket.
del b[k] # delete it from the old bucket
index[k] = current
# change the value
current[k] = v
def __getitem__(self, k):
current = self._getCurrentBucket()
index = self._ring._index
# the next line will raise the proper error if the item has expired
b = index[k]
v = b[k] # grab the value before we potentially time it out.
if b is not current:
# it's not optimal to do writes in getitem, but there's no choice.
# we accessed the object, so it should become current.
index[k] = current # change the index to the current bucket.
current[k] = v # add the value to the current bucket.
del b[k] # delete the item from the old bucket.
return v
security.declareProtected(ACCESS_SESSIONDATA_PERM, 'get')
def set(self, k, v):
""" """
if type(k) is not type(''):
raise TypeError, "Transient Object Container keys must be strings"
self[k] = v
security.declareProtected(ACCESS_SESSIONDATA_PERM, 'get')
# Uses a different marker than _notfound
def get(self, k, default=_marker):
try: v = self[k]
except KeyError: v = _marker
if v is _marker:
if default is _marker:
return None
else:
return default
return v
def __delitem__(self, k):
self._getCurrentBucket()
index = self._ring._index
b = index[k]
del index[k]
del b[k]
security.declareProtected(ACCESS_SESSIONDATA_PERM, '__len__')
def __len__(self):
self._getCurrentBucket()
return len(self._ring._index)
security.declareProtected(ACCESS_SESSIONDATA_PERM, 'has_key')
def has_key(self, k):
self._getCurrentBucket()
index = self._ring._index
return index.get(k, _notfound) is not _notfound
def values(self):
return map(lambda k, self=self: self[k], self.keys())
def items(self):
return map(lambda k, self=self: (k, self[k]), self.keys())
def keys(self):
self._getCurrentBucket()
index = self._ring._index
return map(lambda x: x, index.keys())
def update(self):
raise NotImplementedError
def clear(self):
raise NotImplementedError
def copy(self):
raise NotImplementedError
security.declareProtected(ACCESS_SESSIONDATA_PERM, 'getLen')
getLen = __len__
class Ring(Persistent):
""" Instances of this class will be frequently written to the ZODB,
so it's optimized as best possible for write-friendliness """
def __init__(self, l, index):
if not len(l):
raise "ring must have at least one element"
self._data = l
self._index = index
def __repr__(self):
return repr(self._data)
def __len__(self):
return len(self._data)
def __getitem__(self, i):
return self._data[i]
def turn(self):
last = self._data.pop(-1)
self._data.insert(0, last)
self._p_changed = 1
def _p_independent(self):
return 1
class TransientObject(Persistent, Implicit): class TransientObject(Persistent, Implicit):
""" akin to Session Data Object """ """ akin to Session Data Object """
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment