feat: add migration to move old files to xdg directory

+ add db column for migration version
+ handle pre-init migrations
+ handle post-init migration
This commit is contained in:
geoffrey45 2023-02-12 03:22:21 +03:00
parent 97e29c3254
commit b77b1747f1
10 changed files with 215 additions and 19 deletions

View File

@ -0,0 +1,64 @@
"""
Reads and saves the latest database migrations version.
"""
from app.db.sqlite.utils import SQLiteManager
class MigrationManager:
all_get_sql = "SELECT * FROM migrations"
pre_init_set_sql = "UPDATE migrations SET pre_init_version = ? WHERE id = 1"
post_init_set_sql = "UPDATE migrations SET post_init_version = ? WHERE id = 1"
@classmethod
def get_preinit_version(cls) -> int:
"""
Returns the latest userdata pre-init database version.
"""
with SQLiteManager() as cur:
cur.execute(cls.all_get_sql)
return int(cur.fetchone()[1])
@classmethod
def get_maindb_postinit_version(cls) -> int:
"""
Returns the latest maindb post-init database version.
"""
with SQLiteManager() as cur:
cur.execute(cls.all_get_sql)
return int(cur.fetchone()[2])
@classmethod
def get_userdatadb_postinit_version(cls) -> int:
"""
Returns the latest userdata post-init database version.
"""
with SQLiteManager(userdata_db=True) as cur:
cur.execute(cls.all_get_sql)
return cur.fetchone()[2]
# 👇 Setters 👇
@classmethod
def set_preinit_version(cls, version: int):
"""
Sets the userdata pre-init database version.
"""
with SQLiteManager() as cur:
cur.execute(cls.pre_init_set_sql, (version,))
@classmethod
def set_maindb_postinit_version(cls, version: int):
"""
Sets the maindb post-init database version.
"""
with SQLiteManager() as cur:
cur.execute(cls.post_init_set_sql, (version,))
@classmethod
def set_userdatadb_postinit_version(cls, version: int):
"""
Sets the userdata post-init database version.
"""
with SQLiteManager(userdata_db=True) as cur:
cur.execute(cls.post_init_set_sql, (version,))

View File

@ -69,3 +69,13 @@ CREATE TABLE IF NOT EXISTS folders (
trackcount integer NOT NULL
);
"""
CREATE_MIGRATIONS_TABLE = """
CREATE TABLE IF NOT EXISTS migrations (
id integer PRIMARY KEY,
pre_init_version integer NOT NULL DEFAULT 0,
post_init_version integer NOT NULL DEFAULT 0
);
INSERT INTO migrations (pre_init_version, post_init_version) VALUES (0, 0);
"""

View File

@ -2,19 +2,43 @@
Migrations module.
Reads and applies the latest database migrations.
PLEASE NOTE: OLDER MIGRATIONS CAN NEVER BE DELETED.
ONLY MODIFY OLD MIGRATIONS FOR BUG FIXES OR ENHANCEMENTS ONLY
[TRY NOT TO MODIFY BEHAVIOR, UNLESS YOU KNOW WHAT YOU'RE DOING].
"""
from app.db.sqlite.migrations import MigrationManager
from app.logger import log
from .main import main_db_migrations
from .userdata import userdata_db_migrations
def apply_migrations():
userdb_version = 0
maindb_version = 0
"""
Applies the latest database migrations.
"""
userdb_version = MigrationManager.get_userdatadb_postinit_version()
maindb_version = MigrationManager.get_maindb_postinit_version()
for migration in main_db_migrations:
if migration.version > maindb_version:
log.info("Running new MAIN-DB post-init migration: %s", migration.name)
migration.migrate()
for migration in userdata_db_migrations:
if migration.version > userdb_version:
log.info("Running new USERDATA-DB post-init migration: %s", migration.name)
migration.migrate()
def set_postinit_migration_versions():
"""
Sets the post-init migration versions.
"""
# TODO: Don't forget to remove the zeros below when you add a valid migration 👇.
MigrationManager.set_maindb_postinit_version(0)
MigrationManager.set_userdatadb_postinit_version(0)

View File

@ -0,0 +1,38 @@
"""
Pre-init migrations are executed before the database is created.
Useful when you need to move files or folders before the database is created.
PLEASE NOTE: OLDER MIGRATIONS CAN NEVER BE DELETED.
ONLY MODIFY OLD MIGRATIONS FOR BUG FIXES OR ENHANCEMENTS ONLY.
[TRY NOT TO MODIFY BEHAVIOR, UNLESS YOU KNOW WHAT YOU'RE DOING].
"""
from sqlite3 import OperationalError
from app.db.sqlite.migrations import MigrationManager
from app.logger import log
from .move_to_xdg_folder import MoveToXdgFolder
all_preinits = [MoveToXdgFolder]
def run_preinit_migrations():
"""
Runs all pre-init migrations.
"""
try:
userdb_version = MigrationManager.get_preinit_version()
except (OperationalError):
userdb_version = 0
for migration in all_preinits:
if migration.version > userdb_version:
log.warn("Running new pre-init migration: %s", migration.name)
migration.migrate()
def set_preinit_migration_versions():
"""
Sets the migration versions.
"""
MigrationManager.set_preinit_version(all_preinits[-1].version)

