profile id support in api requests
This commit is contained in:
@@ -23,7 +23,7 @@ from flask import Blueprint, jsonify, request, abort, send_file, redirect, Respo
|
||||
from ..config import DB_PATH, JOBS_RETENTION_DAYS, SMART_QUEUE_HISTORY_RETENTION_DAYS, LOG_RETENTION_DAYS, WORKERS, PYTORRENT_TMP_DIR
|
||||
from ..db import connect, utcnow
|
||||
from ..services.auth import current_user_id as default_user_id, current_user, list_users, save_user, delete_user, login_user, logout_user, enabled as auth_enabled, require_profile_write, require_admin, is_admin
|
||||
from ..services import preferences, rtorrent, torrent_stats, speed_peaks, tracker_cache, rss as rss_service, ratio_rules, backup as backup_service, download_planner, operation_logs, poller_control, database_maintenance
|
||||
from ..services import auth, preferences, rtorrent, torrent_stats, speed_peaks, tracker_cache, rss as rss_service, ratio_rules, backup as backup_service, download_planner, operation_logs, poller_control, database_maintenance
|
||||
from ..services.torrent_cache import torrent_cache
|
||||
from ..services.torrent_summary import cached_summary
|
||||
from ..services.workers import enqueue, list_jobs, cancel_job, retry_job, force_job, clear_jobs, emergency_clear_jobs
|
||||
@@ -39,6 +39,78 @@ from .auth_api import register_auth_routes
|
||||
register_auth_routes(bp)
|
||||
|
||||
|
||||
|
||||
|
||||
def _request_profile_selector() -> tuple[int | None, str]:
|
||||
"""Return the optional profile selector supplied by external API clients."""
|
||||
payload = {}
|
||||
if request.method in {"POST", "PUT", "PATCH", "DELETE"}:
|
||||
try:
|
||||
payload = request.get_json(silent=True) or {}
|
||||
except Exception:
|
||||
payload = {}
|
||||
profile_id = request.args.get("profile_id") or request.form.get("profile_id") or payload.get("profile_id") or request.headers.get("X-PyTorrent-Profile-Id")
|
||||
profile_name = request.args.get("profile_name") or request.form.get("profile_name") or payload.get("profile_name") or request.headers.get("X-PyTorrent-Profile-Name") or ""
|
||||
try:
|
||||
return (int(profile_id), "") if profile_id not in (None, "") else (None, str(profile_name or "").strip())
|
||||
except (TypeError, ValueError):
|
||||
raise ValueError("profile_id must be an integer")
|
||||
|
||||
|
||||
def _profile_by_name(profile_name: str, user_id: int | None = None):
|
||||
name = str(profile_name or "").strip()
|
||||
if not name:
|
||||
return None
|
||||
user_id = user_id or default_user_id()
|
||||
visible = auth.visible_profile_ids(user_id)
|
||||
with connect() as conn:
|
||||
if visible is None:
|
||||
return conn.execute(
|
||||
"SELECT * FROM rtorrent_profiles WHERE lower(name)=lower(?) ORDER BY is_default DESC, id LIMIT 1",
|
||||
(name,),
|
||||
).fetchone()
|
||||
if not visible:
|
||||
return None
|
||||
placeholders = ",".join("?" for _ in visible)
|
||||
return conn.execute(
|
||||
f"SELECT * FROM rtorrent_profiles WHERE id IN ({placeholders}) AND lower(name)=lower(?) ORDER BY is_default DESC, id LIMIT 1",
|
||||
(*tuple(visible), name),
|
||||
).fetchone()
|
||||
|
||||
|
||||
def request_profile(require_write: bool = False):
|
||||
"""Resolve API profile context from profile_id/profile_name, then active profile for compatibility."""
|
||||
try:
|
||||
profile_id, profile_name = _request_profile_selector()
|
||||
except ValueError:
|
||||
raise
|
||||
user_id = default_user_id()
|
||||
profile = None
|
||||
if profile_id:
|
||||
profile = preferences.get_profile(int(profile_id), user_id)
|
||||
elif profile_name:
|
||||
profile = _profile_by_name(profile_name, user_id)
|
||||
else:
|
||||
profile = preferences.active_profile(user_id)
|
||||
if not profile and auth.can_access_profile(1, user_id):
|
||||
profile = preferences.get_profile(1, user_id)
|
||||
if not profile and (profile_id or profile_name):
|
||||
abort(404)
|
||||
if not profile:
|
||||
return None
|
||||
pid = int(profile["id"])
|
||||
if require_write and not auth.can_write_profile(pid, user_id):
|
||||
abort(403)
|
||||
if not require_write and not auth.can_access_profile(pid, user_id):
|
||||
abort(403)
|
||||
return profile
|
||||
|
||||
|
||||
def request_profile_id(require_write: bool = False) -> int | None:
|
||||
profile = request_profile(require_write=require_write)
|
||||
return int(profile["id"]) if profile else None
|
||||
|
||||
|
||||
def _job_profile_id(job_id: str) -> int | None:
|
||||
with connect() as conn:
|
||||
row = conn.execute("SELECT profile_id FROM jobs WHERE id=?", (job_id,)).fetchone()
|
||||
|
||||
@@ -10,7 +10,7 @@ def _automation_user_id() -> int:
|
||||
@bp.get('/automations')
|
||||
def automations_get():
|
||||
from ..services import automation_rules
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return ok({'rules': [], 'history': [], 'error': 'No profile'})
|
||||
try:
|
||||
@@ -26,7 +26,7 @@ def automations_get():
|
||||
@bp.get('/automations/export')
|
||||
def automations_export():
|
||||
from ..services import automation_rules
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||
try:
|
||||
@@ -39,7 +39,7 @@ def automations_export():
|
||||
@bp.post('/automations/import')
|
||||
def automations_import():
|
||||
from ..services import automation_rules
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||
try:
|
||||
@@ -55,7 +55,7 @@ def automations_import():
|
||||
@bp.post('/automations')
|
||||
def automations_save():
|
||||
from ..services import automation_rules
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||
try:
|
||||
@@ -69,7 +69,7 @@ def automations_save():
|
||||
@bp.delete('/automations/<int:rule_id>')
|
||||
def automations_delete(rule_id: int):
|
||||
from ..services import automation_rules
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||
try:
|
||||
@@ -83,7 +83,7 @@ def automations_delete(rule_id: int):
|
||||
@bp.post('/automations/<int:rule_id>/run')
|
||||
def automations_run_rule(rule_id: int):
|
||||
from ..services import automation_rules
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||
try:
|
||||
@@ -100,7 +100,7 @@ def automations_run_rule(rule_id: int):
|
||||
@bp.post('/automations/check')
|
||||
def automations_check():
|
||||
from ..services import automation_rules
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||
try:
|
||||
@@ -117,7 +117,7 @@ def automations_check():
|
||||
@bp.delete('/automations/history')
|
||||
def automations_history_clear():
|
||||
from ..services import automation_rules
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||
try:
|
||||
|
||||
@@ -4,8 +4,8 @@ from ._shared import *
|
||||
from ..services import auth
|
||||
|
||||
|
||||
def _active_profile_id() -> int | None:
|
||||
profile = preferences.active_profile()
|
||||
def _active_profile_id(require_write: bool = False) -> int | None:
|
||||
profile = request_profile(require_write=require_write)
|
||||
return int(profile["id"]) if profile else None
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ def backup_list():
|
||||
@bp.post("/backup/profile")
|
||||
def backup_create_profile():
|
||||
data = request.get_json(silent=True) or {}
|
||||
pid = _active_profile_id()
|
||||
pid = _active_profile_id(require_write=True)
|
||||
if not pid:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
try:
|
||||
@@ -84,7 +84,7 @@ def profile_backup_settings_get():
|
||||
@bp.post("/backup/profile/settings")
|
||||
def profile_backup_settings_save():
|
||||
data = request.get_json(silent=True) or {}
|
||||
pid = _active_profile_id()
|
||||
pid = _active_profile_id(require_write=True)
|
||||
if not pid:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
try:
|
||||
@@ -104,7 +104,7 @@ def backup_preview(backup_id: int):
|
||||
@bp.post("/backup/<int:backup_id>/restore")
|
||||
def backup_restore(backup_id: int):
|
||||
try:
|
||||
pid = _active_profile_id()
|
||||
pid = _active_profile_id(require_write=True)
|
||||
return ok({"result": backup_service.restore_backup(backup_id, default_user_id(), profile_id=pid)})
|
||||
except Exception as exc:
|
||||
return jsonify({"ok": False, "error": str(exc)}), 403 if isinstance(exc, PermissionError) else 400
|
||||
|
||||
@@ -5,7 +5,7 @@ from ..services import operation_logs
|
||||
|
||||
|
||||
def _active_profile_or_400():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return None
|
||||
return profile
|
||||
|
||||
@@ -16,7 +16,7 @@ def ok(payload=None):
|
||||
|
||||
|
||||
def _profile_or_error():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return None, (jsonify({"ok": False, "error": "No profile"}), 400)
|
||||
return profile, None
|
||||
|
||||
@@ -6,7 +6,15 @@ from ..services import auth
|
||||
|
||||
@bp.get("/profiles")
|
||||
def profiles_list():
|
||||
return ok({"profiles": preferences.list_profiles(), "active": preferences.active_profile()})
|
||||
profiles = []
|
||||
for row in preferences.list_profiles():
|
||||
item = dict(row)
|
||||
settings = backup_service.get_auto_backup_settings(default_user_id(), "profile", int(item.get("id") or 0))
|
||||
item["profile_backup_enabled"] = bool(settings.get("enabled"))
|
||||
item["profile_backup_interval_hours"] = settings.get("interval_hours")
|
||||
item["profile_backup_retention_days"] = settings.get("retention_days")
|
||||
profiles.append(item)
|
||||
return ok({"profiles": profiles, "active": preferences.active_profile()})
|
||||
|
||||
|
||||
|
||||
@@ -89,25 +97,25 @@ def profiles_import():
|
||||
|
||||
@bp.get("/preferences")
|
||||
def prefs_get():
|
||||
return ok({"preferences": preferences.get_preferences()})
|
||||
return ok({"preferences": preferences.get_preferences(profile_id=request_profile_id())})
|
||||
|
||||
|
||||
|
||||
@bp.post("/preferences")
|
||||
def prefs_save():
|
||||
return ok({"preferences": preferences.save_preferences(request.json or {})})
|
||||
return ok({"preferences": preferences.save_preferences(request.json or {}, profile_id=request_profile_id(require_write=True))})
|
||||
|
||||
|
||||
@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()})
|
||||
return ok({"preferences": preferences.apply_recommended_table_columns(profile_id=request_profile_id(require_write=True))})
|
||||
|
||||
|
||||
|
||||
@bp.get("/labels")
|
||||
def labels_list():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
pid = profile["id"] if profile else None
|
||||
if not pid:
|
||||
return ok({"labels": []})
|
||||
@@ -128,7 +136,7 @@ def labels_list():
|
||||
|
||||
@bp.post("/labels")
|
||||
def labels_save():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
data = request.get_json(silent=True) or {}
|
||||
@@ -150,7 +158,7 @@ def labels_save():
|
||||
|
||||
@bp.delete("/labels/<int:label_id>")
|
||||
def labels_delete(label_id: int):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
pid = profile["id"] if profile else None
|
||||
if not pid or not auth.can_write_profile(int(pid), default_user_id()):
|
||||
return jsonify({"ok": False, "error": "No write access to profile"}), 403
|
||||
@@ -162,7 +170,7 @@ def labels_delete(label_id: int):
|
||||
|
||||
@bp.get("/ratio-groups")
|
||||
def ratio_groups_list():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
pid = profile["id"] if profile else None
|
||||
with connect() as conn:
|
||||
rows = conn.execute(
|
||||
@@ -182,7 +190,7 @@ def ratio_groups_list():
|
||||
|
||||
@bp.post("/ratio-groups")
|
||||
def ratio_groups_save():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
data = request.get_json(silent=True) or {}
|
||||
@@ -212,7 +220,7 @@ def ratio_groups_save():
|
||||
|
||||
@bp.post("/ratio-groups/check")
|
||||
def ratio_groups_check():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
return ok({"result": ratio_rules.check(profile, default_user_id())})
|
||||
|
||||
@@ -4,7 +4,7 @@ from ._shared import *
|
||||
|
||||
|
||||
def _active_profile_or_400():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return None
|
||||
return profile
|
||||
@@ -117,7 +117,7 @@ def rss_rule_test():
|
||||
|
||||
@bp.post("/rss/check")
|
||||
def rss_check():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
return ok(rss_service.check(profile, only_due=False))
|
||||
|
||||
@@ -5,7 +5,7 @@ from ._shared import *
|
||||
@bp.get('/smart-queue')
|
||||
def smart_queue_get():
|
||||
from ..services import smart_queue
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return ok({'settings': {}, 'exclusions': [], 'error': 'No profile'})
|
||||
try:
|
||||
@@ -23,7 +23,7 @@ def smart_queue_get():
|
||||
@bp.post('/smart-queue')
|
||||
def smart_queue_save():
|
||||
from ..services import smart_queue
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return ok({'settings': {}, 'error': 'No profile'})
|
||||
try:
|
||||
@@ -37,7 +37,7 @@ def smart_queue_save():
|
||||
|
||||
@bp.post('/smart-queue/check')
|
||||
def smart_queue_check():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return ok({'result': {'ok': False, 'error': 'No profile'}})
|
||||
if str(request.args.get('sync') or '').lower() in {'1', 'true', 'yes'}:
|
||||
@@ -66,7 +66,7 @@ def smart_queue_check():
|
||||
@bp.post('/smart-queue/exclusion')
|
||||
def smart_queue_exclusion():
|
||||
from ..services import smart_queue
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||
data = request.get_json(silent=True) or {}
|
||||
@@ -79,7 +79,7 @@ def smart_queue_exclusion():
|
||||
@bp.delete('/smart-queue/history')
|
||||
def smart_queue_history_clear():
|
||||
from ..services import smart_queue
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||
try:
|
||||
|
||||
+19
-19
@@ -7,7 +7,7 @@ from ..services.frontend_assets import static_hash
|
||||
|
||||
@bp.get("/system/disk")
|
||||
def system_disk():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"})
|
||||
try:
|
||||
@@ -19,7 +19,7 @@ def system_disk():
|
||||
|
||||
@bp.get("/system/status")
|
||||
def system_status():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"})
|
||||
try:
|
||||
@@ -80,7 +80,7 @@ def health_check_nagios():
|
||||
@bp.get("/app/status")
|
||||
def app_status():
|
||||
started = time.perf_counter()
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
proc = psutil.Process(os.getpid())
|
||||
try:
|
||||
jobs = list_jobs(10, 0)
|
||||
@@ -178,7 +178,7 @@ def cleanup_status():
|
||||
|
||||
@bp.post("/cleanup/cache")
|
||||
def cleanup_profile_cache():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
profile_id = int(profile["id"])
|
||||
@@ -225,7 +225,7 @@ def cleanup_database_vacuum():
|
||||
|
||||
@bp.post("/cleanup/smart-queue")
|
||||
def cleanup_smart_queue():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
profile_id = int(profile["id"])
|
||||
@@ -243,7 +243,7 @@ def cleanup_smart_queue():
|
||||
|
||||
@bp.post("/cleanup/operation-logs")
|
||||
def cleanup_operation_logs():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
# Note: Operation log cleanup removes only profile-scoped log entries; torrents, jobs and settings stay intact.
|
||||
@@ -254,7 +254,7 @@ def cleanup_operation_logs():
|
||||
|
||||
@bp.post("/cleanup/planner")
|
||||
def cleanup_planner():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
# Note: Planner cleanup removes only the active profile action history, not saved Planner settings.
|
||||
@@ -264,7 +264,7 @@ def cleanup_planner():
|
||||
|
||||
@bp.post("/cleanup/automations")
|
||||
def cleanup_automations():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
profile_id = int(profile["id"])
|
||||
@@ -284,7 +284,7 @@ def cleanup_automations():
|
||||
|
||||
@bp.post("/cleanup/poller-diagnostics")
|
||||
def cleanup_poller_diagnostics():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
profile_id = int(profile["id"])
|
||||
@@ -295,7 +295,7 @@ def cleanup_poller_diagnostics():
|
||||
@bp.post("/cleanup/all")
|
||||
def cleanup_all():
|
||||
deleted_jobs = clear_jobs()
|
||||
active_profile = preferences.active_profile()
|
||||
active_profile = request_profile()
|
||||
active_profile_id = int(active_profile["id"]) if active_profile else 0
|
||||
deleted_logs = operation_logs.clear(active_profile_id) if active_profile_id else 0
|
||||
deleted_planner = download_planner.clear_history(active_profile_id) if active_profile_id else 0
|
||||
@@ -371,7 +371,7 @@ def _annotate_path_directories(profile: dict, payload: dict) -> dict:
|
||||
|
||||
@bp.get("/path/default")
|
||||
def path_default():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
try:
|
||||
@@ -383,7 +383,7 @@ def path_default():
|
||||
|
||||
@bp.get("/path/browse")
|
||||
def path_browse():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
base = request.args.get("path") or ""
|
||||
@@ -395,7 +395,7 @@ def path_browse():
|
||||
|
||||
@bp.post("/path/directories")
|
||||
def path_directory_create():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
require_profile_write(profile.get("id"))
|
||||
@@ -410,7 +410,7 @@ def path_directory_create():
|
||||
|
||||
@bp.post("/path/directories/rename")
|
||||
def path_directory_rename():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
require_profile_write(profile.get("id"))
|
||||
@@ -429,7 +429,7 @@ def path_directory_rename():
|
||||
|
||||
@bp.get('/rtorrent-config')
|
||||
def rtorrent_config_get():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||
try:
|
||||
@@ -440,7 +440,7 @@ def rtorrent_config_get():
|
||||
|
||||
@bp.post('/rtorrent-config')
|
||||
def rtorrent_config_save():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||
try:
|
||||
@@ -457,7 +457,7 @@ def rtorrent_config_save():
|
||||
|
||||
@bp.post('/rtorrent-config/reset')
|
||||
def rtorrent_config_reset():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||
try:
|
||||
@@ -468,7 +468,7 @@ def rtorrent_config_reset():
|
||||
|
||||
@bp.post('/rtorrent-config/generate')
|
||||
def rtorrent_config_generate():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({'ok': False, 'error': 'No profile'}), 400
|
||||
try:
|
||||
@@ -481,7 +481,7 @@ def rtorrent_config_generate():
|
||||
@bp.get('/traffic/history')
|
||||
def traffic_history_get():
|
||||
from ..services import traffic_history
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return ok({'history': {'range': request.args.get('range') or '7d', 'bucket': 'day', 'rows': []}})
|
||||
range_name = request.args.get('range') or '7d'
|
||||
|
||||
@@ -7,7 +7,7 @@ from ..services.reverse_dns import attach_reverse_dns
|
||||
|
||||
@bp.get("/torrents")
|
||||
def torrents():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return ok({"torrents": [], "summary": cached_summary(0, []), "error": "No rTorrent profile"})
|
||||
rows = torrent_cache.snapshot(profile["id"])
|
||||
@@ -23,7 +23,7 @@ def torrents():
|
||||
|
||||
@bp.get("/trackers/summary")
|
||||
def trackers_summary():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return ok({"summary": {"hashes": {}, "trackers": [], "errors": [], "scanned": 0, "pending": 0}, "error": "No profile"})
|
||||
try:
|
||||
@@ -78,7 +78,7 @@ def tracker_favicon_query():
|
||||
|
||||
@bp.get("/torrent-stats")
|
||||
def torrent_stats_get():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return ok({"stats": {}, "error": "No profile"})
|
||||
force = str(request.args.get("force") or "").lower() in {"1", "true", "yes"}
|
||||
@@ -92,7 +92,7 @@ def torrent_stats_get():
|
||||
|
||||
@bp.get("/torrents/<torrent_hash>/files")
|
||||
def torrent_files(torrent_hash: str):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
return ok({"files": rtorrent.torrent_files(profile, torrent_hash)})
|
||||
@@ -101,7 +101,7 @@ def torrent_files(torrent_hash: str):
|
||||
|
||||
@bp.get("/torrents/<torrent_hash>/files/<int:file_index>/mediainfo")
|
||||
def torrent_file_media_info(torrent_hash: str, file_index: int):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
try:
|
||||
@@ -124,7 +124,7 @@ def torrent_file_media_info(torrent_hash: str, file_index: int):
|
||||
|
||||
@bp.post("/torrents/<torrent_hash>/files/priority")
|
||||
def torrent_file_priority(torrent_hash: str):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
data = request.get_json(silent=True) or {}
|
||||
@@ -139,7 +139,7 @@ def torrent_file_priority(torrent_hash: str):
|
||||
|
||||
@bp.get("/torrents/<torrent_hash>/files/tree")
|
||||
def torrent_file_tree(torrent_hash: str):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
return ok({"tree": rtorrent.torrent_file_tree(profile, torrent_hash)})
|
||||
@@ -148,7 +148,7 @@ def torrent_file_tree(torrent_hash: str):
|
||||
|
||||
@bp.post("/torrents/<torrent_hash>/files/folder-priority")
|
||||
def torrent_folder_priority(torrent_hash: str):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
data = request.get_json(silent=True) or {}
|
||||
@@ -214,7 +214,7 @@ def _send_staged_file(profile: dict, path: str, download_name: str, local: bool
|
||||
|
||||
@bp.post("/torrents/<torrent_hash>/files/<int:file_index>/download-link")
|
||||
def torrent_file_download_link(torrent_hash: str, file_index: int):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
try:
|
||||
@@ -238,7 +238,7 @@ def torrent_file_download_link_from_body(torrent_hash: str):
|
||||
|
||||
@bp.post("/torrents/<torrent_hash>/files/download.zip/link")
|
||||
def torrent_files_download_zip_link(torrent_hash: str):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
data = request.get_json(silent=True) or {}
|
||||
@@ -254,7 +254,7 @@ def torrent_files_download_zip_link(torrent_hash: str):
|
||||
|
||||
@bp.get("/torrents/<torrent_hash>/torrent-file/link")
|
||||
def torrent_file_export_link(torrent_hash: str):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
try:
|
||||
@@ -267,7 +267,7 @@ def torrent_file_export_link(torrent_hash: str):
|
||||
|
||||
@bp.post("/torrents/torrent-files.zip/link")
|
||||
def torrent_files_export_zip_link():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
data = request.get_json(silent=True) or {}
|
||||
@@ -284,7 +284,7 @@ def torrent_files_export_zip_link():
|
||||
|
||||
@bp.get("/torrents/<torrent_hash>/files/<int:file_index>/download")
|
||||
def torrent_file_download(torrent_hash: str, file_index: int):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
try:
|
||||
@@ -377,7 +377,7 @@ def _stream_torrent_files_zip(profile: dict, items: list[dict]):
|
||||
|
||||
@bp.post("/torrents/<torrent_hash>/files/download.zip")
|
||||
def torrent_files_download_zip(torrent_hash: str):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
data = request.get_json(silent=True) or {}
|
||||
@@ -393,7 +393,7 @@ def torrent_files_download_zip(torrent_hash: str):
|
||||
|
||||
@bp.get("/torrents/<torrent_hash>/torrent-file")
|
||||
def torrent_file_export(torrent_hash: str):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
try:
|
||||
@@ -406,7 +406,7 @@ def torrent_file_export(torrent_hash: str):
|
||||
|
||||
@bp.post("/torrents/torrent-files.zip")
|
||||
def torrent_files_export_zip():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
data = request.get_json(silent=True) or {}
|
||||
@@ -455,7 +455,7 @@ def torrent_files_export_zip():
|
||||
|
||||
@bp.get("/torrents/<torrent_hash>/chunks")
|
||||
def torrent_chunks(torrent_hash: str):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
try:
|
||||
@@ -467,7 +467,7 @@ def torrent_chunks(torrent_hash: str):
|
||||
|
||||
@bp.post("/torrents/<torrent_hash>/chunks/<action_name>")
|
||||
def torrent_chunk_action(torrent_hash: str, action_name: str):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
try:
|
||||
@@ -480,7 +480,7 @@ def torrent_chunk_action(torrent_hash: str, action_name: str):
|
||||
|
||||
@bp.get("/torrents/<torrent_hash>/peers")
|
||||
def torrent_peers(torrent_hash: str):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
peers = rtorrent.torrent_peers(profile, torrent_hash)
|
||||
@@ -496,7 +496,7 @@ def torrent_peers(torrent_hash: str):
|
||||
|
||||
@bp.get("/torrents/<torrent_hash>/trackers")
|
||||
def torrent_trackers(torrent_hash: str):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
return ok({"trackers": rtorrent.torrent_trackers(profile, torrent_hash)})
|
||||
@@ -505,7 +505,7 @@ def torrent_trackers(torrent_hash: str):
|
||||
|
||||
@bp.post("/torrents/<torrent_hash>/trackers/<action_name>")
|
||||
def torrent_tracker_action(torrent_hash: str, action_name: str):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
try:
|
||||
@@ -518,7 +518,7 @@ def torrent_tracker_action(torrent_hash: str, action_name: str):
|
||||
|
||||
@bp.post("/torrents/<action_name>")
|
||||
def torrent_action(action_name: str):
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
data = request.get_json(silent=True) or {}
|
||||
@@ -547,7 +547,7 @@ def torrent_action(action_name: str):
|
||||
|
||||
@bp.post("/torrents/create")
|
||||
def torrent_create():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
form = request.form if request.content_type and request.content_type.startswith("multipart/form-data") else (request.get_json(silent=True) or {})
|
||||
@@ -577,7 +577,7 @@ def torrent_create():
|
||||
|
||||
@bp.post("/torrents/add")
|
||||
def torrent_add():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
job_ids = []
|
||||
@@ -634,7 +634,7 @@ def torrent_add():
|
||||
|
||||
@bp.post("/torrents/preview")
|
||||
def torrent_preview():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
existing_hashes = set()
|
||||
if profile:
|
||||
try:
|
||||
@@ -664,7 +664,7 @@ def torrent_preview():
|
||||
|
||||
@bp.post("/speed/limits")
|
||||
def speed_limits():
|
||||
profile = preferences.active_profile()
|
||||
profile = request_profile()
|
||||
if not profile:
|
||||
return jsonify({"ok": False, "error": "No profile"}), 400
|
||||
data = request.get_json(silent=True) or {}
|
||||
|
||||
Reference in New Issue
Block a user