fix: Race Condition bei gleichzeitigen Downloads in savetv_web.py

- Thread-Lock fuer Progress-Datei: verhindert korruptes JSON bei vielen simultanen Downloads
- start_new_session=True in Popen: wget-Prozesse ueberleben Service-Neustarts
- Atomic load+modify+save Pattern via _load_progress_raw/_save_progress_raw
This commit is contained in:
Homelab Cursor 2026-03-21 18:56:11 +01:00
parent 25642d6b62
commit 32da34b3c2

View file

@ -43,9 +43,11 @@ def _save_download_log(log):
DOWNLOAD_PROGRESS = Path("/mnt/savetv/.download_progress.json") DOWNLOAD_PROGRESS = Path("/mnt/savetv/.download_progress.json")
DOWNLOAD_DIR = Path("/mnt/savetv") DOWNLOAD_DIR = Path("/mnt/savetv")
_PROGRESS_LOCK = threading.Lock()
def _load_progress(): def _load_progress_raw():
"""Liest Progress-Datei ohne Lock (nur innerhalb von _PROGRESS_LOCK aufrufen)."""
if DOWNLOAD_PROGRESS.exists(): if DOWNLOAD_PROGRESS.exists():
try: try:
return json.loads(DOWNLOAD_PROGRESS.read_text()) return json.loads(DOWNLOAD_PROGRESS.read_text())
@ -54,11 +56,22 @@ def _load_progress():
return {} return {}
def _save_progress_raw(prog):
"""Schreibt Progress-Datei ohne Lock (nur innerhalb von _PROGRESS_LOCK aufrufen)."""
DOWNLOAD_PROGRESS.write_text(json.dumps(prog, ensure_ascii=False, indent=2))
def _load_progress():
with _PROGRESS_LOCK:
return _load_progress_raw()
def _save_progress(prog): def _save_progress(prog):
try: with _PROGRESS_LOCK:
DOWNLOAD_PROGRESS.write_text(json.dumps(prog, ensure_ascii=False, indent=2)) try:
except Exception: _save_progress_raw(prog)
pass except Exception:
pass
def _head_content_length(url): def _head_content_length(url):
@ -722,12 +735,14 @@ def api_download():
filename = f"{safe_title}_{tid}.mp4" filename = f"{safe_title}_{tid}.mp4"
target = DOWNLOAD_DIR / filename target = DOWNLOAD_DIR / filename
progress[str(tid)] = { with _PROGRESS_LOCK:
"filename": filename, cur_progress = _load_progress_raw()
"expected_bytes": expected_bytes, cur_progress[str(tid)] = {
"started_at": datetime.now().isoformat(), "filename": filename,
} "expected_bytes": expected_bytes,
_save_progress(progress) "started_at": datetime.now().isoformat(),
}
_save_progress_raw(cur_progress)
DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True) DOWNLOAD_DIR.mkdir(parents=True, exist_ok=True)
try: try:
@ -735,10 +750,13 @@ def api_download():
["wget", "-q", "-O", str(target), url], ["wget", "-q", "-O", str(target), url],
stdout=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
start_new_session=True,
) )
except Exception as e: except Exception as e:
progress.pop(str(tid), None) with _PROGRESS_LOCK:
_save_progress(progress) cur_progress = _load_progress_raw()
cur_progress.pop(str(tid), None)
_save_progress_raw(cur_progress)
return {"tid": tid, "ok": False, "error": f"wget: {e}"} return {"tid": tid, "ok": False, "error": f"wget: {e}"}
dl_log[str(tid)] = "running" dl_log[str(tid)] = "running"