[Zope3-checkins] SVN: Zope3/trunk/src/zope/app/locking/ commit simple locking package

Brian Lloyd brian at zope.com
Thu Feb 3 13:20:33 EST 2005


Log message for revision 29029:
  commit simple locking package

Changed:
  A   Zope3/trunk/src/zope/app/locking/
  A   Zope3/trunk/src/zope/app/locking/README.txt
  A   Zope3/trunk/src/zope/app/locking/__init__.py
  A   Zope3/trunk/src/zope/app/locking/adapter.py
  A   Zope3/trunk/src/zope/app/locking/configure.zcml
  A   Zope3/trunk/src/zope/app/locking/interfaces.py
  A   Zope3/trunk/src/zope/app/locking/lockinfo.py
  A   Zope3/trunk/src/zope/app/locking/storage.py
  A   Zope3/trunk/src/zope/app/locking/tests.py

-=-
Added: Zope3/trunk/src/zope/app/locking/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/locking/README.txt	2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/README.txt	2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1,366 @@
+Object Locking
+==============
+
+This package provides a framework for object locking. The implementation
+is intended to provide a simple general-purpose locking architecture upon 
+which other locking applications can be built (WebDAV locking, for example).
+
+The locking system is purely *advisory* - it provides a way to associate a 
+lock with an object, but it does not enforce locking in any way. It is up 
+to application-level code to ensure that locked objects are restricted in 
+a way appropriate to the application.
+
+The Zope 3 locking model defines interfaces and default implemenations 
+that:
+
+  - allows for a single lock on an object, owned by a specific
+    principal
+
+  - does not necessarily impose inherent semantic meaning (exclusive
+    vs. non-exclusive, write vs. read) on locks, though it will 
+    provide fields that higher-level application components can use 
+    to implement and enforce such semantics
+
+  - can potentially be be used to build more ambitious locking
+    mechanisms (such as WebDAV locking equivalent to Zope 2)
+
+  - supports common use cases that have been uncovered in several years
+    of development of real-world applications (such as reporting all of
+    the objects locked by a given user)
+
+
+The Zope3 locking architecture defines an `ILockable` interface and
+provides a default adapter implementation that requires only that an 
+object be adaptable to `IKeyReference`. All persistent objects can be 
+adapted to this interface by default in Zope 3, so in practice all 
+persistent objects are lockable.
+
+The default `ILockable` adapter implementation provides support for:
+
+  - locking and unlocking an object
+
+  - breaking an existing lock on an object
+
+  - obtaining the lock information for an object
+
+
+Locking operations (lock, unlock, break lock) fire events that may be
+handled by applications or other components to interact with the locking 
+system in a loosely-coupled way.
+
+Lock information is accessible through an object that supports the 
+`ILockInfo` interface. The `ILockInfo` interface implies IAnnotatable, 
+so that other locking implementations (superceding or complementing the 
+default implementation) can store more information if needed to support
+extended locking semantics.
+
+The locking architecture also supports an efficient method of lock tracking 
+that allows you to determine what locks are held on objects. The default 
+implementation provides an `ILockTracker` utility that can be used by 
+applications to quickly find all objects locked by a particular principal.
+
+
+Locking essentials
+------------------
+
+Normally, locking is provided by the default locking implementation. In 
+this example, we'll create a simple content class. The content class 
+is persistent, which allows us to use the default locking adapters and 
+utilities.
+
+  >>> import persistent
+
+  >>> class Content(persistent.Persistent):
+  ...     """A sample content object"""
+
+  ...     def __init__(self, value):
+  ...         self.value = value
+
+  ...     def __call__(self):
+  ...         return self
+
+  ...     def __hash__(self):
+  ...         return self.value
+
+  ...     def __cmp__(self, other):
+  ...         return cmp(self.value, other.value)
+
+
+Now we will create a few sample objects to work with:
+
+  >>> item1 = Content("item1")
+  >>> item1.__name__ = "item1"
+
+  >>> item2 = Content("item2")
+  >>> item2.__name__ = "item2"
+
+  >>> item3 = Content("item3")
+  >>> item3.__name__ = "item3"
+
+
+It is possible to test whether an object supports locking by attempting 
+to adapt it to the ILockable interface:
+
+  >>> from zope.app.locking.interfaces import ILockable, ILockTracker
+  >>> from zope.app.locking.interfaces import ILockInfo
+
+  >>> ILockable(item1, None)
+  <Locking adapter for...
+
+  >>> ILockable(42, None)
+  ...
+
+
+There must be an active interaction to use locking, to allow the framework 
+to determine the principal performing locking operations. This example sets 
+up some sample principals and a helper to switch principals for further 
+examples:
+
+  >>> class FauxPrincipal:
+  ...    def __init__(self, id):
+  ...        self.id = id
+
+  >>> britney = FauxPrincipal('britney')
+  >>> tim = FauxPrincipal('tim')
+
+  >>> class FauxParticipation:
+  ...     interaction=None
+  ...     def __init__(self, principal):
+  ...         self.principal = principal
+
+  >>> import zope.security.management
+  >>> def set_principal(principal):
+  ...     if zope.security.management.queryInteraction():
+  ...         zope.security.management.endInteraction()
+  ...     participation = FauxParticipation(principal)
+  ...     zope.security.management.newInteraction(participation)
+
+  >>> set_principal(britney)
+
+
+Now, let's look at basic locking. To perform locking operations, we first 
+have to adapt an object to `ILockable`:
+
+  >>> obj = ILockable(item1)
+
+
+We can ask if the object is locked:
+
+  >>> obj.locked()
+  False
+
+
+If it were locked, we could get the id of the principal that owns the 
+lock. Since it is not locked, this will return None:
+
+  >>> obj.locker()
+  ...
+
+
+Now lets lock the object. Note that the lock method return an instance 
+of an object that implements `ILockInfo` on success:
+
+  >>> info = obj.lock()
+  >>> ILockInfo.providedBy(info)
+  True
+
+  >>> obj.locked()
+  True
+
+  >>> obj.locker()
+  'britney'
+
+
+Methods are provided to check whether the current principal already has 
+the lock on an object and whether the lock is already owned by a different 
+principal:
+
+  >>> obj.ownLock()
+  True
+
+  >>> obj.isLockedOut()
+  False
+
+
+If we switch principals, we see that the answers reflect the current 
+principal:
+
+  >>> set_principal(tim)
+  >>> obj.ownLock()
+  False
+
+  >>> obj.isLockedOut()
+  True
+
+
+A principal can only release his or her own locks:
+
+  >>> obj.unlock()
+  Traceback (most recent call last):
+    ...
+  LockingError: Principal is not lock owner
+
+
+If we switch back to the original principal, we see that the original 
+principal can unlock the object:
+
+  >>> set_principal(britney)
+  >>> obj.unlock()
+  ...
+
+
+There is a mechanism for breaking locks that does not take the current 
+principal into account. This will break any existing lock on an object:
+
+  >>> obj.lock()
+  <...LockInfo...>
+
+  >>> set_principal(tim)
+  >>> obj.locked()
+  True
+
+  >>> obj.breaklock()
+  >>> obj.locked()
+  False
+
+
+Locks can be created with an optional timeout. If a timeout is provided, 
+it should be an integer number of seconds from the time the lock is 
+created. 
+
+  >>> # fake time function to avoid a time.sleep in tests!
+  >>> import time
+  >>> def faketime():
+  ...    return time.time() + 3600.0
+
+  >>> obj.lock(timeout=10)
+  <...LockInfo...>
+
+  >>> obj.locked()
+  True
+
+  >>> import zope.app.locking.storage
+  >>> zope.app.locking.storage.timefunc = faketime
+  >>> obj.locked()
+  False
+
+  >>> # undo our time hack
+  >>> zope.app.locking.storage.timefunc = time.time
+
+
+Finally, it is possible to explicitly get an `ILockInfo` object that 
+contains the lock information for the object. Note that locks that do 
+not have a timeout set have a timeout value of None.
+
+  >>> obj = ILockable(item2)
+  >>> obj.lock()
+  <...LockInfo...>
+
+  >>> info = obj.getLockInfo()
+  >>> info.principal_id
+  'tim'
+  >>> info.timeout
+  ...
+
+
+It is possible to get the object associated with a lock directly from 
+an ILockInfo instance:
+
+  >>> target = info.target
+  >>> target.__name__ == 'item2'
+  True
+
+
+The `ILockInfo` interface extends the IMapping interface, so application 
+code can store extra information on locks if necessary. It is recommended 
+that keys for extra data use qualified names following the convention that 
+is commonly used for annotations:
+
+  >>> info['my.namespace.extra'] = 'spam'
+  >>> info['my.namespace.extra'] == 'spam'
+  True
+  >>> obj.unlock()
+  >>> obj.locked()
+  False
+
+
+Lock tracking
+-------------
+
+It is often desirable to be able to report on the currently held locks in 
+a system (particularly on a per-user basis), without requiring an expensive 
+brute-force search. An `ILockTracker` utility allows an application to get 
+the current locks for a principal, or all current locks:
+
+  >>> set_principal(tim)
+  >>> obj = ILockable(item2)
+  >>> obj.lock()
+  <...LockInfo...>
+
+  >>> set_principal(britney)
+  >>> obj = ILockable(item3)
+  >>> obj.lock()
+  <...LockInfo...>
+
+  >>> from zope.app.locking.interfaces import ILockTracker
+  >>> from zope.app.zapi import getUtility
+  >>> util = getUtility(ILockTracker)
+
+  >>> items = util.getLocksForPrincipal('britney')
+  >>> len(items) == 1
+  True
+
+  >>> items = util.getAllLocks()
+  >>> len(items) >= 2
+  True
+
+
+These methods allow an application to create forms and other code that 
+performs unlocking or breaking of locks on sets of objects:
+
+  >>> items = util.getAllLocks()
+  >>> for item in items:
+  ...     obj = ILockable(item.target)
+  ...     obj.breaklock()
+
+  >>> items = util.getAllLocks()
+  >>> len(items) == 0
+  True
+
+
+Locking events
+--------------
+
+Locking operations (lock, unlock, break lock) fire events that can be used 
+by applications. Note that expiration of a lock *does not* fire an event 
+(because the current implementation uses a lazy expiration approach). 
+
+  >>> import zope.event
+
+  >>> def log_event(event):
+  ...     print event
+
+  >>> zope.event.subscribers.append(log_event)
+
+  >>> obj = ILockable(item2)
+  >>> obj.lock()
+  LockedEvent ...
+
+  >>> obj.unlock()
+  UnlockedEvent ...
+
+  >>> obj.lock()
+  LockedEvent ...
+
+  >>> obj.breaklock()
+  BreakLockEvent ...
+
+
+
+
+
+
+
+
+
+


