Files
AstrBot/astrbot/core/utils/astrbot_path.py

251 lines
7.5 KiB
Python

"""Astrbot统一路径获取
项目路径:固定为源码所在路径
根目录路径:默认为当前工作目录,可通过环境变量 ASTRBOT_ROOT 指定
数据目录路径:固定为根目录下的 data 目录
配置文件路径:固定为数据目录下的 config 目录
插件目录路径:固定为数据目录下的 plugins 目录
插件数据目录路径:固定为数据目录下的 plugin_data 目录
T2I 模板目录路径:固定为数据目录下的 t2i_templates 目录
WebChat 数据目录路径:固定为数据目录下的 webchat 目录
临时文件目录路径:固定为数据目录下的 temp 目录
Skills 目录路径:固定为数据目录下的 skills 目录
第三方依赖目录路径:固定为数据目录下的 site-packages 目录
"""
import os
from importlib import resources
from pathlib import Path
import anyio
from astrbot.core.utils.runtime_env import is_packaged_desktop_runtime
class AstrbotPaths:
"""Astrbot 项目路径管理类"""
def __init__(self) -> None:
self._root_override: Path | None = None
from dotenv import load_dotenv
env_candidates = []
# 1) current working directory .env
env_candidates.append(Path.cwd() / ".env")
# 2) ASTRBOT_ROOT/.env if ASTRBOT_ROOT already set in the environment
root_env = os.environ.get("ASTRBOT_ROOT")
if root_env:
env_candidates.append(Path(root_env) / ".env")
for p in env_candidates:
if p.exists():
load_dotenv(dotenv_path=str(p), override=False)
def _resolve_root(self) -> Path:
if path := os.environ.get("ASTRBOT_ROOT"):
return Path(path)
if is_packaged_desktop_runtime():
return Path().home() / ".astrbot"
return Path(os.getcwd())
@property
def root(self) -> Path:
if self._root_override is not None:
return self._root_override
return self._resolve_root()
@root.setter
def root(self, value: Path) -> None:
self._root_override = value
@property
def is_root(self) -> bool:
"""Check if the path is an AstrBot root directory"""
if not self.root.exists() or not self.root.is_dir():
return False
if not (self.root / ".astrbot").exists():
return False
return True
@property
def has_dashboard(self) -> bool:
"""Check if the dashboard is installed"""
if self.bundled_dist.is_dir():
return True
dashboard_version = self.dashboard_version
match dashboard_version:
case None:
return False
case str():
return True
case _:
return False
async def async_has_dashboard(self) -> bool:
"""Check if the dashboard is installed (async)"""
if self.bundled_dist.is_dir():
return True
dashboard_version = await self.async_dashboard_version()
match dashboard_version:
case None:
return False
case str():
return True
case _:
return False
@property
def dashboard_version(self) -> str | None:
try:
with open(self.dist / "assets" / "version") as f:
return f.read().strip()
except FileNotFoundError:
return None
@property
def bundled_dist(self) -> Path:
return self.project_root / "dashboard" / "dist"
async def async_dashboard_version(self) -> str | None:
try:
# anyio.open_file returns a coroutine that yields an async file object.
# Await it to get the file object, then use it and close it explicitly.
f = await anyio.open_file(self.dist / "assets" / "version", mode="r")
try:
data = await f.read()
if data is None:
return None
return data.strip()
finally:
# Ensure we close the async file handle; ignore close errors defensively.
try:
await f.aclose()
except Exception:
pass
except (FileNotFoundError, OSError):
return None
except Exception:
# Be defensive: any unexpected error should not raise during path utils
return None
@property
def project_root(self) -> Path:
"""获取项目根目录路径 (package root)"""
with resources.as_file(resources.files("astrbot")) as path:
return Path(path)
@property
def data(self) -> Path:
return self.root / "data"
@property
def dist(self) -> Path:
return self.data / "dist"
@property
def config(self) -> Path:
return self.data / "config"
@property
def plugins(self) -> Path:
return self.data / "plugins"
@property
def temp(self) -> Path:
return self.data / "temp"
@property
def skills(self) -> Path:
return self.data / "skills"
@property
def site_packages(self) -> Path:
return self.data / "site-packages"
@property
def knowledge_base(self) -> Path:
return self.data / "knowledge_base"
@property
def backups(self) -> Path:
return self.data / "backups"
@property
def t2i_templates(self) -> Path:
return self.data / "t2i_templates"
@property
def webchat(self) -> Path:
return self.data / "webchat"
astrbot_paths = AstrbotPaths()
def get_astrbot_path() -> str:
"""获取Astrbot项目路径"""
return str(astrbot_paths.project_root)
def get_astrbot_root() -> str:
"""获取Astrbot根目录路径"""
return str(astrbot_paths.root)
def get_astrbot_data_path() -> str:
"""获取Astrbot数据目录路径"""
return os.path.realpath(os.path.join(get_astrbot_root(), "data"))
def get_astrbot_config_path() -> str:
"""获取Astrbot配置文件路径"""
return os.path.realpath(os.path.join(get_astrbot_data_path(), "config"))
def get_astrbot_plugin_path() -> str:
"""获取Astrbot插件目录路径"""
return os.path.realpath(os.path.join(get_astrbot_data_path(), "plugins"))
def get_astrbot_plugin_data_path() -> str:
"""获取Astrbot插件数据目录路径"""
return os.path.realpath(os.path.join(get_astrbot_data_path(), "plugin_data"))
def get_astrbot_t2i_templates_path() -> str:
"""获取Astrbot T2I 模板目录路径"""
return os.path.realpath(os.path.join(get_astrbot_data_path(), "t2i_templates"))
def get_astrbot_webchat_path() -> str:
"""获取Astrbot WebChat 数据目录路径"""
return os.path.realpath(os.path.join(get_astrbot_data_path(), "webchat"))
def get_astrbot_temp_path() -> str:
"""获取Astrbot临时文件目录路径"""
return os.path.realpath(os.path.join(get_astrbot_data_path(), "temp"))
def get_astrbot_skills_path() -> str:
"""获取Astrbot Skills 目录路径"""
return os.path.realpath(os.path.join(get_astrbot_data_path(), "skills"))
def get_astrbot_site_packages_path() -> str:
"""获取Astrbot第三方依赖目录路径"""
return os.path.realpath(os.path.join(get_astrbot_data_path(), "site-packages"))
def get_astrbot_knowledge_base_path() -> str:
"""获取Astrbot知识库根目录路径"""
return os.path.realpath(os.path.join(get_astrbot_data_path(), "knowledge_base"))
def get_astrbot_backups_path() -> str:
"""获取Astrbot备份目录路径"""
return os.path.realpath(os.path.join(get_astrbot_data_path(), "backups"))