View File

@ -0,0 +1,49 @@
"""
This migration handles moving the config folder to the XDG standard location.
It also handles moving the userdata and the downloaded artist images to the new location.
"""
import os
import shutil
from app.settings import APP_DIR, USER_HOME_DIR
from app.logger import log
class MoveToXdgFolder:
version = 1
name = "MoveToXdgFolder"
@staticmethod
def migrate():
old_config_dir = os.path.join(USER_HOME_DIR, ".swing")
new_config_dir = APP_DIR
if not os.path.exists(old_config_dir):
log.info("No old config folder found. Skipping migration.")
return
log.info("Found old config folder: %s", old_config_dir)
old_imgs_dir = os.path.join(old_config_dir, "images")
# move images to new location
if os.path.exists(old_imgs_dir):
shutil.copytree(
old_imgs_dir,
os.path.join(new_config_dir, "images"),
copy_function=shutil.copy2,
dirs_exist_ok=True,
)
log.warn("Moved artist images to: %s", new_config_dir)
# move userdata.db to new location
userdata_db = os.path.join(old_config_dir, "userdata.db")
if os.path.exists(userdata_db):
shutil.copy2(userdata_db, new_config_dir)
log.warn("Moved userdata.db to: %s", new_config_dir)
log.warn("Migration complete. ✅")
# swing.db is not moved because the new code fixes bugs which require
# the whole database to be recreated anyway. (ie. the bug which caused duplicate album and artist color entries)

View File

@ -1,4 +1,10 @@
from .sample import SampleMigrationModel
"""
Migrations for the main database.
main_db_migrations = [SampleMigrationModel]
PLEASE NOTE: OLDER MIGRATIONS CAN NEVER BE DELETED.
ONLY MODIFY OLD MIGRATIONS FOR BUG FIXES OR ENHANCEMENTS ONLY
[TRY NOT TO MODIFY BEHAVIOR, UNLESS YOU KNOW WHAT YOU'RE DOING].
"""
main_db_migrations = []

View File

@ -1,6 +0,0 @@
class SampleMigrationModel:
version = 1
@staticmethod
def migrate():
print("executing sample main db migration")

View File

@ -1,4 +1,10 @@
from .sample import SampleMigrationModel
"""
Migrations for the userdata database.
userdata_db_migrations = [SampleMigrationModel]
PLEASE NOTE: OLDER MIGRATIONS CAN NEVER BE DELETED.
ONLY MODIFY OLD MIGRATIONS FOR BUG FIXES OR ENHANCEMENTS ONLY
[TRY NOT TO MODIFY BEHAVIOR, UNLESS YOU KNOW WHAT YOU'RE DOING].
"""
userdata_db_migrations = []

View File

@ -1,6 +0,0 @@
class SampleMigrationModel:
version = 1
@staticmethod
def migrate():
print("executing sample userdata db migration")

View File

@ -3,14 +3,19 @@ Contains the functions to prepare the server for use.
"""
import os
import shutil
import time
from configparser import ConfigParser
from app import settings
from app.db.sqlite import create_connection, create_tables, queries
from app.db.store import Store
from app.migrations import apply_migrations, set_postinit_migration_versions
from app.migrations._preinit import (
run_preinit_migrations,
set_preinit_migration_versions,
)
from app.settings import APP_DB_PATH, USERDATA_DB_PATH
from app.utils import get_home_res_path
from app.migrations import apply_migrations
config = ConfigParser()
@ -102,6 +107,7 @@ def setup_sqlite():
"""
# if os.path.exists(DB_PATH):
# os.remove(DB_PATH)
run_preinit_migrations()
app_db_conn = create_connection(APP_DB_PATH)
playlist_db_conn = create_connection(USERDATA_DB_PATH)
@ -109,10 +115,15 @@ def setup_sqlite():
create_tables(app_db_conn, queries.CREATE_APPDB_TABLES)
create_tables(playlist_db_conn, queries.CREATE_USERDATA_TABLES)
create_tables(app_db_conn, queries.CREATE_MIGRATIONS_TABLE)
create_tables(playlist_db_conn, queries.CREATE_MIGRATIONS_TABLE)
app_db_conn.close()
playlist_db_conn.close()
apply_migrations()
set_preinit_migration_versions()
set_postinit_migration_versions()
Store.load_all_tracks()
Store.process_folders()