From 09df681ebe2751c829ea0bae72c3c0721916dae6 Mon Sep 17 00:00:00 2001 From: Terry Geng Date: Fri, 24 Apr 2020 10:41:47 +0800 Subject: [PATCH] feat: database migration function mentioned in #133 --- .gitignore | 4 +- database.py | 145 +++++++++++++++++++++++++++++++------------------- media/file.py | 4 -- mumbleBot.py | 14 ++--- 4 files changed, 100 insertions(+), 67 deletions(-) diff --git a/.gitignore b/.gitignore index 6ae27de..c5b54af 100644 --- a/.gitignore +++ b/.gitignore @@ -108,6 +108,8 @@ venv.bak/ configuration.ini .vscode/settings.json 2019-07-27 22_09_08-radiobrowser.py - botamusique - Visual Studio Code.png +.DS_Store +*.pem music_folder/ tmp/ @@ -115,4 +117,4 @@ tmp/ database.db # Pycharm -.idea/ \ No newline at end of file +.idea/ diff --git a/database.py b/database.py index 01f4eb7..b5c6b6d 100644 --- a/database.py +++ b/database.py @@ -3,7 +3,9 @@ import sqlite3 import json import datetime import time +import logging +log = logging.getLogger("bot") class DatabaseError(Exception): pass @@ -187,7 +189,7 @@ class Condition: return self SETTING_DB_VERSION = 1 -MUSIC_DB_VERSION = 1 +MUSIC_DB_VERSION = 2 class SettingsDatabase: def __init__(self, db_path): @@ -319,60 +321,9 @@ class MusicDatabase: def __init__(self, db_path): self.db_path = db_path - self.db_version_check_and_create() + MusicDatabaseMigration(self).migrate() self.manage_special_tags() - def has_table(self, table): - conn = sqlite3.connect(self.db_path) - cursor = conn.cursor() - tables = cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?;", (table,)).fetchall() - conn.close() - if len(tables) == 0: - return False - return True - - def create_table(self): - conn = sqlite3.connect(self.db_path) - cursor = conn.cursor() - - cursor.execute("CREATE TABLE music (" - "id TEXT PRIMARY KEY, " - "type TEXT, " - "title TEXT, " - "keywords TEXT, " - "metadata TEXT, " - "tags TEXT, " - "path TEXT, " - "create_at DATETIME DEFAULT CURRENT_TIMESTAMP" - ")") - cursor.execute("INSERT INTO music (id, title) " - "VALUES ('info', ?)", (MUSIC_DB_VERSION,)) - - conn.commit() - conn.close() - - def db_version_check_and_create(self): - conn = sqlite3.connect(self.db_path) - cursor = conn.cursor() - if self.has_table('music'): - ver = cursor.execute("SELECT title FROM music WHERE id='info'").fetchone() - if ver and int(ver[0]) == MUSIC_DB_VERSION: - conn.close() - return True - else: - cursor.execute("ALTER TABLE music RENAME TO music_old") - conn.commit() - - self.create_table() - - cursor.execute("INSERT INTO music (id, type, title, metadata, tags)" - "SELECT id, type, title, metadata, tags FROM music_old") - cursor.execute("DROP TABLE music_old") - conn.commit() - conn.close() - else: - self.create_table() - def insert_music(self, music_dict, _conn=None): conn = sqlite3.connect(self.db_path) if _conn is None else _conn cursor = conn.cursor() @@ -567,8 +518,6 @@ class MusicDatabase: if 'path' not in music_dict or result[5]: music_dict['path'] = result[5] music_dict['keywords'] = result[6] - if not music_dict['tags'][0]: - music_dict['tags'] = [] music_dicts.append(music_dict) @@ -589,3 +538,89 @@ class MusicDatabase: cursor = conn.cursor() cursor.execute("DROP TABLE music") conn.close() + + +class MusicDatabaseMigration: + def __init__(self, db: MusicDatabase): + self.db = db + self.migrate_func = {} + self.migrate_func[0] = self.migrate_from_0_to_1 + self.migrate_func[1] = self.migrate_from_1_to_2 + + def migrate(self): + conn = sqlite3.connect(self.db.db_path) + cursor = conn.cursor() + if self.has_table('music', conn): + current_version = 0 + ver = cursor.execute("SELECT title FROM music WHERE id='info'").fetchone() + if ver: + current_version = int(ver[0]) + + if current_version == MUSIC_DB_VERSION: + conn.close() + return + else: + log.info(f"database: migrating from music table version {current_version} to {MUSIC_DB_VERSION}...") + while current_version < MUSIC_DB_VERSION: + log.debug(f"database: migrate step {current_version}/{MUSIC_DB_VERSION - 1}") + current_version = self.migrate_func[current_version](conn) + log.info(f"database: migration done.") + + else: + log.info(f"database: no music table found. Creating music table version {MUSIC_DB_VERSION}.") + self.create_table_version_2(conn) + + conn.close() + + def has_table(self, table, conn): + cursor = conn.cursor() + tables = cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name=?;", (table,)).fetchall() + if len(tables) == 0: + return False + return True + + def create_table_version_1(self, conn): + cursor = conn.cursor() + + cursor.execute("CREATE TABLE music (" + "id TEXT PRIMARY KEY, " + "type TEXT, " + "title TEXT, " + "keywords TEXT, " + "metadata TEXT, " + "tags TEXT, " + "path TEXT, " + "create_at DATETIME DEFAULT CURRENT_TIMESTAMP" + ")") + cursor.execute("INSERT INTO music (id, title) " + "VALUES ('info', ?)", (MUSIC_DB_VERSION,)) + + conn.commit() + + def create_table_version_2(self, conn): + self.create_table_version_1(conn) + + def migrate_from_0_to_1(self, conn): + cursor = conn.cursor() + cursor.execute("ALTER TABLE music RENAME TO music_old") + conn.commit() + + self.create_table_version_1(conn) + + cursor.execute("INSERT INTO music (id, type, title, metadata, tags)" + "SELECT id, type, title, metadata, tags FROM music_old") + cursor.execute("DROP TABLE music_old") + conn.commit() + + return 1 # return new version number + + def migrate_from_1_to_2(self, conn): + items_to_update = self.db.query_music(Condition(), conn) + for item in items_to_update: + item['keywords'] = item['title'] + if 'artist' in item: + item['keywords'] += ' ' + item['artist'] + self.db.insert_music(item) + conn.commit() + + return 2 # return new version number diff --git a/media/file.py b/media/file.py index a0bcd70..6527989 100644 --- a/media/file.py +++ b/media/file.py @@ -73,10 +73,6 @@ class FileItem(BaseItem): "file: music file missed for %s" % self.format_debug_string()) raise ValidationFailedError(constants.strings('file_missed', file=self.path)) - if not self.keywords: - self.keywords = self.title + " " + self.artist # migrate from previous version - self.version += 1 - # self.version += 1 # 0 -> 1, notify the wrapper to save me when validate() is visited the first time self.ready = "yes" return True diff --git a/mumbleBot.py b/mumbleBot.py index e8aa8e4..cab12a3 100644 --- a/mumbleBot.py +++ b/mumbleBot.py @@ -48,13 +48,6 @@ class MumbleBot: else: self.channel = var.config.get("server", "channel", fallback=None) - if args.verbose: - self.log.setLevel(logging.DEBUG) - self.log.debug("Starting in DEBUG loglevel") - elif args.quiet: - self.log.setLevel(logging.ERROR) - self.log.error("Starting in ERROR loglevel") - var.user = args.user var.music_folder = util.solve_filepath(var.config.get('bot', 'music_folder')) var.tmp_folder = util.solve_filepath(var.config.get('bot', 'tmp_folder')) @@ -711,6 +704,13 @@ if __name__ == '__main__': formatter = logging.Formatter('[%(asctime)s %(levelname)s %(threadName)s] %(message)s', "%b %d %H:%M:%S") bot_logger.setLevel(logging.INFO) + if args.verbose: + bot_logger.setLevel(logging.DEBUG) + bot_logger.debug("Starting in DEBUG loglevel") + elif args.quiet: + bot_logger.setLevel(logging.ERROR) + bot_logger.error("Starting in ERROR loglevel") + logfile = util.solve_filepath(var.config.get('bot', 'logfile')) handler = None if logfile: