the file may have been removed by another process remove random cache entries if maxentries is reached at a ratio of numentries cullfrequency a value of 0 for cullfrequency means that the entire cache will be purged delete a random selection of entries set the umask because os makedirs doesn t apply the mode argument to intermediatelevel directories convert a key into a cache file path basically this is the root cache path joined with the md5sum of the key and a suffix remove all the cache files take an open cache file f and delete it if it s expired get a list of paths to all the cache files these are all the files in the root cache dir that end on the cachesuffix cache dir can be deleted at any time make some room if necessary the file may have been removed by another process remove random cache entries if max_entries is reached at a ratio of num_entries cull_frequency a value of 0 for cull_frequency means that the entire cache will be purged return early if no culling is required clear the cache when cull_frequency 0 delete a random selection of entries set the umask because os makedirs doesn t apply the mode argument to intermediate level directories convert a key into a cache file path basically this is the root cache path joined with the md5sum of the key and a suffix remove all the cache files take an open cache file f and delete it if it s expired an empty file is considered expired on windows a file has to be closed before deleting get a list of paths to all the cache files these are all the files in the root cache dir that end on the cache_suffix
"File-based cache backend"
import glob
import os
import pickle
import random
import tempfile
import time
import zlib
from hashlib import md5

from django.core.cache.backends.base import DEFAULT_TIMEOUT, BaseCache
from django.core.files import locks
from django.core.files.move import file_move_safe


class FileBasedCache(BaseCache):
    cache_suffix = ".djcache"
    pickle_protocol = pickle.HIGHEST_PROTOCOL

    def __init__(self, dir, params):
        super().__init__(params)
        self._dir = os.path.abspath(dir)
        self._createdir()

    def add(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
        if self.has_key(key, version):
            return False
        self.set(key, value, timeout, version)
        return True

    def get(self, key, default=None, version=None):
        fname = self._key_to_file(key, version)
        try:
            with open(fname, "rb") as f:
                if not self._is_expired(f):
                    return pickle.loads(zlib.decompress(f.read()))
        except FileNotFoundError:
            pass
        return default

    def _write_content(self, file, timeout, value):
        expiry = self.get_backend_timeout(timeout)
        file.write(pickle.dumps(expiry, self.pickle_protocol))
        file.write(zlib.compress(pickle.dumps(value, self.pickle_protocol)))

    def set(self, key, value, timeout=DEFAULT_TIMEOUT, version=None):
        self._createdir()  
        fname = self._key_to_file(key, version)
        self._cull()  
        fd, tmp_path = tempfile.mkstemp(dir=self._dir)
        renamed = False
        try:
            with open(fd, "wb") as f:
                self._write_content(f, timeout, value)
            file_move_safe(tmp_path, fname, allow_overwrite=True)
            renamed = True
        finally:
            if not renamed:
                os.remove(tmp_path)

    def touch(self, key, timeout=DEFAULT_TIMEOUT, version=None):
        try:
            with open(self._key_to_file(key, version), "r+b") as f:
                try:
                    locks.lock(f, locks.LOCK_EX)
                    if self._is_expired(f):
                        return False
                    else:
                        previous_value = pickle.loads(zlib.decompress(f.read()))
                        f.seek(0)
                        self._write_content(f, timeout, previous_value)
                        return True
                finally:
                    locks.unlock(f)
        except FileNotFoundError:
            return False

    def delete(self, key, version=None):
        return self._delete(self._key_to_file(key, version))

    def _delete(self, fname):
        if not fname.startswith(self._dir) or not os.path.exists(fname):
            return False
        try:
            os.remove(fname)
        except FileNotFoundError:
            
            return False
        return True

    def has_key(self, key, version=None):
        fname = self._key_to_file(key, version)
        try:
            with open(fname, "rb") as f:
                return not self._is_expired(f)
        except FileNotFoundError:
            return False

    def _cull(self):
        
        filelist = self._list_cache_files()
        num_entries = len(filelist)
        if num_entries < self._max_entries:
            return  
        if self._cull_frequency == 0:
            return self.clear()  
        
        filelist = random.sample(filelist, int(num_entries / self._cull_frequency))
        for fname in filelist:
            self._delete(fname)

    def _createdir(self):
        
        
        old_umask = os.umask(0o077)
        try:
            os.makedirs(self._dir, 0o700, exist_ok=True)
        finally:
            os.umask(old_umask)

    def _key_to_file(self, key, version=None):
        
        key = self.make_and_validate_key(key, version=version)
        return os.path.join(
            self._dir,
            "".join(
                [
                    md5(key.encode(), usedforsecurity=False).hexdigest(),
                    self.cache_suffix,
                ]
            ),
        )

    def clear(self):
        
        for fname in self._list_cache_files():
            self._delete(fname)

    def _is_expired(self, f):
        
        try:
            exp = pickle.load(f)
        except EOFError:
            exp = 0  
        if exp is not None and exp < time.time():
            f.close()  
            self._delete(f.name)
            return True
        return False

    def _list_cache_files(self):
        
        return [
            os.path.join(self._dir, fname)
            for fname in glob.glob1(self._dir, "*%s" % self.cache_suffix)
        ]