1# -*- coding: utf-8 -*- 2 3# Copyright (c) 2017 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> 4# 5 6""" 7Module implementing classes used for caching objects. 8""" 9 10from PyQt5.QtCore import QDateTime, QTimer 11 12 13class E5Cache: 14 """ 15 Class implementing a LRU cache of a specific size. 16 17 If the maximum number of entries is exceeded, the least recently used item 18 is removed from the cache. A cache hit moves the entry to the front of the 19 cache. 20 """ 21 def __init__(self, size=100): 22 """ 23 Constructor 24 25 @param size maximum number of entries that may be stored in the cache 26 @type int 27 @exception ValueError raised to indicate an illegal 'size' parameter 28 """ 29 if size < 0: 30 raise ValueError("'size' parameter must be positive.") 31 32 self.__size = size 33 34 # internal objects 35 self.__keyList = [] 36 self.__store = {} # stores the cache entries 37 self.__accesStore = {} # stores the last access date and times 38 self.__hits = 0 39 self.__misses = 0 40 self.__maxsize = 0 41 self.__maxCacheTime = 0 # 0 seconds means aging is disabled 42 43 self.__cacheTimer = QTimer() 44 self.__cacheTimer.setSingleShot(True) 45 self.__cacheTimer.timeout.connect(self.__pruneCache) 46 47 def __moveLast(self, key): 48 """ 49 Private method to move a cached item to the MRU position. 50 51 @param key key of the item to be retrieved 52 @type any hashable type that can be used as a dict key 53 """ 54 self.__keyList.remove(key) 55 self.__keyList.append(key) 56 57 def __adjustToSize(self): 58 """ 59 Private method to adjust the cache to its size. 60 """ 61 if self.__size: 62 removeList = self.__keyList[:-self.__size] 63 self.__keyList = self.__keyList[-self.__size:] 64 for key in removeList: 65 del self.__store[key] 66 del self.__accesStore[key] 67 else: 68 self.reset() 69 70 def getSize(self): 71 """ 72 Public method to get the maximum size of the cache. 73 74 @return maximum number of entries of the cache 75 @rtype int 76 """ 77 return self.__size 78 79 def setSize(self, newSize): 80 """ 81 Public method to change the maximum size of the cache. 82 83 @param newSize maximum number of entries that may be stored in the 84 cache 85 @type int 86 """ 87 if newSize >= 0: 88 self.__size = newSize 89 self.__adjustToSize() 90 91 def getMaximumCacheTime(self): 92 """ 93 Public method to get the maximum time entries may exist in the cache. 94 95 @return maximum cache time in seconds 96 @rtype int 97 """ 98 return self.__maxCacheTime 99 100 def setMaximumCacheTime(self, time): 101 """ 102 Public method to set the maximum time entries may exist in the cache. 103 104 @param time maximum cache time in seconds 105 @type int 106 """ 107 if time != self.__maxCacheTime: 108 self.__cacheTimer.stop() 109 self.__pruneCache() 110 self.__maxCacheTime = time 111 if self.__maxCacheTime > 0: 112 self.__cacheTimer.setInterval(self.__maxCacheTime * 1000) 113 self.__cacheTimer.start() 114 115 def get(self, key): 116 """ 117 Public method to get an entry from the cache given its key. 118 119 If the key is present in the cache, it is moved to the MRU position. 120 121 @param key key of the item to be retrieved 122 @type any hashable type that can be used as a dict key 123 @return cached item for the given key or None, if the key is not 124 present 125 @rtype object or None 126 """ 127 if key in self.__store: 128 self.__hits += 1 129 self.__moveLast(key) 130 self.__accesStore[key] = QDateTime.currentDateTimeUtc() 131 return self.__store[key] 132 else: 133 self.__misses += 1 134 return None 135 136 def add(self, key, item): 137 """ 138 Public method to add an item to the cache. 139 140 If the key is already in use, the cached item is replaced by the new 141 one given and is moved to the MRU position 142 143 @param key key of the item to be retrieved 144 @type any hashable type that can be used as a dict key 145 @param item item to be cached under the given key 146 @type object 147 """ 148 if key in self.__store: 149 self.__moveLast(key) 150 else: 151 self.__keyList.append(key) 152 self.__store[key] = item 153 self.__accesStore[key] = QDateTime.currentDateTimeUtc() 154 155 self.__adjustToSize() 156 157 self.__maxsize = max(self.__maxsize, len(self.__keyList)) 158 159 def remove(self, key): 160 """ 161 Public method to remove an item from the cache. 162 163 @param key key of the item to be retrieved 164 @type any hashable type that can be used as a dict key 165 """ 166 if key in self.__store: 167 del self.__store[key] 168 del self.__accesStore[key] 169 self.__keyList.remove(key) 170 171 def clear(self): 172 """ 173 Public method to clear the cache. 174 """ 175 self.__keyList = [] 176 self.__store = {} 177 self.__accesStore = {} 178 179 def reset(self): 180 """ 181 Public method to reset the cache. 182 183 This is like clear() but sets the various counters to their initial 184 value as well. 185 """ 186 self.clear() 187 self.__hits = 0 188 self.__misses = 0 189 self.__maxsize = 0 190 191 def length(self): 192 """ 193 Public method to get the current length of the cache. 194 195 @return current length of the cache 196 @rtype int 197 """ 198 return len(self.__keyList) 199 200 def info(self): 201 """ 202 Public method to get some information about the cache. 203 204 @return dictionary containing the cache info 205 @rtype dict (with keys "hits", "misses", "maxsize", "currsize") 206 """ 207 return { 208 "hits": self.__hits, 209 "misses": self.__misses, 210 "maxsize": self.__maxsize, 211 "currsize": self.length(), 212 } 213 214 def __pruneCache(self): 215 """ 216 Private slot to prune outdated cache entries and restart the timer. 217 """ 218 if self.__maxCacheTime > 0: 219 current = QDateTime.currentDateTimeUtc() 220 221 keysToBeDeleted = [] 222 for key, lastAccessTime in self.__accesStore.items(): 223 if lastAccessTime.secsTo(current) > self.__maxCacheTime: 224 keysToBeDeleted.append(key) 225 for key in keysToBeDeleted: 226 self.remove(key) 227 228 self.__cacheTimer.start() 229