mirror of
https://github.com/YunoHost-Apps/seafile_ynh.git
synced 2024-09-03 20:26:01 +02:00
387 lines
14 KiB
Python
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()
|