mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 07:27:15 +08:00
feat: 完善数据库备份管理与版本读取 (#6450)
This commit is contained in:
Vendored
+5
-5
@@ -62,7 +62,7 @@ from app.foundation.singleton import WeakSingleton
|
||||
from app.foundation.version import compare_version
|
||||
from app.adapters.system.host import SystemUtils
|
||||
from app.foundation.url import UrlUtils
|
||||
from version import APP_VERSION
|
||||
from app.runtime.version import get_app_version
|
||||
|
||||
# 保留模块级可替换入口,代理默认读取组合根的最新 runtime 配置。
|
||||
settings = RuntimeSettingsCompat()
|
||||
@@ -289,9 +289,9 @@ class PluginHelper(metaclass=WeakSingleton):
|
||||
解析当前主程序版本,供插件 package 中的系统版本范围匹配使用。
|
||||
"""
|
||||
try:
|
||||
return Version(str(APP_VERSION))
|
||||
return Version(get_app_version())
|
||||
except InvalidVersion:
|
||||
logger.error(f"当前主程序版本号无法解析:{APP_VERSION}")
|
||||
logger.error(f"当前主程序版本号无法解析:{get_app_version()}")
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
@@ -402,7 +402,7 @@ class PluginHelper(metaclass=WeakSingleton):
|
||||
|
||||
system_version = cls.get_current_system_version()
|
||||
if system_version is None:
|
||||
return False, f"当前 MoviePilot 版本 {APP_VERSION} 无法解析,已拒绝安装带版本限制的插件"
|
||||
return False, f"当前 MoviePilot 版本 {get_app_version()} 无法解析,已拒绝安装带版本限制的插件"
|
||||
|
||||
try:
|
||||
specifier_set = SpecifierSet(raw_specifier)
|
||||
@@ -416,7 +416,7 @@ class PluginHelper(metaclass=WeakSingleton):
|
||||
return True, ""
|
||||
|
||||
return False, (
|
||||
f"插件要求 MoviePilot 版本 {raw_specifier},当前版本 {APP_VERSION} 不满足,已拒绝安装"
|
||||
f"插件要求 MoviePilot 版本 {raw_specifier},当前版本 {get_app_version()} 不满足,已拒绝安装"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
||||
Vendored
+3
-22
@@ -2,7 +2,6 @@ import asyncio
|
||||
import json
|
||||
import platform
|
||||
from collections.abc import Coroutine
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
||||
from urllib.parse import parse_qs, quote, urlparse, urlsplit
|
||||
|
||||
@@ -24,7 +23,7 @@ from app.adapters.network.http import AsyncRequestUtils, RequestUtils
|
||||
from app.domain.media import normalize_music_type
|
||||
from app.schemas.media import resolve_media_identity
|
||||
from app.adapters.system.host import SystemUtils
|
||||
from version import APP_VERSION, FRONTEND_VERSION
|
||||
from app.runtime.version import get_app_version, get_frontend_version
|
||||
|
||||
|
||||
# 保留旧插件可覆盖的模块级入口,默认通过 runtime 代理动态读取配置。
|
||||
@@ -270,24 +269,6 @@ class MoviePilotServerHelper:
|
||||
or permissions.get("workflow_share_manage")
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_frontend_version() -> str:
|
||||
"""
|
||||
获取当前前端版本。
|
||||
"""
|
||||
if SystemUtils.is_frozen() and SystemUtils.is_windows():
|
||||
version_file = settings.CONFIG_PATH.parent / "nginx" / "html" / "version.txt"
|
||||
else:
|
||||
version_file = Path(settings.FRONTEND_PATH) / "version.txt"
|
||||
if version_file.exists():
|
||||
try:
|
||||
with open(version_file, "r", encoding="utf-8", errors="replace") as file:
|
||||
version = str(file.read()).strip()
|
||||
return version or FRONTEND_VERSION
|
||||
except Exception as err:
|
||||
logger.debug(f"加载版本文件 {version_file} 出错:{str(err)}")
|
||||
return FRONTEND_VERSION
|
||||
|
||||
@classmethod
|
||||
def build_usage_payload(cls) -> Dict[str, Any]:
|
||||
"""
|
||||
@@ -295,8 +276,8 @@ class MoviePilotServerHelper:
|
||||
"""
|
||||
return {
|
||||
"user_uid": cls.get_user_uid(),
|
||||
"backend_version": APP_VERSION,
|
||||
"frontend_version": cls.get_frontend_version(),
|
||||
"backend_version": get_app_version(),
|
||||
"frontend_version": get_frontend_version(),
|
||||
"version_flag": settings.VERSION_FLAG,
|
||||
"platform": f"{platform.system()} {platform.release()}".strip(),
|
||||
"arch": SystemUtils.cpu_arch(),
|
||||
|
||||
@@ -8,13 +8,16 @@ import tempfile
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
from app.runtime.version import get_app_version
|
||||
|
||||
_BACKUP_NAME = re.compile(
|
||||
r"^(?P<db_type>sqlite|postgresql)_"
|
||||
r"^(?:moviepilot_(?P<version>v\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)_)?"
|
||||
r"(?P<db_type>sqlite|postgresql)_"
|
||||
r"(?P<timestamp>\d{8}_\d{6})"
|
||||
r"(?:_(?P<sequence>\d+))?"
|
||||
r"(?P<suffix>\.db|\.dump)$"
|
||||
)
|
||||
_RELEASE_VERSION = re.compile(r"^v\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$")
|
||||
|
||||
|
||||
class BackupFiles:
|
||||
@@ -68,10 +71,19 @@ class BackupFiles:
|
||||
"""删除一个已通过名称约束的备份文件。"""
|
||||
self.resolve(name).unlink()
|
||||
|
||||
def available_name(self, *, db_type: str, created_at: datetime, suffix: str) -> str:
|
||||
def available_name(
|
||||
self,
|
||||
*,
|
||||
db_type: str,
|
||||
created_at: datetime,
|
||||
suffix: str,
|
||||
) -> str:
|
||||
"""生成包含数据库类型和秒级时间的简短可读文件名。"""
|
||||
timestamp = created_at.strftime("%Y%m%d_%H%M%S")
|
||||
base = f"{db_type}_{timestamp}"
|
||||
version = get_app_version().strip()
|
||||
if _RELEASE_VERSION.fullmatch(version) is None:
|
||||
raise ValueError("程序版本号无法用于数据库备份命名")
|
||||
base = f"moviepilot_{version}_{db_type}_{timestamp}"
|
||||
candidate = f"{base}{suffix}"
|
||||
sequence = 1
|
||||
while (self.root / candidate).exists():
|
||||
|
||||
@@ -26,7 +26,7 @@ import psutil
|
||||
from app.schemas.dashboard import DashboardMemoryInfo as _SchemaDashboardMemoryInfo
|
||||
from app.schemas.dashboard import DashboardSystemInfo as _SchemaDashboardSystemInfo
|
||||
from app.schemas.dashboard import ProcessInfo as _SchemaProcessInfo
|
||||
from version import APP_VERSION
|
||||
from app.runtime.version import get_app_version
|
||||
from app.foundation.environment import (
|
||||
is_aarch,
|
||||
is_aarch64,
|
||||
@@ -948,7 +948,7 @@ class SystemUtils:
|
||||
hostname=socket.gethostname(),
|
||||
operating_system=SystemUtils._operating_system_name(),
|
||||
runtime=max(0, int(time.time() - psutil.Process().create_time())),
|
||||
version=APP_VERSION,
|
||||
version=get_app_version(),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -16,12 +16,12 @@ from typing import Any, Optional
|
||||
from app.adapters.network.http import RequestUtils
|
||||
from app.foundation.singleton import SingletonClass
|
||||
from app.foundation.version import compare_version
|
||||
from app.runtime.version import get_app_version
|
||||
from app.foundation.environment import is_docker
|
||||
from app.runtime.log import logger
|
||||
from app.runtime.settings import get_runtime_setting
|
||||
from app.runtime.thread import ThreadHelper
|
||||
from app.schemas.system import SystemUpdateStatus
|
||||
from version import APP_VERSION
|
||||
|
||||
|
||||
class SystemUpdateManager(metaclass=SingletonClass):
|
||||
@@ -66,13 +66,17 @@ class SystemUpdateManager(metaclass=SingletonClass):
|
||||
return datetime.now(timezone.utc).isoformat()
|
||||
|
||||
def _default_state(self) -> dict[str, Any]:
|
||||
return SystemUpdateStatus(current_version=APP_VERSION).model_dump()
|
||||
return SystemUpdateStatus(current_version=get_app_version()).model_dump()
|
||||
|
||||
def _read_state(self) -> dict[str, Any]:
|
||||
try:
|
||||
payload = json.loads(self._state_file.read_text(encoding="utf-8"))
|
||||
if isinstance(payload, dict):
|
||||
return {**self._default_state(), **payload, "current_version": APP_VERSION}
|
||||
return {
|
||||
**self._default_state(),
|
||||
**payload,
|
||||
"current_version": get_app_version(),
|
||||
}
|
||||
except (OSError, json.JSONDecodeError):
|
||||
pass
|
||||
return self._default_state()
|
||||
@@ -81,7 +85,7 @@ class SystemUpdateManager(metaclass=SingletonClass):
|
||||
with self._lock:
|
||||
state = self._read_state()
|
||||
state.update(changes)
|
||||
state["current_version"] = APP_VERSION
|
||||
state["current_version"] = get_app_version()
|
||||
state["progress"] = self._progress(
|
||||
state.get("downloaded_bytes", 0), state.get("total_bytes", 0)
|
||||
)
|
||||
@@ -117,7 +121,7 @@ class SystemUpdateManager(metaclass=SingletonClass):
|
||||
can_update=True,
|
||||
can_install=False,
|
||||
)
|
||||
if state.get("state") == "installing" and target == APP_VERSION:
|
||||
if state.get("state") == "installing" and target == get_app_version():
|
||||
self._install_file.unlink(missing_ok=True)
|
||||
state = self._write_state(
|
||||
state="idle",
|
||||
@@ -161,7 +165,7 @@ class SystemUpdateManager(metaclass=SingletonClass):
|
||||
if not release:
|
||||
raise RuntimeError("未找到可用的 v3 稳定版本")
|
||||
version = str(release["tag_name"])
|
||||
has_update = compare_version(version, "gt", APP_VERSION) is True
|
||||
has_update = compare_version(version, "gt", get_app_version()) is True
|
||||
return SystemUpdateStatus.model_validate(
|
||||
self._write_state(
|
||||
state="available" if has_update else "idle",
|
||||
|
||||
Reference in New Issue
Block a user