3253b221 by 柴进

:bug: 干掉 HistoryManager 内部两条 O(N) 全扫 hot path (Mac 仍闪退根因)

第二阶段发布后用户日志显示: 启动 8s 后 5s 内连续 5 次
load_history_index 全扫 513 条 + 513 次 stat, 主线程累计阻塞,
随后日志戛然而止 (典型 jetsam SIGKILL 指纹).

定位到两条阶段 1+2 没修的 hot path:

1. get_history_item(timestamp): 点击历史项查看详情时触发,
   原走 load_history_index() + 线性查找. 改为直接调
   load_history_item_fast() (读单个 metadata.json), O(1).

2. _update_history_index(history_item): 每次生成完图都调,
   原走 load_history_index() 全扫 + from_dict + 路径修正 + 整表写回.
   改为直接对 raw json list dict 操作, 无 stat / 无 from_dict.

改完之后 hot path 完全不再触发 _fix_history_path 循环.
1 parent 57047eb9
...@@ -1006,19 +1006,14 @@ class HistoryManager: ...@@ -1006,19 +1006,14 @@ class HistoryManager:
1006 return [] 1006 return []
1007 1007
1008 def get_history_item(self, timestamp: str) -> Optional[HistoryItem]: 1008 def get_history_item(self, timestamp: str) -> Optional[HistoryItem]:
1009 """获取指定时间戳的历史记录项 1009 """获取指定时间戳的历史记录项
1010 1010
1011 Args: 1011 以前实现走 load_history_index() 全扫 + 路径修正,每次 O(N) + N 次 stat。
1012 timestamp: 时间戳 1012 在 Mac 用户 513 条历史的环境下,点击历史项查看详情会触发主线程
1013 1013 阻塞 ~60ms; 连续点击会累积内存峰值并触发 jetsam SIGKILL。
1014 Returns: 1014 现改为直接读 {timestamp}/metadata.json + 同目录扫描,O(1)。
1015 历史记录项,如果不存在则返回None
1016 """ 1015 """
1017 history_items = self.load_history_index() 1016 return self.load_history_item_fast(timestamp)
1018 for item in history_items:
1019 if item.timestamp == timestamp:
1020 return item
1021 return None
1022 1017
1023 def load_history_item_fast(self, timestamp: str) -> Optional[HistoryItem]: 1018 def load_history_item_fast(self, timestamp: str) -> Optional[HistoryItem]:
1024 """轻量读取单条历史记录:直接从 {timestamp}/metadata.json + 文件扫描。 1019 """轻量读取单条历史记录:直接从 {timestamp}/metadata.json + 文件扫描。
...@@ -1082,18 +1077,35 @@ class HistoryManager: ...@@ -1082,18 +1077,35 @@ class HistoryManager:
1082 return False 1077 return False
1083 1078
1084 def _update_history_index(self, history_item: HistoryItem): 1079 def _update_history_index(self, history_item: HistoryItem):
1085 """更新历史记录索引 1080 """更新历史记录索引(每次生成完图片都会调用,hot path)。
1086 1081
1087 Args: 1082 老实现走 load_history_index() 全扫 + 路径修正 + N 次 stat,再
1088 history_item: 要添加的历史记录项 1083 整个 list 反序列化 + 重新写回。513 条历史时主线程阻塞 ~60ms,
1084 Mac 上累计触发 jetsam SIGKILL。
1085 现改为直接对 raw json 列表 dict 操作:读->过滤->插首位->写。
1086 无 stat、无 from_dict、无路径修正。
1089 """ 1087 """
1090 history_items = self.load_history_index() 1088 try:
1089 if self.history_index_file.exists():
1090 with open(self.history_index_file, 'r', encoding='utf-8') as f:
1091 raw = json.load(f)
1092 if not isinstance(raw, list):
1093 raw = []
1094 else:
1095 raw = []
1096 except Exception as e:
1097 self.logger.error(f"_update_history_index 读取索引失败: {e}")
1098 raw = []
1091 1099
1092 # 检查是否已存在相同时间戳的记录,如果存在则替换 1100 new_ts = history_item.timestamp
1093 history_items = [item for item in history_items if item.timestamp != history_item.timestamp] 1101 raw = [d for d in raw if isinstance(d, dict) and d.get('timestamp') != new_ts]
1094 history_items.insert(0, history_item) # 插入到开头 1102 raw.insert(0, history_item.to_dict())
1095 1103
1096 self._save_history_index(history_items) 1104 try:
1105 with open(self.history_index_file, 'w', encoding='utf-8') as f:
1106 json.dump(raw, f, ensure_ascii=False, indent=2)
1107 except Exception as e:
1108 self.logger.error(f"_update_history_index 写入索引失败: {e}")
1097 1109
1098 def _save_history_index(self, history_items: List[HistoryItem]): 1110 def _save_history_index(self, history_items: List[HistoryItem]):
1099 """保存历史记录索引到文件 1111 """保存历史记录索引到文件
......