f74fd5ab88
SlskdClient enqueued the transfer and polled to Completed but never moved any
files into `dest` — slskd saves them to its OWN downloads dir, so staging stayed
empty and every Soulseek job failed "incomplete download". Qobuz/YouTube write
straight into staging and were unaffected.
- SlskdClient._retrieve() moves the finished files out of slskd's downloads dir
into staging after completion, matching tolerantly by basename and preferring
a parent-folder + size match (slskd's on-disk layout varies by version).
- download() returns the moved count as track_count so the pipeline's
completeness check reflects what actually landed; raises loudly if nothing was
found (misconfigured/unmounted downloads dir).
- Downloads dir resolves slskd.downloadsDir Config > SLSKD_DOWNLOADS_ROOT env >
/slskd-downloads default; docker-compose mounts ${SLSKD_DOWNLOADS_DIR} there.
- .env.example documents SLSKD_DOWNLOADS_DIR; retrieval unit-tested offline.
slskd runs as an external daemon, so the shared dir must be a host path both
slskd and the worker mount — set once Lyra runs on the slskd host. Not yet
verified live end-to-end (no slskd access from this VM).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
74 lines
2.8 KiB
Python
74 lines
2.8 KiB
Python
"""Offline tests for SlskdClient._retrieve — the step that moves slskd's finished
|
|
downloads out of its own dir into the pipeline's staging dir. The search/download REST
|
|
flow stays live-only (test_soulseek_live.py); retrieval is pure filesystem, so it is
|
|
unit-testable here."""
|
|
import os
|
|
|
|
from lyra_worker.adapters._slskd import SlskdClient
|
|
|
|
|
|
def _client(downloads_dir: str) -> SlskdClient:
|
|
return SlskdClient({"slskd.url": "http://x", "slskd.api_key": "k",
|
|
"slskd.downloadsDir": downloads_dir})
|
|
|
|
|
|
def _write(path: str, content: bytes = b"audio") -> None:
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
with open(path, "wb") as fh:
|
|
fh.write(content)
|
|
|
|
|
|
def test_retrieve_moves_files_from_slskd_dir(tmp_path):
|
|
downloads = tmp_path / "slskd"
|
|
dest = tmp_path / "staging"
|
|
# slskd's typical layout: <downloads>/<remote parent folder>/<file>
|
|
_write(str(downloads / "Artist - Album" / "01 One.flac"))
|
|
_write(str(downloads / "Artist - Album" / "02 Two.flac"))
|
|
files = [
|
|
{"filename": r"@@abc\music\Artist - Album\01 One.flac", "size": 5},
|
|
{"filename": r"@@abc\music\Artist - Album\02 Two.flac", "size": 5},
|
|
]
|
|
|
|
moved = _client(str(downloads))._retrieve(files, str(dest))
|
|
|
|
assert moved == 2
|
|
assert sorted(os.listdir(dest)) == ["01 One.flac", "02 Two.flac"]
|
|
# the source files were moved, not copied
|
|
assert os.listdir(downloads / "Artist - Album") == []
|
|
|
|
|
|
def test_retrieve_disambiguates_by_parent_and_size(tmp_path):
|
|
downloads = tmp_path / "slskd"
|
|
dest = tmp_path / "staging"
|
|
# a decoy with the same basename in a different folder + wrong size
|
|
_write(str(downloads / "Other Album" / "01 One.flac"), b"wrongsize!!")
|
|
_write(str(downloads / "Artist - Album" / "01 One.flac"), b"right")
|
|
files = [{"filename": r"x\Artist - Album\01 One.flac", "size": len(b"right")}]
|
|
|
|
moved = _client(str(downloads))._retrieve(files, str(dest))
|
|
|
|
assert moved == 1
|
|
with open(dest / "01 One.flac", "rb") as fh:
|
|
assert fh.read() == b"right" # picked the parent+size match, not the decoy
|
|
|
|
|
|
def test_retrieve_returns_zero_when_nothing_matches(tmp_path):
|
|
downloads = tmp_path / "slskd"
|
|
downloads.mkdir()
|
|
dest = tmp_path / "staging"
|
|
files = [{"filename": r"x\Album\missing.flac", "size": 1}]
|
|
|
|
assert _client(str(downloads))._retrieve(files, str(dest)) == 0
|
|
|
|
|
|
def test_retrieve_raises_when_downloads_dir_absent(tmp_path):
|
|
dest = tmp_path / "staging"
|
|
client = _client(str(tmp_path / "does-not-exist"))
|
|
files = [{"filename": r"x\Album\a.flac", "size": 1}]
|
|
try:
|
|
client._retrieve(files, str(dest))
|
|
except RuntimeError as e:
|
|
assert "downloads dir not found" in str(e)
|
|
else:
|
|
raise AssertionError("expected RuntimeError for a missing downloads dir")
|