# -*- coding: utf-8 -*- from __future__ import absolute_import from builtins import object from . import control from resources.lib.ui.globals import g import xbmcvfs import threading try: from sqlite3 import dbapi2 as db, OperationalError except ImportError: from pysqlite2 import dbapi2 as db, OperationalError migrate_db_lock = threading.Lock() class AnilistSyncDatabase(object): def __init__(self): self.activites = {} self._build_show_table() self._build_episode_table() self._build_sync_activities() self._build_season_table() # If you make changes to the required meta in any indexer that is cached in this database # You will need to update the below version number to match the new addon version # This will ensure that the metadata required for operations is available # You may also update this version number to force a rebuild of the database after updating kaito self.last_meta_update = '0.0.10' migrate_db_lock.acquire() self._refresh_activites() if self.activites is None: cursor = self._get_cursor() cursor.execute("DELETE FROM shows") cursor.execute("DELETE FROM seasons") cursor.execute("DELETE FROM episodes") cursor.connection.commit() self._set_base_activites() cursor.execute('SELECT * FROM activities WHERE sync_id=1') self.activites = cursor.fetchone() cursor.close() control.try_release_lock(migrate_db_lock) if self.activites is not None: self._check_database_version() def _refresh_activites(self): cursor = self._get_cursor() cursor.execute('SELECT * FROM activities WHERE sync_id=1') self.activites = cursor.fetchone() cursor.close() def _set_base_activites(self): cursor = self._get_cursor() cursor.execute('INSERT INTO activities(sync_id, kaito_version)' 'VALUES(1, ?)', (self.last_meta_update,)) cursor.connection.commit() self.activites = cursor.fetchone() cursor.close() def _check_database_version(self): # Migrate from an old version before database migrations if 'kaito_version' not in self.activites: ## control.log('Upgrading Trakt Sync Database Version') self.clear_all_meta() migrate_db_lock.acquire() cursor = self._get_cursor() cursor.execute('ALTER TABLE activities ADD COLUMN kaito_version TEXT') cursor.execute('UPDATE activities SET kaito_version = ?', (self.last_meta_update,)) cursor.connection.commit() cursor.close() control.try_release_lock(migrate_db_lock) if self.check_version_numbers(self.activites['kaito_version'], self.last_meta_update): ## control.log('Rebuilding Trakt Sync Database Version') self.re_build_database(True) return def check_version_numbers(self, current, new): # Compares version numbers and return True if new version is newer current = current.split('.') new = new.split('.') step = 0 for i in current: if int(new[step]) > int(i): return True if int(i) == int(new[step]): step += 1 continue return False def clear_all_meta(self): path = g.ANILIST_SYNC_DB_PATH xbmcvfs.delete(path) file = open(path, 'a+') file.close() self._build_show_table() self._build_episode_table() self._build_sync_activities() self._build_season_table() def _build_show_table(self): migrate_db_lock.acquire() cursor = self._get_cursor() cursor.execute('CREATE TABLE IF NOT EXISTS shows ' '(anilist_id INTEGER PRIMARY KEY, ' 'mal_id INTEGER,' 'simkl_id INTEGER,' 'kitsu_id INTEGER,' 'meta_ids TEXT,' 'kodi_meta TEXT NOT NULL, ' 'last_updated TEXT NOT NULL, ' 'air_date TEXT, ' 'UNIQUE(anilist_id))') cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS ix_shows ON "shows" (anilist_id ASC )') cursor.connection.commit() cursor.close() control.try_release_lock(migrate_db_lock) def _build_season_table(self): migrate_db_lock.acquire() cursor = self._get_cursor() cursor.execute('CREATE TABLE IF NOT EXISTS seasons (' 'anilist_id INTEGER NOT NULL, ' 'season INTEGER NOT NULL, ' 'kodi_meta TEXT NOT NULL, ' 'air_date TEXT, ' 'FOREIGN KEY(anilist_id) REFERENCES shows(anilist_id) ON DELETE CASCADE)') cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS ix_season ON seasons (anilist_id ASC, season ASC)') cursor.connection.commit() cursor.close() control.try_release_lock(migrate_db_lock) def _build_episode_table(self): migrate_db_lock.acquire() cursor = self._get_cursor() cursor.execute('CREATE TABLE IF NOT EXISTS episodes (' 'anilist_id INTEGER NOT NULL, ' 'season INTEGER NOT NULL, ' 'kodi_meta TEXT NOT NULL, ' 'last_updated TEXT NOT NULL, ' 'number INTEGER NOT NULL, ' 'number_abs INTEGER,' 'air_date TEXT, ' 'FOREIGN KEY(anilist_id) REFERENCES shows(anilist_id) ON DELETE CASCADE)') cursor.execute('CREATE UNIQUE INDEX IF NOT EXISTS ix_episodes ON episodes (anilist_id ASC, season ASC, number ASC)') cursor.connection.commit() cursor.close() control.try_release_lock(migrate_db_lock) def _build_sync_activities(self): migrate_db_lock.acquire() cursor = self._get_cursor() cursor.execute('CREATE TABLE IF NOT EXISTS activities (' 'sync_id INTEGER PRIMARY KEY, ' 'kaito_version TEXT NOT NULL) ' ) cursor.connection.commit() cursor.close() control.try_release_lock(migrate_db_lock) def _build_lists_table(self): migrate_db_lock.acquire() cursor = self._get_cursor() cursor.execute('CREATE TABLE IF NOT EXISTS lists (' 'trakt_id INTEGER NOT NULL, ' 'media_type TEXT NOT NULL,' 'name TEXT NOT NULL, ' 'username TEXT NOT NULL, ' 'kodi_meta TEXT NOT NULL, ' 'updated_at TEXT NOT NULL,' 'list_type TEXT NOT NULL,' 'item_count INT NOT NULL,' 'sort_by TEXT NOT NULL,' 'sort_how TEXT NOT NULL,' 'slug TEXT NOT NULL,' 'PRIMARY KEY (trakt_id, media_type)) ' ) cursor.connection.commit() cursor.close() control.try_release_lock(migrate_db_lock) def _get_cursor(self): conn = _get_connection() conn.execute("PRAGMA FOREIGN_KEYS = 1") cursor = conn.cursor() return cursor def flush_activities(self, clear_meta=False): if clear_meta: self.clear_all_meta() migrate_db_lock.acquire() cursor = self._get_cursor() cursor.execute('DROP TABLE activities') cursor.connection.commit() cursor.close() control.try_release_lock(migrate_db_lock) def re_build_database(self, silent=False): if not silent: confirm = control.yesno_dialog(g.ADDON_NAME, g.lang(30203)) if confirm == 0: return path = g.ANILIST_SYNC_DB_PATH xbmcvfs.delete(path) file = open(path, 'a+') file.close() self._build_show_table() self._build_episode_table() self._build_sync_activities() self._build_season_table() self._set_base_activites() self._refresh_activites() g.close_busy_dialog() control.showDialog.notification(g.ADDON_NAME, "Database rebuilt", time=5000) ## from resources.lib.modules.trakt_sync import activities ## sync_errors = activities.TraktSyncDatabase().sync_activities(silent) ## ## if sync_errors: ## control.showDialog.notification(control.addonName + ': Trakt', control.lang(40353), time=5000) ## elif sync_errors is None: ## self._refresh_activites() ## return ## else: ## control.showDialog.notification(control.addonName + ': Trakt', control.lang(40262), time=5000) def _dict_factory(cursor, row): d = {} for idx, col in enumerate(cursor.description): d[col[0]] = row[idx] return d def makeFile(path): try: xbmcvfs.mkdir(path) except: try: file = open(path, 'a+') file.close() except: pass def _get_connection(): makeFile(g.ADDON_USERDATA_PATH) conn = db.connect(g.ANILIST_SYNC_DB_PATH, timeout=60.0) conn.row_factory = _dict_factory return conn