diff --git a/slixmpp/util/__init__.py b/slixmpp/util/__init__.py index 8f70b2bb..ecf118d7 100644 --- a/slixmpp/util/__init__.py +++ b/slixmpp/util/__init__.py @@ -13,3 +13,5 @@ from slixmpp.util.misc_ops import bytes, unicode, hashes, hash, \ num_to_bytes, bytes_to_num, quote, \ XOR +from slixmpp.util.cache import MemoryCache, MemoryPerJidCache, \ + FileSystemCache, FileSystemPerJidCache diff --git a/slixmpp/util/cache.py b/slixmpp/util/cache.py new file mode 100644 index 00000000..059eca5b --- /dev/null +++ b/slixmpp/util/cache.py @@ -0,0 +1,105 @@ +""" + Slixmpp: The Slick XMPP Library + Copyright (C) 2018 Emmanuel Gil Peyrot + This file is part of Slixmpp. + + See the file LICENSE for copying permission. +""" + +import os +import logging + +log = logging.getLogger(__name__) + +class Cache: + def retrieve(self, key): + raise NotImplementedError + + def store(self, key, value): + raise NotImplementedError + +class PerJidCache: + def retrieve_by_jid(self, jid, key): + raise NotImplementedError + + def store_by_jid(self, jid, key, value): + raise NotImplementedError + +class MemoryCache(Cache): + def __init__(self): + self.cache = {} + + def retrieve(self, key): + return self.cache.get(key, None) + + def store(self, key, value): + self.cache[key] = value + return True + +class MemoryPerJidCache(PerJidCache): + def __init__(self): + self.cache = {} + + def retrieve_by_jid(self, jid, key): + cache = self.cache.get(jid.bare, None) + if cache is None: + return None + return cache.get(key, None) + + def store_by_jid(self, jid, key, value): + cache = self.cache.setdefault(jid.bare, {}) + cache[key] = value + return True + +class FileSystemStorage: + def __init__(self, encode, decode, binary): + self.encode = encode + self.decode = decode + self.read = 'rb' if binary else 'r' + self.write = 'wb' if binary else 'w' + + def _retrieve(self, directory, key): + filename = os.path.join(directory, key.replace('/', '_')) + try: + with open(filename, self.read) as cache_file: + return self.decode(cache_file.read()) + except FileNotFoundError: + log.debug('%s not present in cache', key) + except OSError: + log.debug('Failed to read %s from cache:', key, exc_info=True) + return None + + def _store(self, directory, key, value): + filename = os.path.join(directory, key.replace('/', '_')) + try: + os.makedirs(directory, exist_ok=True) + with open(filename, self.write) as output: + output.write(self.encode(value)) + return True + except OSError: + log.debug('Failed to store %s to cache:', key, exc_info=True) + return False + +class FileSystemCache(Cache, FileSystemStorage): + def __init__(self, directory, cache_type, *, encode, decode, binary=False): + FileSystemStorage.__init__(self, encode, decode, binary) + self.base_dir = os.path.join(directory, cache_type) + + def retrieve(self, key): + return self._retrieve(self.base_dir, key) + + def store(self, key, value): + return self._store(self.base_dir, key, value) + +class FileSystemPerJidCache(PerJidCache, FileSystemStorage): + def __init__(self, directory, cache_type, *, encode, decode, binary=False): + FileSystemStorage.__init__(self, encode, decode, binary) + self.base_dir = os.path.join(directory, cache_type) + + def retrieve_by_jid(self, jid, key): + directory = os.path.join(self.base_dir, jid.bare) + return self._retrieve(directory, key) + + def store_by_jid(self, jid, key, value): + directory = os.path.join(self.base_dir, jid.bare) + return self._store(directory, key, value)