diff --git a/app.py b/app.py index 33bb5ed..3948228 100644 --- a/app.py +++ b/app.py @@ -7,7 +7,7 @@ import threading import time import shutil import re -from concurrent.futures import ThreadPoolExecutor +from concurrent.futures import ThreadPoolExecutor, as_completed from datetime import datetime from pathlib import Path @@ -31,6 +31,7 @@ logging.basicConfig(level=os.environ.get("LOG_LEVEL", "INFO").upper(), format="% log = logging.getLogger("radarr-language-dashboard") app = Flask(__name__) executor = ThreadPoolExecutor(max_workers=int(os.environ.get("SCAN_WORKERS", "2"))) +scan_executor = ThreadPoolExecutor(max_workers=int(os.environ.get("SCAN_WORKERS", "4"))) jobs = {"radarr": {"state": "idle", "total": 0, "done": 0, "error": None}, "sonarr": {"state": "idle", "total": 0, "done": 0, "error": None}} job_lock = threading.Lock() system_lock = threading.Lock() @@ -283,8 +284,9 @@ def start_scan(source, force=False): for group in sonarr_groups(): paths.extend(r["local_path"] for r in group["episodes"] if r["local_path"] != "—" and r["pending"]) with job_lock: jobs[source]["total"] = len(paths) - for path in paths: - scan_one(path) + futures = [scan_executor.submit(scan_one, path) for path in paths] + for future in as_completed(futures): + future.result() with job_lock: jobs[source]["done"] += 1 with job_lock: jobs[source]["state"] = "done" except Exception as exc: @@ -329,11 +331,17 @@ def index(): @app.post("/api/rescan") def api_rescan(): source = request.args.get("source", "radarr") - if source not in jobs: return jsonify({"ok":False, "error":"Unbekannte Quelle"}), 400 + if source not in (*jobs.keys(), "all"): return jsonify({"ok":False, "error":"Unbekannte Quelle"}), 400 con = db() - if source == "radarr": con.execute("DELETE FROM media_cache") - else: con.execute("DELETE FROM media_cache WHERE path LIKE ?", (LOCAL_SERIES_PATH.rstrip("/") + "/%",)) - con.commit(); con.close(); start_scan(source, force=True) + if source in ("radarr", "all"): con.execute("DELETE FROM media_cache WHERE path NOT LIKE ?", (LOCAL_SERIES_PATH.rstrip("/") + "/%",)) + if source in ("sonarr", "all"): con.execute("DELETE FROM media_cache WHERE path LIKE ?", (LOCAL_SERIES_PATH.rstrip("/") + "/%",)) + con.commit(); con.close() + if source == "all": + start_scan("radarr", force=True) + if SONARR_URL and SONARR_API_KEY: + start_scan("sonarr", force=True) + else: + start_scan(source, force=True) return jsonify({"ok":True}) @app.get("/api/scan-status") diff --git a/templates/index.html b/templates/index.html index df5d280..5affb7b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -9,8 +9,8 @@ .settings-modal .setting-help{display:block;margin:-3px 0 12px;color:var(--muted);font-size:11px}.settings-modal .setting-select{width:100%;height:38px}.pwa-install{display:none} @media(max-width:700px){body{font-size:12px;padding-bottom:env(safe-area-inset-bottom)}header{height:auto;min-height:62px;padding:9px 12px;gap:9px;align-items:center}.brand-mark,.brand-mark svg{width:30px;height:30px}.brand-name{font-size:14px}.head-title{font-size:12px}.head-sub{font-size:10px}.actions{gap:4px}.actions button{height:34px;padding:0 8px}.settings-button{font-size:15px}.actions #rescan{font-size:0}.actions #rescan::after{content:'↻';font-size:16px}main{padding:10px 10px 24px}.nav{margin-bottom:10px;padding:4px;overflow-x:auto;scrollbar-width:none}.nav::-webkit-scrollbar{display:none}.nav button{flex:0 0 auto;white-space:nowrap;padding:8px 10px;font-size:11px}.toolbar{grid-template-columns:1fr;gap:7px;padding:8px}.toolbar input,.toolbar select{width:100%;height:38px}.stats{gap:6px}.stat{padding:6px 8px}.tablewrap{overflow-x:auto;border-radius:8px}table{min-width:860px}.dashboard-hero{margin-bottom:10px;padding:18px;border-radius:11px}.dashboard-hero h1{font-size:21px}.dashboard-hero p{font-size:11px;line-height:1.4}.overview-grid{grid-template-columns:1fr 1fr;gap:7px}.overview-card{min-height:84px;padding:12px 10px;gap:8px}.overview-icon{width:32px;height:32px;font-size:18px}.overview-card strong{font-size:18px}.overview-card span:not(.overview-icon){font-size:9px}.overview-card small{font-size:9px}.dashboard-columns{gap:8px}.system-panel,.service-panel{padding:14px;border-radius:10px}.system-meters{gap:12px}.panel-heading{margin-bottom:13px}.panel-heading h2{font-size:14px}.panel-heading p{font-size:10px}.disk-row{grid-template-columns:82px 1fr auto;gap:7px}.disk-values{font-size:10px}.download-hero{padding:18px;margin-bottom:10px}.download-hero h2{font-size:19px}.download-hero p{font-size:11px}.download-stats{grid-template-columns:1fr 1fr;gap:7px}.download-card{min-height:78px;padding:10px;gap:8px}.card-icon{width:30px;height:30px;font-size:17px}.card-label{font-size:9px}.download-card strong{font-size:17px}.download-card small{font-size:9px}.settings-modal{max-height:calc(100dvh - 24px);overflow:auto;padding:18px}.settings-head{margin-bottom:15px}.settings-modal label{margin-top:13px}} @media(max-width:390px){.brand-name{display:none}.head-sub{display:none}.overview-grid{grid-template-columns:1fr}.dashboard-hero{padding:15px}.download-stats{grid-template-columns:1fr}} -
-