changes in db
This commit is contained in:
120
pytorrent/db.py
120
pytorrent/db.py
@@ -181,8 +181,7 @@ CREATE TABLE IF NOT EXISTS ratio_groups (
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rss_feeds (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
profile_id INTEGER,
|
||||
profile_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
url TEXT NOT NULL,
|
||||
enabled INTEGER DEFAULT 1,
|
||||
@@ -196,8 +195,7 @@ CREATE TABLE IF NOT EXISTS rss_feeds (
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rss_rules (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
profile_id INTEGER,
|
||||
profile_id INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
pattern TEXT NOT NULL,
|
||||
exclude_pattern TEXT,
|
||||
@@ -214,13 +212,12 @@ CREATE TABLE IF NOT EXISTS rss_rules (
|
||||
created_at TEXT NOT NULL,
|
||||
updated_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_rss_feeds_user_profile_enabled_next ON rss_feeds(user_id, profile_id, enabled, next_check_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_rss_rules_user_profile_enabled ON rss_rules(user_id, profile_id, enabled);
|
||||
CREATE INDEX IF NOT EXISTS idx_rss_feeds_profile_enabled_next ON rss_feeds(profile_id, enabled, next_check_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_rss_rules_profile_enabled ON rss_rules(profile_id, enabled);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rss_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
profile_id INTEGER,
|
||||
profile_id INTEGER NOT NULL,
|
||||
feed_id INTEGER,
|
||||
rule_id INTEGER,
|
||||
title TEXT,
|
||||
@@ -230,8 +227,7 @@ CREATE TABLE IF NOT EXISTS rss_history (
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_rss_history_profile_created ON rss_history(profile_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_rss_history_user_profile_created ON rss_history(user_id, profile_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_rss_history_user_profile_status ON rss_history(user_id, profile_id, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_rss_history_profile_status ON rss_history(profile_id, status);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_rss_history_unique_success ON rss_history(profile_id, COALESCE(rule_id,0), link) WHERE status IN ('queued','added');
|
||||
|
||||
CREATE TABLE IF NOT EXISTS ratio_assignments (
|
||||
@@ -275,7 +271,6 @@ CREATE TABLE IF NOT EXISTS app_backups (
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS smart_queue_settings (
|
||||
user_id INTEGER NOT NULL,
|
||||
profile_id INTEGER NOT NULL,
|
||||
enabled INTEGER DEFAULT 0,
|
||||
max_active_downloads INTEGER DEFAULT 5,
|
||||
@@ -296,7 +291,7 @@ CREATE TABLE IF NOT EXISTS smart_queue_settings (
|
||||
protect_active_below_cap INTEGER DEFAULT 1,
|
||||
auto_stop_idle INTEGER DEFAULT 0,
|
||||
updated_at TEXT NOT NULL,
|
||||
PRIMARY KEY(user_id, profile_id)
|
||||
PRIMARY KEY(profile_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS smart_queue_stalled (
|
||||
@@ -317,19 +312,17 @@ CREATE TABLE IF NOT EXISTS smart_queue_start_grace (
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS smart_queue_exclusions (
|
||||
user_id INTEGER NOT NULL,
|
||||
profile_id INTEGER NOT NULL,
|
||||
torrent_hash TEXT NOT NULL,
|
||||
reason TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
PRIMARY KEY(user_id, profile_id, torrent_hash)
|
||||
PRIMARY KEY(profile_id, torrent_hash)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_smart_queue_exclusions_user_profile_created ON smart_queue_exclusions(user_id, profile_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_smart_queue_exclusions_profile_created ON smart_queue_exclusions(profile_id, created_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS smart_queue_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
profile_id INTEGER NOT NULL,
|
||||
event TEXT NOT NULL,
|
||||
paused_count INTEGER DEFAULT 0,
|
||||
@@ -340,7 +333,7 @@ CREATE TABLE IF NOT EXISTS smart_queue_history (
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_smart_queue_history_profile_created ON smart_queue_history(profile_id, created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_smart_queue_history_user_profile_created ON smart_queue_history(user_id, profile_id, created_at);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS smart_queue_auto_labels (
|
||||
profile_id INTEGER NOT NULL,
|
||||
@@ -418,14 +411,13 @@ CREATE INDEX IF NOT EXISTS idx_automation_history_profile_created ON automation_
|
||||
CREATE INDEX IF NOT EXISTS idx_automation_history_user_profile_created ON automation_history(user_id, profile_id, created_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS rtorrent_config_overrides (
|
||||
user_id INTEGER NOT NULL,
|
||||
profile_id INTEGER NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value TEXT,
|
||||
baseline_value TEXT,
|
||||
apply_on_start INTEGER DEFAULT 0,
|
||||
updated_at TEXT NOT NULL,
|
||||
PRIMARY KEY(user_id, profile_id, key)
|
||||
PRIMARY KEY(profile_id, key)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_rtorrent_config_overrides_profile ON rtorrent_config_overrides(profile_id, apply_on_start);
|
||||
|
||||
@@ -598,7 +590,7 @@ MIGRATIONS = [
|
||||
"ALTER TABLE automation_history ADD COLUMN rule_name TEXT",
|
||||
"ALTER TABLE automation_history ADD COLUMN actions_json TEXT",
|
||||
"ALTER TABLE automation_history ADD COLUMN torrent_hash TEXT",
|
||||
"CREATE TABLE IF NOT EXISTS rss_history (id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, profile_id INTEGER, feed_id INTEGER, rule_id INTEGER, title TEXT, link TEXT, status TEXT NOT NULL, message TEXT, created_at TEXT NOT NULL)",
|
||||
"CREATE TABLE IF NOT EXISTS rss_history (id INTEGER PRIMARY KEY AUTOINCREMENT, profile_id INTEGER NOT NULL, feed_id INTEGER, rule_id INTEGER, title TEXT, link TEXT, status TEXT NOT NULL, message TEXT, created_at TEXT NOT NULL)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_rss_history_profile_created ON rss_history(profile_id, created_at)",
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS idx_rss_history_unique_success ON rss_history(profile_id, COALESCE(rule_id,0), link) WHERE status IN ('queued','added')",
|
||||
"CREATE TABLE IF NOT EXISTS ratio_assignments (profile_id INTEGER NOT NULL, torrent_hash TEXT NOT NULL, group_id INTEGER, group_name TEXT, applied_at TEXT, last_status TEXT, updated_at TEXT NOT NULL, PRIMARY KEY(profile_id, torrent_hash))",
|
||||
@@ -611,15 +603,15 @@ MIGRATIONS = [
|
||||
"CREATE INDEX IF NOT EXISTS idx_download_plan_paused_profile ON download_plan_paused(profile_id, updated_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_jobs_created ON jobs(created_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_jobs_profile_created ON jobs(profile_id, created_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_rss_feeds_user_profile_enabled_next ON rss_feeds(user_id, profile_id, enabled, next_check_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_rss_rules_user_profile_enabled ON rss_rules(user_id, profile_id, enabled)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_rss_history_user_profile_created ON rss_history(user_id, profile_id, created_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_rss_history_user_profile_status ON rss_history(user_id, profile_id, status)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_rss_feeds_profile_enabled_next ON rss_feeds(profile_id, enabled, next_check_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_rss_rules_profile_enabled ON rss_rules(profile_id, enabled)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_rss_history_profile_status ON rss_history(profile_id, status)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_rss_history_profile_status ON rss_history(profile_id, status)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_ratio_groups_user_profile_enabled ON ratio_groups(user_id, profile_id, enabled)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_ratio_assignments_profile_status ON ratio_assignments(profile_id, last_status)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_ratio_history_user_profile_id ON ratio_history(user_id, profile_id, id)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_smart_queue_exclusions_user_profile_created ON smart_queue_exclusions(user_id, profile_id, created_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_smart_queue_history_user_profile_created ON smart_queue_history(user_id, profile_id, created_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_smart_queue_exclusions_profile_created ON smart_queue_exclusions(profile_id, created_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_smart_queue_history_profile_created ON smart_queue_history(profile_id, created_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_automation_rules_user_profile_enabled ON automation_rules(user_id, profile_id, enabled)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_automation_history_user_profile_created ON automation_history(user_id, profile_id, created_at)",
|
||||
"CREATE INDEX IF NOT EXISTS idx_user_preferences_user ON user_preferences(user_id)",
|
||||
@@ -647,6 +639,81 @@ POST_MIGRATION_INDEXES = [
|
||||
"CREATE INDEX IF NOT EXISTS idx_operation_logs_user_profile_created ON operation_logs(user_id, profile_id, created_at)",
|
||||
]
|
||||
|
||||
|
||||
PROFILE_ONLY_TABLES = {
|
||||
"rss_feeds": {
|
||||
"columns": "id INTEGER PRIMARY KEY AUTOINCREMENT, profile_id INTEGER NOT NULL, name TEXT NOT NULL, url TEXT NOT NULL, enabled INTEGER DEFAULT 1, interval_minutes INTEGER DEFAULT 30, last_error TEXT, last_checked_at TEXT, next_check_at TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL",
|
||||
"copy": ["id", "profile_id", "name", "url", "enabled", "interval_minutes", "last_error", "last_checked_at", "next_check_at", "created_at", "updated_at"],
|
||||
"indexes": ["CREATE INDEX IF NOT EXISTS idx_rss_feeds_profile_enabled_next ON rss_feeds(profile_id, enabled, next_check_at)"],
|
||||
},
|
||||
"rss_rules": {
|
||||
"columns": "id INTEGER PRIMARY KEY AUTOINCREMENT, profile_id INTEGER NOT NULL, name TEXT NOT NULL, pattern TEXT NOT NULL, exclude_pattern TEXT, min_size_mb INTEGER DEFAULT 0, max_size_mb INTEGER DEFAULT 0, category TEXT, quality TEXT, season INTEGER, episode INTEGER, save_path TEXT, label TEXT, start INTEGER DEFAULT 1, enabled INTEGER DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL",
|
||||
"copy": ["id", "profile_id", "name", "pattern", "exclude_pattern", "min_size_mb", "max_size_mb", "category", "quality", "season", "episode", "save_path", "label", "start", "enabled", "created_at", "updated_at"],
|
||||
"indexes": ["CREATE INDEX IF NOT EXISTS idx_rss_rules_profile_enabled ON rss_rules(profile_id, enabled)"],
|
||||
},
|
||||
"rss_history": {
|
||||
"columns": "id INTEGER PRIMARY KEY AUTOINCREMENT, profile_id INTEGER NOT NULL, feed_id INTEGER, rule_id INTEGER, title TEXT, link TEXT, status TEXT NOT NULL, message TEXT, created_at TEXT NOT NULL",
|
||||
"copy": ["id", "profile_id", "feed_id", "rule_id", "title", "link", "status", "message", "created_at"],
|
||||
"indexes": ["CREATE INDEX IF NOT EXISTS idx_rss_history_profile_created ON rss_history(profile_id, created_at)", "CREATE INDEX IF NOT EXISTS idx_rss_history_profile_status ON rss_history(profile_id, status)", "CREATE UNIQUE INDEX IF NOT EXISTS idx_rss_history_unique_success ON rss_history(profile_id, COALESCE(rule_id,0), link) WHERE status IN ('queued','added')"],
|
||||
},
|
||||
"smart_queue_settings": {
|
||||
"columns": "profile_id INTEGER NOT NULL, enabled INTEGER DEFAULT 0, max_active_downloads INTEGER DEFAULT 5, stalled_seconds INTEGER DEFAULT 300, min_speed_bytes INTEGER DEFAULT 1024, min_seeds INTEGER DEFAULT 1, min_peers INTEGER DEFAULT 0, ignore_seed_peer INTEGER DEFAULT 0, ignore_speed INTEGER DEFAULT 0, manage_stopped INTEGER DEFAULT 0, cooldown_minutes INTEGER DEFAULT 10, last_run_at TEXT, refill_enabled INTEGER DEFAULT 1, refill_interval_minutes INTEGER DEFAULT 0, last_refill_at TEXT, stop_batch_size INTEGER DEFAULT 50, start_grace_seconds INTEGER DEFAULT 900, protect_active_below_cap INTEGER DEFAULT 1, auto_stop_idle INTEGER DEFAULT 0, updated_at TEXT NOT NULL, PRIMARY KEY(profile_id)",
|
||||
"copy": ["profile_id", "enabled", "max_active_downloads", "stalled_seconds", "min_speed_bytes", "min_seeds", "min_peers", "ignore_seed_peer", "ignore_speed", "manage_stopped", "cooldown_minutes", "last_run_at", "refill_enabled", "refill_interval_minutes", "last_refill_at", "stop_batch_size", "start_grace_seconds", "protect_active_below_cap", "auto_stop_idle", "updated_at"],
|
||||
"indexes": [],
|
||||
},
|
||||
"smart_queue_exclusions": {
|
||||
"columns": "profile_id INTEGER NOT NULL, torrent_hash TEXT NOT NULL, reason TEXT, created_at TEXT NOT NULL, PRIMARY KEY(profile_id, torrent_hash)",
|
||||
"copy": ["profile_id", "torrent_hash", "reason", "created_at"],
|
||||
"indexes": ["CREATE INDEX IF NOT EXISTS idx_smart_queue_exclusions_profile_created ON smart_queue_exclusions(profile_id, created_at)"],
|
||||
},
|
||||
"smart_queue_history": {
|
||||
"columns": "id INTEGER PRIMARY KEY AUTOINCREMENT, profile_id INTEGER NOT NULL, event TEXT NOT NULL, paused_count INTEGER DEFAULT 0, resumed_count INTEGER DEFAULT 0, checked_count INTEGER DEFAULT 0, details_json TEXT, created_at TEXT NOT NULL",
|
||||
"copy": ["id", "profile_id", "event", "paused_count", "resumed_count", "checked_count", "details_json", "created_at"],
|
||||
"indexes": ["CREATE INDEX IF NOT EXISTS idx_smart_queue_history_profile_created ON smart_queue_history(profile_id, created_at)"],
|
||||
},
|
||||
"rtorrent_config_overrides": {
|
||||
"columns": "profile_id INTEGER NOT NULL, key TEXT NOT NULL, value TEXT, baseline_value TEXT, apply_on_start INTEGER DEFAULT 0, updated_at TEXT NOT NULL, PRIMARY KEY(profile_id, key)",
|
||||
"copy": ["profile_id", "key", "value", "baseline_value", "apply_on_start", "updated_at"],
|
||||
"indexes": ["CREATE INDEX IF NOT EXISTS idx_rtorrent_config_overrides_profile ON rtorrent_config_overrides(profile_id, apply_on_start)"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _table_columns(conn, table: str) -> set[str]:
|
||||
try:
|
||||
return {str(row["name"]) for row in conn.execute(f"PRAGMA table_info({table})").fetchall()}
|
||||
except sqlite3.OperationalError:
|
||||
return set()
|
||||
|
||||
|
||||
def _normalize_profile_only_tables(conn) -> None:
|
||||
"""Move operational settings from user scope to profile scope on existing databases."""
|
||||
for table, spec in PROFILE_ONLY_TABLES.items():
|
||||
columns = _table_columns(conn, table)
|
||||
if not columns or "user_id" not in columns:
|
||||
for index_sql in spec["indexes"]:
|
||||
try:
|
||||
conn.execute(index_sql)
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
continue
|
||||
tmp = f"{table}_profile_scope_tmp"
|
||||
conn.execute("PRAGMA foreign_keys = OFF")
|
||||
conn.execute(f"DROP TABLE IF EXISTS {tmp}")
|
||||
conn.execute(f"CREATE TABLE {tmp} ({spec['columns']})")
|
||||
copy_cols = [col for col in spec["copy"] if col in columns]
|
||||
if copy_cols:
|
||||
col_sql = ",".join(copy_cols)
|
||||
if table in {"smart_queue_settings", "smart_queue_exclusions", "rtorrent_config_overrides"}:
|
||||
conn.execute(f"INSERT OR REPLACE INTO {tmp}({col_sql}) SELECT {col_sql} FROM {table} WHERE profile_id IS NOT NULL")
|
||||
else:
|
||||
conn.execute(f"INSERT INTO {tmp}({col_sql}) SELECT {col_sql} FROM {table} WHERE profile_id IS NOT NULL")
|
||||
conn.execute(f"DROP TABLE {table}")
|
||||
conn.execute(f"ALTER TABLE {tmp} RENAME TO {table}")
|
||||
for index_sql in spec["indexes"]:
|
||||
conn.execute(index_sql)
|
||||
conn.execute("PRAGMA foreign_keys = ON")
|
||||
|
||||
def utcnow() -> str:
|
||||
return datetime.now(timezone.utc).isoformat(timespec="seconds")
|
||||
|
||||
@@ -687,6 +754,7 @@ def init_db():
|
||||
conn.execute(sql)
|
||||
except sqlite3.OperationalError:
|
||||
pass
|
||||
_normalize_profile_only_tables(conn)
|
||||
now = utcnow()
|
||||
conn.execute(
|
||||
"INSERT OR IGNORE INTO users(id, username, password_hash, role, is_active, created_at, updated_at) VALUES(1, 'default', NULL, 'admin', 1, ?, ?)",
|
||||
|
||||
Reference in New Issue
Block a user