From af6c334ad052681b0fd285f659e9d3d8ae4f109e Mon Sep 17 00:00:00 2001 From: SXP-Simon Date: Thu, 23 Apr 2026 11:18:06 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(=E6=95=B0=E6=8D=AE=E5=A4=87=E4=BB=BD?= =?UTF-8?q?=E4=B8=8E=E6=81=A2=E5=A4=8D):=20=E8=A7=A3=E5=86=B3=E5=A4=87?= =?UTF-8?q?=E4=BB=BD=E6=81=A2=E5=A4=8D=E5=92=8C=E6=8F=92=E4=BB=B6=E5=AE=89?= =?UTF-8?q?=E8=A3=85=E8=BF=87=E7=A8=8B=E4=B8=AD=E7=9A=84=E8=B7=AF=E5=BE=84?= =?UTF-8?q?=E5=86=B2=E7=AA=81=E5=8F=8A=E8=87=AA=E6=84=88=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 修复备份导入时目录条目被误识别为 0 字节文件的问题。 2. 增加插件加载和数据目录创建时的路径冲突自动清理逻辑。 3. 增强插件解压安装过程对现有冲突文件的兼容性。 4. 优化 remove_dir 工具类使其支持同时处理文件和目录的删除。 --- astrbot/core/backup/importer.py | 7 +++++++ astrbot/core/star/star_tools.py | 6 ++++++ astrbot/core/star/updator.py | 3 +++ astrbot/core/utils/io.py | 5 ++++- astrbot/core/zip_updator.py | 3 +++ 5 files changed, 23 insertions(+), 1 deletion(-) diff --git a/astrbot/core/backup/importer.py b/astrbot/core/backup/importer.py index e994242a88..7888222fbc 100644 --- a/astrbot/core/backup/importer.py +++ b/astrbot/core/backup/importer.py @@ -931,6 +931,13 @@ async def _import_directories( if not _validate_path_within(target_path, target_dir): result.add_warning(f"文件路径越界,已跳过: {name}") continue + + if zf.getinfo(name).is_dir(): + if target_path.exists() and not target_path.is_dir(): + target_path.unlink() + target_path.mkdir(parents=True, exist_ok=True) + continue + target_path.parent.mkdir(parents=True, exist_ok=True) with zf.open(name) as src, open(target_path, "wb") as dst: diff --git a/astrbot/core/star/star_tools.py b/astrbot/core/star/star_tools.py index 4d85131fc6..a9ea86c594 100644 --- a/astrbot/core/star/star_tools.py +++ b/astrbot/core/star/star_tools.py @@ -25,6 +25,7 @@ from typing import Any, ClassVar from astrbot.api.platform import AstrBotMessage, MessageMember, MessageType +from astrbot.core import logger from astrbot.core.message.components import BaseMessageComponent from astrbot.core.message.message_event_result import MessageChain from astrbot.core.platform.astr_message_event import MessageSesion @@ -305,6 +306,11 @@ def get_data_dir(cls, plugin_name: str | None = None) -> Path: ) try: + if data_dir.exists() and not data_dir.is_dir(): + logger.warning( + f"路径 {data_dir} 已存在但不是目录。正在尝试删除该文件并创建插件数据目录。", + ) + data_dir.unlink() data_dir.mkdir(parents=True, exist_ok=True) except OSError as e: if isinstance(e, PermissionError): diff --git a/astrbot/core/star/updator.py b/astrbot/core/star/updator.py index 2ae0039db0..55cf3e75a6 100644 --- a/astrbot/core/star/updator.py +++ b/astrbot/core/star/updator.py @@ -53,6 +53,9 @@ async def update(self, plugin: StarMetadata, proxy="") -> str: return plugin_path def unzip_file(self, zip_path: str, target_dir: str) -> None: + if os.path.exists(target_dir) and not os.path.isdir(target_dir): + logger.warning(f"路径 {target_dir} 已存在但不是目录,正在清理。") + os.remove(target_dir) os.makedirs(target_dir, exist_ok=True) update_dir = "" logger.info(f"正在解压压缩包: {zip_path}") diff --git a/astrbot/core/utils/io.py b/astrbot/core/utils/io.py index b565926749..b6d954d9a9 100644 --- a/astrbot/core/utils/io.py +++ b/astrbot/core/utils/io.py @@ -33,7 +33,10 @@ def on_error(func, path, exc_info) -> None: def remove_dir(file_path: str) -> bool: if not os.path.exists(file_path): return True - shutil.rmtree(file_path, onerror=on_error) + if os.path.isfile(file_path) or os.path.islink(file_path): + os.remove(file_path) + else: + shutil.rmtree(file_path, onerror=on_error) return True diff --git a/astrbot/core/zip_updator.py b/astrbot/core/zip_updator.py index 116b55c57d..105e3f7736 100644 --- a/astrbot/core/zip_updator.py +++ b/astrbot/core/zip_updator.py @@ -233,6 +233,9 @@ def parse_github_url(self, url: str): def unzip_file(self, zip_path: str, target_dir: str) -> None: """解压缩文件, 并将压缩包内**第一个**文件夹内的文件移动到 target_dir""" + if os.path.exists(target_dir) and not os.path.isdir(target_dir): + logger.warning(f"路径 {target_dir} 已存在但不是目录,正在清理。") + os.remove(target_dir) os.makedirs(target_dir, exist_ok=True) update_dir = "" with zipfile.ZipFile(zip_path, "r") as z: From 169b77e40f8d9c713f7cb2b5b6cd1432356443a7 Mon Sep 17 00:00:00 2001 From: SXP-Simon Date: Thu, 23 Apr 2026 11:37:33 +0800 Subject: [PATCH 2/2] =?UTF-8?q?fix(core):=20=E6=A0=B9=E6=8D=AE=20CR=20?= =?UTF-8?q?=E5=BB=BA=E8=AE=AE=E5=AE=9E=E7=8E=B0=E9=80=9A=E7=94=A8=E7=9A=84?= =?UTF-8?q?=E8=B7=AF=E5=BE=84=E5=86=B2=E7=AA=81=E8=87=AA=E6=84=88=E6=9C=BA?= =?UTF-8?q?=E5=88=B6=E5=B9=B6=E5=A2=9E=E5=BC=BA=E6=8D=9F=E5=9D=8F=E7=AC=A6?= =?UTF-8?q?=E5=8F=B7=E9=93=BE=E6=8E=A5=E7=9A=84=E5=A4=84=E7=90=86=E8=83=BD?= =?UTF-8?q?=E5=8A=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/core/backup/importer.py | 5 ++--- astrbot/core/star/star_tools.py | 9 ++------- astrbot/core/star/updator.py | 7 ++----- astrbot/core/utils/io.py | 23 ++++++++++++++++++++++- astrbot/core/zip_updator.py | 9 +++------ 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/astrbot/core/backup/importer.py b/astrbot/core/backup/importer.py index 7888222fbc..7d30d27c39 100644 --- a/astrbot/core/backup/importer.py +++ b/astrbot/core/backup/importer.py @@ -25,6 +25,7 @@ get_astrbot_data_path, get_astrbot_knowledge_base_path, ) +from astrbot.core.utils.io import ensure_dir from astrbot.core.utils.version_comparator import VersionComparator # 从共享常量模块导入 @@ -933,9 +934,7 @@ async def _import_directories( continue if zf.getinfo(name).is_dir(): - if target_path.exists() and not target_path.is_dir(): - target_path.unlink() - target_path.mkdir(parents=True, exist_ok=True) + ensure_dir(target_path) continue target_path.parent.mkdir(parents=True, exist_ok=True) diff --git a/astrbot/core/star/star_tools.py b/astrbot/core/star/star_tools.py index a9ea86c594..fe5563b7dd 100644 --- a/astrbot/core/star/star_tools.py +++ b/astrbot/core/star/star_tools.py @@ -25,7 +25,6 @@ from typing import Any, ClassVar from astrbot.api.platform import AstrBotMessage, MessageMember, MessageType -from astrbot.core import logger from astrbot.core.message.components import BaseMessageComponent from astrbot.core.message.message_event_result import MessageChain from astrbot.core.platform.astr_message_event import MessageSesion @@ -38,6 +37,7 @@ from astrbot.core.star.context import Context from astrbot.core.star.star import star_map from astrbot.core.utils.astrbot_path import get_astrbot_data_path +from astrbot.core.utils.io import ensure_dir class StarTools: @@ -306,12 +306,7 @@ def get_data_dir(cls, plugin_name: str | None = None) -> Path: ) try: - if data_dir.exists() and not data_dir.is_dir(): - logger.warning( - f"路径 {data_dir} 已存在但不是目录。正在尝试删除该文件并创建插件数据目录。", - ) - data_dir.unlink() - data_dir.mkdir(parents=True, exist_ok=True) + ensure_dir(data_dir) except OSError as e: if isinstance(e, PermissionError): raise RuntimeError(f"无法创建目录 {data_dir}:权限不足") from e diff --git a/astrbot/core/star/updator.py b/astrbot/core/star/updator.py index 55cf3e75a6..ee507454e2 100644 --- a/astrbot/core/star/updator.py +++ b/astrbot/core/star/updator.py @@ -4,7 +4,7 @@ from astrbot.core import logger from astrbot.core.utils.astrbot_path import get_astrbot_plugin_path -from astrbot.core.utils.io import on_error, remove_dir +from astrbot.core.utils.io import ensure_dir, on_error, remove_dir from ..star.star import StarMetadata from ..updator import RepoZipUpdator @@ -53,10 +53,7 @@ async def update(self, plugin: StarMetadata, proxy="") -> str: return plugin_path def unzip_file(self, zip_path: str, target_dir: str) -> None: - if os.path.exists(target_dir) and not os.path.isdir(target_dir): - logger.warning(f"路径 {target_dir} 已存在但不是目录,正在清理。") - os.remove(target_dir) - os.makedirs(target_dir, exist_ok=True) + ensure_dir(target_dir) update_dir = "" logger.info(f"正在解压压缩包: {zip_path}") with zipfile.ZipFile(zip_path, "r") as z: diff --git a/astrbot/core/utils/io.py b/astrbot/core/utils/io.py index b6d954d9a9..a34a03bfcd 100644 --- a/astrbot/core/utils/io.py +++ b/astrbot/core/utils/io.py @@ -31,7 +31,7 @@ def on_error(func, path, exc_info) -> None: def remove_dir(file_path: str) -> bool: - if not os.path.exists(file_path): + if not os.path.lexists(file_path): return True if os.path.isfile(file_path) or os.path.islink(file_path): os.remove(file_path) @@ -40,6 +40,27 @@ def remove_dir(file_path: str) -> bool: return True +def ensure_dir(dir_path: str | Path) -> None: + """确保目录存在。如果路径处存在非目录的文件或损坏的符号链接,则先将其删除。""" + p = Path(dir_path) + if (p.exists() or p.is_symlink()) and not p.is_dir(): + logger.warning(f"路径 {p} 已存在但不是目录,正在清理以创建目录。") + try: + if p.is_dir(): + shutil.rmtree(p, onerror=on_error) + else: + p.unlink() + except Exception as e: + logger.error(f"清理冲突路径 {p} 失败: {e!s}") + raise RuntimeError(f"无法清理冲突路径 {p}:{e!s}") from e + + try: + p.mkdir(parents=True, exist_ok=True) + except Exception as e: + logger.error(f"创建目录 {p} 失败: {e!s}") + raise RuntimeError(f"无法创建目录 {p}:{e!s}") from e + + def port_checker(port: int, host: str = "localhost") -> bool: sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sk.settimeout(1) diff --git a/astrbot/core/zip_updator.py b/astrbot/core/zip_updator.py index 105e3f7736..1408a1c454 100644 --- a/astrbot/core/zip_updator.py +++ b/astrbot/core/zip_updator.py @@ -9,7 +9,7 @@ import httpx from astrbot.core import logger -from astrbot.core.utils.io import on_error +from astrbot.core.utils.io import ensure_dir, on_error from astrbot.core.utils.version_comparator import VersionComparator @@ -56,7 +56,7 @@ async def _download_file( self, url: str, path: str, timeout: float = 1800.0 ) -> None: target_path = Path(path) - target_path.parent.mkdir(parents=True, exist_ok=True) + ensure_dir(target_path.parent) try: async with self._create_httpx_client(timeout=timeout) as client: @@ -233,10 +233,7 @@ def parse_github_url(self, url: str): def unzip_file(self, zip_path: str, target_dir: str) -> None: """解压缩文件, 并将压缩包内**第一个**文件夹内的文件移动到 target_dir""" - if os.path.exists(target_dir) and not os.path.isdir(target_dir): - logger.warning(f"路径 {target_dir} 已存在但不是目录,正在清理。") - os.remove(target_dir) - os.makedirs(target_dir, exist_ok=True) + ensure_dir(target_dir) update_dir = "" with zipfile.ZipFile(zip_path, "r") as z: update_dir = z.namelist()[0]