110 lines
4.1 KiB
Python
110 lines
4.1 KiB
Python
# --- routers/leaf_options.py ---
|
|
from fastapi import APIRouter, BackgroundTasks, HTTPException, Body
|
|
from fastapi.responses import StreamingResponse
|
|
from pydantic import BaseModel
|
|
from typing import List
|
|
import json, os, asyncio, time, re
|
|
|
|
# 🌟 引入新的 asyncssh 版本爬蟲
|
|
from cmts_scraper import sync_cmts_leaves_async
|
|
from shared import CMTS_DEVICE
|
|
|
|
# 🌟 移除原本硬寫的 prefix="/api/v1",只留下 tags
|
|
router = APIRouter(tags=["Options"])
|
|
CACHE_FILE = "cmts_leaf_options_cache.json"
|
|
|
|
# 🌟 1. 改用全域布林變數,狀態切換更即時,消滅非同步時間差
|
|
IS_SCANNING = False
|
|
|
|
# ==========================================
|
|
# 🌟 新增:讓前端查詢目前是否有人正在掃描
|
|
# ==========================================
|
|
@router.get("/scan-status")
|
|
async def get_scan_status():
|
|
global IS_SCANNING
|
|
return {"is_scanning": IS_SCANNING}
|
|
|
|
class SyncOptionsRequest(BaseModel):
|
|
leaf_paths: List[str]
|
|
|
|
@router.get("/cmts-leaf-options")
|
|
async def get_leaf_options():
|
|
if not os.path.exists(CACHE_FILE): return {}
|
|
with open(CACHE_FILE, "r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
|
|
@router.post("/cmts-leaf-options/sync")
|
|
async def sync_leaf_options(request: SyncOptionsRequest):
|
|
global IS_SCANNING # 宣告我們要修改全域變數
|
|
|
|
if not request.leaf_paths:
|
|
raise HTTPException(status_code=400)
|
|
|
|
# 🌟 2. 第一時間檢查並「立即」阻擋
|
|
if IS_SCANNING:
|
|
async def busy_stream():
|
|
yield json.dumps({"event": "error", "message": "⚠️ 掃描任務正在執行中,請勿重複點擊!"}) + "\n"
|
|
return StreamingResponse(busy_stream(), media_type="text/event-stream")
|
|
|
|
# 如果沒人掃描,立刻把狀態改為 True (在建立串流之前就上鎖)
|
|
IS_SCANNING = True
|
|
|
|
async def event_generator():
|
|
global IS_SCANNING
|
|
try:
|
|
cmts_query_paths = []
|
|
for p in request.leaf_paths:
|
|
clean_p = p.replace("::", " ")
|
|
clean_p = re.sub(r"\s*\[\d+\]", "", clean_p)
|
|
cmts_query_paths.append(clean_p)
|
|
|
|
cmts_query_paths = list(dict.fromkeys(cmts_query_paths))
|
|
|
|
# 🌟 因為爬蟲自己會存檔,我們只要專心把進度轉發給前端就好!
|
|
async for chunk in sync_cmts_leaves_async(
|
|
host=CMTS_DEVICE.get("host"),
|
|
username=CMTS_DEVICE.get("username"),
|
|
password=CMTS_DEVICE.get("password"),
|
|
leaf_paths=cmts_query_paths
|
|
):
|
|
yield chunk
|
|
|
|
except Exception as e:
|
|
yield json.dumps({"event": "error", "message": str(e)}) + "\n"
|
|
|
|
finally:
|
|
# 🌟 3. 無論成功、失敗、或使用者提早關閉網頁,都確保會解鎖
|
|
IS_SCANNING = False
|
|
|
|
return StreamingResponse(event_generator(), media_type="text/event-stream")
|
|
|
|
# 🌟 新增:清除特定選項快取的 API (使用您原本定義的 CACHE_FILE)
|
|
@router.post("/clear_cache")
|
|
async def clear_specific_cache(paths_to_clear: list = Body(...)):
|
|
cleared_count = 0
|
|
|
|
# 1. 檢查快取檔案是否存在
|
|
if not os.path.exists(CACHE_FILE):
|
|
return {"status": "success", "cleared_count": 0, "message": "快取檔案不存在"}
|
|
|
|
try:
|
|
# 2. 讀取現有的 JSON 快取資料
|
|
with open(CACHE_FILE, "r", encoding="utf-8") as f:
|
|
cache_data = json.load(f)
|
|
|
|
# 3. 逐一比對並刪除指定的路徑
|
|
for path in paths_to_clear:
|
|
if path in cache_data:
|
|
del cache_data[path]
|
|
cleared_count += 1
|
|
|
|
# 4. 如果有刪除資料,將更新後的內容寫回 JSON 檔案
|
|
if cleared_count > 0:
|
|
with open(CACHE_FILE, "w", encoding="utf-8") as f:
|
|
json.dump(cache_data, f, ensure_ascii=False, indent=4)
|
|
|
|
return {"status": "success", "cleared_count": cleared_count}
|
|
|
|
except Exception as e:
|
|
return {"status": "error", "message": f"清除快取時發生錯誤: {str(e)}"}
|