Ticket #6099: filebased_md5.patch

File filebased_md5.patch, 7.1 KB (added by sherbang, 8 years ago)
  • django/core/cache/backends/filebased.py

    diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py
    index 72cb877..1464901 100644
    a b  
    11"File-based cache backend"
    22
     3import md5
    34import os, time
    45try:
    56    import cPickle as pickle
    67except ImportError:
    78    import pickle
    89from django.core.cache.backends.base import BaseCache
    9 from django.utils.http import urlquote_plus
     10
     11class EntryCount:
     12    #TODO: I assume it would be a worthwhile performance improvement (on large caches)
     13    #to keep a count in a file, and use that here instead of recounting everytime.
     14    #
     15    #The hooks are all in place below to allow that just implement
     16    #increment/decrement/__int__ here, as you see fit.
     17    def __init__(self, dir):
     18        self._dir = dir
     19       
     20    def increment(self):
     21        pass
     22    def decrement(self):
     23        pass
     24    def __int__(self):
     25        count = 0
     26        for _,_,files in os.walk(self._dir):
     27            count += len(files)
     28        return count
     29           
    1030
    1131class CacheClass(BaseCache):
    1232    def __init__(self, dir, params):
    class CacheClass(BaseCache): 
    2747        self._dir = dir
    2848        if not os.path.exists(self._dir):
    2949            self._createdir()
     50           
     51        self._num_entries = EntryCount(self._dir)
    3052
    3153    def add(self, key, value, timeout=None):
    32         fname = self._key_to_file(key)
    33         if timeout is None:
    34             timeout = self.default_timeout
    35         try:
    36             filelist = os.listdir(self._dir)
    37         except (IOError, OSError):
    38             self._createdir()
    39             filelist = []
    40         if len(filelist) > self._max_entries:
    41             self._cull(filelist)
    42         if os.path.basename(fname) not in filelist:
    43             try:
    44                 f = open(fname, 'wb')
    45                 now = time.time()
    46                 pickle.dump(now + timeout, f, 2)
    47                 pickle.dump(value, f, 2)
    48             except (IOError, OSError):
    49                 pass
     54        if self.has_key(key):
     55            return None
     56       
     57        self.set(key, value, timeout)
    5058
    5159    def get(self, key, default=None):
    5260        fname = self._key_to_file(key)
    class CacheClass(BaseCache): 
    5664            now = time.time()
    5765            if exp < now:
    5866                f.close()
    59                 os.remove(fname)
     67                self._delete(fname)
    6068            else:
    6169                return pickle.load(f)
    6270        except (IOError, OSError, EOFError, pickle.PickleError):
    class CacheClass(BaseCache): 
    6573
    6674    def set(self, key, value, timeout=None):
    6775        fname = self._key_to_file(key)
     76        dir = os.path.dirname(fname)
     77       
    6878        if timeout is None:
    6979            timeout = self.default_timeout
     80           
     81        self._cull()
     82       
    7083        try:
    71             filelist = os.listdir(self._dir)
    72         except (IOError, OSError):
    73             self._createdir()
    74             filelist = []
    75         if len(filelist) > self._max_entries:
    76             self._cull(filelist)
    77         try:
     84            if not os.path.exists(dir):
     85                os.makedirs(dir)
     86
    7887            f = open(fname, 'wb')
    7988            now = time.time()
    8089            pickle.dump(now + timeout, f, 2)
    8190            pickle.dump(value, f, 2)
     91            self._num_entries.increment()
    8292        except (IOError, OSError):
    8393            pass
    8494
    8595    def delete(self, key):
    8696        try:
    87             os.remove(self._key_to_file(key))
     97            self._delete(self._key_to_file(key))
    8898        except (IOError, OSError):
    8999            pass
    90100
     101    def _delete(self, file):
     102        os.remove(file)
     103        self._num_entries.decrement()
     104        try:
     105            #remove the 2 subdirs if they're empty
     106            dir = os.path.dirname(file)
     107            os.rmdir(dir)
     108            os.rmdir(os.path.dirname(dir))
     109        except:
     110            pass
     111
    91112    def has_key(self, key):
    92         return os.path.exists(self._key_to_file(key))
     113        fname = self._key_to_file(key)
     114        try:
     115            f = open(fname, 'rb')
     116            exp = pickle.load(f)
     117            now = time.time()
     118            if exp < now:
     119                f.close()
     120                self._delete(fname)
     121                return False
     122            else:
     123                return True
     124        except (IOError, OSError, EOFError, pickle.PickleError):
     125            return False
    93126
    94     def _cull(self, filelist):
     127    def _cull(self):
     128        if int(self._num_entries) < self._max_entries:
     129            return
     130       
     131        try:
     132            filelist = os.listdir(self._dir)
     133        except (IOError, OSError):
     134            return
     135       
    95136        if self._cull_frequency == 0:
    96137            doomed = filelist
    97138        else:
    98             doomed = [k for (i, k) in enumerate(filelist) if i % self._cull_frequency == 0]
    99         for fname in doomed:
     139            doomed = [os.path.join(self._dir, k) for (i, k) in enumerate(filelist) if i % self._cull_frequency == 0]
     140
     141        for topdir in doomed:
    100142            try:
    101                 os.remove(os.path.join(self._dir, fname))
     143                for root, _, files in os.walk(topdir):
     144                    for file in files:
     145                        self._delete(os.path.join(root,file))
    102146            except (IOError, OSError):
    103147                pass
    104 
     148           
    105149    def _createdir(self):
    106150        try:
    107151            os.makedirs(self._dir)
    class CacheClass(BaseCache): 
    109153            raise EnvironmentError, "Cache directory '%s' does not exist and could not be created'" % self._dir
    110154
    111155    def _key_to_file(self, key):
    112         return os.path.join(self._dir, urlquote_plus(key))
     156        path = md5.new(key.encode('utf-8')).hexdigest()
     157        path = os.path.join(path[:2], path[2:4], path[4:])
     158        return os.path.join(self._dir, path)
  • tests/regressiontests/cache/tests.py

    diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
    index 9ac2722..c6b8742 100644
    a b class Cache(unittest.TestCase): 
    2424
    2525    def test_add(self):
    2626        # test add (only add if key isn't already in cache)
    27         cache.add("addkey1", "value")
     27        cache.set("addkey1", "value")
    2828        cache.add("addkey1", "newvalue")
    2929        self.assertEqual(cache.get("addkey1"), "value")
    30 
     30       
    3131    def test_non_existent(self):
    3232        # get with non-existent keys
    3333        self.assertEqual(cache.get("does_not_exist"), None)
    class Cache(unittest.TestCase): 
    7777
    7878    def test_expiration(self):
    7979        # expiration
    80         cache.set('expire', 'very quickly', 1)
     80        cache.set('expire1', 'very quickly', 1)
     81        cache.set('expire2', 'very quickly', 1)
     82        cache.set('expire3', 'very quickly', 1)
    8183        time.sleep(2)
    82         self.assertEqual(cache.get("expire"), None)
     84       
     85        self.assertEqual(cache.get("expire1"), None)
     86       
     87        cache.add("expire2", "newvalue")
     88        self.assertEqual(cache.get("expire2"), "newvalue")
     89   
     90        self.assertEqual(cache.has_key("expire3"), False)
    8391
    8492    def test_unicode(self):
    8593        stuff = {
Back to Top