soulsync/api/downloads.py
Broque Thomas d9aa8303a7 Add SoulSync REST API (v1) with API key authentication
Adds a full public REST API at /api/v1/ with 32 endpoints covering library, search, downloads, wishlist, watchlist, playlists, system status, and settings. Includes API key authentication (Bearer token), per-endpoint rate limiting, and consistent JSON response format. API keys can be generated and managed from the Settings page. No changes to existing functionality — the API delegates to the same backend services the web UI uses.
2026-03-03 09:49:00 -08:00

81 lines
3.1 KiB
Python

"""
Download management endpoints — list, cancel active downloads.
"""
from flask import request, current_app
from .auth import require_api_key
from .helpers import api_success, api_error
def register_routes(bp):
@bp.route("/downloads", methods=["GET"])
@require_api_key
def list_downloads():
"""List active and recent download tasks."""
try:
from web_server import download_tasks, tasks_lock
tasks = []
with tasks_lock:
for task_id, task in download_tasks.items():
tasks.append({
"id": task_id,
"status": task.get("status"),
"track_name": task.get("track_name"),
"artist_name": task.get("artist_name"),
"album_name": task.get("album_name"),
"username": task.get("username"),
"filename": task.get("filename"),
"progress": task.get("progress", 0),
"size": task.get("size"),
"error": task.get("error"),
})
return api_success({"downloads": tasks})
except ImportError:
return api_error("NOT_AVAILABLE", "Download tracking not available.", 501)
except Exception as e:
return api_error("DOWNLOAD_ERROR", str(e), 500)
@bp.route("/downloads/<download_id>/cancel", methods=["POST"])
@require_api_key
def cancel_download(download_id):
"""Cancel a specific download.
Body: {"username": "..."}
"""
body = request.get_json(silent=True) or {}
username = body.get("username")
if not username:
return api_error("BAD_REQUEST", "Missing 'username' in body.", 400)
try:
from utils.async_helpers import run_async
soulseek = current_app.soulsync.get("soulseek_client")
if not soulseek:
return api_error("NOT_AVAILABLE", "Soulseek client not configured.", 503)
ok = run_async(soulseek.cancel_download(download_id, username, remove=True))
if ok:
return api_success({"message": "Download cancelled."})
return api_error("CANCEL_FAILED", "Failed to cancel download.", 500)
except Exception as e:
return api_error("DOWNLOAD_ERROR", str(e), 500)
@bp.route("/downloads/cancel-all", methods=["POST"])
@require_api_key
def cancel_all_downloads():
"""Cancel all active downloads and clear completed ones."""
try:
from utils.async_helpers import run_async
soulseek = current_app.soulsync.get("soulseek_client")
if not soulseek:
return api_error("NOT_AVAILABLE", "Soulseek client not configured.", 503)
run_async(soulseek.cancel_all_downloads())
run_async(soulseek.clear_all_completed_downloads())
return api_success({"message": "All downloads cancelled and cleared."})
except Exception as e:
return api_error("DOWNLOAD_ERROR", str(e), 500)