/* Transfers on the phone: the downloads the backend is running — every Save lands here — fed by soulseek:download exactly as the desktop's `downloads` store is (latest update wins, a terminal update without numbers keeps the last known progress), pruned once a burst drains. */ import * as api from "../lib/api.js"; import { listen } from "../lib/events.js"; export const transfers = $state({ /** id → payload { id, username, path, name, state, queue_place, local_path, error, received, total } */ items: {}, }); export const DONE = new Set(["complete", "failed", "cancelled"]); const HISTORY_MAX = 60; export function list() { return Object.values(transfers.items).sort((a, b) => b.id - a.id); } export function active() { return Object.values(transfers.items).filter((d) => !DONE.has(d.state)); } export function clearFinished() { for (const d of Object.values(transfers.items)) if (DONE.has(d.state)) delete transfers.items[d.id]; } export async function cancel(id) { try { await api.cancelDownload(id); } catch { /* already gone */ } } /** "queued · 3rd in line", "transferring · 42%", "saved", "failed · reason" */ export function stateLine(d) { switch (d.state) { case "connecting": return "connecting…"; case "requested": return "asked…"; case "queued": return d.queue_place ? `queued · ${d.queue_place} ahead` : "queued"; case "transferring": return d.total ? `${Math.round((d.received / d.total) * 100)}%` : "transferring…"; case "complete": return "saved"; case "failed": return d.error ? `failed · ${d.error}` : "failed"; case "cancelled": return "cancelled"; default: return d.state || ""; } } let bound = false; export async function bindTransfers() { if (bound) return; bound = true; try { await listen("soulseek:download", (ev) => { const d = ev.payload; if (!d || d.id == null) return; const prev = transfers.items[d.id]; if (prev && !d.total) { d.received = prev.received; d.total = prev.total; } transfers.items[d.id] = d; if (DONE.has(d.state)) { /* keep an active burst whole; prune the oldest finished past the cap */ const done = Object.values(transfers.items).filter((x) => DONE.has(x.state)).sort((a, b) => a.id - b.id); for (const x of done.slice(0, Math.max(0, done.length - HISTORY_MAX))) delete transfers.items[x.id]; } }); } catch { /* not a phone */ } }