Improve migration architecture
This commit is contained in:
@@ -3,6 +3,15 @@ import sqlite3
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from .v3_0_6 import (
|
||||
check_history_3_0_6,
|
||||
check_watch_playlists_3_0_6,
|
||||
check_watch_artists_3_0_6,
|
||||
update_history_3_0_6,
|
||||
update_watch_playlists_3_0_6,
|
||||
update_watch_artists_3_0_6,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DATA_DIR = Path("./data")
|
||||
@@ -143,174 +152,8 @@ def _update_children_tables_for_history(conn: sqlite3.Connection) -> None:
|
||||
logger.error("Failed migrating children history tables", exc_info=True)
|
||||
|
||||
|
||||
def _history_needs_306(conn: sqlite3.Connection) -> bool:
|
||||
"""Detect if history DB needs 3.0.6 schema (missing columns or tables)."""
|
||||
# If table missing entirely, we definitely need to create it
|
||||
cur = conn.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='download_history'"
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
return True
|
||||
cols = _table_columns(conn, "download_history")
|
||||
required = {
|
||||
"id",
|
||||
"download_type",
|
||||
"title",
|
||||
"artists",
|
||||
"timestamp",
|
||||
"status",
|
||||
"service",
|
||||
"quality_format",
|
||||
"quality_bitrate",
|
||||
"total_tracks",
|
||||
"successful_tracks",
|
||||
"failed_tracks",
|
||||
"skipped_tracks",
|
||||
"children_table",
|
||||
"task_id",
|
||||
"external_ids",
|
||||
"metadata",
|
||||
"release_date",
|
||||
"genres",
|
||||
"images",
|
||||
"owner",
|
||||
"album_type",
|
||||
"duration_total_ms",
|
||||
"explicit",
|
||||
}
|
||||
return not required.issubset(cols)
|
||||
|
||||
|
||||
def _watch_playlists_needs_306(conn: sqlite3.Connection) -> bool:
|
||||
cur = conn.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='watched_playlists'"
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
return True
|
||||
cols = _table_columns(conn, "watched_playlists")
|
||||
required = {
|
||||
"spotify_id",
|
||||
"name",
|
||||
"owner_id",
|
||||
"owner_name",
|
||||
"total_tracks",
|
||||
"link",
|
||||
"snapshot_id",
|
||||
"last_checked",
|
||||
"added_at",
|
||||
"is_active",
|
||||
}
|
||||
return not required.issubset(cols)
|
||||
|
||||
|
||||
def _watch_artists_needs_306(conn: sqlite3.Connection) -> bool:
|
||||
cur = conn.execute(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name='watched_artists'"
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
return True
|
||||
cols = _table_columns(conn, "watched_artists")
|
||||
required = {
|
||||
"spotify_id",
|
||||
"name",
|
||||
"link",
|
||||
"total_albums_on_spotify",
|
||||
"last_checked",
|
||||
"added_at",
|
||||
"is_active",
|
||||
"genres",
|
||||
"popularity",
|
||||
"image_url",
|
||||
}
|
||||
return not required.issubset(cols)
|
||||
|
||||
|
||||
def _apply_history_306(conn: sqlite3.Connection) -> None:
|
||||
logger.info("Applying 3.0.6 migration for history DB")
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS download_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
download_type TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
artists TEXT,
|
||||
timestamp REAL NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
service TEXT,
|
||||
quality_format TEXT,
|
||||
quality_bitrate TEXT,
|
||||
total_tracks INTEGER,
|
||||
successful_tracks INTEGER,
|
||||
failed_tracks INTEGER,
|
||||
skipped_tracks INTEGER,
|
||||
children_table TEXT,
|
||||
task_id TEXT,
|
||||
external_ids TEXT,
|
||||
metadata TEXT,
|
||||
release_date TEXT,
|
||||
genres TEXT,
|
||||
images TEXT,
|
||||
owner TEXT,
|
||||
album_type TEXT,
|
||||
duration_total_ms INTEGER,
|
||||
explicit BOOLEAN
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_download_history_timestamp ON download_history(timestamp);
|
||||
CREATE INDEX IF NOT EXISTS idx_download_history_type_status ON download_history(download_type, status);
|
||||
CREATE INDEX IF NOT EXISTS idx_download_history_task_id ON download_history(task_id);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_download_history_task_type_ids ON download_history(task_id, download_type, external_ids);
|
||||
"""
|
||||
)
|
||||
# After ensuring main table, also ensure children tables
|
||||
_update_children_tables_for_history(conn)
|
||||
|
||||
|
||||
def _apply_watch_playlists_306(conn: sqlite3.Connection) -> None:
|
||||
logger.info("Applying 3.0.6 migration for watch playlists DB")
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS watched_playlists (
|
||||
spotify_id TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
owner_id TEXT,
|
||||
owner_name TEXT,
|
||||
total_tracks INTEGER,
|
||||
link TEXT,
|
||||
snapshot_id TEXT,
|
||||
last_checked INTEGER,
|
||||
added_at INTEGER,
|
||||
is_active INTEGER DEFAULT 1
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def _apply_watch_artists_306(conn: sqlite3.Connection) -> None:
|
||||
logger.info("Applying 3.0.6 migration for watch artists DB")
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS watched_artists (
|
||||
spotify_id TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
link TEXT,
|
||||
total_albums_on_spotify INTEGER,
|
||||
last_checked INTEGER,
|
||||
added_at INTEGER,
|
||||
is_active INTEGER DEFAULT 1,
|
||||
genres TEXT,
|
||||
popularity INTEGER,
|
||||
image_url TEXT
|
||||
);
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def run_migrations_if_needed() -> None:
|
||||
"""Detect and apply necessary migrations to align DB schema for v3.1.x.
|
||||
Currently implements 3.0.6 baseline creation for history and watch DBs.
|
||||
"""Detect and apply necessary migrations by version for each DB.
|
||||
Idempotent by design.
|
||||
"""
|
||||
try:
|
||||
@@ -318,11 +161,10 @@ def run_migrations_if_needed() -> None:
|
||||
h_conn = _safe_connect(HISTORY_DB)
|
||||
if h_conn:
|
||||
try:
|
||||
if _history_needs_306(h_conn):
|
||||
_apply_history_306(h_conn)
|
||||
else:
|
||||
# Even if main table is OK, ensure children tables are up-to-date
|
||||
_update_children_tables_for_history(h_conn)
|
||||
if not check_history_3_0_6(h_conn):
|
||||
update_history_3_0_6(h_conn)
|
||||
# Ensure children tables regardless
|
||||
_update_children_tables_for_history(h_conn)
|
||||
h_conn.commit()
|
||||
finally:
|
||||
h_conn.close()
|
||||
@@ -331,8 +173,8 @@ def run_migrations_if_needed() -> None:
|
||||
p_conn = _safe_connect(PLAYLISTS_DB)
|
||||
if p_conn:
|
||||
try:
|
||||
if _watch_playlists_needs_306(p_conn):
|
||||
_apply_watch_playlists_306(p_conn)
|
||||
if not check_watch_playlists_3_0_6(p_conn):
|
||||
update_watch_playlists_3_0_6(p_conn)
|
||||
p_conn.commit()
|
||||
finally:
|
||||
p_conn.close()
|
||||
@@ -340,8 +182,8 @@ def run_migrations_if_needed() -> None:
|
||||
a_conn = _safe_connect(ARTISTS_DB)
|
||||
if a_conn:
|
||||
try:
|
||||
if _watch_artists_needs_306(a_conn):
|
||||
_apply_watch_artists_306(a_conn)
|
||||
if not check_watch_artists_3_0_6(a_conn):
|
||||
update_watch_artists_3_0_6(a_conn)
|
||||
a_conn.commit()
|
||||
finally:
|
||||
a_conn.close()
|
||||
|
||||
Reference in New Issue
Block a user