diff --git a/api/video/__init__.py b/api/video/__init__.py index 5646fc1f..76346c10 100644 --- a/api/video/__init__.py +++ b/api/video/__init__.py @@ -50,6 +50,7 @@ def create_video_blueprint() -> Blueprint: from .watchlist import register_routes as reg_watchlist from .wishlist import register_routes as reg_wishlist from .youtube import register_routes as reg_youtube + from .downloads import register_routes as reg_downloads reg_dashboard(bp) reg_scan(bp) reg_library(bp) @@ -63,5 +64,6 @@ def create_video_blueprint() -> Blueprint: reg_watchlist(bp) reg_wishlist(bp) reg_youtube(bp) + reg_downloads(bp) return bp diff --git a/api/video/downloads.py b/api/video/downloads.py new file mode 100644 index 00000000..16ea10a0 --- /dev/null +++ b/api/video/downloads.py @@ -0,0 +1,46 @@ +"""Video-side download SETTINGS (isolated). + +Persists the video download configuration in video.db's ``video_settings`` KV +table — fully separate from the music ``soulseek.*`` paths so the two libraries +never share a folder or collide. The actual download fulfillment engine (wishlist +→ search → grab) is a later roadmap phase; these endpoints just store/serve the +config the Settings → Downloads tab edits. + +Keys persisted here (all under video.db): + - ``download_path`` : input folder a video download lands in + - ``transfer_path`` : output folder finished video files move to (video library) + +Connection settings that are genuinely SHARED with music (the slskd instance, the +torrent/usenet clients, Prowlarr indexers) are NOT stored here — those live in the +music config_manager and are surfaced on the shared Indexers tab + shared slskd +block (a deliberate shared boundary, since they're one physical resource). +""" + +from __future__ import annotations + +from flask import jsonify, request + +from utils.logging_config import get_logger + +logger = get_logger("video_api.downloads") + +# Video-specific path keys (vs. the shared connection settings). +_PATH_KEYS = ("download_path", "transfer_path") + + +def register_routes(bp): + @bp.route("/downloads/config", methods=["GET"]) + def video_downloads_config(): + from . import get_video_db + db = get_video_db() + return jsonify({k: db.get_setting(k) or "" for k in _PATH_KEYS}) + + @bp.route("/downloads/config", methods=["POST"]) + def video_downloads_config_save(): + from . import get_video_db + db = get_video_db() + body = request.get_json(silent=True) or {} + for key in _PATH_KEYS: + if key in body: + db.set_setting(key, (str(body.get(key) or "")).strip()) + return jsonify({"status": "saved"}) diff --git a/tests/test_video_api.py b/tests/test_video_api.py index 56a66560..b16e4050 100644 --- a/tests/test_video_api.py +++ b/tests/test_video_api.py @@ -356,6 +356,29 @@ def test_enrichment_config_save_load(tmp_path, monkeypatch): videoapi._video_db = None +def test_downloads_config_save_load(tmp_path): + import api.video as videoapi + from database.video_database import VideoDatabase + + db = VideoDatabase(database_path=str(tmp_path / "video_library.db")) + videoapi._video_db = db + app = Flask(__name__) + app.register_blueprint(videoapi.create_video_blueprint(), url_prefix="/api/video") + client = app.test_client() + try: + # Defaults are empty. + assert client.get("/api/video/downloads/config").get_json() == { + "download_path": "", "transfer_path": ""} + # Round-trips + persists to video.db (separate from any music config). + client.post("/api/video/downloads/config", + json={"download_path": " /mnt/v/dl ", "transfer_path": "/mnt/v/lib"}) + assert client.get("/api/video/downloads/config").get_json() == { + "download_path": "/mnt/v/dl", "transfer_path": "/mnt/v/lib"} # trimmed + assert db.get_setting("download_path") == "/mnt/v/dl" + finally: + videoapi._video_db = None + + def test_video_api_imports_nothing_from_music(): base = Path(__file__).resolve().parent.parent / "api" / "video" for py in base.glob("*.py"): diff --git a/webui/index.html b/webui/index.html index 5cd37145..873f3271 100644 --- a/webui/index.html +++ b/webui/index.html @@ -6011,6 +6011,23 @@