Property changes on: Zope3/trunk/src/zope/app/locking/README.txt
___________________________________________________________________
Name: svn:executable
   + *

Added: Zope3/trunk/src/zope/app/locking/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/locking/__init__.py	2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/__init__.py	2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1 @@
+# This is a Python package


Property changes on: Zope3/trunk/src/zope/app/locking/__init__.py
___________________________________________________________________
Name: svn:executable
   + *

Added: Zope3/trunk/src/zope/app/locking/adapter.py
===================================================================
--- Zope3/trunk/src/zope/app/locking/adapter.py	2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/adapter.py	2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1,151 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Locking adapter implementation.
+
+$Id: $
+"""
+
+from zope.app.locking.interfaces import ILockable, ILockedEvent
+from zope.app.locking.interfaces import IUnlockedEvent, IBreakLockEvent
+from zope.app.keyreference.interfaces import IKeyReference
+from zope.component.exceptions import ComponentLookupError
+from zope.app.event.objectevent import ObjectEvent
+from zope.app.locking.interfaces import LockingError
+from zope.app.locking.storage import ILockStorage
+from zope.app.locking.lockinfo import LockInfo
+from zope.app.locking.interfaces import _
+from zope.component import getUtility
+import zope.security.management
+from zope.event import notify
+import zope.interface
+
+
+
+def LockingAdapterFactory(target):
+    """
+    Return target adapted to ILockable, or None. This should be registered
+    against zope.interface.Interface to provide adaptation to ILockable.
+    """
+    if IKeyReference(target, None) is None:
+        return None
+    return LockingAdapter(target)
+    
+
+class LockingAdapter(object):
+    """
+    Default ILockable adapter implementation.
+    """
+
+    zope.interface.implements(ILockable)
+    
+    def __init__(self, context):
+        try:
+            self.storage = getUtility(ILockStorage, context=context)
+        except ComponentLookupError:
+            self.storage = getUtility(ILockStorage)
+        self.context = context
+
+    def _findPrincipal(self):
+        # Find the current principal. Note that it is possible for there
+        # to be more than one principal - in this case we throw an error.
+        interaction = zope.security.management.getInteraction()
+        principal = None
+        for p in interaction.participations:
+            if principal is None:
+                principal = p.principal
+            else:
+                raise LockingError(_("Multiple principals found"))
+        if principal is None:
+            raise LockingError(_("No principal found"))
+        return principal
+
+    def lock(self, principal=None, timeout=None):
+        if principal is None:
+            principal = self._findPrincipal()
+        principal_id = principal.id
+        lock = self.storage.getLock(self.context)
+        if lock is not None:
+            raise LockingError(_("Object is already locked"))
+        lock = LockInfo(self.context, principal_id, timeout)
+        self.storage.setLock(self.context, lock)
+        notify(LockedEvent(self.context, lock))
+        return lock
+
+    def unlock(self):
+        lock = self.storage.getLock(self.context)
+        if lock is None:
+            raise LockingError(_("Object is not locked"))
+        principal = self._findPrincipal()
+        if lock.principal_id != principal.id:
+            raise LockingError(_("Principal is not lock owner"))
+        self.storage.delLock(self.context)
+        notify(UnlockedEvent(self.context))
+
+    def breaklock(self):
+        lock = self.storage.getLock(self.context)
+        if lock is None:
+            raise LockingError(_("Object is not locked"))
+        self.storage.delLock(self.context)
+        notify(BreakLockEvent(self.context))
+
+    def locked(self):
+        lock = self.storage.getLock(self.context)
+        return lock is not None
+
+    def locker(self):
+        lock = self.storage.getLock(self.context)
+        if lock is not None:
+            return lock.principal_id
+        return None
+
+    def getLockInfo(self):
+        return self.storage.getLock(self.context)
+
+    def ownLock(self):
+        lock = self.storage.getLock(self.context)
+        if lock is not None:
+            principal = self._findPrincipal()
+            return lock.principal_id == principal.id
+        return False
+
+    def isLockedOut(self):
+        lock = self.storage.getLock(self.context)
+        if lock is not None:
+            principal = self._findPrincipal()
+            return lock.principal_id != principal.id
+        return False
+
+    def __repr__(self):
+        return '<Locking adapter for %s>' % repr(self.context)
+
+
+
+class EventBase(ObjectEvent):
+    def __repr__(self):
+        return '%s for %s' % (self.__class__.__name__, `self.object`)
+
+class LockedEvent(EventBase):
+    zope.interface.implements(ILockedEvent)
+
+    def __init__(self, object, lock):
+        self.object = object
+        self.lock = lock
+
+
+class UnlockedEvent(EventBase):
+    zope.interface.implements(IUnlockedEvent)
+
+class BreakLockEvent(UnlockedEvent):
+    zope.interface.implements(IBreakLockEvent)


Property changes on: Zope3/trunk/src/zope/app/locking/adapter.py
___________________________________________________________________
Name: svn:executable
   + *

Added: Zope3/trunk/src/zope/app/locking/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/locking/configure.zcml	2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/configure.zcml	2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1,6 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+           i18n_domain="zope.app.locking">
+
+  <permission id="zope.app.locking.UseLocking" title="Use locking" />
+
+</configure>


Property changes on: Zope3/trunk/src/zope/app/locking/configure.zcml
___________________________________________________________________
Name: svn:executable
   + *

Added: Zope3/trunk/src/zope/app/locking/interfaces.py
===================================================================
--- Zope3/trunk/src/zope/app/locking/interfaces.py	2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/interfaces.py	2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1,142 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Locking interfaces
+
+$Id: $
+"""
+
+from zope.app.annotation.interfaces import IAttributeAnnotatable
+from zope.app.event.interfaces import IObjectEvent
+from zope.interface import Interface, Attribute
+from zope.i18nmessageid import MessageIDFactory
+from zope.interface.common.mapping import IMapping
+import zope.interface
+import zope.schema
+
+_ = MessageIDFactory('zope.app.locking')
+
+
+class ILockable(Interface):
+    """
+    The ILockable interface defines the locking operations that are
+    supported for lockable objects.
+    """
+
+    def lock(timeout=None):
+        """
+        Lock the object in the name of the current principal. This method
+        raises a LockingError if the object cannot be locked by the current
+        principal.
+        """
+
+    def unlock():
+        """
+        Unlock the object. If the current principal does not hold a lock
+        on the object, this method raises a LockingError.
+        """
+
+    def breaklock():
+        """
+        Break all existing locks on an object for all principals.
+        """
+
+    def locked():
+        """
+        Returns true if the object is locked.
+        """
+
+    def locker():
+        """
+        Return the principal id of the principal that owns the lock on
+        the object, or None if the object is not locked.
+        """
+
+    def getLockInfo(obj):
+        """
+        Return a (possibly empty) sequence of ILockInfo objects describing
+        the current locks on the object.
+        """
+
+    def ownLock():
+        """
+        Returns true if the object is locked by the current principal.
+        """
+
+    def isLockedOut():
+        """
+        Returns true if the object is locked by a principal other than
+        the current principal.
+        """
+
+
+class ILockTracker(Interface):
+    """
+    An ILockTracker implementation is responsible for tracking what
+    objects are locked within its scope.
+    """
+
+    def getLocksForPrincipal(principal_id):
+        """
+        Return a sequence of all locks held by the given principal.
+        """
+
+    def getAllLocks():
+        """
+        Return a sequence of all currently held locks.
+        """
+
+
+class ILockInfo(IMapping):
+    """
+    An ILockInfo implementation is responsible for 
+    """
+
+    def getObject():
+        """Return the actual locked object."""
+
+    creator = zope.schema.TextLine(
+        description=_("id of the principal owning the lock")
+        )
+
+    created = zope.schema.Float(
+        description=_("time value indicating the creation time"),
+        required=False
+        )
+
+    timeout = zope.schema.Float(
+        description=_("time value indicating the lock timeout from creation"),
+        required=False
+        )
+
+
+
+class ILockedEvent(IObjectEvent):
+    """An object has been locked"""
+
+    lock = Attribute("The lock set on the object")
+    
+class IUnlockedEvent(IObjectEvent):
+    """An object has been unlocked"""
+
+class IBreakLockEvent(IUnlockedEvent):
+    """Lock has been broken on an object"""
+
+
+
+class LockingError(Exception):
+    """
+    The exception raised for locking errors.
+    """
+


