mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 16:07:01 +08:00
fix: support site resources from legacy docker updater
This commit is contained in:
@@ -54,6 +54,8 @@ For work that changes or reviews repository behavior, identify the domains actua
|
|||||||
|
|
||||||
The historical `app/core`, `app/helper`, and `app/utils` directories are compatibility-only virtual import roots. Never add physical Python source there and never use those imports from host code. Choose an owner by responsibility, not by whether a function is "shared" or has historically been called a helper.
|
The historical `app/core`, `app/helper`, and `app/utils` directories are compatibility-only virtual import roots. Never add physical Python source there and never use those imports from host code. Choose an owner by responsibility, not by whether a function is "shared" or has historically been called a helper.
|
||||||
|
|
||||||
|
`app/helper/` may contain only the non-Python `.resource-compat` marker so source archives retain a write target for old Docker images whose updater is fixed to that path. If such an updater writes the compiled site resources there, `app/application/site/__init__.py` exposes that directory only as a fallback when the canonical extension is absent. New images and current update flows must continue writing exclusively to `app/application/site/`.
|
||||||
|
|
||||||
| Package | Owns | Must Not Own | Representative Files |
|
| Package | Owns | Must Not Own | Representative Files |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| `app/foundation/` | 无状态、无配置和无 I/O 的底层机制:反射/动态导入、加密、DOM、身份、集合、单例、文本、URL 和版本比较 | `settings`、DB/SystemConfig、网络请求、运行日志、MoviePilot 业务规则、旧导入路径 | `reflection.py`, `crypto.py`, `collections.py`, `text.py`, `url.py` |
|
| `app/foundation/` | 无状态、无配置和无 I/O 的底层机制:反射/动态导入、加密、DOM、身份、集合、单例、文本、URL 和版本比较 | `settings`、DB/SystemConfig、网络请求、运行日志、MoviePilot 业务规则、旧导入路径 | `reflection.py`, `crypto.py`, `collections.py`, `text.py`, `url.py` |
|
||||||
|
|||||||
@@ -1 +1,25 @@
|
|||||||
"""站点目录、认证与索引资源的应用能力包。"""
|
"""站点目录、认证与索引资源的应用能力包。"""
|
||||||
|
|
||||||
|
from importlib.machinery import EXTENSION_SUFFIXES
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def _include_legacy_resource_directory(
|
||||||
|
package_paths: list[str], package_dir: Path
|
||||||
|
) -> None:
|
||||||
|
"""canonical 扩展缺失时允许读取旧 Docker 更新器写入的资源目录。"""
|
||||||
|
extension_names = tuple(f"sites{suffix}" for suffix in EXTENSION_SUFFIXES)
|
||||||
|
if any((package_dir / name).is_file() for name in extension_names):
|
||||||
|
return
|
||||||
|
|
||||||
|
legacy_dir = package_dir.parent.parent / "helper"
|
||||||
|
if (
|
||||||
|
legacy_dir.is_dir()
|
||||||
|
and any((legacy_dir / name).is_file() for name in extension_names)
|
||||||
|
and str(legacy_dir) not in package_paths
|
||||||
|
):
|
||||||
|
# 旧镜像内固化的 mp_update.sh 无法随源码热更新,只在过渡场景扩展包搜索路径。
|
||||||
|
package_paths.append(str(legacy_dir))
|
||||||
|
|
||||||
|
|
||||||
|
_include_legacy_resource_directory(__path__, Path(__file__).resolve().parent)
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
该目录仅用于兼容旧 Docker 镜像中固化的资源更新脚本,不承载 Python 源码。
|
||||||
|
新更新器和新镜像仍只把站点资源安装到 app/application/site。
|
||||||
@@ -465,6 +465,12 @@ render_nginx_config
|
|||||||
|
|
||||||
# 自动更新
|
# 自动更新
|
||||||
cd /
|
cd /
|
||||||
|
if [ -f /app/docker/update.sh ] && ! cmp -s /app/docker/update.sh /usr/local/bin/mp_update.sh; then
|
||||||
|
# 后端源码可独立于镜像更新,启动前同步更新器,避免它长期保留过期目录约定。
|
||||||
|
cp -f /app/docker/update.sh /usr/local/bin/mp_update.sh
|
||||||
|
chmod +x /usr/local/bin/mp_update.sh
|
||||||
|
INFO "→ 已同步后端内置更新脚本"
|
||||||
|
fi
|
||||||
source /usr/local/bin/mp_update.sh
|
source /usr/local/bin/mp_update.sh
|
||||||
if [ "${ONE_SHOT_UPDATE_APPLIED}" = "true" ]; then
|
if [ "${ONE_SHOT_UPDATE_APPLIED}" = "true" ]; then
|
||||||
MOVIEPILOT_AUTO_UPDATE="${MOVIEPILOT_AUTO_UPDATE_ORIGINAL}"
|
MOVIEPILOT_AUTO_UPDATE="${MOVIEPILOT_AUTO_UPDATE_ORIGINAL}"
|
||||||
|
|||||||
@@ -8,6 +8,14 @@ MoviePilot keeps the established product packages such as `app/chain`,
|
|||||||
`app/helper` and `app/utils` roots are virtual compatibility packages only;
|
`app/helper` and `app/utils` roots are virtual compatibility packages only;
|
||||||
physical Python sources must not be recreated there.
|
physical Python sources must not be recreated there.
|
||||||
|
|
||||||
|
The sole filesystem exception is the non-Python `app/helper/.resource-compat`
|
||||||
|
marker retained in source archives for old Docker images whose updater still
|
||||||
|
writes compiled site resources to `/app/app/helper`. When the canonical site
|
||||||
|
extension is absent, `app/application/site/__init__.py` may add that directory
|
||||||
|
as a package search fallback. Current images and update flows must still write
|
||||||
|
only to `app/application/site/`; no Python implementation may return to the
|
||||||
|
legacy root.
|
||||||
|
|
||||||
Capabilities migrated out of those legacy roots are organized by technical
|
Capabilities migrated out of those legacy roots are organized by technical
|
||||||
responsibility:
|
responsibility:
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
from importlib.machinery import EXTENSION_SUFFIXES, PathFinder
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from app.application.site import _include_legacy_resource_directory
|
||||||
from app.runtime.config import settings
|
from app.runtime.config import settings
|
||||||
from app.adapters.system.resource import (
|
from app.adapters.system.resource import (
|
||||||
ResourceHelper,
|
ResourceHelper,
|
||||||
@@ -11,6 +13,41 @@ from app.startup import modules_initializer
|
|||||||
ROOT_DIR = Path(__file__).resolve().parents[1]
|
ROOT_DIR = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
def test_legacy_docker_updater_resource_directory_remains_importable(tmp_path):
|
||||||
|
"""旧镜像把资源写入 helper 时,canonical 站点包仍应找到对应扩展。"""
|
||||||
|
package_dir = tmp_path / "app" / "application" / "site"
|
||||||
|
legacy_dir = tmp_path / "app" / "helper"
|
||||||
|
package_dir.mkdir(parents=True)
|
||||||
|
legacy_dir.mkdir(parents=True)
|
||||||
|
extension_path = legacy_dir / f"sites{EXTENSION_SUFFIXES[0]}"
|
||||||
|
extension_path.touch()
|
||||||
|
package_paths = [str(package_dir)]
|
||||||
|
|
||||||
|
_include_legacy_resource_directory(package_paths, package_dir)
|
||||||
|
|
||||||
|
assert (ROOT_DIR / "app" / "helper" / ".resource-compat").is_file()
|
||||||
|
assert package_paths == [str(package_dir), str(legacy_dir)]
|
||||||
|
spec = PathFinder.find_spec("app.application.site.sites", package_paths)
|
||||||
|
assert spec is not None
|
||||||
|
assert spec.origin == str(extension_path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_canonical_site_extension_takes_priority_over_legacy_directory(tmp_path):
|
||||||
|
"""canonical 扩展存在时不得把旧资源目录加入站点包搜索路径。"""
|
||||||
|
package_dir = tmp_path / "app" / "application" / "site"
|
||||||
|
legacy_dir = tmp_path / "app" / "helper"
|
||||||
|
package_dir.mkdir(parents=True)
|
||||||
|
legacy_dir.mkdir(parents=True)
|
||||||
|
extension_name = f"sites{EXTENSION_SUFFIXES[0]}"
|
||||||
|
(package_dir / extension_name).touch()
|
||||||
|
(legacy_dir / extension_name).touch()
|
||||||
|
package_paths = [str(package_dir)]
|
||||||
|
|
||||||
|
_include_legacy_resource_directory(package_paths, package_dir)
|
||||||
|
|
||||||
|
assert package_paths == [str(package_dir)]
|
||||||
|
|
||||||
|
|
||||||
def test_resource_helper_uses_v3_only():
|
def test_resource_helper_uses_v3_only():
|
||||||
"""在线资源更新器必须只请求 V3 清单、目录和站点索引文件。"""
|
"""在线资源更新器必须只请求 V3 清单、目录和站点索引文件。"""
|
||||||
assert settings.VERSION_FLAG == "v3"
|
assert settings.VERSION_FLAG == "v3"
|
||||||
@@ -101,6 +138,16 @@ def test_install_and_docker_paths_do_not_reference_v2_resources():
|
|||||||
assert "app/application/site" in content
|
assert "app/application/site" in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_docker_entrypoint_refreshes_stale_update_script_before_use():
|
||||||
|
"""新镜像应在自动更新前同步源码内置脚本,避免目录约定再次陈旧。"""
|
||||||
|
content = (ROOT_DIR / "docker" / "entrypoint.sh").read_text(encoding="utf-8")
|
||||||
|
refresh = "cp -f /app/docker/update.sh /usr/local/bin/mp_update.sh"
|
||||||
|
source = "source /usr/local/bin/mp_update.sh"
|
||||||
|
|
||||||
|
assert refresh in content
|
||||||
|
assert content.index(refresh) < content.index(source)
|
||||||
|
|
||||||
|
|
||||||
def test_v3_release_workflows_use_main_wiki_and_isolated_images():
|
def test_v3_release_workflows_use_main_wiki_and_isolated_images():
|
||||||
"""V3 正式版和 Beta 构建应读取主 Wiki 分支并保持镜像仓库隔离。"""
|
"""V3 正式版和 Beta 构建应读取主 Wiki 分支并保持镜像仓库隔离。"""
|
||||||
build_workflow = (ROOT_DIR / ".github" / "workflows" / "build-v3.yml").read_text(encoding="utf-8")
|
build_workflow = (ROOT_DIR / ".github" / "workflows" / "build-v3.yml").read_text(encoding="utf-8")
|
||||||
|
|||||||
Reference in New Issue
Block a user