11import json
22import os
3+ import tempfile
4+ import shutil
35from abc import ABC , abstractmethod
46from typing import Any
57
@@ -524,6 +526,7 @@ async def retriever(query_text):
524526 def _load_metadata (self ):
525527 """加载元数据"""
526528 meta_file = os .path .join (self .work_dir , f"metadata_{ self .kb_type } .json" )
529+
527530 if os .path .exists (meta_file ):
528531 try :
529532 with open (meta_file , encoding = "utf-8" ) as f :
@@ -533,19 +536,74 @@ def _load_metadata(self):
533536 logger .info (f"Loaded { self .kb_type } metadata for { len (self .databases_meta )} databases" )
534537 except Exception as e :
535538 logger .error (f"Failed to load { self .kb_type } metadata: { e } " )
539+ # 尝试从备份恢复
540+ backup_file = f"{ meta_file } .backup"
541+ if os .path .exists (backup_file ):
542+ try :
543+ with open (backup_file , encoding = "utf-8" ) as f :
544+ data = json .load (f )
545+ self .databases_meta = data .get ("databases" , {})
546+ self .files_meta = data .get ("files" , {})
547+ logger .info (f"Loaded { self .kb_type } metadata from backup" )
548+ # 恢复备份文件
549+ shutil .copy2 (backup_file , meta_file )
550+ return
551+ except Exception as backup_e :
552+ logger .error (f"Failed to load backup: { backup_e } " )
553+
554+ # 如果加载失败,初始化为空状态
555+ logger .warning (f"Initializing empty { self .kb_type } metadata" )
556+ self .databases_meta = {}
557+ self .files_meta = {}
558+
559+ def _serialize_metadata (self , obj ):
560+ """递归序列化元数据中的 Pydantic 模型"""
561+ if hasattr (obj , 'dict' ):
562+ return obj .dict ()
563+ elif isinstance (obj , dict ):
564+ return {k : self ._serialize_metadata (v ) for k , v in obj .items ()}
565+ elif isinstance (obj , list ):
566+ return [self ._serialize_metadata (item ) for item in obj ]
567+ else :
568+ return obj
536569
537570 def _save_metadata (self ):
538571 """保存元数据"""
539572 self ._normalize_metadata_state ()
540573 meta_file = os .path .join (self .work_dir , f"metadata_{ self .kb_type } .json" )
574+ backup_file = f"{ meta_file } .backup"
575+
541576 try :
577+ # 创建简单备份
578+ if os .path .exists (meta_file ):
579+ shutil .copy2 (meta_file , backup_file )
580+
581+ # 准备数据并序列化 Pydantic 模型
542582 data = {
543- "databases" : self .databases_meta ,
544- "files" : self .files_meta ,
583+ "databases" : self ._serialize_metadata ( self . databases_meta ) ,
584+ "files" : self ._serialize_metadata ( self . files_meta ) ,
545585 "kb_type" : self .kb_type ,
546586 "updated_at" : utc_isoformat (),
547587 }
548- with open (meta_file , "w" , encoding = "utf-8" ) as f :
549- json .dump (data , f , ensure_ascii = False , indent = 2 )
588+
589+ # 原子性写入(使用临时文件)
590+ with tempfile .NamedTemporaryFile (
591+ mode = 'w' , dir = os .path .dirname (meta_file ),
592+ prefix = '.tmp_' , suffix = '.json' , delete = False
593+ ) as tmp_file :
594+ json .dump (data , tmp_file , ensure_ascii = False , indent = 2 )
595+ temp_path = tmp_file .name
596+
597+ os .replace (temp_path , meta_file )
598+ logger .debug (f"Saved { self .kb_type } metadata" )
599+
550600 except Exception as e :
551601 logger .error (f"Failed to save { self .kb_type } metadata: { e } " )
602+ # 尝试恢复备份
603+ if os .path .exists (backup_file ):
604+ try :
605+ shutil .copy2 (backup_file , meta_file )
606+ logger .info ("Restored metadata from backup" )
607+ except Exception as restore_e :
608+ logger .error (f"Failed to restore backup: { restore_e } " )
609+ raise e
0 commit comments