import argparse import os import sqlite3 import sys import bot import ycdl def upgrade_1_to_2(ycdldb): ''' In this version, the `duration` column was added to the videos table. ''' ycdldb.sql.execute('ALTER TABLE videos RENAME TO videos_old') ycdldb.sql.execute(''' CREATE TABLE videos( id TEXT, published INT, author_id TEXT, title TEXT, description TEXT, duration INT, thumbnail TEXT, download TEXT ) ''') ycdldb.sql.execute(''' INSERT INTO videos SELECT id, published, author_id, title, description, NULL, thumbnail, download FROM videos_old ''') ycdldb.sql.execute('DROP TABLE videos_old') def upgrade_2_to_3(ycdldb): ''' In this version, the `automark` column was added to the channels table, where you can set channels to automatically mark videos as ignored or downloaded. ''' ycdldb.sql.execute('ALTER TABLE channels ADD COLUMN automark TEXT') def upgrade_3_to_4(ycdldb): ''' In this version, the `views` column was added to the videos table. ''' ycdldb.sql.execute('ALTER TABLE videos RENAME TO videos_old') ycdldb.sql.execute(''' CREATE TABLE videos( id TEXT, published INT, author_id TEXT, title TEXT, description TEXT, duration INT, views INT, thumbnail TEXT, download TEXT ) ''') ycdldb.sql.execute(''' INSERT INTO videos SELECT id, published, author_id, title, description, duration, NULL, thumbnail, download FROM videos_old ''') ycdldb.sql.execute('DROP TABLE videos_old') def upgrade_4_to_5(ycdldb): ''' In this version, the `uploads_playlist` column was added to the channels table. ''' ycdldb.sql.execute('ALTER TABLE channels RENAME TO channels_old') ycdldb.sql.execute(''' CREATE TABLE channels( id TEXT, name TEXT, uploads_playlist TEXT, directory TEXT COLLATE NOCASE, automark TEXT ) ''') ycdldb.sql.execute(''' INSERT INTO channels SELECT id, name, NULL, directory, automark FROM channels_old ''') ycdldb.sql.execute('DROP TABLE channels_old') rows = ycdldb.sql.execute('SELECT id FROM channels').fetchall() channels = [row[0] for row in rows] for channel in channels: try: uploads_playlist = ycdldb.youtube.get_user_uploads_playlist_id(channel) except ycdl.ytapi.ChannelNotFound: continue print(f'{channel} has playlist {uploads_playlist}.') ycdldb.sql.execute( 'UPDATE channels SET uploads_playlist = ? WHERE id = ?', [uploads_playlist, channel] ) def upgrade_5_to_6(ycdldb): ''' In this version, the `directory` column of the channels table was renamed to `download_directory` to be in line with the default config's name for the same value, and the `queuefile_extension` column was added. ''' ycdldb.sql.execute('ALTER TABLE channels RENAME TO channels_old') ycdldb.sql.execute(''' CREATE TABLE channels( id TEXT, name TEXT, uploads_playlist TEXT, download_directory TEXT COLLATE NOCASE, queuefile_extension TEXT COLLATE NOCASE, automark TEXT ) ''') ycdldb.sql.execute(''' INSERT INTO channels SELECT id, name, uploads_playlist, directory, NULL, automark FROM channels_old ''') ycdldb.sql.execute('DROP TABLE channels_old') def upgrade_6_to_7(ycdldb): ''' In this version, the `download` column of the videos table was renamed to `state`. The vocabulary throughout the rest of the program had already evolved and the database column was behind the times. ''' ycdldb.sql.execute('ALTER TABLE videos RENAME COLUMN download TO state') ycdldb.sql.execute('DROP INDEX IF EXISTS index_video_author_download') ycdldb.sql.execute('DROP INDEX IF EXISTS index_video_download') ycdldb.sql.execute('DROP INDEX IF EXISTS index_video_download_published') # /videos/state?orderby=published ycdldb.sql.execute('CREATE INDEX index_video_state_published on videos(state, published)') def upgrade_7_to_8(ycdldb): ''' In this version, indexes were optimized by adding indexes that satisfy the major use cases, and deleting indexes that are redundant in the presence of another multi-column index. ''' # /channel?orderby=published ycdldb.sql.execute(''' CREATE INDEX IF NOT EXISTS index_video_author_published on videos(author_id, published); ''') # /channel/state?orderby=published ycdldb.sql.execute(''' CREATE INDEX IF NOT EXISTS index_video_author_state_published on videos(author_id, state, published); ''') # Redundant due to (author, published) ycdldb.sql.execute('DROP INDEX IF EXISTS index_video_author') # Redundant due to (author, state, published) ycdldb.sql.execute('DROP INDEX IF EXISTS index_video_author_state') # Redundant due to (state, published) ycdldb.sql.execute('DROP INDEX IF EXISTS index_video_state') def upgrade_all(data_directory): ''' Given the directory containing a ycdl database, apply all of the needed upgrade_x_to_y functions in order. ''' youtube = ycdl.ytapi.Youtube(bot.get_youtube_key()) ycdldb = ycdl.ycdldb.YCDLDB(youtube, data_directory, skip_version_check=True) cur = ycdldb.sql.cursor() cur.execute('PRAGMA user_version') current_version = cur.fetchone()[0] needed_version = ycdl.constants.DATABASE_VERSION if current_version == needed_version: print('Already up to date with version %d.' % needed_version) return for version_number in range(current_version + 1, needed_version + 1): print('Upgrading from %d to %d.' % (current_version, version_number)) upgrade_function = 'upgrade_%d_to_%d' % (current_version, version_number) upgrade_function = eval(upgrade_function) try: ycdldb.sql.execute('BEGIN') upgrade_function(ycdldb) except Exception as exc: ycdldb.rollback() raise else: ycdldb.sql.cursor().execute('PRAGMA user_version = %d' % version_number) ycdldb.commit() current_version = version_number print('Upgrades finished.') def upgrade_all_argparse(args): return upgrade_all(data_directory=args.data_directory) def main(argv): parser = argparse.ArgumentParser() parser.add_argument('data_directory') parser.set_defaults(func=upgrade_all_argparse) args = parser.parse_args(argv) return args.func(args) if __name__ == '__main__': raise SystemExit(main(sys.argv[1:]))