import time from tracim_backend.lib.webdav.model import Lock, Url2Token from wsgidav import util from wsgidav.lock_manager import normalizeLockRoot, lockString, generateLockToken, validateLock from wsgidav.rw_lock import ReadWriteLock _logger = util.getModuleLogger(__name__) def from_dict_to_base(lock): return Lock( token=lock["token"], depth=lock["depth"], root=lock["root"], type=lock["type"], scopre=lock["scope"], owner=lock["owner"], timeout=lock["timeout"], principal=lock["principal"], expire=lock["expire"] ) def from_base_to_dict(lock): return { 'token': lock.token, 'depth': lock.depth, 'root': lock.root, 'type': lock.type, 'scope': lock.scope, 'owner': lock.owner, 'timeout': lock.timeout, 'principal': lock.principal, 'expire': lock.expire } class LockStorage(object): LOCK_TIME_OUT_DEFAULT = 604800 # 1 week, in seconds LOCK_TIME_OUT_MAX = 4 * 604800 # 1 month, in seconds def __init__(self): self._session = None# todo Session() self._lock = ReadWriteLock() def __repr__(self): return "C'est bien mon verrou..." def __del__(self): pass def get_lock_db_from_token(self, token): return self._session.query(Lock).filter(Lock.token == token).one_or_none() def _flush(self): """Overloaded by Shelve implementation.""" pass def open(self): """Called before first use. May be implemented to initialize a storage. """ pass def close(self): """Called on shutdown.""" pass def cleanup(self): """Purge expired locks (optional).""" pass def clear(self): """Delete all entries.""" self._session.query(Lock).all().delete(synchronize_session=False) self._session.commit() 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 Side effect: if lock is expired, it will be purged and None is returned. """ self._lock.acquireRead() try: lock_base = self._session.query(Lock).filter(Lock.token == token).one_or_none() if lock_base is None: # Lock not found: purge dangling URL2TOKEN entries _logger.debug("Lock purged dangling: %s" % token) self.delete(token) return None expire = float(lock_base.expire) if 0 <= expire < time.time(): _logger.debug("Lock timed-out(%s): %s" % (expire, lockString(from_base_to_dict(lock_base)))) self.delete(token) return None return from_base_to_dict(lock_base) 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['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 = LockStorage.LOCK_TIME_OUT_DEFAULT elif timeout < 0 or timeout > LockStorage.LOCK_TIME_OUT_MAX: timeout = LockStorage.LOCK_TIME_OUT_MAX lock["timeout"] = timeout lock["expire"] = time.time() + timeout validateLock(lock) token = generateLockToken() lock["token"] = token # Store lock lock_db = from_dict_to_base(lock) self._session.add(lock_db) # Store locked path reference url2token = Url2Token( path=path, token=token ) self._session.add(url2token) self._session.commit() 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. """ lock_db = self._session.query(Lock).filter(Lock.token == token).one_or_none() assert lock_db is not None, "Lock must exist" assert timeout == -1 or timeout > 0 if timeout < 0 or timeout > LockStorage.LOCK_TIME_OUT_MAX: timeout = LockStorage.LOCK_TIME_OUT_MAX self._lock.acquireWrite() try: # Note: shelve dictionary returns copies, so we must reassign values: lock_db.timeout = timeout lock_db.expire = time.time() + timeout self._session.commit() self._flush() finally: self._lock.release() return from_base_to_dict(lock_db) def delete(self, token): """Delete lock. Returns True on success. False, if token does not exist, or is expired. """ self._lock.acquireWrite() try: lock_db = self._session.query(Lock).filter(Lock.token == token).one_or_none() _logger.debug("delete %s" % lockString(from_base_to_dict(lock_db))) if lock_db is None: return False # Remove url to lock mapping url2token = self._session.query(Url2Token).filter( Url2Token.path == lock_db.root, Url2Token.token == token).one_or_none() if url2token is not None: self._session.delete(url2token) # Remove the lock self._session.delete(lock_db) self._session.commit() self._flush() finally: self._lock.release() return True def getLockList(self, path, includeRoot, includeChildren, tokenOnly): """Return a list of direct locks for . Expired locks are *not* returned (but may be purged). path: Normalized path (utf8 encoded string, no trailing '/') includeRoot: False: don't add 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_db = self.get_lock_db_from_token(token) if lock_db: if tokenOnly: lockList.append(lock_db.token) else: lockList.append(from_base_to_dict(lock_db)) path = normalizeLockRoot(path) self._lock.acquireRead() try: tokList = self._session.query(Url2Token.token).filter(Url2Token.path == path).all() lockList = [] if includeRoot: __appendLocks(tokList) if includeChildren: for url, in self._session.query(Url2Token.path).group_by(Url2Token.path): if util.isChildUri(path, url): __appendLocks(self._session.query(Url2Token.token).filter(Url2Token.path == url)) return lockList finally: self._lock.release()