cache.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. # -*- coding: utf-8 -*-
  2. """
  3. cache module
  4. 文件缓存
  5. """
  6. from logging import getLogger, Logger # noqa: F401
  7. from os import path, stat
  8. from json import load, dump
  9. from tempfile import gettempdir
  10. from time import time
  11. class Cache(dict):
  12. """
  13. using file to Cache data as dictionary
  14. """
  15. def __init__(self, path, logger=None, sync=False):
  16. # type: (str, Logger | None, bool) -> None
  17. super(Cache, self).__init__()
  18. self.__filename = path
  19. self.__sync = sync
  20. self.__time = time()
  21. self.__changed = False
  22. self.__logger = (logger or getLogger()).getChild("Cache")
  23. self.load()
  24. @property
  25. def time(self):
  26. """
  27. 缓存修改时间
  28. """
  29. return self.__time or 0
  30. def load(self, file=None):
  31. """
  32. load data from path
  33. """
  34. if not file:
  35. file = self.__filename
  36. self.__logger.debug("load cache data from %s", file)
  37. if file:
  38. try:
  39. with open(file, "r") as data:
  40. loaded_data = load(data)
  41. self.clear()
  42. self.update(loaded_data)
  43. self.__time = stat(file).st_mtime
  44. return self
  45. except (IOError, OSError):
  46. self.__logger.info("cache file not exist or cannot be opened")
  47. except ValueError:
  48. pass
  49. except Exception as e:
  50. self.__logger.warning(e)
  51. else:
  52. self.__logger.info("cache file not exist")
  53. self.clear()
  54. self.__time = time()
  55. self.__changed = True
  56. return self
  57. def sync(self):
  58. """Sync the write buffer with the cache files and clear the buffer."""
  59. if self.__changed and self.__filename:
  60. with open(self.__filename, "w") as data:
  61. # 只保存非私有字段(不以__开头的字段)
  62. filtered_data = {k: v for k, v in super(Cache, self).items() if not k.startswith("__")}
  63. dump(filtered_data, data, separators=(",", ":"))
  64. self.__logger.debug("save cache data to %s", self.__filename)
  65. self.__time = time()
  66. self.__changed = False
  67. return self
  68. def close(self):
  69. """Sync the write buffer, then close the cache.
  70. If a closed :class:`FileCache` object's methods are called, a
  71. :exc:`ValueError` will be raised.
  72. """
  73. if self.__filename:
  74. self.sync()
  75. self.__filename = None
  76. self.__time = None
  77. self.__sync = False
  78. def __update(self):
  79. self.__changed = True
  80. if self.__sync:
  81. self.sync()
  82. else:
  83. self.__time = time()
  84. def clear(self):
  85. # 只清除非私有字段(不以__开头的字段)
  86. keys_to_remove = [key for key in super(Cache, self).keys() if not key.startswith("__")]
  87. if keys_to_remove:
  88. for key in keys_to_remove:
  89. super(Cache, self).__delitem__(key)
  90. self.__update()
  91. def get(self, key, default=None):
  92. """
  93. 获取指定键的值,如果键不存在则返回默认值
  94. :param key: 键
  95. :param default: 默认值
  96. :return: 键对应的值或默认值
  97. """
  98. if key is None and default is None:
  99. return {k: v for k, v in super(Cache, self).items() if not k.startswith("__")}
  100. return super(Cache, self).get(key, default)
  101. def __setitem__(self, key, value):
  102. if self.get(key) != value:
  103. super(Cache, self).__setitem__(key, value)
  104. # 私有字段(以__开头)不触发同步
  105. if not key.startswith("__"):
  106. self.__update()
  107. def __delitem__(self, key):
  108. # 检查键是否存在,如果不存在则直接返回,不抛错
  109. if not super(Cache, self).__contains__(key):
  110. return
  111. super(Cache, self).__delitem__(key)
  112. # 私有字段(以__开头)不触发同步
  113. if not key.startswith("__"):
  114. self.__update()
  115. def __getitem__(self, key):
  116. return super(Cache, self).__getitem__(key)
  117. def __iter__(self):
  118. # 只迭代非私有字段(不以__开头的字段)
  119. for key in super(Cache, self).__iter__():
  120. if not key.startswith("__"):
  121. yield key
  122. def __items__(self):
  123. # 只返回非私有字段(不以__开头的字段)
  124. return ((key, value) for key, value in super(Cache, self).items() if not key.startswith("__"))
  125. def __len__(self):
  126. # 不计算以__开头的私有字段
  127. return len([key for key in super(Cache, self).keys() if not key.startswith("__")])
  128. def __contains__(self, key):
  129. return super(Cache, self).__contains__(key)
  130. def __str__(self):
  131. return super(Cache, self).__str__()
  132. def __del__(self):
  133. self.close()
  134. @staticmethod
  135. def new(config_cache, hash, logger):
  136. # type: (str|bool, str, Logger) -> Cache|None
  137. """
  138. new cache from a file path.
  139. :param path: Path to the cache file.
  140. :param logger: Optional logger for debug messages.
  141. :return: Cache instance with loaded data.
  142. """
  143. if config_cache is False:
  144. cache = None
  145. elif config_cache is True:
  146. cache_path = path.join(gettempdir(), "ddns.%s.cache" % hash)
  147. cache = Cache(cache_path, logger)
  148. else:
  149. cache = Cache(config_cache, logger)
  150. if cache is None:
  151. logger.debug("Cache is disabled!")
  152. elif cache.time + 72 * 3600 < time(): # 72小时有效期
  153. logger.info("Cache file is outdated.")
  154. cache.clear()
  155. elif len(cache) == 0:
  156. logger.debug("Cache is empty.")
  157. else:
  158. logger.debug("Cache loaded with %d entries.", len(cache))
  159. return cache