Property changes on: Zope3/trunk/src/zope/app/locking/interfaces.py
___________________________________________________________________
Name: svn:executable
   + *

Added: Zope3/trunk/src/zope/app/locking/lockinfo.py
===================================================================
--- Zope3/trunk/src/zope/app/locking/lockinfo.py	2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/lockinfo.py	2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1,64 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+ILockInfo implementation.
+
+$Id: $
+"""
+
+from zope.app.locking.interfaces import ILockInfo, LockingError
+import zope.interface, time
+
+
+class LockInfo(object):
+    
+    zope.interface.implements(ILockInfo)
+
+    def __init__(self, target, principal_id, timeout=None):
+        self.target = target
+        self.principal_id = principal_id
+        self.created = time.time()
+        self.timeout = timeout
+        self.data = {}
+
+    def get(self, key, default=None):
+        return self.data.get(key, default)
+
+    def keys(self):
+        return self.data.keys()
+
+    def values(self):
+        return self.data.values()
+
+    def items(self):
+        return self.data.items()
+
+    def __getitem__(self, key):
+        return self.data[key]
+
+    def __setitem__(self, key, value):
+        self.data[key] = value
+
+    def __delitem__(self, key):
+        del self.data[key]
+
+    def __contains__(self, key):
+        return self.data.has_key(key)
+
+    def __iter__(self):
+        return iter(self.data)
+
+    def __len__(self):
+        return len(self.data)
+


Property changes on: Zope3/trunk/src/zope/app/locking/lockinfo.py
___________________________________________________________________
Name: svn:executable
   + *

Added: Zope3/trunk/src/zope/app/locking/storage.py
===================================================================
--- Zope3/trunk/src/zope/app/locking/storage.py	2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/storage.py	2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1,117 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Lock storage implementation.
+
+$Id: $
+"""
+
+from zope.app.keyreference.interfaces import IKeyReference
+from zope.app.locking.interfaces import ILockTracker
+from zope.app.locking.interfaces import LockingError
+from BTrees.OOBTree import OOBTree
+from BTrees.IOBTree import IOBTree
+import zope.interface, time
+
+
+timefunc = time.time
+
+
+class ILockStorage(zope.interface.Interface):
+    """
+    This interface is internal to the default locking implementation. It
+    lets us store lock information in a central place rather than store
+    it on individual objects.
+    """
+
+
+class LockStorage(object):
+    """
+    This class implements both the ILockTracker utility as well as the
+    internal ILockStorage utility which is used by the ILockable adapter
+    implementation. It acts as the persistent storage for locks.
+    """
+
+    zope.interface.implements(ILockStorage, ILockTracker)
+    
+    def __init__(self):
+        self.timeouts = IOBTree()
+        self.locks = OOBTree()
+
+    # ILockTracker implementation
+    
+    def getLocksForPrincipal(self, principal_id):
+        return self.currentLocks(principal_id)
+
+    def getAllLocks(self):
+        return self.currentLocks()
+
+    # ILockStorage implementation
+
+    def currentLocks(self, principal_id=None):
+        """
+        Return the currently active locks, possibly filtered by principal.
+        """
+        result = []
+        for lock in self.locks.values():
+            if principal_id is None or principal_id == lock.principal_id:
+                if (lock.timeout is None or 
+                   (lock.created + lock.timeout > timefunc())
+                    ):
+                    result.append(lock)
+        return result
+                    
+    def getLock(self, object):
+        """
+        Get the current lock for an object.
+        """
+        keyref = IKeyReference(object)
+        lock = self.locks.get(keyref, None)
+        if lock is not None and lock.timeout is not None:
+            if lock.created + lock.timeout < timefunc():
+                return None
+        return lock
+
+    def setLock(self, object, lock):
+        """
+        Set the current lock for an object.
+        """
+        keyref = IKeyReference(object)
+        self.locks[keyref] = lock
+        pid = lock.principal_id
+        if lock.timeout:
+            ts = int(lock.created + lock.timeout)
+            value = self.timeouts.get(ts, [])
+            value.append(keyref)
+            self.timeouts[ts] = value
+        self.cleanup()
+
+    def delLock(self, object):
+        """
+        Delete the current lock for an object.
+        """
+        keyref = IKeyReference(object)
+        del self.locks[keyref]
+
+    def cleanup(self):
+        # We occasionally want to clean up expired locks to keep them
+        # from accumulating over time and slowing things down. 
+        for key in self.timeouts.keys(max=int(timefunc())):
+            for keyref in self.timeouts[key]:
+                if self.locks.get(keyref, None) is not None:
+                    del self.locks[keyref]
+            del self.timeouts[key]
+
+        
+


