The durable "I asked for this" record was called Want, and the one-shot search-and-grab attempt was called Request — names that didn't match what either actually did. Want is now Request, and the old Request/Item is now Download/DownloadItem, with a table-rename migration (download_wants -> download_requests, old download_requests -> download_downloads) safe against both fresh installs and existing data. Every anchored manual download now upserts/reuses a durable Request before running, so a "download now" that finds nothing is picked up by the background reconciler automatically instead of just failing with no trace — the gap that caused this session's repeated "no candidates found" failures on the same album. Also adds auto-download guardrails (file-size min/max with a preferred target, allowed file types) that gate what the pipeline may grab unattended, live-editable from a new settings section. The frontend's wanted-view becomes downloads-view, with a new Downloads tab showing attempt/transfer history that previously had no UI at all. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y2Agd9af5hE7qzti2ackiS
53 lines
2.3 KiB
SQL
53 lines
2.3 KiB
SQL
-- One row per grab attempt against one candidate. A download can have
|
|
-- several: the first pick stalls, the user picks another, or a
|
|
-- search-only provider's candidate is fetched by a separate transport
|
|
-- (in which case provider_id is the searcher and transport_id is the
|
|
-- fetcher).
|
|
--
|
|
-- `candidate` is the full ranked Candidate as JSON. It is stored
|
|
-- rather than re-derived because the provider's result set is
|
|
-- ephemeral — a Soulseek peer that had the files an hour ago may be
|
|
-- offline now, and the item still has to render in the UI and explain
|
|
-- why it was chosen.
|
|
--
|
|
-- external_id holds a delegating manager's own identifier (a Lidarr
|
|
-- queue id), which is how polling finds the record again after a
|
|
-- restart.
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS download_items (
|
|
id TEXT PRIMARY KEY,
|
|
download_id TEXT NOT NULL,
|
|
provider_id INTEGER NOT NULL,
|
|
transport_id INTEGER,
|
|
external_id TEXT NOT NULL DEFAULT '',
|
|
candidate TEXT NOT NULL DEFAULT '{}',
|
|
state TEXT NOT NULL DEFAULT 'queued'
|
|
CHECK(state IN ('searching', 'found', 'queued', 'grabbing',
|
|
'verifying', 'tagging', 'importing',
|
|
'complete', 'cancelled', 'failed')),
|
|
staging_dir TEXT NOT NULL DEFAULT '',
|
|
bytes_done INTEGER NOT NULL DEFAULT 0,
|
|
bytes_total INTEGER NOT NULL DEFAULT 0,
|
|
-- imported_paths is a JSON array of the library paths the files
|
|
-- ended up at, so an import can be undone without guessing.
|
|
imported_paths TEXT NOT NULL DEFAULT '[]',
|
|
error TEXT NOT NULL DEFAULT '',
|
|
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY(download_id) REFERENCES download_downloads(id) ON DELETE CASCADE
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_download_items_live
|
|
ON download_items(state)
|
|
WHERE state NOT IN ('complete', 'cancelled', 'failed');
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_download_items_state
|
|
ON download_items(state);
|
|
|
|
-- idx_download_items_download is deliberately NOT declared here: on an
|
|
-- existing database this table already exists at schema-pass time with
|
|
-- its old column still named request_id, so an inline CREATE INDEX on
|
|
-- download_id would fail outright. See ensureDownloadIndexes in
|
|
-- backend/database/download_rename_migration.go.
|