Source code for wsgidav.prop_man.property_manager

# (c) 2009-2023 Martin Wendt and contributors; see WsgiDAV https://github.com/mar10/wsgidav
# Original PyFileServer (c) 2005 Ho Chun Wei.
# Licensed under the MIT license:
# http://www.opensource.org/licenses/mit-license.php
"""
Implements two property managers: one in-memory (dict-based), and one
persistent low performance variant using shelve.

The properties dictionaray is built like::

    { ref-url1: {propname1: value1,
                 propname2: value2,
                 },
      ref-url2: {propname1: value1,
                 propname2: value2,
                 },
      }

"""
import os
import shelve

from wsgidav import util
from wsgidav.rw_lock import ReadWriteLock

# 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)

__docformat__ = "reStructuredText"

_logger = util.get_module_logger("wsgidav.prop_man")


# ========================================================================
# PropertyManager
# ========================================================================
[docs] class PropertyManager: """ An in-memory property manager implementation using a dictionary. This is obviously not persistent, but should be enough in some cases. For a persistent implementation, see property_manager.ShelvePropertyManager(). """ def __init__(self): self._dict = None self._loaded = False self._lock = ReadWriteLock() self._verbose = 3 def __repr__(self): return "PropertyManager" def __del__(self): if __debug__ and self._verbose >= 4: self._check() self._close() def _lazy_open(self): _logger.debug("_lazy_open()") self._lock.acquire_write() try: self._dict = {} self._loaded = True finally: self._lock.release() def _sync(self): pass def _close(self): _logger.debug("_close()") self._lock.acquire_write() try: self._dict = None self._loaded = False finally: self._lock.release() def _check(self, msg=""): try: if not self._loaded: return True for k, v in self._dict.items(): _dummy = "{}, {}".format(k, v) # noqa # _logger.debug("{} checks ok {}".format(self.__class__.__name__, msg)) return True except Exception: _logger.exception(f"{self.__class__.__name__} _check: ERROR {msg}") return False def _dump(self, msg=""): _logger.info(f"{self.__class__.__name__}({self.__repr__()}): {msg}") if not self._loaded: self._lazy_open() if self._verbose >= 4: return # Already dumped in _lazy_open try: for k, v in self._dict.items(): _logger.info(f" {k}") for k2, v2 in v.items(): try: _logger.info(f" {k2}: {v2!r}") except Exception as e: _logger.info(f" {k2}: ERROR {e}") # _logger.flush() except Exception as e: _logger.error(f"PropertyManager._dump() ERROR: {e}")
[docs] def get_properties(self, norm_url, environ=None): _logger.debug(f"get_properties({norm_url})") self._lock.acquire_read() try: if not self._loaded: self._lazy_open() returnlist = [] if norm_url in self._dict: for propdata in self._dict[norm_url].keys(): returnlist.append(propdata) return returnlist finally: self._lock.release()
[docs] def get_property(self, norm_url, name, environ=None): _logger.debug(f"get_property({norm_url}, {name})") self._lock.acquire_read() try: if not self._loaded: self._lazy_open() if norm_url not in self._dict: return None # TODO: sometimes we get exceptions here: (catch or otherwise make # more robust?) try: resourceprops = self._dict[norm_url] except Exception as e: _logger.exception(f"get_property({norm_url}, {name}) failed : {e}") raise return resourceprops.get(name) finally: self._lock.release()
[docs] def write_property( self, norm_url, name, property_value, dry_run=False, environ=None ): assert norm_url and norm_url.startswith("/") assert name # and name.startswith("{") assert property_value is not None _logger.debug( f"write_property({norm_url}, {name}, dry_run={dry_run}):\n\t{property_value}" ) if dry_run: return # TODO: can we check anything here? self._lock.acquire_write() try: if not self._loaded: self._lazy_open() if norm_url in self._dict: locatordict = self._dict[norm_url] else: locatordict = {} # dict([]) locatordict[name] = property_value # This re-assignment is important, so Shelve realizes the change: self._dict[norm_url] = locatordict self._sync() if __debug__ and self._verbose >= 4: self._check() finally: self._lock.release()
[docs] def remove_property(self, norm_url, name, dry_run=False, environ=None): """ Specifying the removal of a property that does not exist is NOT an error. """ _logger.debug(f"remove_property({norm_url}, {name}, dry_run={dry_run})") if dry_run: # TODO: can we check anything here? return self._lock.acquire_write() try: if not self._loaded: self._lazy_open() if norm_url in self._dict: locatordict = self._dict[norm_url] if name in locatordict: del locatordict[name] # This re-assignment is important, so Shelve realizes the # change: self._dict[norm_url] = locatordict self._sync() if __debug__ and self._verbose >= 4: self._check() finally: self._lock.release()
[docs] def remove_properties(self, norm_url, environ=None): _logger.debug(f"remove_properties({norm_url})") self._lock.acquire_write() try: if not self._loaded: self._lazy_open() if norm_url in self._dict: del self._dict[norm_url] self._sync() finally: self._lock.release()
[docs] def copy_properties(self, src_url, dest_url, environ=None): _logger.debug(f"copy_properties({src_url}, {dest_url})") self._lock.acquire_write() try: if __debug__ and self._verbose >= 4: self._check() if not self._loaded: self._lazy_open() if src_url in self._dict: self._dict[dest_url] = self._dict[src_url].copy() self._sync() if __debug__ and self._verbose >= 4: self._check("after copy") finally: self._lock.release()
[docs] def move_properties(self, src_url, dest_url, with_children, environ=None): _logger.debug(f"move_properties({src_url}, {dest_url}, {with_children})") self._lock.acquire_write() try: if __debug__ and self._verbose >= 4: self._check() if not self._loaded: self._lazy_open() if with_children: # Move src_url\* for url in list(self._dict.keys()): if util.is_equal_or_child_uri(src_url, url): d = url.replace(src_url, dest_url) self._dict[d] = self._dict[url] del self._dict[url] elif src_url in self._dict: # Move src_url only self._dict[dest_url] = self._dict[src_url] del self._dict[src_url] self._sync() if __debug__ and self._verbose >= 4: self._check("after move") finally: self._lock.release()
# ======================================================================== # ShelvePropertyManager # ========================================================================
[docs] class ShelvePropertyManager(PropertyManager): """ A low performance property manager implementation using shelve """ def __init__(self, storage_path): self._storage_path = os.path.abspath(storage_path) super().__init__() def __repr__(self): return f"ShelvePropertyManager({self._storage_path})" def _lazy_open(self): _logger.debug(f"_lazy_open({self._storage_path})") self._lock.acquire_write() try: # Test again within the critical section if self._loaded: return True # 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._storage_path, writeback=False) self._loaded = True if __debug__ and self._verbose >= 4: self._check("After shelve.open()") self._dump("After shelve.open()") finally: self._lock.release() def _sync(self): """Write persistent dictionary to disc.""" _logger.debug("_sync()") self._lock.acquire_write() # TODO: read access is enough? try: if self._loaded: self._dict.sync() finally: self._lock.release() def _close(self): _logger.debug("_close()") self._lock.acquire_write() try: if self._loaded: self._dict.close() self._dict = None self._loaded = False finally: self._lock.release()
[docs] def clear(self): """Delete all entries.""" self._lock.acquire_write() try: was_closed = self._dict is None if was_closed: self.open() if len(self._dict): self._dict.clear() self._dict.sync() if was_closed: self.close() finally: self._lock.release()