Property changes on: Zope3/trunk/src/zope/app/locking/storage.py
___________________________________________________________________
Name: svn:executable
   + *

Added: Zope3/trunk/src/zope/app/locking/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/locking/tests.py	2005-02-03 16:34:52 UTC (rev 29028)
+++ Zope3/trunk/src/zope/app/locking/tests.py	2005-02-03 18:20:33 UTC (rev 29029)
@@ -0,0 +1,97 @@
+##############################################################################
+#
+# Copyright (c) 2005 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""
+Locking tests
+
+$Id:$
+"""
+
+import sys, unittest
+from zope.component.tests.placelesssetup import PlacelessSetup
+from zope.testing import doctest
+from transaction import abort
+
+
+class FakeModule:
+    def __init__(self, dict):
+        self.__dict = dict
+    def __getattr__(self, name):
+        try:
+            return self.__dict[name]
+        except KeyError:
+            raise AttributeError, name
+
+name = 'zope.app.locking.README'
+
+ps = PlacelessSetup()
+
+
+from zope.app.keyreference.interfaces import IKeyReference
+
+class FakeKeyReference(object):
+    """Fake keyref for testing"""
+    def __init__(self, object):
+        self.object = object
+
+    def __call__(self):
+        return self.object
+
+    def __hash__(self):
+        return id(self.object)
+
+    def __cmp__(self, other):
+        return cmp(id(self.object), id(other.object))
+
+
+
+def setUp(test):
+    ps.setUp()
+    dict = test.globs
+    dict.clear()
+    dict['__name__'] = name    
+    sys.modules[name] = FakeModule(dict)
+
+    from zope.app.tests import ztapi
+    from zope.interface import Interface
+    from zope.app.locking.interfaces import ILockable, ILockTracker
+    from zope.app.locking.adapter import LockingAdapterFactory
+    from zope.app.locking.storage import ILockStorage, LockStorage
+    
+    ztapi.provideAdapter(Interface, IKeyReference, FakeKeyReference)
+    ztapi.provideAdapter(Interface, ILockable, LockingAdapterFactory)
+    storage = LockStorage()
+    ztapi.provideUtility(ILockStorage, storage)
+    ztapi.provideUtility(ILockTracker, storage)
+    test._storage = storage # keep-alive
+
+
+def tearDown(test):
+    del sys.modules[name]
+    abort()
+    db = test.globs.get('db')
+    if db is not None:
+        db.close()
+    ps.tearDown()
+    del test._storage
+
+
+def test_suite():
+    return doctest.DocFileSuite('README.txt', setUp=setUp, tearDown=tearDown,
+                                optionflags=(doctest.ELLIPSIS)
+                                )
+
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')
+


Property changes on: Zope3/trunk/src/zope/app/locking/tests.py
___________________________________________________________________
Name: svn:executable
   + *



More information about the Zope3-Checkins mailing list