seafile_ynh/sources/seafile-server-3.1.1/seahub/thirdpart/wsgidav/lock_storage.py
Elie 0f063400be Add sources
Former-commit-id: 2e47976ec5
2014-08-06 22:42:37 -04:00

387 lines
14 KiB
Python

# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/
# Original PyFileServer (c) 2005 Ho Chun Wei.
# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php
"""
Implements two storage providers for `LockManager`.
Two alternative lock storage classes are defined here: one in-memory
(dict-based), and one persistent low performance variant using shelve.
See wsgidav.lock_manager.LockManager
See `Developers info`_ for more information about the WsgiDAV architecture.
.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html
"""
from wsgidav.lock_manager import normalizeLockRoot, lockString,\
generateLockToken, validateLock
import os
import util
import shelve
import time
from rw_lock import ReadWriteLock
__docformat__ = "reStructuredText"
_logger = util.getModuleLogger(__name__)
# TODO: comment's from Ian Bicking (2005)
#@@: Use of shelve means this is only really useful in a threaded environment.
# And if you have just a single-process threaded environment, you could get
# nearly the same effect with a dictionary of threading.Lock() objects. Of course,
# it would be better to move off shelve anyway, probably to a system with
# a directory of per-file locks, using the file locking primitives (which,
# sadly, are not quite portable).
# @@: It would probably be easy to store the properties as pickle objects
# in a parallel directory structure to the files you are describing.
# Pickle is expedient, but later you could use something more readable
# (pickles aren't particularly readable)
#===============================================================================
# LockStorageDict
#===============================================================================
class LockStorageDict(object):
"""
An in-memory lock manager storage implementation using a dictionary.
R/W access is guarded by a thread.lock object.
Also, to make it work with a Shelve dictionary, modifying dictionary
members is done by re-assignment and we call a _flush() method.
This is obviously not persistent, but should be enough in some cases.
For a persistent implementation, see lock_manager.LockStorageShelve().
Notes:
expire is stored as expiration date in seconds since epoch (not in
seconds until expiration).
The dictionary is built like::
{ 'URL2TOKEN:/temp/litmus/lockme': ['opaquelocktoken:0x1d7b86...',
'opaquelocktoken:0xd7d4c0...'],
'opaquelocktoken:0x1d7b86...': { 'depth': '0',
'owner': "<?xml version=\'1.0\' encoding=\'UTF-8\'?>\\n<owner xmlns="DAV:">litmus test suite</owner>\\n",
'principal': 'tester',
'root': '/temp/litmus/lockme',
'scope': 'shared',
'expire': 1261328382.4530001,
'token': 'opaquelocktoken:0x1d7b86...',
'type': 'write',
},
'opaquelocktoken:0xd7d4c0...': { 'depth': '0',
'owner': '<?xml version=\'1.0\' encoding=\'UTF-8\'?>\\n<owner xmlns="DAV:">litmus: notowner_sharedlock</owner>\\n',
'principal': 'tester',
'root': '/temp/litmus/lockme',
'scope': 'shared',
'expire': 1261328381.6040001,
'token': 'opaquelocktoken:0xd7d4c0...',
'type': 'write'
},
}
"""
LOCK_TIME_OUT_DEFAULT = 604800 # 1 week, in seconds
LOCK_TIME_OUT_MAX = 4 * 604800 # 1 month, in seconds
def __init__(self):
self._dict = None
self._lock = ReadWriteLock()
def __repr__(self):
return self.__class__.__name__
def __del__(self):
pass
def _flush(self):
"""Overloaded by Shelve implementation."""
pass
def open(self):
"""Called before first use.
May be implemented to initialize a storage.
"""
assert self._dict is None
self._dict = {}
def close(self):
"""Called on shutdown."""
self._dict = None
def cleanup(self):
"""Purge expired locks (optional)."""
pass
def get(self, token):
"""Return a lock dictionary for a token.
If the lock does not exist or is expired, None is returned.
token:
lock token
Returns:
Lock dictionary or <None>
Side effect: if lock is expired, it will be purged and None is returned.
"""
self._lock.acquireRead()
try:
lock = self._dict.get(token)
if lock is None:
# Lock not found: purge dangling URL2TOKEN entries
_logger.debug("Lock purged dangling: %s" % token)
self.delete(token)
return None
expire = float(lock["expire"])
if expire >= 0 and expire < time.time():
_logger.debug("Lock timed-out(%s): %s" % (expire, lockString(lock)))
self.delete(token)
return None
return lock
finally:
self._lock.release()
def create(self, path, lock):
"""Create a direct lock for a resource path.
path:
Normalized path (utf8 encoded string, no trailing '/')
lock:
lock dictionary, without a token entry
Returns:
New unique lock token.: <lock
**Note:** the lock dictionary may be modified on return:
- lock['root'] is ignored and set to the normalized <path>
- lock['timeout'] may be normalized and shorter than requested
- lock['token'] is added
"""
self._lock.acquireWrite()
try:
# We expect only a lock definition, not an existing lock
assert lock.get("token") is None
assert lock.get("expire") is None, "Use timeout instead of expire"
assert path and "/" in path
# Normalize root: /foo/bar
org_path = path
path = normalizeLockRoot(path)
lock["root"] = path
# Normalize timeout from ttl to expire-date
timeout = float(lock.get("timeout"))
if timeout is None:
timeout = LockStorageDict.LOCK_TIME_OUT_DEFAULT
elif timeout < 0 or timeout > LockStorageDict.LOCK_TIME_OUT_MAX:
timeout = LockStorageDict.LOCK_TIME_OUT_MAX
lock["timeout"] = timeout
lock["expire"] = time.time() + timeout
validateLock(lock)
token = generateLockToken()
lock["token"] = token
# Store lock
self._dict[token] = lock
# Store locked path reference
key = "URL2TOKEN:%s" % path
if not key in self._dict:
self._dict[key] = [ token ]
else:
# Note: Shelve dictionary returns copies, so we must reassign values:
tokList = self._dict[key]
tokList.append(token)
self._dict[key] = tokList
self._flush()
_logger.debug("LockStorageDict.set(%r): %s" % (org_path, lockString(lock)))
# print("LockStorageDict.set(%r): %s" % (org_path, lockString(lock)))
return lock
finally:
self._lock.release()
def refresh(self, token, timeout):
"""Modify an existing lock's timeout.
token:
Valid lock token.
timeout:
Suggested lifetime in seconds (-1 for infinite).
The real expiration time may be shorter than requested!
Returns:
Lock dictionary.
Raises ValueError, if token is invalid.
"""
assert token in self._dict, "Lock must exist"
assert timeout == -1 or timeout > 0
if timeout < 0 or timeout > LockStorageDict.LOCK_TIME_OUT_MAX:
timeout = LockStorageDict.LOCK_TIME_OUT_MAX
self._lock.acquireWrite()
try:
# Note: shelve dictionary returns copies, so we must reassign values:
lock = self._dict[token]
lock["timeout"] = timeout
lock["expire"] = time.time() + timeout
self._dict[token] = lock
self._flush()
finally:
self._lock.release()
return lock
def delete(self, token):
"""Delete lock.
Returns True on success. False, if token does not exist, or is expired.
"""
self._lock.acquireWrite()
try:
lock = self._dict.get(token)
_logger.debug("delete %s" % lockString(lock))
if lock is None:
return False
# Remove url to lock mapping
key = "URL2TOKEN:%s" % lock.get("root")
if key in self._dict:
# _logger.debug(" delete token %s from url %s" % (token, lock.get("root")))
tokList = self._dict[key]
if len(tokList) > 1:
# Note: shelve dictionary returns copies, so we must reassign values:
tokList.remove(token)
self._dict[key] = tokList
else:
del self._dict[key]
# Remove the lock
del self._dict[token]
self._flush()
finally:
self._lock.release()
return True
def getLockList(self, path, includeRoot, includeChildren, tokenOnly):
"""Return a list of direct locks for <path>.
Expired locks are *not* returned (but may be purged).
path:
Normalized path (utf8 encoded string, no trailing '/')
includeRoot:
False: don't add <path> lock (only makes sense, when includeChildren
is True).
includeChildren:
True: Also check all sub-paths for existing locks.
tokenOnly:
True: only a list of token is returned. This may be implemented
more efficiently by some providers.
Returns:
List of valid lock dictionaries (may be empty).
"""
assert path and path.startswith("/")
assert includeRoot or includeChildren
def __appendLocks(toklist):
# Since we can do this quickly, we use self.get() even if
# tokenOnly is set, so expired locks are purged.
for token in toklist:
lock = self.get(token)
if lock:
if tokenOnly:
lockList.append(lock["token"])
else:
lockList.append(lock)
path = normalizeLockRoot(path)
self._lock.acquireRead()
try:
key = "URL2TOKEN:%s" % path
tokList = self._dict.get(key, [])
lockList = []
if includeRoot:
__appendLocks(tokList)
if includeChildren:
for u, ltoks in self._dict.items():
if util.isChildUri(key, u):
__appendLocks(ltoks)
return lockList
finally:
self._lock.release()
#===============================================================================
# LockStorageShelve
#===============================================================================
class LockStorageShelve(LockStorageDict):
"""
A low performance lock manager implementation using shelve.
"""
def __init__(self, storagePath):
super(LockStorageShelve, self).__init__()
self._storagePath = os.path.abspath(storagePath)
def __repr__(self):
return "LockStorageShelve(%r)" % self._storagePath
def _flush(self):
"""Write persistent dictionary to disc."""
_logger.debug("_flush()")
self._lock.acquireWrite() # TODO: read access is enough?
try:
self._dict.sync()
finally:
self._lock.release()
def open(self):
_logger.debug("open(%r)" % self._storagePath)
# Open with writeback=False, which is faster, but we have to be
# careful to re-assign values to _dict after modifying them
self._dict = shelve.open(self._storagePath, writeback=False)
# if __debug__ and self._verbose >= 2:
## self._check("After shelve.open()")
# self._dump("After shelve.open()")
def close(self):
_logger.debug("close()")
self._lock.acquireWrite()
try:
if self._dict is not None:
self._dict.close()
self._dict = None
finally:
self._lock.release()
#===============================================================================
# test
#===============================================================================
def test():
# l = ShelveLockManager("wsgidav-locks.shelve")
# l._lazyOpen()
# l._dump()
# l.generateLock("martin", "", lockscope, lockdepth, lockowner, lockroot, timeout)
pass
if __name__ == "__main__":
test()