#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import, division, unicode_literals from threading import Lock from .. import db, path_ops KODIDB_LOCK = Lock() # Names of tables we generally leave untouched and e.g. don't wipe UNTOUCHED_TABLES = ('version', 'versiontagscan') class KodiDBBase(object): """ Kodi database methods used for all types of items """ def __init__(self, texture_db=False, kodiconn=None, artconn=None, lock=True, wal_mode=True): """ Allows direct use with a cursor instead of context mgr Pass wal_mode=False if you want the standard sqlite journal_mode, e.g. when wiping entire tables """ self._texture_db = texture_db self.lock = lock self.kodiconn = kodiconn self.cursor = self.kodiconn.cursor() if self.kodiconn else None self.artconn = artconn self.artcursor = self.artconn.cursor() if self.artconn else None self.wal_mode = wal_mode def __enter__(self): if self.lock: KODIDB_LOCK.acquire() self.kodiconn = db.connect(self.db_kind, self.wal_mode) self.cursor = self.kodiconn.cursor() self.artconn = db.connect('texture', self.wal_mode) if self._texture_db \ else None self.artcursor = self.artconn.cursor() if self._texture_db else None return self def __exit__(self, e_typ, e_val, trcbak): try: if e_typ: # re-raise any exception return False self.kodiconn.commit() if self.artconn: self.artconn.commit() finally: self.kodiconn.close() if self.artconn: self.artconn.close() if self.lock: KODIDB_LOCK.release() def art_urls(self, kodi_id, kodi_type): return (x[0] for x in self.cursor.execute('SELECT url FROM art WHERE media_id = ? AND media_type = ?', (kodi_id, kodi_type))) def artwork_generator(self, kodi_type, limit, offset): query = 'SELECT url FROM art WHERE type == ? LIMIT ? OFFSET ?' return (x[0] for x in self.cursor.execute(query, (kodi_type, limit, offset))) def add_artwork(self, artworks, kodi_id, kodi_type): """ Pass in an artworks dict (see PlexAPI) to set an items artwork. """ for kodi_art, url in artworks.iteritems(): self.add_art(url, kodi_id, kodi_type, kodi_art) @db.catch_operationalerrors def add_art(self, url, kodi_id, kodi_type, kodi_art): """ Adds or modifies the artwork of kind kodi_art (e.g. 'poster') in the Kodi art table for item kodi_id/kodi_type. Will also cache everything except actor portraits. """ self.cursor.execute(''' INSERT INTO art(media_id, media_type, type, url) VALUES (?, ?, ?, ?) ''', (kodi_id, kodi_type, kodi_art, url)) def modify_artwork(self, artworks, kodi_id, kodi_type): """ Pass in an artworks dict (see PlexAPI) to set an items artwork. """ for kodi_art, url in artworks.iteritems(): self.modify_art(url, kodi_id, kodi_type, kodi_art) @db.catch_operationalerrors def modify_art(self, url, kodi_id, kodi_type, kodi_art): """ Adds or modifies the artwork of kind kodi_art (e.g. 'poster') in the Kodi art table for item kodi_id/kodi_type. Will also cache everything except actor portraits. """ self.cursor.execute(''' SELECT url FROM art WHERE media_id = ? AND media_type = ? AND type = ? LIMIT 1 ''', (kodi_id, kodi_type, kodi_art,)) try: # Update the artwork old_url = self.cursor.fetchone()[0] except TypeError: # Add the artwork self.cursor.execute(''' INSERT INTO art(media_id, media_type, type, url) VALUES (?, ?, ?, ?) ''', (kodi_id, kodi_type, kodi_art, url)) else: if url == old_url: # Only cache artwork if it changed return self.delete_cached_artwork(old_url) self.cursor.execute(''' UPDATE art SET url = ? WHERE media_id = ? AND media_type = ? AND type = ? ''', (url, kodi_id, kodi_type, kodi_art)) def delete_artwork(self, kodi_id, kodi_type): self.cursor.execute('SELECT url FROM art WHERE media_id = ? AND media_type = ?', (kodi_id, kodi_type, )) for row in self.cursor.fetchall(): self.delete_cached_artwork(row[0]) @db.catch_operationalerrors def delete_cached_artwork(self, url): try: self.artcursor.execute("SELECT cachedurl FROM texture WHERE url = ? LIMIT 1", (url, )) cachedurl = self.artcursor.fetchone()[0] except TypeError: # Could not find cached url pass else: # Delete thumbnail as well as the entry path = path_ops.translate_path("special://thumbnails/%s" % cachedurl) if path_ops.exists(path): path_ops.rmtree(path, ignore_errors=True) self.artcursor.execute("DELETE FROM texture WHERE url = ?", (url, )) @db.catch_operationalerrors def wipe(self): """ Completely wipes the corresponding Kodi database """ self.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table'") tables = [i[0] for i in self.cursor.fetchall()] for table in UNTOUCHED_TABLES: if table in tables: tables.remove(table) for table in tables: self.cursor.execute('DELETE FROM %s' % table)