mirror of
https://github.com/zhenxun-org/zhenxun_bot.git
synced 2025-12-15 14:22:55 +08:00
688 lines
24 KiB
Python
688 lines
24 KiB
Python
"""
|
||
真寻仓库管理器
|
||
负责真寻主仓库的更新、版本检查、文件处理等功能
|
||
"""
|
||
|
||
import os
|
||
from pathlib import Path
|
||
import shutil
|
||
import tarfile
|
||
from typing import ClassVar
|
||
import zipfile
|
||
|
||
from zhenxun.configs.path_config import DATA_PATH, FONT_PATH, TEMP_PATH
|
||
from zhenxun.services.log import logger
|
||
from zhenxun.utils.github_utils import GithubUtils
|
||
from zhenxun.utils.github_utils.models import RepoInfo
|
||
from zhenxun.utils.http_utils import AsyncHttpx
|
||
from zhenxun.utils.manager.virtual_env_package_manager import VirtualEnvPackageManager
|
||
from zhenxun.utils.platform import PlatformUtils
|
||
from zhenxun.utils.repo_utils import AliyunRepoManager, GithubRepoManager
|
||
from zhenxun.utils.repo_utils.models import (
|
||
SubmoduleConfig,
|
||
)
|
||
from zhenxun.utils.repo_utils.submodule_manager import SubmoduleManager
|
||
from zhenxun.utils.repo_utils.utils import clean_git
|
||
|
||
LOG_COMMAND = "ZhenxunRepoManager"
|
||
|
||
|
||
class DownloadException(Exception):
|
||
"""资源下载异常"""
|
||
|
||
pass
|
||
|
||
|
||
class ZhenxunRepoConfig:
|
||
"""真寻仓库配置"""
|
||
|
||
# GitHub 仓库 URL
|
||
ZHENXUN_BOT_GIT = "https://github.com/zhenxun-org/zhenxun_bot.git"
|
||
ZHENXUN_BOT_GITHUB_URL = "https://github.com/HibiKier/zhenxun_bot/tree/main"
|
||
DEFAULT_GITHUB_URL = "https://github.com/HibiKier/zhenxun_bot/tree/main"
|
||
RELEASE_URL = "https://api.github.com/repos/HibiKier/zhenxun_bot/releases/latest"
|
||
|
||
# 资源仓库 URL
|
||
RESOURCE_GITHUB_URL = (
|
||
"https://github.com/zhenxun-org/zhenxun-bot-resources/tree/main"
|
||
)
|
||
|
||
# Web UI 仓库 URL
|
||
WEBUI_GIT = "https://github.com/HibiKier/zhenxun_bot_webui.git"
|
||
|
||
# 文件路径配置
|
||
VERSION_FILE_STRING = "__version__"
|
||
VERSION_FILE = Path() / VERSION_FILE_STRING
|
||
|
||
PYPROJECT_FILE_STRING = "pyproject.toml"
|
||
PYPROJECT_FILE = Path() / PYPROJECT_FILE_STRING
|
||
PYPROJECT_LOCK_FILE_STRING = "poetry.lock"
|
||
PYPROJECT_LOCK_FILE = Path() / PYPROJECT_LOCK_FILE_STRING
|
||
REQ_TXT_FILE_STRING = "requirements.txt"
|
||
REQ_TXT_FILE = Path() / REQ_TXT_FILE_STRING
|
||
|
||
BASE_PATH_STRING = "zhenxun"
|
||
BASE_PATH = Path() / BASE_PATH_STRING
|
||
|
||
# 资源路径配置
|
||
RESOURCE_PATH = Path() / "resources"
|
||
|
||
# Web UI 路径配置
|
||
WEBUI_PATH = DATA_PATH / "web_ui" / "public"
|
||
WEBUI_BACKUP_PATH = DATA_PATH / "web_ui" / "backup_public"
|
||
WEBUI_GIT_PATH = DATA_PATH / "web_ui" / "git_web_ui"
|
||
|
||
# 临时文件路径
|
||
TMP_PATH = TEMP_PATH / "zhenxun_update"
|
||
BACKUP_PATH = Path() / "backup"
|
||
RESOURCE_TMP_PATH = TEMP_PATH / "_resource_tmp"
|
||
|
||
# 下载文件配置
|
||
DOWNLOAD_GZ_FILE_STRING = "download_latest_file.tar.gz"
|
||
DOWNLOAD_ZIP_FILE_STRING = "download_latest_file.zip"
|
||
DOWNLOAD_GZ_FILE = TMP_PATH / DOWNLOAD_GZ_FILE_STRING
|
||
DOWNLOAD_ZIP_FILE = TMP_PATH / DOWNLOAD_ZIP_FILE_STRING
|
||
|
||
# 资源文件配置
|
||
RESOURCE_ZIP_FILE = RESOURCE_TMP_PATH / "resources.zip"
|
||
UNZIP_PATH: Path | None = None
|
||
|
||
# 需要替换的文件夹
|
||
REPLACE_FOLDERS: ClassVar[list[str]] = [
|
||
"builtin_plugins",
|
||
"services",
|
||
"utils",
|
||
"models",
|
||
"configs",
|
||
]
|
||
|
||
# 日志标识
|
||
COMMAND = "真寻仓库管理"
|
||
|
||
|
||
class ZhenxunRepoManager:
|
||
"""真寻仓库管理器"""
|
||
|
||
def __init__(self):
|
||
self.config = ZhenxunRepoConfig()
|
||
# 初始化子模块管理器
|
||
self.submodule_manager = SubmoduleManager(GithubRepoManager)
|
||
|
||
def __clear_folder(self, folder_path: Path):
|
||
for filename in os.listdir(folder_path):
|
||
file_path = folder_path / filename
|
||
try:
|
||
if file_path.is_file():
|
||
os.unlink(file_path)
|
||
elif file_path.is_dir() and not filename.startswith("."):
|
||
shutil.rmtree(file_path)
|
||
except Exception as e:
|
||
logger.warning(f"无法删除 {file_path}", LOG_COMMAND, e=e)
|
||
|
||
async def check_version(self) -> str:
|
||
"""检查真寻更新版本
|
||
|
||
返回:
|
||
str: 更新信息
|
||
"""
|
||
cur_version = self._get_current_version()
|
||
data = await self._get_latest_release_data()
|
||
if not data:
|
||
return "检查更新获取版本失败..."
|
||
return (
|
||
"检测到当前版本更新\n"
|
||
f"当前版本:{cur_version}\n"
|
||
f"最新版本:{data.get('name')}\n"
|
||
f"创建日期:{data.get('created_at')}\n"
|
||
f"更新内容:\n{data.get('body')}"
|
||
)
|
||
|
||
async def update_repository(
|
||
self,
|
||
bot,
|
||
user_id: str,
|
||
version_type: str,
|
||
force: bool,
|
||
source: str,
|
||
zip_update: bool,
|
||
update_type: str,
|
||
) -> str:
|
||
"""更新真寻仓库
|
||
|
||
参数:
|
||
bot: Bot实例
|
||
user_id: 用户ID
|
||
version_type: 更新版本类型 (main/release)
|
||
force: 是否强制更新
|
||
source: 更新源 (git/ali)
|
||
zip_update: 是否下载zip文件
|
||
update_type: 更新方式 (git/download)
|
||
|
||
返回:
|
||
str: 更新结果消息
|
||
"""
|
||
cur_version = self._get_current_version()
|
||
await PlatformUtils.send_superuser(
|
||
bot,
|
||
f"检测真寻已更新,当前版本:{cur_version}\n开始更新...",
|
||
user_id,
|
||
)
|
||
|
||
if zip_update:
|
||
return await self._zip_update(version_type)
|
||
elif source == "git":
|
||
result = await GithubRepoManager.update(
|
||
self.config.ZHENXUN_BOT_GIT,
|
||
Path(),
|
||
use_git=update_type == "git",
|
||
force=force,
|
||
)
|
||
else:
|
||
result = await AliyunRepoManager.update(
|
||
self.config.ZHENXUN_BOT_GIT,
|
||
Path(),
|
||
force=force,
|
||
)
|
||
|
||
if not result.success:
|
||
return f"版本更新失败...错误: {result.error_message}"
|
||
|
||
await PlatformUtils.send_superuser(
|
||
bot, "真寻更新完成,开始安装依赖...", user_id
|
||
)
|
||
await VirtualEnvPackageManager.install_requirement(self.config.REQ_TXT_FILE)
|
||
|
||
return (
|
||
f"版本更新完成!\n"
|
||
f"版本: {cur_version} -> {result.new_version}\n"
|
||
f"变更文件个数: {len(result.changed_files)}"
|
||
f"{'' if source == 'git' else '(阿里云更新不支持查看变更文件)'}\n"
|
||
"请重新启动真寻以完成更新!"
|
||
)
|
||
|
||
async def _zip_update(self, version_type: str) -> str:
|
||
"""ZIP文件更新
|
||
|
||
参数:
|
||
version_type: 版本类型 (main/release)
|
||
|
||
返回:
|
||
str: 更新结果
|
||
"""
|
||
logger.info("开始下载真寻最新版文件....", self.config.COMMAND)
|
||
cur_version = self._get_current_version()
|
||
url = None
|
||
new_version = None
|
||
repo_info = GithubUtils.parse_github_url(self.config.DEFAULT_GITHUB_URL)
|
||
|
||
if version_type in {"main"}:
|
||
repo_info.branch = version_type
|
||
new_version = await self._get_version_from_repo(repo_info)
|
||
if new_version:
|
||
new_version = new_version.split(":")[-1].strip()
|
||
url = await repo_info.get_archive_download_urls()
|
||
elif version_type == "release":
|
||
data = await self._get_latest_release_data()
|
||
if not data:
|
||
return "获取更新版本失败..."
|
||
new_version = data.get("name", "")
|
||
url = await repo_info.get_release_source_download_urls_tgz(new_version)
|
||
|
||
if not url:
|
||
return "获取版本下载链接失败..."
|
||
|
||
if self.config.TMP_PATH.exists():
|
||
logger.debug(f"删除临时文件夹 {self.config.TMP_PATH}", self.config.COMMAND)
|
||
shutil.rmtree(self.config.TMP_PATH)
|
||
|
||
logger.debug(
|
||
f"开始更新版本:{cur_version} -> {new_version} | 下载链接:{url}",
|
||
self.config.COMMAND,
|
||
)
|
||
|
||
download_file = (
|
||
self.config.DOWNLOAD_GZ_FILE
|
||
if version_type == "release"
|
||
else self.config.DOWNLOAD_ZIP_FILE
|
||
)
|
||
|
||
if await AsyncHttpx.download_file(url, download_file, stream=True):
|
||
logger.debug("下载真寻最新版文件完成...", self.config.COMMAND)
|
||
self._handle_downloaded_files(new_version)
|
||
result = "版本更新完成"
|
||
return (
|
||
f"{result}\n"
|
||
f"版本: {cur_version} -> {new_version}\n"
|
||
"请重新启动真寻以完成更新!"
|
||
)
|
||
else:
|
||
logger.debug("下载真寻最新版文件失败...", self.config.COMMAND)
|
||
return ""
|
||
|
||
def _handle_downloaded_files(self, latest_version: str | None):
|
||
"""处理下载的文件
|
||
|
||
参数:
|
||
latest_version: 最新版本号
|
||
"""
|
||
self.config.BACKUP_PATH.mkdir(exist_ok=True, parents=True)
|
||
logger.debug("开始解压文件压缩包...", self.config.COMMAND)
|
||
|
||
download_file = self.config.DOWNLOAD_GZ_FILE
|
||
if self.config.DOWNLOAD_GZ_FILE.exists():
|
||
tf = tarfile.open(self.config.DOWNLOAD_GZ_FILE)
|
||
else:
|
||
download_file = self.config.DOWNLOAD_ZIP_FILE
|
||
tf = zipfile.ZipFile(self.config.DOWNLOAD_ZIP_FILE)
|
||
|
||
tf.extractall(self.config.TMP_PATH)
|
||
logger.debug("解压文件压缩包完成...", self.config.COMMAND)
|
||
|
||
download_file_path = self.config.TMP_PATH / next(
|
||
x
|
||
for x in os.listdir(self.config.TMP_PATH)
|
||
if (self.config.TMP_PATH / x).is_dir()
|
||
)
|
||
|
||
_pyproject = download_file_path / self.config.PYPROJECT_FILE_STRING
|
||
_lock_file = download_file_path / self.config.PYPROJECT_LOCK_FILE_STRING
|
||
_req_file = download_file_path / self.config.REQ_TXT_FILE_STRING
|
||
extract_path = download_file_path / self.config.BASE_PATH_STRING
|
||
target_path = self.config.BASE_PATH
|
||
|
||
# 备份现有文件
|
||
if self.config.PYPROJECT_FILE.exists():
|
||
logger.debug(
|
||
f"移除备份文件: {self.config.PYPROJECT_FILE}", self.config.COMMAND
|
||
)
|
||
shutil.move(
|
||
self.config.PYPROJECT_FILE,
|
||
self.config.BACKUP_PATH / self.config.PYPROJECT_FILE_STRING,
|
||
)
|
||
if self.config.PYPROJECT_LOCK_FILE.exists():
|
||
logger.debug(
|
||
f"移除备份文件: {self.config.PYPROJECT_LOCK_FILE}", self.config.COMMAND
|
||
)
|
||
shutil.move(
|
||
self.config.PYPROJECT_LOCK_FILE,
|
||
self.config.BACKUP_PATH / self.config.PYPROJECT_LOCK_FILE_STRING,
|
||
)
|
||
if self.config.REQ_TXT_FILE.exists():
|
||
logger.debug(
|
||
f"移除备份文件: {self.config.REQ_TXT_FILE}", self.config.COMMAND
|
||
)
|
||
shutil.move(
|
||
self.config.REQ_TXT_FILE,
|
||
self.config.BACKUP_PATH / self.config.REQ_TXT_FILE_STRING,
|
||
)
|
||
|
||
# 移动新文件
|
||
if _pyproject.exists():
|
||
logger.debug("移动文件: pyproject.toml", self.config.COMMAND)
|
||
shutil.move(_pyproject, self.config.PYPROJECT_FILE)
|
||
if _lock_file.exists():
|
||
logger.debug("移动文件: poetry.lock", self.config.COMMAND)
|
||
shutil.move(_lock_file, self.config.PYPROJECT_LOCK_FILE)
|
||
if _req_file.exists():
|
||
logger.debug("移动文件: requirements.txt", self.config.COMMAND)
|
||
shutil.move(_req_file, self.config.REQ_TXT_FILE)
|
||
|
||
# 处理文件夹
|
||
for folder in self.config.REPLACE_FOLDERS:
|
||
_dir = self.config.BASE_PATH / folder
|
||
_backup_dir = self.config.BACKUP_PATH / folder
|
||
if _backup_dir.exists():
|
||
logger.debug(f"删除备份文件夹 {_backup_dir}", self.config.COMMAND)
|
||
shutil.rmtree(_backup_dir)
|
||
if _dir.exists():
|
||
logger.debug(f"移动旧文件夹 {_dir}", self.config.COMMAND)
|
||
shutil.move(_dir, _backup_dir)
|
||
else:
|
||
logger.warning(f"文件夹 {_dir} 不存在,跳过删除", self.config.COMMAND)
|
||
|
||
for folder in self.config.REPLACE_FOLDERS:
|
||
src_folder_path = extract_path / folder
|
||
dest_folder_path = target_path / folder
|
||
if src_folder_path.exists():
|
||
logger.debug(
|
||
f"移动文件夹: {src_folder_path} -> {dest_folder_path}",
|
||
self.config.COMMAND,
|
||
)
|
||
shutil.move(src_folder_path, dest_folder_path)
|
||
else:
|
||
logger.debug(f"源文件夹不存在: {src_folder_path}", self.config.COMMAND)
|
||
|
||
# 清理临时文件
|
||
if tf:
|
||
tf.close()
|
||
if download_file.exists():
|
||
logger.debug(f"删除下载文件: {download_file}", self.config.COMMAND)
|
||
download_file.unlink()
|
||
if extract_path.exists():
|
||
logger.debug(f"删除解压文件夹: {extract_path}", self.config.COMMAND)
|
||
shutil.rmtree(extract_path)
|
||
if self.config.TMP_PATH.exists():
|
||
shutil.rmtree(self.config.TMP_PATH)
|
||
|
||
# 更新版本文件
|
||
if latest_version:
|
||
with open(self.config.VERSION_FILE, "w", encoding="utf8") as f:
|
||
f.write(f"__version__: {latest_version}")
|
||
|
||
def _get_current_version(self) -> str:
|
||
"""获取当前版本
|
||
|
||
返回:
|
||
str: 当前版本号
|
||
"""
|
||
_version = "v0.0.0"
|
||
if self.config.VERSION_FILE.exists():
|
||
if text := self.config.VERSION_FILE.open(encoding="utf8").readline():
|
||
_version = text.split(":")[-1].strip()
|
||
return _version
|
||
|
||
async def _get_latest_release_data(self) -> dict:
|
||
"""获取最新版本信息
|
||
|
||
返回:
|
||
dict: 最新版本数据
|
||
"""
|
||
for _ in range(3):
|
||
try:
|
||
res = await AsyncHttpx.get(self.config.RELEASE_URL)
|
||
if res.status_code == 200:
|
||
return res.json()
|
||
except TimeoutError:
|
||
pass
|
||
except Exception as e:
|
||
logger.error("检查更新真寻获取版本失败", e=e)
|
||
return {}
|
||
|
||
async def _get_version_from_repo(self, repo_info: RepoInfo) -> str:
|
||
"""从指定分支获取版本号
|
||
|
||
参数:
|
||
repo_info: 仓库信息
|
||
|
||
返回:
|
||
str: 版本号
|
||
"""
|
||
version_url = await repo_info.get_raw_download_urls(path="__version__")
|
||
try:
|
||
res = await AsyncHttpx.get(version_url)
|
||
if res.status_code == 200:
|
||
return res.text.strip()
|
||
except Exception as e:
|
||
logger.error(f"获取 {repo_info.branch} 分支版本失败", e=e)
|
||
return "未知版本"
|
||
|
||
# ==================== 资源管理相关方法 ====================
|
||
|
||
async def init_resources(
|
||
self, force: bool = False, is_zip: bool = False, git_source: str = "ali"
|
||
) -> str:
|
||
"""初始化资源文件
|
||
|
||
参数:
|
||
force: 是否强制更新
|
||
is_zip: 是否下载zip文件
|
||
git_source: 更新源 (ali/git)
|
||
|
||
返回:
|
||
str: 操作结果
|
||
"""
|
||
if (FONT_PATH.exists() and os.listdir(FONT_PATH)) and not force:
|
||
return "资源文件已存在,跳过初始化"
|
||
|
||
try:
|
||
if is_zip:
|
||
if self.config.RESOURCE_TMP_PATH.exists():
|
||
logger.debug(
|
||
"resources临时文件夹已存在,移除resources临时文件夹",
|
||
self.config.COMMAND,
|
||
)
|
||
await clean_git(self.config.RESOURCE_TMP_PATH)
|
||
shutil.rmtree(self.config.RESOURCE_TMP_PATH, ignore_errors=True)
|
||
self.config.RESOURCE_TMP_PATH.mkdir(parents=True, exist_ok=True)
|
||
await self._download_resources()
|
||
self._handle_resource_files()
|
||
else:
|
||
if git_source == "ali":
|
||
result = await AliyunRepoManager.update(
|
||
self.config.RESOURCE_GITHUB_URL, self.config.RESOURCE_PATH
|
||
)
|
||
else:
|
||
result = await GithubRepoManager.update(
|
||
self.config.RESOURCE_GITHUB_URL, self.config.RESOURCE_PATH
|
||
)
|
||
if not result.success:
|
||
return f"资源更新失败...错误: {result.error_message}"
|
||
self.config.UNZIP_PATH = self.config.RESOURCE_TMP_PATH / "resources"
|
||
self._handle_resource_files()
|
||
|
||
if self.config.RESOURCE_TMP_PATH.exists():
|
||
logger.debug("移除resources临时文件夹", self.config.COMMAND)
|
||
await clean_git(self.config.RESOURCE_TMP_PATH)
|
||
shutil.rmtree(self.config.RESOURCE_TMP_PATH)
|
||
|
||
return "资源文件初始化成功!"
|
||
except Exception as e:
|
||
logger.error("资源文件初始化失败", self.config.COMMAND, e=e)
|
||
return f"资源文件初始化失败: {e}"
|
||
|
||
def _handle_resource_files(self):
|
||
"""处理资源文件"""
|
||
if not hasattr(self.config, "UNZIP_PATH") or not self.config.UNZIP_PATH:
|
||
return
|
||
self._recursive_folder(self.config.UNZIP_PATH, ".")
|
||
|
||
def _recursive_folder(self, dir: Path, parent_path: str):
|
||
"""递归处理文件夹
|
||
|
||
参数:
|
||
dir: 目录路径
|
||
parent_path: 父路径
|
||
"""
|
||
for file in dir.iterdir():
|
||
if file.is_dir():
|
||
self._recursive_folder(file, f"{parent_path}/{file.name}")
|
||
else:
|
||
res_file = Path(parent_path) / file.name
|
||
if res_file.exists():
|
||
res_file.unlink()
|
||
res_file.parent.mkdir(parents=True, exist_ok=True)
|
||
file.rename(res_file)
|
||
|
||
async def _download_resources(self):
|
||
"""下载资源文件"""
|
||
repo_info = GithubUtils.parse_github_url(self.config.RESOURCE_GITHUB_URL)
|
||
url = await repo_info.get_archive_download_urls()
|
||
logger.debug("开始下载resources资源包...", self.config.COMMAND)
|
||
|
||
if not await AsyncHttpx.download_file(
|
||
url, self.config.RESOURCE_ZIP_FILE, stream=True
|
||
):
|
||
logger.error(
|
||
"下载resources资源包失败,请尝试重启重新下载或前往 "
|
||
"https://github.com/zhenxun-org/zhenxun-bot-resources 手动下载..."
|
||
)
|
||
raise DownloadException("下载resources资源包失败...")
|
||
|
||
logger.debug("下载resources资源文件压缩包完成...", self.config.COMMAND)
|
||
tf = zipfile.ZipFile(self.config.RESOURCE_ZIP_FILE)
|
||
tf.extractall(self.config.RESOURCE_TMP_PATH)
|
||
logger.debug("解压文件压缩包完成...", self.config.COMMAND)
|
||
|
||
download_file_path = self.config.RESOURCE_TMP_PATH / next(
|
||
x
|
||
for x in os.listdir(self.config.RESOURCE_TMP_PATH)
|
||
if (self.config.RESOURCE_TMP_PATH / x).is_dir()
|
||
)
|
||
self.config.UNZIP_PATH = download_file_path / "resources"
|
||
|
||
if tf:
|
||
tf.close()
|
||
|
||
# ==================== 子模块管理相关方法 ====================
|
||
|
||
async def init_submodules(self) -> str:
|
||
"""初始化子模块
|
||
|
||
返回:
|
||
str: 操作结果
|
||
"""
|
||
try:
|
||
# 定义子模块配置
|
||
submodule_configs = [
|
||
SubmoduleConfig(
|
||
name="resources",
|
||
path="resources",
|
||
repo_url=self.config.RESOURCE_GITHUB_URL,
|
||
branch="main",
|
||
enabled=True,
|
||
),
|
||
SubmoduleConfig(
|
||
name="web_ui",
|
||
path="data/web_ui/public",
|
||
repo_url=self.config.WEBUI_GIT,
|
||
branch="main",
|
||
enabled=True,
|
||
),
|
||
]
|
||
|
||
# 初始化子模块
|
||
success = await self.submodule_manager.init_submodules(
|
||
Path(), submodule_configs
|
||
)
|
||
|
||
if success:
|
||
return "子模块初始化成功!"
|
||
else:
|
||
return "子模块初始化失败!"
|
||
|
||
except Exception as e:
|
||
logger.error("子模块初始化失败", self.config.COMMAND, e=e)
|
||
return f"子模块初始化失败: {e}"
|
||
|
||
async def update_submodules(self) -> str:
|
||
"""更新子模块
|
||
|
||
返回:
|
||
str: 操作结果
|
||
"""
|
||
try:
|
||
# 定义子模块配置
|
||
submodule_configs = [
|
||
SubmoduleConfig(
|
||
name="resources",
|
||
path="resources",
|
||
repo_url=self.config.RESOURCE_GITHUB_URL,
|
||
branch="main",
|
||
enabled=True,
|
||
),
|
||
SubmoduleConfig(
|
||
name="web_ui",
|
||
path="data/web_ui/public",
|
||
repo_url=self.config.WEBUI_GIT,
|
||
branch="main",
|
||
enabled=True,
|
||
),
|
||
]
|
||
|
||
# 更新子模块
|
||
results = await self.submodule_manager.update_submodules(
|
||
Path(), submodule_configs
|
||
)
|
||
|
||
success_count = sum(1 for result in results if result.success)
|
||
total_count = len(results)
|
||
|
||
return f"子模块更新完成!成功: {success_count}/{total_count}"
|
||
|
||
except Exception as e:
|
||
logger.error("子模块更新失败", self.config.COMMAND, e=e)
|
||
return f"子模块更新失败: {e}"
|
||
|
||
async def get_submodule_info(self) -> str:
|
||
"""获取子模块信息
|
||
|
||
返回:
|
||
str: 子模块信息
|
||
"""
|
||
try:
|
||
# 定义子模块配置
|
||
submodule_configs = [
|
||
SubmoduleConfig(
|
||
name="resources",
|
||
path="resources",
|
||
repo_url=self.config.RESOURCE_GITHUB_URL,
|
||
branch="main",
|
||
enabled=True,
|
||
),
|
||
SubmoduleConfig(
|
||
name="web_ui",
|
||
path="data/web_ui/public",
|
||
repo_url=self.config.WEBUI_GIT,
|
||
branch="main",
|
||
enabled=True,
|
||
),
|
||
]
|
||
|
||
# 获取子模块信息
|
||
submodule_infos = await self.submodule_manager.get_submodule_info(
|
||
Path(), submodule_configs
|
||
)
|
||
|
||
info_text = "子模块信息:\n"
|
||
for info in submodule_infos:
|
||
info_text += f"- {info.config.name}:\n"
|
||
info_text += f" 路径: {info.config.path}\n"
|
||
info_text += f" 当前版本: {info.current_version}\n"
|
||
info_text += f" 最新版本: {info.latest_version}\n"
|
||
info_text += f" 状态: {info.update_status}\n"
|
||
|
||
return info_text
|
||
|
||
except Exception as e:
|
||
logger.error("获取子模块信息失败", self.config.COMMAND, e=e)
|
||
return f"获取子模块信息失败: {e}"
|
||
|
||
# ==================== Web UI 管理相关方法 ====================
|
||
|
||
async def webui_download_zip(self) -> str:
|
||
"""下载 WEBUI_ASSETS 资源"""
|
||
webui_assets_path = TEMP_PATH / "webui_assets.zip"
|
||
download_url = await GithubUtils.parse_github_url(
|
||
self.config.WEBUI_GIT
|
||
).get_archive_download_urls()
|
||
logger.info("开始下载 WEBUI_ASSETS 资源...", LOG_COMMAND)
|
||
if await AsyncHttpx.download_file(
|
||
download_url, webui_assets_path, follow_redirects=True
|
||
):
|
||
logger.info("下载 WEBUI_ASSETS 成功!", LOG_COMMAND)
|
||
raise DownloadException("下载 WEBUI_ASSETS 失败", LOG_COMMAND)
|
||
|
||
def __backup_webui(self):
|
||
"""备份 WEBUI_ASSERT 资源"""
|
||
if self.config.WEBUI_PATH.exists():
|
||
if self.config.WEBUI_BACKUP_PATH.exists():
|
||
logger.debug(
|
||
f"删除旧的备份webui文件夹 {self.config.WEBUI_BACKUP_PATH}",
|
||
LOG_COMMAND,
|
||
)
|
||
shutil.rmtree(self.config.WEBUI_BACKUP_PATH)
|
||
shutil.copytree(self.config.WEBUI_PATH, self.config.WEBUI_BACKUP_PATH)
|
||
|
||
# async def webui_unzip(self) -> str:
|
||
# """使用zip更新 Web UI
|
||
|
||
# 参数:
|
||
# is_zip: 是否下载 ZIP 文件
|
||
# source: 更新源 (git/ali)
|
||
|
||
# 返回:
|
||
# str: 更新结果
|
||
# """
|
||
# self.__backup_webui()
|
||
# self.__clear_folder(self.config.WEBUI_PATH)
|