增加头像缓存旧文件清理机制
This commit is contained in:
@@ -147,6 +147,44 @@ class ContactManager:
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
self._logger.warning(f"保存头像缓存索引失败: {exc}")
|
self._logger.warning(f"保存头像缓存索引失败: {exc}")
|
||||||
|
|
||||||
|
def _delete_avatar_file_by_name(self, file_name: str) -> None:
|
||||||
|
"""按文件名删除缓存头像,删除失败时只记日志不打断主流程。"""
|
||||||
|
file_name = str(file_name or "").strip()
|
||||||
|
if not file_name or not self._avatar_cache_dir:
|
||||||
|
return
|
||||||
|
target_path = self._avatar_cache_dir / file_name
|
||||||
|
if not target_path.exists():
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
target_path.unlink()
|
||||||
|
except Exception as exc:
|
||||||
|
self._logger.warning(f"删除旧头像缓存失败 file={target_path}: {exc}")
|
||||||
|
|
||||||
|
def _cleanup_avatar_cache_files(self) -> None:
|
||||||
|
"""清理缓存目录里的孤儿头像文件与残留临时文件。"""
|
||||||
|
if not self._avatar_cache_dir:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 允许保留 manifest 自身,其余不在当前索引里的文件一律视为孤儿文件。
|
||||||
|
referenced_files = {
|
||||||
|
str(meta.get("file_name") or "").strip()
|
||||||
|
for meta in self._avatar_manifest.values()
|
||||||
|
if str(meta.get("file_name") or "").strip()
|
||||||
|
}
|
||||||
|
manifest_name = self._avatar_manifest_path.name if self._avatar_manifest_path else ""
|
||||||
|
|
||||||
|
for file_path in self._avatar_cache_dir.iterdir():
|
||||||
|
if not file_path.is_file():
|
||||||
|
continue
|
||||||
|
if file_path.name == manifest_name:
|
||||||
|
continue
|
||||||
|
# `.tmp` 文件说明上次下载未完整落盘,直接清掉,避免长时间堆积。
|
||||||
|
if file_path.suffix.lower() == ".tmp":
|
||||||
|
self._delete_avatar_file_by_name(file_path.name)
|
||||||
|
continue
|
||||||
|
if file_path.name not in referenced_files:
|
||||||
|
self._delete_avatar_file_by_name(file_path.name)
|
||||||
|
|
||||||
def _guess_avatar_extension(self, avatar_url: str, content_type: str) -> str:
|
def _guess_avatar_extension(self, avatar_url: str, content_type: str) -> str:
|
||||||
"""推断头像文件扩展名,尽量保留真实图片类型。"""
|
"""推断头像文件扩展名,尽量保留真实图片类型。"""
|
||||||
guessed_from_type = mimetypes.guess_extension((content_type or "").split(";")[0].strip()) or ""
|
guessed_from_type = mimetypes.guess_extension((content_type or "").split(";")[0].strip()) or ""
|
||||||
@@ -211,6 +249,7 @@ class ContactManager:
|
|||||||
if not remote_url:
|
if not remote_url:
|
||||||
continue
|
continue
|
||||||
manifest_item = self._avatar_manifest.get(wxid, {})
|
manifest_item = self._avatar_manifest.get(wxid, {})
|
||||||
|
old_file_name = str(manifest_item.get("file_name") or "").strip()
|
||||||
cached_path = self.get_cached_head_image_path(wxid)
|
cached_path = self.get_cached_head_image_path(wxid)
|
||||||
# 只有“URL 变了”或“本地文件丢了”才重新下载,避免刷新通讯录时重复打远端。
|
# 只有“URL 变了”或“本地文件丢了”才重新下载,避免刷新通讯录时重复打远端。
|
||||||
if manifest_item.get("remote_url") == remote_url and cached_path:
|
if manifest_item.get("remote_url") == remote_url and cached_path:
|
||||||
@@ -222,16 +261,23 @@ class ContactManager:
|
|||||||
"file_name": Path(downloaded_path).name,
|
"file_name": Path(downloaded_path).name,
|
||||||
"remote_url": remote_url,
|
"remote_url": remote_url,
|
||||||
}
|
}
|
||||||
|
# 同一联系人头像地址变化后,旧文件已经失去引用,这里立刻删掉旧版本。
|
||||||
|
if old_file_name and old_file_name != Path(downloaded_path).name:
|
||||||
|
self._delete_avatar_file_by_name(old_file_name)
|
||||||
manifest_changed = True
|
manifest_changed = True
|
||||||
|
|
||||||
# 把已经不在通讯录中的头像记录清理掉,避免 manifest 无限增长。
|
# 把已经不在通讯录中的头像记录清理掉,避免 manifest 无限增长。
|
||||||
removed_wxids = [wxid for wxid in self._avatar_manifest.keys() if wxid not in self._head_images]
|
removed_wxids = [wxid for wxid in self._avatar_manifest.keys() if wxid not in self._head_images]
|
||||||
for wxid in removed_wxids:
|
for wxid in removed_wxids:
|
||||||
self._avatar_manifest.pop(wxid, None)
|
removed_meta = self._avatar_manifest.pop(wxid, None) or {}
|
||||||
|
self._delete_avatar_file_by_name(str(removed_meta.get("file_name") or "").strip())
|
||||||
manifest_changed = True
|
manifest_changed = True
|
||||||
|
|
||||||
if manifest_changed:
|
if manifest_changed:
|
||||||
self._save_avatar_manifest()
|
self._save_avatar_manifest()
|
||||||
|
# 无论本轮 manifest 是否有变化,都顺手做一次目录对账,
|
||||||
|
# 保证历史异常中断留下的孤儿文件也能逐步被回收。
|
||||||
|
self._cleanup_avatar_cache_files()
|
||||||
|
|
||||||
def get_cached_head_image_path(self, wxid: str) -> str:
|
def get_cached_head_image_path(self, wxid: str) -> str:
|
||||||
"""返回头像缓存本地路径,若缓存不存在则返回空字符串。"""
|
"""返回头像缓存本地路径,若缓存不存在则返回空字符串。"""
|
||||||
@@ -258,6 +304,7 @@ class ContactManager:
|
|||||||
with self._avatar_cache_lock:
|
with self._avatar_cache_lock:
|
||||||
# 双重检查避免高并发下重复下载同一张头像。
|
# 双重检查避免高并发下重复下载同一张头像。
|
||||||
manifest_item = self._avatar_manifest.get(wxid) or {}
|
manifest_item = self._avatar_manifest.get(wxid) or {}
|
||||||
|
old_file_name = str(manifest_item.get("file_name") or "").strip()
|
||||||
cached_path = self.get_cached_head_image_path(wxid)
|
cached_path = self.get_cached_head_image_path(wxid)
|
||||||
if cached_path and manifest_item.get("remote_url") == remote_url:
|
if cached_path and manifest_item.get("remote_url") == remote_url:
|
||||||
return cached_path
|
return cached_path
|
||||||
@@ -268,7 +315,11 @@ class ContactManager:
|
|||||||
"file_name": Path(downloaded_path).name,
|
"file_name": Path(downloaded_path).name,
|
||||||
"remote_url": remote_url,
|
"remote_url": remote_url,
|
||||||
}
|
}
|
||||||
|
# 单头像补下载时同样清掉旧版本,避免访问链路把目录越堆越大。
|
||||||
|
if old_file_name and old_file_name != Path(downloaded_path).name:
|
||||||
|
self._delete_avatar_file_by_name(old_file_name)
|
||||||
self._save_avatar_manifest()
|
self._save_avatar_manifest()
|
||||||
|
self._cleanup_avatar_cache_files()
|
||||||
return downloaded_path
|
return downloaded_path
|
||||||
|
|
||||||
def get_head_image_version(self, wxid: str) -> str:
|
def get_head_image_version(self, wxid: str) -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user