from __future__ import annotations from ._shared import * from ..services.rtorrent.diagnostics import profile_diagnostics @bp.get("/profiles") def profiles_list(): return ok({"profiles": preferences.list_profiles(), "active": preferences.active_profile()}) @bp.post("/profiles") def profiles_create(): try: return ok({"profile": preferences.save_profile(request.json or {})}) except Exception as exc: return jsonify({"ok": False, "error": str(exc)}), 400 @bp.put("/profiles/") def profiles_update(profile_id: int): try: return ok({"profile": preferences.update_profile(profile_id, request.json or {})}) except Exception as exc: return jsonify({"ok": False, "error": str(exc)}), 400 @bp.delete("/profiles/") def profiles_delete(profile_id: int): preferences.delete_profile(profile_id) return ok({"profiles": preferences.list_profiles(), "active": preferences.active_profile()}) @bp.post("/profiles//activate") def profiles_activate(profile_id: int): try: return ok({"profile": preferences.activate_profile(profile_id)}) except Exception as exc: return jsonify({"ok": False, "error": str(exc)}), 404 @bp.post("/profiles/test") def profiles_test_unsaved(): data = request.get_json(silent=True) or {} profile = { "id": data.get("id"), "name": data.get("name") or "test", "scgi_url": data.get("scgi_url") or "", "timeout_seconds": data.get("timeout_seconds") or 5, } return ok({"diagnostics": profile_diagnostics(profile)}) @bp.get("/profiles//diagnostics") def profiles_diagnostics(profile_id: int): profile = preferences.get_profile(profile_id) if not profile: return jsonify({"ok": False, "error": "Profile not found"}), 404 return ok({"diagnostics": profile_diagnostics(profile)}) @bp.get("/profiles/diagnostics") def profiles_diagnostics_all(): rows = preferences.list_profiles() diagnostics = [] for profile in rows: diagnostics.append(profile_diagnostics(profile)) return ok({"diagnostics": diagnostics}) @bp.get("/profiles/export") def profiles_export(): return ok(preferences.export_profiles()) @bp.post("/profiles/import") def profiles_import(): try: rows = preferences.import_profiles(request.get_json(silent=True) or {}) return ok({"profiles": rows}) except Exception as exc: return jsonify({"ok": False, "error": str(exc)}), 400 @bp.get("/preferences") def prefs_get(): return ok({"preferences": preferences.get_preferences()}) @bp.post("/preferences") def prefs_save(): return ok({"preferences": preferences.save_preferences(request.json or {})}) @bp.post("/preferences/table-columns/recommended") def prefs_table_columns_recommended(): # Note: Applies the backend-owned recommended desktop and mobile column layout. return ok({"preferences": preferences.apply_recommended_table_columns()}) @bp.get("/labels") def labels_list(): profile = preferences.active_profile() pid = profile["id"] if profile else None with connect() as conn: rows = conn.execute("SELECT * FROM labels WHERE user_id=? AND (profile_id=? OR profile_id IS NULL) ORDER BY name COLLATE NOCASE", (default_user_id(), pid)).fetchall() return ok({"labels": rows}) @bp.post("/labels") def labels_save(): profile = preferences.active_profile() if not profile: return jsonify({"ok": False, "error": "No profile"}), 400 data = request.get_json(silent=True) or {} name = str(data.get("name") or "").strip() if not name: return jsonify({"ok": False, "error": "Missing label name"}), 400 now = utcnow() with connect() as conn: conn.execute("INSERT OR IGNORE INTO labels(user_id,profile_id,name,color,created_at,updated_at) VALUES(?,?,?,?,?,?)", (default_user_id(), profile["id"], name, data.get("color") or "#64748b", now, now)) return labels_list() @bp.delete("/labels/") def labels_delete(label_id: int): profile = preferences.active_profile() pid = profile["id"] if profile else None with connect() as conn: conn.execute("DELETE FROM labels WHERE id=? AND user_id=? AND (profile_id=? OR profile_id IS NULL)", (label_id, default_user_id(), pid)) return labels_list() @bp.get("/ratio-groups") def ratio_groups_list(): profile = preferences.active_profile() pid = profile["id"] if profile else None with connect() as conn: rows = conn.execute("SELECT * FROM ratio_groups WHERE user_id=? AND (profile_id=? OR profile_id IS NULL) ORDER BY name COLLATE NOCASE", (default_user_id(), pid)).fetchall() history = conn.execute("SELECT * FROM ratio_history WHERE user_id=? AND profile_id=? ORDER BY id DESC LIMIT 50", (default_user_id(), pid or 0)).fetchall() if pid else [] return ok({"groups": rows, "history": history}) @bp.post("/ratio-groups") def ratio_groups_save(): profile = preferences.active_profile() if not profile: return jsonify({"ok": False, "error": "No profile"}), 400 data = request.get_json(silent=True) or {} name = str(data.get("name") or "").strip() if not name: return jsonify({"ok": False, "error": "Missing group name"}), 400 now = utcnow() with connect() as conn: conn.execute( """INSERT INTO ratio_groups(user_id,profile_id,name,min_ratio,max_ratio,seed_time_minutes,min_seed_time_minutes,ignore_private,ignore_active_upload,active_upload_min_bytes,move_path,set_label,action,enabled,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT(user_id,profile_id,name) DO UPDATE SET min_ratio=excluded.min_ratio,max_ratio=excluded.max_ratio,seed_time_minutes=excluded.seed_time_minutes,min_seed_time_minutes=excluded.min_seed_time_minutes,ignore_private=excluded.ignore_private,ignore_active_upload=excluded.ignore_active_upload,active_upload_min_bytes=excluded.active_upload_min_bytes,move_path=excluded.move_path,set_label=excluded.set_label,action=excluded.action,enabled=excluded.enabled,updated_at=excluded.updated_at""", (default_user_id(), profile["id"], name, float(data.get("min_ratio") or 1), float(data.get("max_ratio") or 2), int(data.get("seed_time_minutes") or 0), int(data.get("min_seed_time_minutes") or 0), 1 if data.get("ignore_private", True) else 0, 1 if data.get("ignore_active_upload", True) else 0, int(data.get("active_upload_min_bytes") or 1024), data.get("move_path") or "", data.get("set_label") or "", data.get("action") or "stop", 1 if data.get("enabled", True) else 0, now, now), ) return ratio_groups_list() @bp.post("/ratio-groups/check") def ratio_groups_check(): profile = preferences.active_profile() if not profile: return jsonify({"ok": False, "error": "No profile"}), 400 return ok({"result": ratio_rules.check(profile, default_user_id())})