feat: implement RSS subscriptions, downloader routing and local media checks

This commit is contained in:
junior
2026-10-17 22:49:11 +08:00
parent 645ea6fe2d
commit 68c972dadd
28 changed files with 9226 additions and 43 deletions
+34
View File
@@ -0,0 +1,34 @@
CREATE TABLE subscriptions (
id TEXT PRIMARY KEY,
spec TEXT NOT NULL,
deleted INTEGER NOT NULL DEFAULT 0,
last_checked_at INTEGER,
last_error TEXT
);
CREATE TABLE releases (
subscription_id TEXT NOT NULL REFERENCES subscriptions(id),
resource_key TEXT NOT NULL,
payload TEXT NOT NULL,
PRIMARY KEY (subscription_id, resource_key)
);
CREATE TABLE download_jobs (
id TEXT PRIMARY KEY,
subscription_id TEXT NOT NULL REFERENCES subscriptions(id),
resource_key TEXT NOT NULL,
title TEXT NOT NULL,
season INTEGER NOT NULL,
episode INTEGER NOT NULL,
download_url TEXT NOT NULL,
torrent_bytes BLOB,
info_hash TEXT,
state TEXT NOT NULL DEFAULT 'queued'
CHECK (state IN ('queued', 'submitting', 'submitted', 'downloading', 'completed', 'missing', 'failed')),
attempts INTEGER NOT NULL DEFAULT 0,
last_error TEXT,
next_attempt_at INTEGER NOT NULL DEFAULT 0,
UNIQUE(subscription_id, resource_key),
UNIQUE(subscription_id, season, episode)
);
CREATE INDEX jobs_state ON download_jobs(state, next_attempt_at);
+18
View File
@@ -0,0 +1,18 @@
CREATE TABLE downloader_instances (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
kind TEXT NOT NULL CHECK (kind = 'qbittorrent'),
endpoint TEXT NOT NULL UNIQUE,
config TEXT NOT NULL
);
CREATE TABLE downloader_selection (
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
downloader_id TEXT REFERENCES downloader_instances(id)
);
INSERT INTO downloader_selection(singleton) VALUES (1);
ALTER TABLE download_jobs ADD COLUMN downloader_id TEXT REFERENCES downloader_instances(id);
ALTER TABLE download_jobs ADD COLUMN legacy_unassigned INTEGER NOT NULL DEFAULT 0;
-- Existing jobs have no recorded owner. Do not assign them to an arbitrary new instance.
UPDATE download_jobs SET legacy_unassigned = 1;
CREATE INDEX jobs_downloader ON download_jobs(downloader_id, state);
+37
View File
@@ -0,0 +1,37 @@
CREATE TABLE media_destinations (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
kind TEXT NOT NULL CHECK (kind = 'local'),
root_path TEXT NOT NULL
);
CREATE TABLE destination_selection (
singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
destination_id TEXT REFERENCES media_destinations(id)
);
INSERT INTO destination_selection(singleton) VALUES (1);
CREATE TABLE destination_mappings (
id TEXT PRIMARY KEY,
destination_id TEXT NOT NULL REFERENCES media_destinations(id),
downloader_id TEXT NOT NULL REFERENCES downloader_instances(id),
version INTEGER NOT NULL,
download_root TEXT NOT NULL,
UNIQUE(destination_id, downloader_id, version)
);
ALTER TABLE download_jobs ADD COLUMN destination_id TEXT REFERENCES media_destinations(id);
ALTER TABLE download_jobs ADD COLUMN mapping_id TEXT REFERENCES destination_mappings(id);
ALTER TABLE download_jobs ADD COLUMN download_save_path TEXT;
ALTER TABLE download_jobs ADD COLUMN file_status TEXT NOT NULL DEFAULT 'unchecked'
CHECK (file_status IN ('unchecked', 'present', 'absent', 'unavailable'));
ALTER TABLE download_jobs ADD COLUMN file_checked_at INTEGER;
ALTER TABLE download_jobs ADD COLUMN file_last_success_at INTEGER;
ALTER TABLE download_jobs ADD COLUMN file_error TEXT;
CREATE TABLE media_files (
job_id TEXT NOT NULL REFERENCES download_jobs(id),
relative_path TEXT NOT NULL,
expected_size INTEGER NOT NULL,
observed_size INTEGER,
status TEXT NOT NULL DEFAULT 'unchecked'
CHECK (status IN ('unchecked', 'present', 'absent', 'unavailable')),
error TEXT,
PRIMARY KEY(job_id, relative_path)
);