Sync all playlists and track deletions
This commit is contained in:
+111
-17
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
from typing import Tuple
|
||||
from app.utils.config import server_config
|
||||
from app.utils.playlist_merge import SyncMode, sync_all_playlists, TEST_PLAYLIST_DIR
|
||||
from fastapi import FastAPI, Request, Form
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
@@ -23,6 +24,30 @@ app.mount(
|
||||
)
|
||||
|
||||
|
||||
SYNC_MODE_OPTIONS = [
|
||||
{
|
||||
"value": SyncMode.LOCAL_FORCE.value,
|
||||
"label": "完全本地优先(local_force)",
|
||||
"description": "单向同步,本地覆盖云端且顺序以本地为准。",
|
||||
},
|
||||
{
|
||||
"value": SyncMode.REMOTE_FORCE.value,
|
||||
"label": "完全云端优先(remote_force)",
|
||||
"description": "单向同步,云端覆盖本地且顺序以云端为准。",
|
||||
},
|
||||
{
|
||||
"value": SyncMode.MERGE_LOCAL_PRIMARY.value,
|
||||
"label": "双向合并(本地优先)",
|
||||
"description": "三方合并,冲突时选择本地版本。",
|
||||
},
|
||||
{
|
||||
"value": SyncMode.MERGE_REMOTE_PRIMARY.value,
|
||||
"label": "双向合并(云端优先)",
|
||||
"description": "三方合并,冲突时选择云端版本。",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def _get_cloud_playlists() -> Tuple[list[dict], str, dict]:
|
||||
"""Fetch playlists and connection state from the remote Plex server."""
|
||||
|
||||
@@ -74,27 +99,96 @@ def _get_cloud_playlists() -> Tuple[list[dict], str, dict]:
|
||||
playlists.sort(key=lambda item: item["name"].lower())
|
||||
return playlists, status, server_info
|
||||
|
||||
|
||||
# 显示主页
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def home(request: Request, local_path: str = "playlist"):
|
||||
def _build_home_context(
|
||||
request: Request,
|
||||
local_path: str,
|
||||
message: str | None = None,
|
||||
message_type: str | None = None,
|
||||
sync_result: dict | None = None,
|
||||
selected_mode: str | None = None,
|
||||
):
|
||||
server_config.load()
|
||||
local_playlists = scan_local_playlists(local_path)
|
||||
cloud_playlists, connection_status, server_info = _get_cloud_playlists()
|
||||
|
||||
return templates.TemplateResponse(
|
||||
"home.html",
|
||||
{
|
||||
"request": request,
|
||||
"theme": server_config.theme,
|
||||
"path": "/",
|
||||
"local_playlists": local_playlists,
|
||||
"local_path": local_path,
|
||||
"cloud_playlists": cloud_playlists,
|
||||
"connection_status": connection_status,
|
||||
"server_info": server_info,
|
||||
},
|
||||
)
|
||||
return {
|
||||
"request": request,
|
||||
"theme": server_config.theme,
|
||||
"path": "/",
|
||||
"local_playlists": local_playlists,
|
||||
"local_path": local_path,
|
||||
"cloud_playlists": cloud_playlists,
|
||||
"connection_status": connection_status,
|
||||
"server_info": server_info,
|
||||
"sync_modes": SYNC_MODE_OPTIONS,
|
||||
"selected_mode": selected_mode,
|
||||
"message": message,
|
||||
"message_type": message_type,
|
||||
"sync_result": sync_result,
|
||||
}
|
||||
|
||||
|
||||
# 显示主页
|
||||
@app.get("/", response_class=HTMLResponse)
|
||||
async def home(request: Request, local_path: str = "playlist"):
|
||||
context = _build_home_context(request, local_path)
|
||||
|
||||
return templates.TemplateResponse("home.html", context)
|
||||
|
||||
|
||||
@app.post("/sync", response_class=HTMLResponse)
|
||||
async def trigger_sync(request: Request, mode: str = Form(...), local_path: str = Form("playlist")):
|
||||
try:
|
||||
sync_mode = SyncMode(mode)
|
||||
except ValueError:
|
||||
context = _build_home_context(
|
||||
request,
|
||||
local_path,
|
||||
message=f"未知的同步策略:{mode}",
|
||||
message_type="danger",
|
||||
selected_mode=mode,
|
||||
)
|
||||
return templates.TemplateResponse("home.html", context)
|
||||
|
||||
try:
|
||||
results = sync_all_playlists(
|
||||
local_dir=local_path,
|
||||
mode=sync_mode,
|
||||
test_folder=TEST_PLAYLIST_DIR,
|
||||
)
|
||||
merged_count = sum(len(item.merged_paths) for item in results)
|
||||
conflict_count = sum(len(item.conflicts) for item in results)
|
||||
deleted_count = sum(1 for item in results if item.action == "deleted")
|
||||
context = _build_home_context(
|
||||
request,
|
||||
local_path,
|
||||
message="同步完成,输出已写入测试目录用于验证。",
|
||||
message_type="success",
|
||||
sync_result={
|
||||
"mode": sync_mode.value,
|
||||
"mode_label": next(
|
||||
(item["label"] for item in SYNC_MODE_OPTIONS if item["value"] == sync_mode.value),
|
||||
sync_mode.value,
|
||||
),
|
||||
"merged_count": merged_count,
|
||||
"conflict_count": conflict_count,
|
||||
"delete_count": deleted_count,
|
||||
"playlist_count": len(results),
|
||||
"output_dir": TEST_PLAYLIST_DIR,
|
||||
},
|
||||
selected_mode=sync_mode.value,
|
||||
)
|
||||
return templates.TemplateResponse("home.html", context)
|
||||
except Exception as exc:
|
||||
logger.warning(f"Sync failed: {exc}")
|
||||
context = _build_home_context(
|
||||
request,
|
||||
local_path,
|
||||
message=f"同步失败:{exc}",
|
||||
message_type="danger",
|
||||
selected_mode=sync_mode.value,
|
||||
)
|
||||
return templates.TemplateResponse("home.html", context)
|
||||
|
||||
|
||||
# 登录页面和处理
|
||||
|
||||
Reference in New Issue
Block a user