e3fa71b458
DirSize for large directories (e.g. /docker, 8441 folders, 46832 files) takes ~800ms to compute. While running, status returns intermediate progress (finished=false). But on the very first poll the task can return 599 transiently (task just started, not yet available). Previously _poll_task caught any SynologyError and returned immediately, making dir_size always fail on the first 599. Fix: treat 599 as a transient condition and continue polling. Give up only after 5 consecutive 599 responses. All other error codes remain immediately fatal. Investigation confirmed with test_dirsize_md5.py: - /test-mcp (2937 B): finished=true at 0ms - /docker (3.9 GB, 46832 files): finished=false at 35ms, finished=true at 789ms Tests: 2 new cases (retry-succeeds, 5x-599-gives-up) → 95 total Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
124 lines
4.2 KiB
Python
124 lines
4.2 KiB
Python
"""Wegwerfskript: DirSize + MD5 direkt gegen die NAS testen.
|
|
|
|
Ausfuehren: uv run python test_dirsize_md5.py
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import time
|
|
|
|
import httpx
|
|
|
|
from mcp_synology_filestation.auth import AuthManager
|
|
from mcp_synology_filestation.client import FileStationClient
|
|
from mcp_synology_filestation.config import load_config
|
|
|
|
DIRSIZE_PATHS = ["/test-mcp", "/docker"]
|
|
MD5_PATH = "/test-mcp/test.zip"
|
|
|
|
|
|
def pp(label: str, data: object, elapsed_ms: float | None = None) -> None:
|
|
print(f"\n{'='*60}")
|
|
suffix = f" [{elapsed_ms:.1f} ms]" if elapsed_ms is not None else ""
|
|
print(f" {label}{suffix}")
|
|
print("=" * 60)
|
|
print(json.dumps(data, indent=2, ensure_ascii=False))
|
|
|
|
|
|
async def raw(http: httpx.AsyncClient, url: str, sid: str, **params) -> dict:
|
|
r = await http.get(url, params={"_sid": sid, **params})
|
|
r.raise_for_status()
|
|
try:
|
|
return r.json()
|
|
except Exception:
|
|
return {"_raw": r.text[:300], "_http_status": r.status_code}
|
|
|
|
|
|
async def probe_dirsize_long(
|
|
http: httpx.AsyncClient, sid: str, api_url: str, path: str
|
|
) -> None:
|
|
"""Start DirSize and poll v1 every 200ms for up to 15s.
|
|
|
|
Goal: find out if 599 means 'task still running' (keep polling)
|
|
or 'task gone' (give up). If the task eventually returns data,
|
|
599 = 'not ready yet'. If it never returns data, 599 = 'task gone'.
|
|
"""
|
|
print(f"\n{'#'*60}")
|
|
print(f" DIRSIZE {path} — long poll (15s, every 200ms)")
|
|
print(f"{'#'*60}")
|
|
|
|
t0 = time.perf_counter()
|
|
start_body = await raw(
|
|
http, api_url, sid,
|
|
api="SYNO.FileStation.DirSize", version="2", method="start",
|
|
path=json.dumps([path]),
|
|
)
|
|
elapsed_start = (time.perf_counter() - t0) * 1000
|
|
pp(f"DirSize::start ({path})", start_body, elapsed_start)
|
|
|
|
taskid = (start_body.get("data") or {}).get("taskid")
|
|
if not taskid:
|
|
print("[!] No taskid.")
|
|
return
|
|
|
|
print(f"\n[*] Polling status v1 every 200ms for up to 15s (taskid={taskid[:12]}...)")
|
|
for attempt in range(75): # 75 * 200ms = 15s
|
|
if attempt > 0:
|
|
await asyncio.sleep(0.2)
|
|
t = time.perf_counter()
|
|
r = await raw(
|
|
http, api_url, sid,
|
|
api="SYNO.FileStation.DirSize", version="1", method="status",
|
|
taskid=taskid,
|
|
)
|
|
elapsed = (t - t0) * 1000
|
|
success = r.get("success")
|
|
data = (r.get("data") or {})
|
|
finished = data.get("finished")
|
|
error_code = (r.get("error") or {}).get("code")
|
|
|
|
if finished:
|
|
print(f" [{elapsed:.0f}ms] attempt {attempt+1}: FERTIG! "
|
|
f"num_dir={data.get('num_dir')} "
|
|
f"num_file={data.get('num_file')} "
|
|
f"total_size={data.get('total_size')}")
|
|
pp(f"DirSize::status final ({path})", r, elapsed)
|
|
return
|
|
elif success and not finished:
|
|
# Still running — show current progress
|
|
print(f" [{elapsed:.0f}ms] attempt {attempt+1}: running... "
|
|
f"num_dir={data.get('num_dir', '?')} "
|
|
f"num_file={data.get('num_file', '?')} "
|
|
f"total_size={data.get('total_size', '?')}")
|
|
else:
|
|
print(f" [{elapsed:.0f}ms] attempt {attempt+1}: error code={error_code}")
|
|
# Continue polling — 599 might mean 'not ready yet'
|
|
|
|
print(f"\n[!] No result after 15s — task never returned data.")
|
|
|
|
|
|
async def main() -> None:
|
|
config = load_config()
|
|
auth = AuthManager(config)
|
|
|
|
async with FileStationClient(config.base_url, config.connection.verify_ssl) as client:
|
|
client.set_auth_manager(auth)
|
|
await client._ensure_initialized() # noqa: SLF001
|
|
sid = client.sid
|
|
base = config.base_url
|
|
|
|
info = client._api_cache.get("SYNO.FileStation.DirSize", {}) # noqa: SLF001
|
|
api_url = f"{base}/webapi/{info.get('path', 'entry.cgi')}"
|
|
print(f"[*] DirSize API: {api_url} v{info.get('minVersion')}-v{info.get('maxVersion')}")
|
|
|
|
async with httpx.AsyncClient(verify=config.connection.verify_ssl, timeout=30.0) as http:
|
|
for path in DIRSIZE_PATHS:
|
|
await probe_dirsize_long(http, sid, api_url, path)
|
|
|
|
await auth.logout(client)
|
|
print("\n[*] Logout OK.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|