refind logger

This commit is contained in:
gcw_4spBpAfv
2026-01-20 18:40:54 +08:00
parent 0ce140a210
commit 945077a453
10 changed files with 621 additions and 661 deletions

View File

@@ -51,6 +51,11 @@ class OTAManager:
# ==================== 状态访问(只读属性)====================
@property
def logger(self):
"""获取 logger 对象"""
return logger_manager.logger
@property
def update_thread_started(self):
"""OTA线程是否已启动"""
@@ -128,14 +133,10 @@ class OTAManager:
filename_lower = filename.lower()
if filename_lower.endswith('.zip'):
logger = logger_manager.logger
if logger:
logger.info(f"[EXTRACT] 检测到ZIP文件扩展名: .zip")
self.logger.info(f"[EXTRACT] 检测到ZIP文件扩展名: .zip")
return True, 'zip'
logger = logger_manager.logger
if logger:
logger.info(f"[EXTRACT] 不是ZIP格式扩展名: {os.path.splitext(filename)[1] or ''}")
self.logger.info(f"[EXTRACT] 不是ZIP格式扩展名: {os.path.splitext(filename)[1] or ''}")
return False, None
def extract_zip_archive(self, archive_path, extract_to_dir=None, target_file=None):
@@ -153,8 +154,7 @@ class OTAManager:
if extract_to_dir is None:
extract_to_dir = os.path.dirname(archive_path) or '/tmp'
logger = logger_manager.logger
logger.info(f"[EXTRACT] 开始解压ZIP文件: {archive_path}")
self.logger.info(f"[EXTRACT] 开始解压ZIP文件: {archive_path}")
try:
os.makedirs(extract_to_dir, exist_ok=True)
@@ -167,18 +167,18 @@ class OTAManager:
result = os.system(cmd)
if result != 0:
logger.warning(f"[EXTRACT] 直接解压目标文件失败,尝试解压所有文件...")
self.logger.warning(f"[EXTRACT] 直接解压目标文件失败,尝试解压所有文件...")
cmd_all = f"unzip -q -o '{archive_path}' -d '{extract_to_dir}' 2>&1"
result_all = os.system(cmd_all)
if result_all != 0:
logger.error(f"[EXTRACT] 解压失败,退出码: {result_all}")
self.logger.error(f"[EXTRACT] 解压失败,退出码: {result_all}")
return False, None
return True, extract_to_dir
except Exception as e:
logger.error(f"[EXTRACT] 解压过程出错: {e}")
self.logger.error(f"[EXTRACT] 解压过程出错: {e}")
return False, None
def apply_ota_and_reboot(self, ota_url=None, downloaded_file=None):
@@ -203,12 +203,11 @@ class OTAManager:
ota_pending = f"{config.APP_DIR}/ota_pending.json"
logger = logger_manager.logger
logger.info(f"[OTA] 准备应用OTA更新下载文件: {downloaded_file}")
self.logger.info(f"[OTA] 准备应用OTA更新下载文件: {downloaded_file}")
try:
if not os.path.exists(downloaded_file):
logger.error(f"[OTA] 错误:{downloaded_file} 不存在")
self.logger.error(f"[OTA] 错误:{downloaded_file} 不存在")
return False
# 备份
@@ -230,9 +229,9 @@ class OTAManager:
f.write(str(counter))
backup_dir = os.path.join(backup_base, f"backup_{counter:04d}")
logger.info(f"[OTA] 使用备份目录: {backup_dir} (第{counter}次OTA)")
self.logger.info(f"[OTA] 使用备份目录: {backup_dir} (第{counter}次OTA)")
except Exception as e:
logger.error(f"[OTA] 生成备份目录名失败: {e},使用默认目录")
self.logger.error(f"[OTA] 生成备份目录名失败: {e},使用默认目录")
backup_dir = os.path.join(backup_base, "backup_0000")
# 清理旧备份
@@ -255,11 +254,11 @@ class OTAManager:
for item, dir_num, item_path in backup_dirs[config.MAX_BACKUPS:]:
try:
shutil.rmtree(item_path, ignore_errors=True)
logger.info(f"[OTA] 已删除旧备份: {item}")
self.logger.info(f"[OTA] 已删除旧备份: {item}")
except Exception as e:
logger.warning(f"[OTA] 删除旧备份失败: {e}")
self.logger.warning(f"[OTA] 删除旧备份失败: {e}")
except Exception as e:
logger.warning(f"[OTA] 清理旧备份时出错: {e}")
self.logger.warning(f"[OTA] 清理旧备份时出错: {e}")
os.makedirs(backup_dir, exist_ok=True)
@@ -286,16 +285,15 @@ class OTAManager:
shutil.copy2(source_path, backup_path)
backed_up_files.append(rel_path)
except Exception as e:
if logger:
logger.error(f"[OTA] 备份 {rel_path} 失败: {e}")
self.logger.error(f"[OTA] 备份 {rel_path} 失败: {e}")
if backed_up_files:
logger.info(f"[OTA] 总共备份了 {len(backed_up_files)} 个文件到 {backup_dir}")
self.logger.info(f"[OTA] 总共备份了 {len(backed_up_files)} 个文件到 {backup_dir}")
else:
logger.warning(f"[OTA] 没有备份任何文件")
self.logger.warning(f"[OTA] 没有备份任何文件")
except Exception as e:
logger.error(f"[OTA] 备份过程出错: {e}")
self.logger.error(f"[OTA] 备份过程出错: {e}")
if not backup_dir:
backup_dir = None
@@ -309,15 +307,15 @@ class OTAManager:
with open(downloaded_file, "rb") as f:
zip_header = f.read(4)
if zip_header[:2] != b'PK':
logger.error(f"[OTA] ZIP文件头验证失败: {zip_header.hex()}")
self.logger.error(f"[OTA] ZIP文件头验证失败: {zip_header.hex()}")
return False
file_size = os.path.getsize(downloaded_file)
logger.info(f"[OTA] ZIP文件验证通过: 大小={file_size} bytes, 头={zip_header.hex()}")
self.logger.info(f"[OTA] ZIP文件验证通过: 大小={file_size} bytes, 头={zip_header.hex()}")
except Exception as e:
logger.error(f"[OTA] ZIP文件验证异常: {e}")
self.logger.error(f"[OTA] ZIP文件验证异常: {e}")
return False
logger.info(f"[OTA] 检测到ZIP压缩包开始解压...")
self.logger.info(f"[OTA] 检测到ZIP压缩包开始解压...")
extract_dir = "/tmp/ota_extract"
try:
os.makedirs(extract_dir, exist_ok=True)
@@ -339,12 +337,12 @@ class OTAManager:
files_to_copy.append((source_path, rel_path))
if files_to_copy:
logger.info(f"[OTA] 解压成功,共 {len(files_to_copy)} 个文件")
self.logger.info(f"[OTA] 解压成功,共 {len(files_to_copy)} 个文件")
else:
logger.error(f"[OTA] 解压成功但未找到任何文件")
self.logger.error(f"[OTA] 解压成功但未找到任何文件")
return False
else:
logger.error(f"[OTA] 解压失败")
self.logger.error(f"[OTA] 解压失败")
return False
else:
# 单个文件更新:从下载的文件名推断目标文件名
@@ -359,11 +357,11 @@ class OTAManager:
target_rel_path = filename
files_to_copy = [(downloaded_file, target_rel_path)]
logger.info(f"[OTA] 单个文件更新: {downloaded_file} -> {target_rel_path}")
self.logger.info(f"[OTA] 单个文件更新: {downloaded_file} -> {target_rel_path}")
# 复制文件
if not files_to_copy:
logger.error(f"[OTA] 没有文件需要复制")
self.logger.error(f"[OTA] 没有文件需要复制")
return False
copied_files = []
@@ -372,7 +370,7 @@ class OTAManager:
# 检查源文件和目标文件是否是同一个文件(避免复制到自身)
if os.path.abspath(source_path) == os.path.abspath(dest_path):
logger.warning(f"[OTA] 源文件和目标文件相同,跳过复制: {rel_path} (文件已在正确位置)")
self.logger.warning(f"[OTA] 源文件和目标文件相同,跳过复制: {rel_path} (文件已在正确位置)")
copied_files.append(rel_path)
continue
@@ -386,13 +384,13 @@ class OTAManager:
try:
shutil.copy2(source_path, dest_path)
copied_files.append(rel_path)
logger.info(f"[OTA] 已复制: {rel_path}")
self.logger.info(f"[OTA] 已复制: {rel_path}")
except Exception as e:
logger.error(f"[OTA] 复制 {rel_path} 失败: {e}")
self.logger.error(f"[OTA] 复制 {rel_path} 失败: {e}")
return False
if copied_files:
logger.info(f"[OTA] 成功复制 {len(copied_files)} 个文件到应用目录")
self.logger.info(f"[OTA] 成功复制 {len(copied_files)} 个文件到应用目录")
# 确保写入磁盘
try:
@@ -421,7 +419,7 @@ class OTAManager:
except:
pass
except Exception as e:
logger.error(f"[OTA] 写入 ota_pending 失败: {e}")
self.logger.error(f"[OTA] 写入 ota_pending 失败: {e}")
# 通知服务器(延迟导入避免循环导入)
from network import safe_enqueue
@@ -433,9 +431,9 @@ class OTAManager:
try:
if os.path.exists(extract_dir):
shutil.rmtree(extract_dir, ignore_errors=True)
logger.info(f"[OTA] 已清理临时解压目录: {extract_dir}")
self.logger.info(f"[OTA] 已清理临时解压目录: {extract_dir}")
except Exception as e:
logger.warning(f"[OTA] 清理临时目录失败(可忽略): {e}")
self.logger.warning(f"[OTA] 清理临时目录失败(可忽略): {e}")
# 清理下载文件
try:
@@ -443,9 +441,9 @@ class OTAManager:
# 删除下载的文件
try:
os.remove(downloaded_file)
logger.info(f"[OTA] 已删除下载文件: {downloaded_file}")
self.logger.info(f"[OTA] 已删除下载文件: {downloaded_file}")
except Exception as e:
logger.warning(f"[OTA] 删除下载文件失败(可忽略): {e}")
self.logger.warning(f"[OTA] 删除下载文件失败(可忽略): {e}")
# 尝试删除时间戳目录(如果为空)
try:
@@ -457,25 +455,24 @@ class OTAManager:
files_in_dir = os.listdir(download_dir)
if not files_in_dir:
os.rmdir(download_dir)
logger.info(f"[OTA] 已删除空时间戳目录: {download_dir}")
self.logger.info(f"[OTA] 已删除空时间戳目录: {download_dir}")
except Exception as e:
logger.debug(f"[OTA] 删除时间戳目录失败(可忽略): {e}")
self.logger.debug(f"[OTA] 删除时间戳目录失败(可忽略): {e}")
except Exception as e:
logger.debug(f"[OTA] 清理时间戳目录时出错(可忽略): {e}")
self.logger.debug(f"[OTA] 清理时间戳目录时出错(可忽略): {e}")
except Exception as e:
logger.warning(f"[OTA] 清理下载文件时出错(可忽略): {e}")
self.logger.warning(f"[OTA] 清理下载文件时出错(可忽略): {e}")
# 重启设备
logger.info("[OTA] 准备重启设备...")
self.logger.info("[OTA] 准备重启设备...")
os.system("reboot")
return True
except Exception as e:
logger = logger_manager.logger
logger.error(f"[OTA] apply_ota_and_reboot 异常: {e}")
self.logger.error(f"[OTA] apply_ota_and_reboot 异常: {e}")
import traceback
logger.error(traceback.format_exc())
self.logger.error(traceback.format_exc())
return False
def get_download_timestamp_dir(self):
@@ -512,8 +509,7 @@ class OTAManager:
try:
os.makedirs(download_dir, exist_ok=True)
except Exception as e:
logger = logger_manager.logger
logger.warning(f"[OTA] 创建下载目录失败: {e},使用基础目录")
self.logger.warning(f"[OTA] 创建下载目录失败: {e},使用基础目录")
download_dir = download_base
try:
os.makedirs(download_dir, exist_ok=True)
@@ -522,8 +518,7 @@ class OTAManager:
return download_dir
except Exception as e:
logger = logger_manager.logger
logger.error(f"[OTA] 获取下载目录失败: {e},使用默认目录")
self.logger.error(f"[OTA] 获取下载目录失败: {e},使用默认目录")
return "/tmp/download"
def get_filename_from_url(self, url, default_name="main_tmp"):
@@ -553,16 +548,14 @@ class OTAManager:
# 只有在完全无法提取文件名时,才使用默认名称
return f"{download_dir}/{default_name}"
except Exception as e:
logger = logger_manager.logger
logger.error(f"[OTA] 从URL提取文件名失败: {e},使用默认文件名")
self.logger.error(f"[OTA] 从URL提取文件名失败: {e},使用默认文件名")
download_dir = self.get_download_timestamp_dir()
return f"{download_dir}/{default_name}"
def download_file(self, url, filename):
"""从指定 URL 下载文件根据文件类型自动选择文本或二进制模式并支持MD5校验"""
try:
logger = logger_manager.logger
logger.info(f"正在从 {url} 下载文件...")
self.logger.info(f"正在从 {url} 下载文件...")
response = requests.get(url)
response.raise_for_status()
@@ -570,7 +563,7 @@ class OTAManager:
md5_b64_expected = None
if 'Content-Md5' in response.headers:
md5_b64_expected = response.headers['Content-Md5'].strip()
logger.info(f"[DOWNLOAD] 服务器提供了MD5校验值: {md5_b64_expected}")
self.logger.info(f"[DOWNLOAD] 服务器提供了MD5校验值: {md5_b64_expected}")
# 根据文件扩展名判断是否为二进制文件
filename_lower = filename.lower()
@@ -586,14 +579,14 @@ class OTAManager:
os.sync()
except:
pass
logger.info(f"[DOWNLOAD] 使用二进制模式下载: {filename}, 大小: {len(data)} bytes")
self.logger.info(f"[DOWNLOAD] 使用二进制模式下载: {filename}, 大小: {len(data)} bytes")
else:
# 文本文件:使用文本模式写入
response.encoding = 'utf-8'
with open(filename, 'w', encoding='utf-8') as file:
file.write(response.text)
logger.info(f"[DOWNLOAD] 使用文本模式下载: {filename}")
self.logger.info(f"[DOWNLOAD] 使用文本模式下载: {filename}")
# MD5 校验如果服务器提供了MD5值
if md5_b64_expected and hashlib is not None:
@@ -604,18 +597,18 @@ class OTAManager:
md5_b64_got = binascii.b2a_base64(digest).decode().strip()
if md5_b64_got != md5_b64_expected:
logger.error(f"[DOWNLOAD] MD5校验失败: 期望={md5_b64_expected}, 实际={md5_b64_got}")
self.logger.error(f"[DOWNLOAD] MD5校验失败: 期望={md5_b64_expected}, 实际={md5_b64_got}")
return f"下载失败MD5校验失败: 期望={md5_b64_expected}, 实际={md5_b64_got}"
else:
logger.info(f"[DOWNLOAD] MD5校验通过: {md5_b64_got}")
self.logger.info(f"[DOWNLOAD] MD5校验通过: {md5_b64_got}")
except Exception as e:
logger.warning(f"[DOWNLOAD] MD5校验过程出错: {e}")
self.logger.warning(f"[DOWNLOAD] MD5校验过程出错: {e}")
# MD5校验出错时如果是二进制文件特别是ZIP应该失败
if is_binary:
return f"下载失败MD5校验异常: {e}"
elif is_binary and not md5_b64_expected:
# 二进制文件特别是ZIP建议有MD5校验
logger.warning(f"[DOWNLOAD] 警告: 服务器未提供MD5校验值无法验证文件完整性")
self.logger.warning(f"[DOWNLOAD] 警告: 服务器未提供MD5校验值无法验证文件完整性")
return f"下载成功!文件已保存为: {filename}"
except requests.exceptions.RequestException as e:
@@ -647,11 +640,10 @@ class OTAManager:
return
downloaded_filename = self.get_filename_from_url(ota_url, default_name="main_tmp")
logger = logger_manager.logger
logger.info(f"[OTA] 下载文件将保存为: {downloaded_filename}")
logger.info(f"[OTA] 开始下载: {ota_url}")
self.logger.info(f"[OTA] 下载文件将保存为: {downloaded_filename}")
self.logger.info(f"[OTA] 开始下载: {ota_url}")
result_msg = self.download_file(ota_url, downloaded_filename)
logger.info(f"[OTA] {result_msg}")
self.logger.info(f"[OTA] {result_msg}")
if "成功" in result_msg or "下载成功" in result_msg:
if self.apply_ota_and_reboot(ota_url, downloaded_filename):
@@ -662,8 +654,7 @@ class OTAManager:
except Exception as e:
error_msg = f"OTA 异常: {str(e)}"
logger = logger_manager.logger
logger.error(error_msg)
self.logger.error(error_msg)
from network import safe_enqueue
safe_enqueue({"result": "ota_failed", "reason": error_msg}, 2)
finally:
@@ -707,11 +698,11 @@ class OTAManager:
else:
base_url = f"http://{host}"
self._is_https = False
logger = logger_manager.logger
# logger removed - use self.logger instead
def _log(*a):
if debug and logger:
logger.debug(" ".join(str(x) for x in a))
if debug:
self.logger.debug(" ".join(str(x) for x in a))
def _pwr_log(prefix=""):
"""debug 用:输出电压/电量"""
@@ -720,12 +711,10 @@ class OTAManager:
try:
v = get_bus_voltage()
p = voltage_to_percent(v)
if logger:
logger.debug(f"[PWR]{prefix} v={v:.3f}V p={p}%")
self.logger.debug(f"[PWR]{prefix} v={v:.3f}V p={p}%")
except Exception as e:
try:
if logger:
logger.debug(f"[PWR]{prefix} read_failed: {e}")
self.logger.debug(f"[PWR]{prefix} read_failed: {e}")
except:
pass
@@ -792,7 +781,7 @@ class OTAManager:
if self._is_https:
resp = hardware_manager.at_client.send(f'AT+MHTTPCFG="ssl",{hid},1,1', "OK", 2000)
if "ERROR" in resp or "CME ERROR" in resp:
logger_manager.logger.error(f"MHTTPCFG SSL failed: {resp}")
self.logger.error(f"MHTTPCFG SSL failed: {resp}")
# 尝试https 降级到http
downgraded_base_url = base_url.replace("https://", "http://")
resp = hardware_manager.at_client.send(f'AT+MHTTPCREATE="{downgraded_base_url}"', "OK", 8000)
@@ -1035,14 +1024,12 @@ class OTAManager:
got_b64 = binascii.b2a_base64(digest).decode().strip()
if got_b64 != expect_md5_b64:
return False, f"md5_mismatch got={got_b64} expected={expect_md5_b64}"
if logger:
logger.debug(f"[4G-DL] MD5 verified: {got_b64}")
self.logger.debug(f"[4G-DL] MD5 verified: {got_b64}")
except Exception as e:
return False, f"md5_check_failed: {e}"
t_cost = time.ticks_diff(time.ticks_ms(), t_func0)
if logger:
logger.info(f"[4G-DL] download complete: size={offset} ip={ip} cost_ms={t_cost}")
self.logger.info(f"[4G-DL] download complete: size={offset} ip={ip} cost_ms={t_cost}")
return True, f"OK size={offset} ip={ip} cost_ms={t_cost}"
finally:
@@ -1076,9 +1063,9 @@ class OTAManager:
# 从URL中提取文件名保留原始扩展名
downloaded_filename = self.get_filename_from_url(ota_url, default_name="main_tmp")
logger_manager.logger.info(f"[OTA-4G] 下载文件将保存为: {downloaded_filename}")
self.logger.info(f"[OTA-4G] 下载文件将保存为: {downloaded_filename}")
logger_manager.logger.info(f"[OTA-4G] 开始通过 4G 下载: {ota_url}")
self.logger.info(f"[OTA-4G] 开始通过 4G 下载: {ota_url}")
# 重要说明:
# - AT+MDIALUP / RNDIS 是"USB 主机拨号上网"模式,在不少 ML307R 固件上会占用/切换内部网络栈,
# 从而导致 AT+MIPOPEN / +MIPURC 这套 TCP 连接无法工作(你会看到一直"连接到服务器...")。
@@ -1090,15 +1077,15 @@ class OTAManager:
import power
v = power.get_bus_voltage()
p = power.voltage_to_percent(v)
logger_manager.logger.info(f"[OTA-4G][PWR] before_urc v={v:.3f}V p={p}%")
self.logger.info(f"[OTA-4G][PWR] before_urc v={v:.3f}V p={p}%")
except Exception as e:
logger_manager.logger.error(f"[OTA-4G][PWR] before_urc read_failed: {e}")
self.logger.error(f"[OTA-4G][PWR] before_urc read_failed: {e}")
t_dl0 = time.ticks_ms()
success, msg = self.download_file_via_4g(ota_url, downloaded_filename, debug=False)
t_dl_cost = time.ticks_diff(t_dl0, time.ticks_ms())
logger_manager.logger.info(f"[OTA-4G] {msg}")
logger_manager.logger.info(f"[OTA-4G] download_cost_ms={t_dl_cost}")
self.logger.info(f"[OTA-4G] {msg}")
self.logger.info(f"[OTA-4G] download_cost_ms={t_dl_cost}")
if success and "OK" in msg:
if self.apply_ota_and_reboot(ota_url, downloaded_filename):
@@ -1108,13 +1095,13 @@ class OTAManager:
except Exception as e:
error_msg = f"OTA-4G 异常: {str(e)}"
logger_manager.logger.error(error_msg)
self.logger.error(error_msg)
safe_enqueue({"result": "ota_failed", "reason": error_msg}, 2)
finally:
# 总耗时(注意:若成功并 reboot这行可能来不及打印
try:
t_cost = time.ticks_diff(time.ticks_ms(), t_ota0)
logger_manager.logger.info(f"[OTA-4G] total_cost_ms={t_cost}")
self.logger.info(f"[OTA-4G] total_cost_ms={t_cost}")
except:
pass
self._stop_update_thread()
@@ -1147,21 +1134,15 @@ class OTAManager:
if not network_manager.is_server_reachable(host, port, timeout=8):
err_msg = f"网络不通:无法连接 {host}:{port}"
safe_enqueue({"result": err_msg}, 2)
logger = logger_manager.logger
if logger:
logger.error(err_msg)
self.logger.error(err_msg)
return
downloaded_filename = self.get_filename_from_url(ota_url, default_name="main_tmp")
logger = logger_manager.logger
if logger:
logger.info(f"[OTA] 下载文件将保存为: {downloaded_filename}")
self.logger.info(f"[OTA] 下载文件将保存为: {downloaded_filename}")
if logger:
logger.info(f"[NET] 已确认可访问 {host}:{port},开始下载...")
self.logger.info(f"[NET] 已确认可访问 {host}:{port},开始下载...")
result = self.download_file(ota_url, downloaded_filename)
if logger:
logger.info(result)
self.logger.info(result)
if "成功" in result or "下载成功" in result:
if self.apply_ota_and_reboot(ota_url, downloaded_filename):
@@ -1188,9 +1169,7 @@ class OTAManager:
try:
if backup_dir_path is None:
if not os.path.exists(backup_base):
logger = logger_manager.logger
if logger:
logger.error(f"[RESTORE] 备份目录不存在: {backup_base}")
self.logger.error(f"[RESTORE] 备份目录不存在: {backup_base}")
return False
backup_dirs = []
@@ -1207,9 +1186,7 @@ class OTAManager:
pass
if not backup_dirs:
logger = logger_manager.logger
if logger:
logger.error(f"[RESTORE] 没有找到备份目录")
self.logger.error(f"[RESTORE] 没有找到备份目录")
return False
backup_dirs.sort(key=lambda x: x[1], reverse=True)
@@ -1217,14 +1194,10 @@ class OTAManager:
backup_dir_path = os.path.join(backup_base, latest_backup)
if not os.path.exists(backup_dir_path):
logger = logger_manager.logger
if logger:
logger.error(f"[RESTORE] 备份目录不存在: {backup_dir_path}")
self.logger.error(f"[RESTORE] 备份目录不存在: {backup_dir_path}")
return False
logger = logger_manager.logger
if logger:
logger.info(f"[RESTORE] 开始从备份恢复: {backup_dir_path}")
self.logger.info(f"[RESTORE] 开始从备份恢复: {backup_dir_path}")
restored_files = []
for root, dirs, files in os.walk(backup_dir_path):
@@ -1240,25 +1213,19 @@ class OTAManager:
try:
shutil.copy2(source_path, dest_path)
restored_files.append(rel_path)
if logger:
logger.info(f"[RESTORE] 已恢复: {rel_path}")
self.logger.info(f"[RESTORE] 已恢复: {rel_path}")
except Exception as e:
if logger:
logger.error(f"[RESTORE] 恢复 {rel_path} 失败: {e}")
self.logger.error(f"[RESTORE] 恢复 {rel_path} 失败: {e}")
if restored_files:
if logger:
logger.info(f"[RESTORE] 成功恢复 {len(restored_files)} 个文件")
self.logger.info(f"[RESTORE] 成功恢复 {len(restored_files)} 个文件")
return True
else:
if logger:
logger.info(f"[RESTORE] 没有文件被恢复")
self.logger.info(f"[RESTORE] 没有文件被恢复")
return False
except Exception as e:
logger = logger_manager.logger
if logger:
logger.error(f"[RESTORE] 恢复过程出错: {e}")
self.logger.error(f"[RESTORE] 恢复过程出错: {e}")
return False