lock_storage.py 8.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. import time
  2. from tracim_backend.lib.webdav.model import Lock, Url2Token
  3. from wsgidav import util
  4. from wsgidav.lock_manager import normalizeLockRoot, lockString, generateLockToken, validateLock
  5. from wsgidav.rw_lock import ReadWriteLock
  6. _logger = util.getModuleLogger(__name__)
  7. def from_dict_to_base(lock):
  8. return Lock(
  9. token=lock["token"],
  10. depth=lock["depth"],
  11. root=lock["root"],
  12. type=lock["type"],
  13. scopre=lock["scope"],
  14. owner=lock["owner"],
  15. timeout=lock["timeout"],
  16. principal=lock["principal"],
  17. expire=lock["expire"]
  18. )
  19. def from_base_to_dict(lock):
  20. return {
  21. 'token': lock.token,
  22. 'depth': lock.depth,
  23. 'root': lock.root,
  24. 'type': lock.type,
  25. 'scope': lock.scope,
  26. 'owner': lock.owner,
  27. 'timeout': lock.timeout,
  28. 'principal': lock.principal,
  29. 'expire': lock.expire
  30. }
  31. class LockStorage(object):
  32. LOCK_TIME_OUT_DEFAULT = 604800 # 1 week, in seconds
  33. LOCK_TIME_OUT_MAX = 4 * 604800 # 1 month, in seconds
  34. def __init__(self):
  35. self._session = None# todo Session()
  36. self._lock = ReadWriteLock()
  37. def __repr__(self):
  38. return "C'est bien mon verrou..."
  39. def __del__(self):
  40. pass
  41. def get_lock_db_from_token(self, token):
  42. return self._session.query(Lock).filter(Lock.token == token).one_or_none()
  43. def _flush(self):
  44. """Overloaded by Shelve implementation."""
  45. pass
  46. def open(self):
  47. """Called before first use.
  48. May be implemented to initialize a storage.
  49. """
  50. pass
  51. def close(self):
  52. """Called on shutdown."""
  53. pass
  54. def cleanup(self):
  55. """Purge expired locks (optional)."""
  56. pass
  57. def clear(self):
  58. """Delete all entries."""
  59. self._session.query(Lock).all().delete(synchronize_session=False)
  60. self._session.commit()
  61. def get(self, token):
  62. """Return a lock dictionary for a token.
  63. If the lock does not exist or is expired, None is returned.
  64. token:
  65. lock token
  66. Returns:
  67. Lock dictionary or <None>
  68. Side effect: if lock is expired, it will be purged and None is returned.
  69. """
  70. self._lock.acquireRead()
  71. try:
  72. lock_base = self._session.query(Lock).filter(Lock.token == token).one_or_none()
  73. if lock_base is None:
  74. # Lock not found: purge dangling URL2TOKEN entries
  75. _logger.debug("Lock purged dangling: %s" % token)
  76. self.delete(token)
  77. return None
  78. expire = float(lock_base.expire)
  79. if 0 <= expire < time.time():
  80. _logger.debug("Lock timed-out(%s): %s" % (expire, lockString(from_base_to_dict(lock_base))))
  81. self.delete(token)
  82. return None
  83. return from_base_to_dict(lock_base)
  84. finally:
  85. self._lock.release()
  86. def create(self, path, lock):
  87. """Create a direct lock for a resource path.
  88. path:
  89. Normalized path (utf8 encoded string, no trailing '/')
  90. lock:
  91. lock dictionary, without a token entry
  92. Returns:
  93. New unique lock token.: <lock
  94. **Note:** the lock dictionary may be modified on return:
  95. - lock['root'] is ignored and set to the normalized <path>
  96. - lock['timeout'] may be normalized and shorter than requested
  97. - lock['token'] is added
  98. """
  99. self._lock.acquireWrite()
  100. try:
  101. # We expect only a lock definition, not an existing lock
  102. assert lock.get("token") is None
  103. assert lock.get("expire") is None, "Use timeout instead of expire"
  104. assert path and "/" in path
  105. # Normalize root: /foo/bar
  106. org_path = path
  107. path = normalizeLockRoot(path)
  108. lock["root"] = path
  109. # Normalize timeout from ttl to expire-date
  110. timeout = float(lock.get("timeout"))
  111. if timeout is None:
  112. timeout = LockStorage.LOCK_TIME_OUT_DEFAULT
  113. elif timeout < 0 or timeout > LockStorage.LOCK_TIME_OUT_MAX:
  114. timeout = LockStorage.LOCK_TIME_OUT_MAX
  115. lock["timeout"] = timeout
  116. lock["expire"] = time.time() + timeout
  117. validateLock(lock)
  118. token = generateLockToken()
  119. lock["token"] = token
  120. # Store lock
  121. lock_db = from_dict_to_base(lock)
  122. self._session.add(lock_db)
  123. # Store locked path reference
  124. url2token = Url2Token(
  125. path=path,
  126. token=token
  127. )
  128. self._session.add(url2token)
  129. self._session.commit()
  130. self._flush()
  131. _logger.debug("LockStorageDict.set(%r): %s" % (org_path, lockString(lock)))
  132. # print("LockStorageDict.set(%r): %s" % (org_path, lockString(lock)))
  133. return lock
  134. finally:
  135. self._lock.release()
  136. def refresh(self, token, timeout):
  137. """Modify an existing lock's timeout.
  138. token:
  139. Valid lock token.
  140. timeout:
  141. Suggested lifetime in seconds (-1 for infinite).
  142. The real expiration time may be shorter than requested!
  143. Returns:
  144. Lock dictionary.
  145. Raises ValueError, if token is invalid.
  146. """
  147. lock_db = self._session.query(Lock).filter(Lock.token == token).one_or_none()
  148. assert lock_db is not None, "Lock must exist"
  149. assert timeout == -1 or timeout > 0
  150. if timeout < 0 or timeout > LockStorage.LOCK_TIME_OUT_MAX:
  151. timeout = LockStorage.LOCK_TIME_OUT_MAX
  152. self._lock.acquireWrite()
  153. try:
  154. # Note: shelve dictionary returns copies, so we must reassign values:
  155. lock_db.timeout = timeout
  156. lock_db.expire = time.time() + timeout
  157. self._session.commit()
  158. self._flush()
  159. finally:
  160. self._lock.release()
  161. return from_base_to_dict(lock_db)
  162. def delete(self, token):
  163. """Delete lock.
  164. Returns True on success. False, if token does not exist, or is expired.
  165. """
  166. self._lock.acquireWrite()
  167. try:
  168. lock_db = self._session.query(Lock).filter(Lock.token == token).one_or_none()
  169. _logger.debug("delete %s" % lockString(from_base_to_dict(lock_db)))
  170. if lock_db is None:
  171. return False
  172. # Remove url to lock mapping
  173. url2token = self._session.query(Url2Token).filter(
  174. Url2Token.path == lock_db.root,
  175. Url2Token.token == token).one_or_none()
  176. if url2token is not None:
  177. self._session.delete(url2token)
  178. # Remove the lock
  179. self._session.delete(lock_db)
  180. self._session.commit()
  181. self._flush()
  182. finally:
  183. self._lock.release()
  184. return True
  185. def getLockList(self, path, includeRoot, includeChildren, tokenOnly):
  186. """Return a list of direct locks for <path>.
  187. Expired locks are *not* returned (but may be purged).
  188. path:
  189. Normalized path (utf8 encoded string, no trailing '/')
  190. includeRoot:
  191. False: don't add <path> lock (only makes sense, when includeChildren
  192. is True).
  193. includeChildren:
  194. True: Also check all sub-paths for existing locks.
  195. tokenOnly:
  196. True: only a list of token is returned. This may be implemented
  197. more efficiently by some providers.
  198. Returns:
  199. List of valid lock dictionaries (may be empty).
  200. """
  201. assert path and path.startswith("/")
  202. assert includeRoot or includeChildren
  203. def __appendLocks(toklist):
  204. # Since we can do this quickly, we use self.get() even if
  205. # tokenOnly is set, so expired locks are purged.
  206. for token in toklist:
  207. lock_db = self.get_lock_db_from_token(token)
  208. if lock_db:
  209. if tokenOnly:
  210. lockList.append(lock_db.token)
  211. else:
  212. lockList.append(from_base_to_dict(lock_db))
  213. path = normalizeLockRoot(path)
  214. self._lock.acquireRead()
  215. try:
  216. tokList = self._session.query(Url2Token.token).filter(Url2Token.path == path).all()
  217. lockList = []
  218. if includeRoot:
  219. __appendLocks(tokList)
  220. if includeChildren:
  221. for url, in self._session.query(Url2Token.path).group_by(Url2Token.path):
  222. if util.isChildUri(path, url):
  223. __appendLocks(self._session.query(Url2Token.token).filter(Url2Token.path == url))
  224. return lockList
  225. finally:
  226. self._lock.release()