soulseek: queue downloads straight out of the cached tree

a browsed folder is a path, not a file list, and what clicking one means is
"everything under here" — so the expansion happens in the sidecar, off the
cache, rather than making the browser walk the tree a level at a time and
rebuild paths it only half knows.

browse reports file names as basenames, unlike search, so the peer's real
path is rejoined from the folder row. sizes come from the cache too: slskd
matches a queued download on filename AND size, so a number supplied by the
client would be a transfer that silently never starts.

a subtree can be the peer's whole share (284k files on one measured peer), so
an over-limit request is refused with its count rather than truncated into a
partial download nobody asked for.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 01:57:01 +00:00
co-authored by Claude Opus 4.8
parent b94dccd17c
commit 184adc0e8e
6 changed files with 215 additions and 36 deletions
+18
View File
@@ -10,6 +10,7 @@ import {
deleteSoulseekBrowse,
} from 'officerdb';
import { startBrowse } from './browse';
import { enqueueFromCache, DownloadError } from './download';
// Officer-owned Soulseek routes — everything slskd itself has no concept of. These are served HERE, by
// the sidecar, not forwarded upstream: the platform API stays a pure auth-and-forward proxy forever, and
@@ -29,6 +30,7 @@ import { startBrowse } from './browse';
// GET /_officer/browse/<u>/tree?parent=&limit=&offset= → { nodes, total } one level (roots if no parent)
// GET /_officer/browse/<u>/tree/search?q=&limit= → { nodes, matched, total, truncated }
// GET /_officer/browse/<u>/dirs/<id>/files → BrowsedFile[] one folder's files
// POST /_officer/browse/<u>/download { path, file? } → { queued } enqueue a folder, or one file
// ─────────────────────────────────────────────────────────────────────────────────────────────────
// The authenticated user id arrives in X-Officer-User, injected by the platform proxy after auth. We
@@ -145,6 +147,22 @@ async function handleBrowse({ req, url, userId, segments }: BrowseRouteParams):
return Response.json(result);
}
// /_officer/browse/<username>/download — a folder's whole subtree, or one file inside it.
if (segments[2] === 'download' && segments.length === 3) {
if (req.method !== 'POST') return methodNotAllowed();
const body = (await req.json().catch(() => null)) as { path?: unknown; file?: unknown } | null;
const path = typeof body?.path === 'string' ? body.path.trim() : '';
if (!path) return badRequest('path required');
const file = typeof body?.file === 'string' && body.file.trim() ? body.file : undefined;
try {
const queued = await enqueueFromCache({ userId, username, path, file });
return Response.json({ queued });
} catch (err) {
if (err instanceof DownloadError) return Response.json({ error: err.message }, { status: err.status });
throw err;
}
}
if (segments[2] !== 'dirs') return notFound();
// /_officer/browse/<username>/dirs/<id>/files