mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-11-05 06:58:12 +08:00
Merge remote-tracking branch 'origin/refactor/4.0-final' into docs/4.0-site
This commit is contained in:
@@ -27,6 +27,7 @@
|
||||
- **修复(在线更新)**:在线更新包带上 `ab_sdk`,4.0 镜像启动时与 `module` 一起替换;更新包要求镜像不低于 4.0.0-beta.1,已安装 3.3 的用户在 beta 通道不会应用 4.x 更新包(缺少 `ab_sdk` 与新依赖,应用后无法启动),需要拉取 4.0 镜像
|
||||
- **修复(多下载器)**:删除规则并删除文件时,若某个下载器不可用,规则与种子记录会保留并提示稍后重试;此前规则和种子记录已先删除,该下载器上的种子之后无法再删
|
||||
- **修复(多下载器)**:aria2 任务与番剧的关联按下载器实例区分,两个 aria2 实例分配到相同 gid 时不再互相覆盖,同一种子也可以分别加入两个实例;插件下载器的工厂参数 `DownloaderConnection` 新增 `instance_id`
|
||||
- **修复(回退 3.3)**:4.x 改把运行版本写入 `config/version_v4.info`,`config/version.info` 保留 3.x 的最后记录。此前 3.3 读到 `4.0.0` 只比较 minor,会当作 3.0 数据运行 3.0→3.1 迁移并重建数据库。首次以 4.x 启动 3.x 数据时还会把数据库备份为 `data/data.db.v3.bak`,回退时与 `config.json.v3.bak` 一起还原
|
||||
- **新增(插件目录界面)**:设置 → 插件 新增「插件目录」:浏览签名目录中的插件并安装、更新;目录安装的插件可在卡片上卸载(内置、本地、pip 与 LLM 提供商插件不提供卸载)。插件卡片标出已签名或未签名。目录不可达时在区内显示原因,不弹全局提示
|
||||
- **修复(插件设置)**:经签名目录安装的插件在插件卡片上的来源显示为未翻译的 `config.plugins_set.source_catalog`
|
||||
- **修复(插件安装)**:`DELETE /api/v1/plugins/{id}` 不再能卸载 LLM 提供商插件;安装通用插件后,LLM 插件列举不再反复输出 `Skipping broken plugin` 警告
|
||||
|
||||
@@ -7,7 +7,10 @@ from .log import LOG_PATH, setup_logger
|
||||
TMDB_API = "32b19d6a05b512190a056fa4e747cbbc"
|
||||
DATA_PATH = "sqlite:///data/data.db"
|
||||
LEGACY_DATA_PATH = Path("data/data.json")
|
||||
VERSION_PATH = Path("config/version.info")
|
||||
# 3.x 只比较 version.info 末行的 minor,读到 4.x 会当成 3.0 并重建数据库,
|
||||
# 所以 4.x 另写一份记录,version.info 保持 3.x 的最后状态,回退时仍安全。
|
||||
VERSION_PATH = Path("config/version_v4.info")
|
||||
V3_VERSION_PATH = Path("config/version.info")
|
||||
POSTERS_PATH = Path("data/posters")
|
||||
|
||||
PLATFORM = "Windows" if sys.platform == "win32" else "Unix"
|
||||
|
||||
@@ -5,7 +5,13 @@ from pathlib import Path
|
||||
|
||||
import semver
|
||||
|
||||
from module.conf import DATA_PATH, LEGACY_DATA_PATH, VERSION, VERSION_PATH
|
||||
from module.conf import (
|
||||
DATA_PATH,
|
||||
LEGACY_DATA_PATH,
|
||||
V3_VERSION_PATH,
|
||||
VERSION,
|
||||
VERSION_PATH,
|
||||
)
|
||||
from module.conf.config import CONFIG_PATH
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -67,6 +73,30 @@ def _read_last_version() -> semver.Version | None:
|
||||
return None
|
||||
|
||||
|
||||
def _read_v3_version() -> semver.Version | None:
|
||||
"""3.x 的记录只读不写:回退到 3.3 时它仍要看到自己的版本。"""
|
||||
try:
|
||||
lines = V3_VERSION_PATH.read_text().splitlines()
|
||||
return semver.Version.parse(lines[-1].strip())
|
||||
except (IndexError, ValueError) as e:
|
||||
logger.warning(f"{V3_VERSION_PATH} is empty or malformed ({e}).")
|
||||
return None
|
||||
|
||||
|
||||
def _backup_v3_database() -> None:
|
||||
"""首次以 4.x 启动 3.x 数据时备份数据库,供回退 3.3 时还原;已有备份不覆盖。"""
|
||||
backup = DATABASE_PATH.with_name(DATABASE_PATH.name + ".v3.bak")
|
||||
if backup.exists():
|
||||
return
|
||||
# 用 SQLite 备份接口而不是复制文件:WAL 中未合并的数据也要带上
|
||||
with (
|
||||
closing(sqlite3.connect(DATABASE_PATH)) as src,
|
||||
closing(sqlite3.connect(backup)) as dst,
|
||||
):
|
||||
src.backup(dst)
|
||||
logger.info(f"Backed up the 3.x database to {backup}.")
|
||||
|
||||
|
||||
def version_check() -> semver.Version | None:
|
||||
"""校验并记录版本,返回上一次运行的版本(未知时为 None)。
|
||||
|
||||
@@ -75,17 +105,25 @@ def version_check() -> semver.Version | None:
|
||||
"""
|
||||
if VERSION in ("DEV_VERSION", "local"):
|
||||
return None
|
||||
if not VERSION_PATH.exists():
|
||||
if VERSION_PATH.exists():
|
||||
last_ver = _read_last_version()
|
||||
if last_ver is None:
|
||||
return None
|
||||
elif V3_VERSION_PATH.exists():
|
||||
last_ver = _read_v3_version()
|
||||
if last_ver is not None and (last_ver.major, last_ver.minor) < (
|
||||
MIN_UPGRADE_FROM
|
||||
):
|
||||
raise UnsupportedUpgradeError(_upgrade_hint(str(last_ver)))
|
||||
_backup_v3_database()
|
||||
VERSION_PATH.write_text(VERSION + "\n")
|
||||
return last_ver
|
||||
else:
|
||||
# 3.3 也不补跑 3.0 → 3.1 的数据迁移(缺 version.info 时跳过),只有 3.1.x 会
|
||||
if _is_v30_database():
|
||||
raise UnsupportedUpgradeError(_upgrade_hint("3.0", via="3.1.x"))
|
||||
VERSION_PATH.write_text(VERSION + "\n")
|
||||
return None
|
||||
last_ver = _read_last_version()
|
||||
if last_ver is None:
|
||||
return None
|
||||
if (last_ver.major, last_ver.minor) < MIN_UPGRADE_FROM:
|
||||
raise UnsupportedUpgradeError(_upgrade_hint(str(last_ver)))
|
||||
if semver.Version.parse(VERSION) > last_ver:
|
||||
with VERSION_PATH.open("a") as f:
|
||||
f.write(VERSION + "\n")
|
||||
|
||||
@@ -18,11 +18,12 @@ def _make_db(path, *tables: str) -> None:
|
||||
|
||||
@pytest.fixture
|
||||
def paths(tmp_path, monkeypatch):
|
||||
version_path = tmp_path / "version.info"
|
||||
version_path = tmp_path / "version_v4.info"
|
||||
legacy_path = tmp_path / "data.json"
|
||||
db_path = tmp_path / "data.db"
|
||||
_make_db(db_path, "bangumi", "rssitem")
|
||||
monkeypatch.setattr(vc, "VERSION_PATH", version_path)
|
||||
monkeypatch.setattr(vc, "V3_VERSION_PATH", tmp_path / "version.info")
|
||||
monkeypatch.setattr(vc, "LEGACY_DATA_PATH", legacy_path)
|
||||
monkeypatch.setattr(vc, "DATABASE_PATH", db_path)
|
||||
monkeypatch.setattr(vc, "VERSION", "4.0.0")
|
||||
@@ -49,20 +50,42 @@ def test_missing_version_file_with_30_database_is_refused(paths, tmp_path, monke
|
||||
assert not version_path.exists()
|
||||
|
||||
|
||||
def test_upgrade_from_33_is_recorded(paths):
|
||||
def test_upgrade_from_33_keeps_v3_record_and_backs_up_database(paths, tmp_path):
|
||||
"""3.3 只比较 version.info 末行的 minor:写入 4.x 会被读成 3.0 并重建数据库。"""
|
||||
version_path, _ = paths
|
||||
version_path.write_text("3.2.7\n3.3.6\n")
|
||||
v3_path = tmp_path / "version.info"
|
||||
v3_path.write_text("3.2.7\n3.3.6\n")
|
||||
last = vc.version_check()
|
||||
assert str(last) == "3.3.6"
|
||||
assert version_path.read_text().splitlines()[-1] == "4.0.0"
|
||||
assert v3_path.read_text() == "3.2.7\n3.3.6\n"
|
||||
assert version_path.read_text() == "4.0.0\n"
|
||||
with closing(sqlite3.connect(tmp_path / "data.db.v3.bak")) as conn:
|
||||
tables = {r[0] for r in conn.execute("SELECT name FROM sqlite_master")}
|
||||
assert tables == {"bangumi", "rssitem"}
|
||||
|
||||
|
||||
def test_upgrade_from_33_prerelease_is_allowed(paths):
|
||||
version_path, _ = paths
|
||||
version_path.write_text("3.3.0-beta.2\n")
|
||||
def test_upgrade_does_not_replace_existing_database_backup(paths, tmp_path):
|
||||
(tmp_path / "version.info").write_text("3.3.6\n")
|
||||
backup = tmp_path / "data.db.v3.bak"
|
||||
backup.write_text("older")
|
||||
vc.version_check()
|
||||
assert backup.read_text() == "older"
|
||||
|
||||
|
||||
def test_upgrade_from_33_prerelease_is_allowed(paths, tmp_path):
|
||||
(tmp_path / "version.info").write_text("3.3.0-beta.2\n")
|
||||
assert str(vc.version_check()) == "3.3.0-beta.2"
|
||||
|
||||
|
||||
def test_later_4x_boot_makes_no_database_backup(paths, tmp_path):
|
||||
version_path, _ = paths
|
||||
(tmp_path / "version.info").write_text("3.3.6\n")
|
||||
version_path.write_text("4.0.0-beta.1\n")
|
||||
assert str(vc.version_check()) == "4.0.0-beta.1"
|
||||
assert version_path.read_text() == "4.0.0-beta.1\n4.0.0\n"
|
||||
assert not (tmp_path / "data.db.v3.bak").exists()
|
||||
|
||||
|
||||
def test_same_version_is_not_duplicated(paths):
|
||||
version_path, _ = paths
|
||||
version_path.write_text("4.0.0\n")
|
||||
@@ -78,15 +101,17 @@ def test_downgrade_is_not_recorded(paths):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("previous", ["3.2.7", "3.1.0", "2.6.4"])
|
||||
def test_upgrade_from_pre_33_is_refused(paths, previous):
|
||||
def test_upgrade_from_pre_33_is_refused(paths, tmp_path, previous):
|
||||
version_path, _ = paths
|
||||
version_path.write_text(previous + "\n")
|
||||
v3_path = tmp_path / "version.info"
|
||||
v3_path.write_text(previous + "\n")
|
||||
with pytest.raises(vc.UnsupportedUpgradeError, match=previous) as exc:
|
||||
vc.version_check()
|
||||
# 本次启动已把 config.json 改写为 4.0 格式,提示要先还原备份再回到旧版本
|
||||
assert "config.json.v3.bak" in str(exc.value)
|
||||
# 拒绝时不改写记录,用户回退到 3.3.x 后仍能正常迁移
|
||||
assert version_path.read_text() == previous + "\n"
|
||||
assert v3_path.read_text() == previous + "\n"
|
||||
assert not version_path.exists()
|
||||
|
||||
|
||||
def test_legacy_data_json_is_refused(paths):
|
||||
|
||||
Reference in New Issue
Block a user