From a16eae143a2b383afa9aace953244d230ff2617b Mon Sep 17 00:00:00 2001 From: croneter Date: Thu, 8 Nov 2018 21:22:16 +0100 Subject: [PATCH] Rewire kodi database access --- resources/lib/artwork.py | 89 +-- resources/lib/itemtypes/common.py | 57 +- resources/lib/itemtypes/movies.py | 212 ++++--- resources/lib/itemtypes/music.py | 34 +- resources/lib/itemtypes/tvshows.py | 393 ++++++------ resources/lib/kodi_db/__init__.py | 129 ++++ resources/lib/kodi_db/common.py | 102 ++++ resources/lib/kodi_db/movies.py | 11 + resources/lib/kodi_db/music.py | 247 ++++++++ resources/lib/kodi_db/texture.py | 17 + .../{kodidb_functions.py => kodi_db/video.py} | 562 +++++------------- resources/lib/kodimonitor.py | 30 +- resources/lib/library_sync/fanart.py | 13 +- resources/lib/library_sync/sections.py | 41 +- resources/lib/library_sync/websocket.py | 6 +- resources/lib/playback.py | 6 +- resources/lib/playback_starter.py | 6 +- resources/lib/playlist_func.py | 18 +- resources/lib/playlists/db.py | 6 +- resources/lib/plex_api.py | 18 +- resources/lib/sync.py | 8 +- resources/lib/utils.py | 7 +- 22 files changed, 1078 insertions(+), 934 deletions(-) create mode 100644 resources/lib/kodi_db/__init__.py create mode 100644 resources/lib/kodi_db/common.py create mode 100644 resources/lib/kodi_db/movies.py create mode 100644 resources/lib/kodi_db/music.py create mode 100644 resources/lib/kodi_db/texture.py rename resources/lib/{kodidb_functions.py => kodi_db/video.py} (65%) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index e07c4b91..961acb89 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -6,10 +6,10 @@ from urllib import quote_plus, unquote import requests import xbmc -from . import backgroundthread, path_ops, utils +from .kodi_db import KodiVideoDB, KodiMusicDB, KodiTextureDB +from . import backgroundthread, utils from . import state -############################################################################### LOG = getLogger('PLEX.artwork') # Disable annoying requests warnings @@ -26,8 +26,6 @@ IMAGE_CACHING_SUSPENDS = [ if not utils.settings('imageSyncDuringPlayback') == 'true': IMAGE_CACHING_SUSPENDS.append(state.SUSPEND_SYNC) -############################################################################### - def double_urlencode(text): return quote_plus(quote_plus(text)) @@ -53,18 +51,16 @@ class ImageCachingThread(backgroundthread.KillableThread): @staticmethod def _art_url_generator(): - from . import kodidb_functions as kodidb - for kind in ('video', 'music'): - with kodidb.GetKodiDB(kind) as kodi_db: + for kind in (KodiVideoDB, KodiMusicDB): + with kind() as kodidb: for kodi_type in ('poster', 'fanart'): - for url in kodi_db.artwork_generator(kodi_type): + for url in kodidb.artwork_generator(kodi_type): yield url def missing_art_cache_generator(self): - from . import kodidb_functions as kodidb - with kodidb.GetKodiDB('texture') as kodi_db: + with KodiTextureDB() as kodidb: for url in self._art_url_generator(): - if kodi_db.url_not_yet_cached(url): + if kodidb.url_not_yet_cached(url): yield url def run(self): @@ -126,74 +122,3 @@ def cache_url(url): break # We did not even get a timeout break - - -def modify_artwork(artworks, kodi_id, kodi_type, cursor): - """ - Pass in an artworks dict (see PlexAPI) to set an items artwork. - """ - for kodi_art, url in artworks.iteritems(): - modify_art(url, kodi_id, kodi_type, kodi_art, cursor) - - -def modify_art(url, kodi_id, kodi_type, kodi_art, cursor): - """ - 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. - """ - query = ''' - SELECT url FROM art - WHERE media_id = ? AND media_type = ? AND type = ? - LIMIT 1 - ''' - cursor.execute(query, (kodi_id, kodi_type, kodi_art,)) - try: - # Update the artwork - old_url = cursor.fetchone()[0] - except TypeError: - # Add the artwork - query = ''' - INSERT INTO art(media_id, media_type, type, url) - VALUES (?, ?, ?, ?) - ''' - cursor.execute(query, (kodi_id, kodi_type, kodi_art, url)) - else: - if url == old_url: - # Only cache artwork if it changed - return - delete_cached_artwork(old_url) - query = ''' - UPDATE art SET url = ? - WHERE media_id = ? AND media_type = ? AND type = ? - ''' - cursor.execute(query, (url, kodi_id, kodi_type, kodi_art)) - - -def delete_artwork(kodiId, mediaType, cursor): - cursor.execute('SELECT url FROM art WHERE media_id = ? AND media_type = ?', - (kodiId, mediaType, )) - for row in cursor.fetchall(): - delete_cached_artwork(row[0]) - - -def delete_cached_artwork(url): - """ - Deleted the cached artwork with path url (if it exists) - """ - from . import kodidb_functions as kodidb - with kodidb.GetKodiDB('texture') as kodi_db: - try: - kodi_db.cursor.execute("SELECT cachedurl FROM texture WHERE url=? LIMIT 1", - (url, )) - cachedurl = kodi_db.cursor.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) - kodi_db.cursor.execute("DELETE FROM texture WHERE url = ?", (url,)) diff --git a/resources/lib/itemtypes/common.py b/resources/lib/itemtypes/common.py index 0f574520..d368c1fd 100644 --- a/resources/lib/itemtypes/common.py +++ b/resources/lib/itemtypes/common.py @@ -6,8 +6,8 @@ from ntpath import dirname from ..plex_api import API from ..plex_db import PlexDB -from .. import kodidb_functions as kodidb -from .. import artwork, utils +from ..kodi_db import KodiVideoDB +from .. import utils LOG = getLogger('PLEX.itemtypes.common') @@ -39,14 +39,16 @@ class ItemBase(object): Input: kodiType: optional argument; e.g. 'video' or 'music' """ - def __init__(self, last_sync, plexdb=None, kodi_db=None): + def __init__(self, last_sync, plexdb=None, kodidb=None): self.last_sync = last_sync self.plexconn = None self.plexcursor = plexdb.cursor if plexdb else None self.kodiconn = None - self.kodicursor = kodi_db.cursor if kodi_db else None + self.kodicursor = kodidb.cursor if kodidb else None + self.artconn = kodidb.artconn if kodidb else None + self.artcursor = kodidb.artcursor if kodidb else None self.plexdb = plexdb - self.kodi_db = kodi_db + self.kodidb = kodidb def __enter__(self): """ @@ -56,8 +58,12 @@ class ItemBase(object): self.plexcursor = self.plexconn.cursor() self.kodiconn = utils.kodi_sql('video') self.kodicursor = self.kodiconn.cursor() + self.artconn = utils.kodi_sql('texture') + self.artcursor = self.artconn.cursor() self.plexdb = PlexDB(self.plexcursor) - self.kodi_db = kodidb.KodiDBMethods(self.kodicursor) + self.kodidb = KodiVideoDB(texture_db=True, + cursor=self.kodicursor, + artcursor=self.artcursor) return self def __exit__(self, exc_type, exc_val, exc_tb): @@ -66,18 +72,19 @@ class ItemBase(object): """ self.plexconn.commit() self.kodiconn.commit() + self.artconn.commit() self.plexconn.close() self.kodiconn.close() + self.artconn.close() return self def set_fanart(self, artworks, kodi_id, kodi_type): """ Writes artworks [dict containing only set artworks] to the Kodi art DB """ - artwork.modify_artwork(artworks, - kodi_id, - kodi_type, - self.kodicursor) + self.kodidb.modify_artwork(artworks, + kodi_id, + kodi_type) def update_userdata(self, xml_element, plex_type): """ @@ -93,15 +100,15 @@ class ItemBase(object): # Grab the user's viewcount, resume points etc. from PMS' answer userdata = api.userdata() # Write to Kodi DB - self.kodi_db.set_resume(db_item['kodi_fileid'], - userdata['Resume'], - userdata['Runtime'], - userdata['PlayCount'], - userdata['LastPlayedDate'], - plex_type) - self.kodi_db.update_userrating(db_item['kodi_id'], - db_item['kodi_type'], - userdata['UserRating']) + self.kodidb.set_resume(db_item['kodi_fileid'], + userdata['Resume'], + userdata['Runtime'], + userdata['PlayCount'], + userdata['LastPlayedDate'], + plex_type) + self.kodidb.update_userrating(db_item['kodi_id'], + db_item['kodi_type'], + userdata['UserRating']) def update_playstate(self, mark_played, view_count, resume, duration, kodi_fileid, lastViewedAt, plex_type): @@ -118,9 +125,9 @@ class ItemBase(object): view_count = 1 resume = 0 # Do the actual update - self.kodi_db.set_resume(kodi_fileid, - resume, - duration, - view_count, - utils.unix_date_to_kodi(lastViewedAt), - plex_type) + self.kodidb.set_resume(kodi_fileid, + resume, + duration, + view_count, + utils.unix_date_to_kodi(lastViewedAt), + plex_type) diff --git a/resources/lib/itemtypes/movies.py b/resources/lib/itemtypes/movies.py index 4c654624..5ba8433e 100644 --- a/resources/lib/itemtypes/movies.py +++ b/resources/lib/itemtypes/movies.py @@ -5,7 +5,7 @@ from logging import getLogger from .common import ItemBase from ..plex_api import API -from .. import artwork, state, variables as v, plex_functions as PF +from .. import state, variables as v, plex_functions as PF LOG = getLogger('PLEX.movies') @@ -20,32 +20,20 @@ class Movie(ItemBase): Process single movie """ api = API(xml) - update_item = True plex_id = api.plex_id() # Cannot parse XML, abort if not plex_id: LOG.error('Cannot parse XML data for movie: %s', xml.attrib) return movie = self.plexdb.movie(plex_id) - try: + if movie: + update_item = True kodi_id = movie['kodi_id'] old_kodi_fileid = movie['kodi_fileid'] kodi_pathid = movie['kodi_pathid'] - except TypeError: - update_item = False - self.kodicursor.execute('SELECT COALESCE(MAX(idMovie), 0) FROM movie') - kodi_id = self.kodicursor.fetchone()[0] + 1 else: - # Verification the item is still in Kodi - self.kodicursor.execute('SELECT idMovie FROM movie WHERE idMovie = ? LIMIT 1', - (kodi_id, )) - try: - self.kodicursor.fetchone()[0] - except TypeError: - # item is not found, let's recreate it. - update_item = False - LOG.info("kodi_id: %s missing from Kodi, repairing the entry.", - kodi_id) + update_item = False + kodi_id = self.kodidb.new_movie_id() userdata = api.userdata() playcount = userdata['PlayCount'] @@ -80,9 +68,9 @@ class Movie(ItemBase): # Network share filename = playurl.rsplit("/", 1)[1] path = playurl.replace(filename, "") - kodi_pathid = self.kodi_db.add_video_path(path, - content='movies', - scraper='metadata.local') + kodi_pathid = self.kodidb.add_video_path(path, + content='movies', + scraper='metadata.local') if do_indirect: # Set plugin path and media flags using real filename filename = api.file_name(force_first_media=True) @@ -90,94 +78,100 @@ class Movie(ItemBase): filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s' % (path, plex_id, v.PLEX_TYPE_MOVIE, filename)) playurl = filename - kodi_pathid = self.kodi_db.get_path(path) + kodi_pathid = self.kodidb.get_path(path) - file_id = self.kodi_db.add_file(filename, - kodi_pathid, - api.date_created()) + file_id = self.kodidb.add_file(filename, + kodi_pathid, + api.date_created()) if update_item: LOG.info('UPDATE movie plex_id: %s - %s', plex_id, api.title()) if file_id != old_kodi_fileid: - self.kodi_db.remove_file(old_kodi_fileid) - rating_id = self.kodi_db.get_ratingid(kodi_id, - v.KODI_TYPE_MOVIE) - self.kodi_db.update_ratings(kodi_id, - v.KODI_TYPE_MOVIE, - "default", - rating, - api.votecount(), - rating_id) + self.kodidb.remove_file(old_kodi_fileid) + rating_id = self.kodidb.get_ratingid(kodi_id, + v.KODI_TYPE_MOVIE) + self.kodidb.update_ratings(kodi_id, + v.KODI_TYPE_MOVIE, + "default", + rating, + api.votecount(), + rating_id) # update new uniqueid Kodi 17 if api.provider('imdb') is not None: - uniqueid = self.kodi_db.get_uniqueid(kodi_id, - v.KODI_TYPE_MOVIE) - self.kodi_db.update_uniqueid(kodi_id, - v.KODI_TYPE_MOVIE, - api.provider('imdb'), - "imdb", - uniqueid) + uniqueid = self.kodidb.get_uniqueid(kodi_id, + v.KODI_TYPE_MOVIE) + self.kodidb.update_uniqueid(kodi_id, + v.KODI_TYPE_MOVIE, + api.provider('imdb'), + "imdb", + uniqueid) else: - self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_MOVIE) + self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_MOVIE) uniqueid = -1 else: LOG.info("ADD movie plex_id: %s - %s", plex_id, title) - rating_id = self.kodi_db.get_ratingid(kodi_id, - v.KODI_TYPE_MOVIE) - self.kodi_db.add_ratings(rating_id, - kodi_id, - v.KODI_TYPE_MOVIE, - "default", - rating, - api.votecount()) + rating_id = self.kodidb.get_ratingid(kodi_id, + v.KODI_TYPE_MOVIE) + self.kodidb.add_ratings(rating_id, + kodi_id, + v.KODI_TYPE_MOVIE, + "default", + rating, + api.votecount()) if api.provider('imdb') is not None: - uniqueid = self.kodi_db.get_uniqueid(kodi_id, - v.KODI_TYPE_MOVIE) - self.kodi_db.add_uniqueid(uniqueid, - kodi_id, - v.KODI_TYPE_MOVIE, - api.provider('imdb'), - "imdb") + uniqueid = self.kodidb.get_uniqueid(kodi_id, + v.KODI_TYPE_MOVIE) + self.kodidb.add_uniqueid(uniqueid, + kodi_id, + v.KODI_TYPE_MOVIE, + api.provider('imdb'), + "imdb") else: uniqueid = -1 # Update Kodi's main entry - query = ''' - INSERT OR REPLACE INTO movie(idMovie, idFile, c00, c01, c02, c03, - c04, c05, c06, c07, c09, c10, c11, c12, c14, c15, c16, - c18, c19, c21, c22, c23, premiered, userrating) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, - ?, ?, ?, ?, ?, ?, ?) - ''' - self.kodicursor.execute( - query, - (kodi_id, file_id, title, api.plot(), api.shortplot(), - api.tagline(), api.votecount(), rating_id, - api.list_to_string(people['Writer']), api.year(), - uniqueid, api.sorttitle(), runtime, api.content_rating(), - api.list_to_string(genres), api.list_to_string(people['Director']), - title, api.list_to_string(studios), api.trailer(), - api.list_to_string(countries), playurl, kodi_pathid, - api.premiere_date(), userdata['UserRating'])) + self.kodidb.add_movie(kodi_id, + file_id, + title, + api.plot(), + api.shortplot(), + api.tagline(), + api.votecount(), + rating_id, + api.list_to_string(people['Writer']), + api.year(), + uniqueid, + api.sorttitle(), + runtime, + api.content_rating(), + api.list_to_string(genres), + api.list_to_string(people['Director']), + title, + api.list_to_string(studios), + api.trailer(), + api.list_to_string(countries), + playurl, + kodi_pathid, + api.premiere_date(), + userdata['UserRating']) - self.kodi_db.modify_countries(kodi_id, v.KODI_TYPE_MOVIE, countries) - self.kodi_db.modify_people(kodi_id, - v.KODI_TYPE_MOVIE, - api.people_list()) - self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_MOVIE, genres) - artwork.modify_artwork(api.artwork(), - kodi_id, - v.KODI_TYPE_MOVIE, - self.kodicursor) - self.kodi_db.modify_streams(file_id, api.mediastreams(), runtime) - self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_MOVIE, studios) + self.kodidb.modify_countries(kodi_id, v.KODI_TYPE_MOVIE, countries) + self.kodidb.modify_people(kodi_id, + v.KODI_TYPE_MOVIE, + api.people_list()) + self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_MOVIE, genres) + self.kodidb.modify_artwork(api.artwork(), + kodi_id, + v.KODI_TYPE_MOVIE) + self.kodidb.modify_streams(file_id, api.mediastreams(), runtime) + self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_MOVIE, studios) tags = [section_name] if collections: for plex_set_id, set_name in collections: tags.append(set_name) # Add any sets from Plex collection tags - kodi_set_id = self.kodi_db.create_collection(set_name) - self.kodi_db.assign_collection(kodi_set_id, kodi_id) + kodi_set_id = self.kodidb.create_collection(set_name) + self.kodidb.assign_collection(kodi_set_id, kodi_id) if children is None: # e.g. when added via websocket LOG.debug('Costly looking up Plex collection %s: %s', @@ -199,18 +193,17 @@ class Movie(ItemBase): set_api = API(children[plex_set_id][0]) else: continue - artwork.modify_artwork(set_api.artwork(), - kodi_set_id, - v.KODI_TYPE_SET, - self.kodicursor) - self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_MOVIE, tags) + self.kodidb.modify_artwork(set_api.artwork(), + kodi_set_id, + v.KODI_TYPE_SET) + self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_MOVIE, tags) # Process playstate - self.kodi_db.set_resume(file_id, - resume, - runtime, - playcount, - dateplayed, - v.PLEX_TYPE_MOVIE) + self.kodidb.set_resume(file_id, + resume, + runtime, + playcount, + dateplayed, + v.PLEX_TYPE_MOVIE) self.plexdb.add_movie(plex_id=plex_id, checksum=api.checksum(), section_id=section_id, @@ -238,19 +231,18 @@ class Movie(ItemBase): # Remove the plex reference self.plexdb.remove(plex_id, v.PLEX_TYPE_MOVIE) # Remove artwork - artwork.delete_artwork(kodi_id, kodi_type, self.self.kodicursor) - set_id = self.kodi_db.get_set_id(kodi_id) - self.kodi_db.modify_countries(kodi_id, kodi_type) - self.kodi_db.modify_people(kodi_id, kodi_type) - self.kodi_db.modify_genres(kodi_id, kodi_type) - self.kodi_db.modify_studios(kodi_id, kodi_type) - self.kodi_db.modify_tags(kodi_id, kodi_type) + self.kodidb.delete_artwork(kodi_id, kodi_type) + set_id = self.kodidb.get_set_id(kodi_id) + self.kodidb.modify_countries(kodi_id, kodi_type) + self.kodidb.modify_people(kodi_id, kodi_type) + self.kodidb.modify_genres(kodi_id, kodi_type) + self.kodidb.modify_studios(kodi_id, kodi_type) + self.kodidb.modify_tags(kodi_id, kodi_type) # Delete kodi movie and file - self.kodi_db.remove_file(file_id) - self.self.kodicursor.execute('DELETE FROM movie WHERE idMovie = ?', - (kodi_id,)) + self.kodidb.remove_file(file_id) + self.kodidb.remove_movie(kodi_id) if set_id: - self.kodi_db.delete_possibly_empty_set(set_id) - self.kodi_db.remove_uniqueid(kodi_id, kodi_type) - self.kodi_db.remove_ratings(kodi_id, kodi_type) + self.kodidb.delete_possibly_empty_set(set_id) + self.kodidb.remove_uniqueid(kodi_id, kodi_type) + self.kodidb.remove_ratings(kodi_id, kodi_type) LOG.debug('Deleted movie %s from kodi database', plex_id) diff --git a/resources/lib/itemtypes/music.py b/resources/lib/itemtypes/music.py index 6b3a455d..df1bb365 100644 --- a/resources/lib/itemtypes/music.py +++ b/resources/lib/itemtypes/music.py @@ -6,8 +6,8 @@ from logging import getLogger from .common import ItemBase from ..plex_api import API from ..plex_db import PlexDB -from .. import artwork, kodidb_functions as kodidb -from .. import plex_functions as PF, utils, state, variables as v +from ..kodi_db import KodiMusicDB +from .. import artwork, plex_functions as PF, utils, state, variables as v LOG = getLogger('PLEX.music') @@ -21,8 +21,12 @@ class MusicMixin(object): self.plexcursor = self.plexconn.cursor() self.kodiconn = utils.kodi_sql('music') self.kodicursor = self.kodiconn.cursor() + self.artconn = utils.kodi_sql('texture') + self.artcursor = self.artconn.cursor() self.plexdb = PlexDB(self.plexcursor) - self.kodi_db = kodidb.KodiDBMethods(self.kodicursor) + self.kodidb = KodiMusicDB(texture_db=True, + cursor=self.kodicursor, + artcursor=self.artcursor) return self def remove(self, plex_id, plex_type=None): @@ -93,7 +97,7 @@ class MusicMixin(object): path_id = self.kodicursor.fetchone()[0] except TypeError: pass - self.kodi_db.delete_song_from_song_artist(kodi_id) + self.kodidb.delete_song_from_song_artist(kodi_id) self.kodicursor.execute('DELETE FROM song WHERE idSong = ?', (kodi_id, )) # Check whether we have orphaned path entries @@ -103,7 +107,7 @@ class MusicMixin(object): self.kodicursor.execute('DELETE FROM path WHERE idPath = ?', (path_id, )) if v.KODIVERSION < 18: - self.kodi_db.delete_song_from_song_genre(kodi_id) + self.kodidb.delete_song_from_song_genre(kodi_id) query = 'DELETE FROM albuminfosong WHERE idAlbumInfoSong = ?' self.kodicursor.execute(query, (kodi_id, )) artwork.delete_artwork(kodi_id, v.KODI_TYPE_SONG, self.kodicursor) @@ -112,9 +116,9 @@ class MusicMixin(object): ''' Remove an album ''' - self.kodi_db.delete_album_from_discography(kodi_id) + self.kodidb.delete_album_from_discography(kodi_id) if v.KODIVERSION < 18: - self.kodi_db.delete_album_from_album_genre(kodi_id) + self.kodidb.delete_album_from_album_genre(kodi_id) query = 'DELETE FROM albuminfosong WHERE idAlbumInfo = ?' self.kodicursor.execute(query, (kodi_id, )) self.kodicursor.execute('DELETE FROM album_artist WHERE idAlbum = ?', @@ -183,7 +187,7 @@ class Artist(MusicMixin, ItemBase): # multiple times. # Kodi doesn't allow that. In case that happens we just merge the # artist entries. - kodi_id = self.kodi_db.add_artist(api.title(), musicBrainzId) + kodi_id = self.kodidb.add_artist(api.title(), musicBrainzId) # Create the reference in plex table query = ''' UPDATE artist @@ -279,7 +283,7 @@ class Album(MusicMixin, ItemBase): # OR ADD THE ALBUM ##### else: LOG.info("ADD album plex_id: %s - Name: %s", plex_id, name) - kodi_id = self.kodi_db.add_album(name, musicBrainzId) + kodi_id = self.kodidb.add_album(name, musicBrainzId) # Process the album info if v.KODIVERSION >= 18: # Kodi Leia @@ -360,7 +364,7 @@ class Album(MusicMixin, ItemBase): self.kodicursor.execute(query, (artist_id, name, api.year())) if v.KODIVERSION < 18: - self.kodi_db.add_music_genres(kodi_id, + self.kodidb.add_music_genres(kodi_id, genres, v.KODI_TYPE_ALBUM) # Update artwork @@ -379,7 +383,7 @@ class Album(MusicMixin, ItemBase): if scan_children: context = Song(self.last_sync, plexdb=self.plexdb, - kodi_db=self.kodi_db) + kodidb=self.kodidb) for song in children: context.add_update(song, section_name=section_name, @@ -426,7 +430,7 @@ class Song(MusicMixin, ItemBase): LOG.error('Grandparent tvartist %s xml download failed for %s', artist_id, xml.attrib) return - Artist(self.last_sync, plexdb=self.plexdb, kodi_db=self.kodi_db).add_update( + Artist(self.last_sync, plexdb=self.plexdb, kodidb=self.kodidb).add_update( artist_xml[0], section_name, section_id) artist = self.plexdb.artist(artist_id) if not artist: @@ -463,7 +467,7 @@ class Song(MusicMixin, ItemBase): LOG.error('Parent album %s xml download failed for %s', album_id, xml.attrib) return - Album(self.last_sync, plexdb=self.plexdb, kodi_db=self.kodi_db).add_update( + Album(self.last_sync, plexdb=self.plexdb, kodidb=self.kodidb).add_update( album_xml[0], section_name, section_id) album = self.plexdb.album(album_id) if not album: @@ -615,7 +619,7 @@ class Song(MusicMixin, ItemBase): else: LOG.info("ADD song plex_id: %s - %s", plex_id, title) # Add path - kodi_pathid = self.kodi_db.add_music_path(path, hash_string="123") + kodi_pathid = self.kodidb.add_music_path(path, hash_string="123") # Create the song entry if v.KODIVERSION >= 18: # Kodi Leia @@ -730,7 +734,7 @@ class Song(MusicMixin, ItemBase): (grandparent_id, kodi_id, 1, 0, artist_name)) # Add genres if genres: - self.kodi_db.add_music_genres(kodi_id, genres, v.KODI_TYPE_SONG) + self.kodidb.add_music_genres(kodi_id, genres, v.KODI_TYPE_SONG) artworks = api.artwork() artwork.modify_artwork(artworks, kodi_id, diff --git a/resources/lib/itemtypes/tvshows.py b/resources/lib/itemtypes/tvshows.py index db7ac7e1..51aa4d78 100644 --- a/resources/lib/itemtypes/tvshows.py +++ b/resources/lib/itemtypes/tvshows.py @@ -5,7 +5,7 @@ from logging import getLogger from .common import ItemBase, process_path from ..plex_api import API -from .. import artwork, plex_functions as PF, state, variables as v +from .. import plex_functions as PF, state, variables as v LOG = getLogger('PLEX.tvshows') @@ -74,38 +74,35 @@ class TvShowMixin(object): """ Remove a TV show, and only the show, no seasons or episodes """ - self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_SHOW) - self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_SHOW) - self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW) - artwork.delete_artwork(kodi_id, v.KODI_TYPE_SHOW, self.kodicursor) - self.kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", - (kodi_id,)) + self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_SHOW) + self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_SHOW) + self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_SHOW) + self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_SHOW) + self.kodidb.remove_show(kodi_id) if v.KODIVERSION >= 17: - self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW) - self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_SHOW) + self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW) + self.kodidb.remove_ratings(kodi_id, v.KODI_TYPE_SHOW) LOG.debug("Removed tvshow: %s", kodi_id) def remove_season(self, kodi_id): """ Remove a season, and only a season, not the show or episodes """ - artwork.delete_artwork(kodi_id, v.KODI_TYPE_SEASON, self.kodicursor) - self.kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", - (kodi_id,)) + self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_SEASON) + self.kodidb.remove_season(kodi_id) LOG.debug("Removed season: %s", kodi_id) def remove_episode(self, kodi_id, file_id): """ Remove an episode, and episode only from the Kodi DB (not Plex DB) """ - self.kodi_db.modify_people(kodi_id, v.KODI_TYPE_EPISODE) - self.kodi_db.remove_file(file_id, plex_type=v.PLEX_TYPE_EPISODE) - artwork.delete_artwork(kodi_id, v.KODI_TYPE_EPISODE, self.kodicursor) - self.kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", - (kodi_id,)) + self.kodidb.modify_people(kodi_id, v.KODI_TYPE_EPISODE) + self.kodidb.remove_file(file_id, plex_type=v.PLEX_TYPE_EPISODE) + self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_EPISODE) + self.kodidb.remove_episode(kodi_id) if v.KODIVERSION >= 17: - self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE) - self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_EPISODE) + self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE) + self.kodidb.remove_ratings(kodi_id, v.KODI_TYPE_EPISODE) LOG.debug("Removed episode: %s", kodi_id) @@ -126,23 +123,11 @@ class Show(ItemBase, TvShowMixin): show = self.plexdb.show(plex_id) if not show: update_item = False - query = 'SELECT COALESCE(MAX(idShow), 0) FROM tvshow' - self.kodicursor.execute(query) - kodi_id = self.kodicursor.fetchone()[0] + 1 + kodi_id = self.kodidb.new_show_id() else: update_item = True kodi_id = show['kodi_id'] kodi_pathid = show['kodi_pathid'] - # Verification the item is still in Kodi - self.kodicursor.execute('SELECT * FROM tvshow WHERE idShow = ?', - (kodi_id,)) - try: - self.kodicursor.fetchone()[0] - except TypeError: - # item is not found, let's recreate it. - update_item = False - LOG.info("idShow: %s missing from Kodi, repairing the entry.", - kodi_id) genres = api.genre_list() genre = api.list_to_string(genres) @@ -158,7 +143,7 @@ class Show(ItemBase, TvShowMixin): if playurl is None: return path, toplevelpath = process_path(playurl) - toppathid = self.kodi_db.add_video_path( + toppathid = self.kodidb.add_video_path( toplevelpath, content='tvshows', scraper='metadata.local') @@ -169,93 +154,91 @@ class Show(ItemBase, TvShowMixin): # Do NOT set a parent id because addon-path cannot be "stacked" toppathid = None - kodi_pathid = self.kodi_db.add_video_path(path, - date_added=api.date_created(), - id_parent_path=toppathid) + kodi_pathid = self.kodidb.add_video_path(path, + date_added=api.date_created(), + id_parent_path=toppathid) # UPDATE THE TVSHOW ##### if update_item: LOG.info("UPDATE tvshow plex_id: %s - %s", plex_id, api.title()) # update new ratings Kodi 17 - rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW) - self.kodi_db.update_ratings(kodi_id, - v.KODI_TYPE_SHOW, - "default", - api.audience_rating(), - api.votecount(), - rating_id) + rating_id = self.kodidb.get_ratingid(kodi_id, v.KODI_TYPE_SHOW) + self.kodidb.update_ratings(kodi_id, + v.KODI_TYPE_SHOW, + "default", + api.audience_rating(), + api.votecount(), + rating_id) # update new uniqueid Kodi 17 if api.provider('tvdb') is not None: - uniqueid = self.kodi_db.get_uniqueid(kodi_id, - v.KODI_TYPE_SHOW) - self.kodi_db.update_uniqueid(kodi_id, - v.KODI_TYPE_SHOW, - api.provider('tvdb'), - "unknown", - uniqueid) + uniqueid = self.kodidb.get_uniqueid(kodi_id, + v.KODI_TYPE_SHOW) + self.kodidb.update_uniqueid(kodi_id, + v.KODI_TYPE_SHOW, + api.provider('tvdb'), + "unknown", + uniqueid) else: - self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW) + self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW) uniqueid = -1 # Update the tvshow entry - query = ''' - UPDATE tvshow - SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?, - c12 = ?, c13 = ?, c14 = ?, c15 = ? - WHERE idShow = ? - ''' - self.kodicursor.execute( - query, (api.title(), api.plot(), rating_id, - api.premiere_date(), genre, api.title(), uniqueid, - api.content_rating(), studio, api.sorttitle(), - kodi_id)) + self.kodidb.update_show(api.title(), + api.plot(), + rating_id, + api.premiere_date(), + genre, + api.title(), + uniqueid, + api.content_rating(), + studio, + api.sorttitle(), + kodi_id) # OR ADD THE TVSHOW ##### else: LOG.info("ADD tvshow plex_id: %s - %s", plex_id, api.title()) # Link the path - query = "INSERT INTO tvshowlinkpath(idShow, idPath) values (?, ?)" - self.kodicursor.execute(query, (kodi_id, kodi_pathid)) - rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW) - self.kodi_db.add_ratings(rating_id, - kodi_id, - v.KODI_TYPE_SHOW, - "default", - api.audience_rating(), - api.votecount()) + self.kodidb.add_showlinkpath(kodi_id, kodi_pathid) + rating_id = self.kodidb.get_ratingid(kodi_id, v.KODI_TYPE_SHOW) + self.kodidb.add_ratings(rating_id, + kodi_id, + v.KODI_TYPE_SHOW, + "default", + api.audience_rating(), + api.votecount()) if api.provider('tvdb') is not None: - uniqueid = self.kodi_db.get_uniqueid(kodi_id, - v.KODI_TYPE_SHOW) - self.kodi_db.add_uniqueid(uniqueid, - kodi_id, - v.KODI_TYPE_SHOW, - api.provider('tvdb'), - "unknown") + uniqueid = self.kodidb.get_uniqueid(kodi_id, + v.KODI_TYPE_SHOW) + self.kodidb.add_uniqueid(uniqueid, + kodi_id, + v.KODI_TYPE_SHOW, + api.provider('tvdb'), + "unknown") else: uniqueid = -1 # Create the tvshow entry - query = ''' - INSERT INTO tvshow( - idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14, - c15) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''' - self.kodicursor.execute( - query, (kodi_id, api.title(), api.plot(), rating_id, - api.premiere_date(), genre, api.title(), uniqueid, - api.content_rating(), studio, api.sorttitle())) - - self.kodi_db.modify_people(kodi_id, - v.KODI_TYPE_SHOW, - api.people_list()) - self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_SHOW, genres) - artwork.modify_artwork(api.artwork(), - kodi_id, - v.KODI_TYPE_SHOW, - self.kodicursor) + self.kodidb.add_show(kodi_id, + api.title(), + api.plot(), + rating_id, + api.premiere_date(), + genre, + api.title(), + uniqueid, + api.content_rating(), + studio, + api.sorttitle()) + self.kodidb.modify_people(kodi_id, + v.KODI_TYPE_SHOW, + api.people_list()) + self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_SHOW, genres) + self.kodidb.modify_artwork(api.artwork(), + kodi_id, + v.KODI_TYPE_SHOW) # Process studios - self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_SHOW, studios) + self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_SHOW, studios) # Process tags: view, PMS collection tags tags = [section_name] tags.extend([i for _, i in api.collection_list()]) - self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags) + self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags) self.plexdb.add_show(plex_id=plex_id, checksum=api.checksum(), section_id=section_id, @@ -287,18 +270,17 @@ class Season(ItemBase, TvShowMixin): except (TypeError, IndexError, AttributeError): LOG.error("Parent tvshow %s xml download failed", show_id) return False - Show(self.last_sync, plexdb=self.plexdb, kodi_db=self.kodi_db).add_update( + Show(self.last_sync, plexdb=self.plexdb, kodidb=self.kodidb).add_update( show_xml[0], section_name, section_id) show = self.plexdb.show(show_id) if not show: LOG.error('Still could not find parent tv show %s', show_id) return parent_id = show['kodi_id'] - kodi_id = self.kodi_db.add_season(parent_id, api.season_number()) - artwork.modify_artwork(api.artwork(), - kodi_id, - v.KODI_TYPE_SEASON, - self.kodicursor) + kodi_id = self.kodidb.add_season(parent_id, api.season_number()) + self.kodidb.modify_artwork(api.artwork(), + kodi_id, + v.KODI_TYPE_SEASON) self.plexdb.add_season(plex_id=plex_id, checksum=api.checksum(), section_id=section_id, @@ -323,24 +305,12 @@ class Episode(ItemBase, TvShowMixin): episode = self.plexdb.episode(plex_id) if not episode: update_item = False - query = 'SELECT COALESCE(MAX(idEpisode), 0) FROM episode' - self.kodicursor.execute(query) - kodi_id = self.kodicursor.fetchone()[0] + 1 + kodi_id = self.kodidb.new_episode_id() else: update_item = True kodi_id = episode['kodi_id'] old_kodi_fileid = episode['kodi_fileid'] kodi_pathid = episode['kodi_pathid'] - # Verification the item is still in Kodi - query = 'SELECT * FROM episode WHERE idEpisode = ? LIMIT 1' - self.kodicursor.execute(query, (kodi_id, )) - try: - self.kodicursor.fetchone()[0] - except TypeError: - # item is not found, let's recreate it. - update_item = False - LOG.info('idEpisode %s missing from Kodi, repairing entry.', - kodi_id) peoples = api.people() director = api.list_to_string(peoples['Director']) @@ -365,7 +335,7 @@ class Episode(ItemBase, TvShowMixin): except (TypeError, IndexError, AttributeError): LOG.error("Grandparent tvshow %s xml download failed", show_id) return False - Show(self.last_sync, plexdb=self.plexdb, kodi_db=self.kodi_db).add_update( + Show(self.last_sync, plexdb=self.plexdb, kodidb=self.kodidb).add_update( show_xml[0], section_name, section_id) show = self.plexdb.show(show_id) if not show: @@ -383,7 +353,7 @@ class Episode(ItemBase, TvShowMixin): except (TypeError, IndexError, AttributeError): LOG.error("Parent season %s xml download failed", season_id) return False - Season(self.last_sync, plexdb=self.plexdb, kodi_db=self.kodi_db).add_update( + Season(self.last_sync, plexdb=self.plexdb, kodidb=self.kodidb).add_update( season_xml[0], section_name, section_id) season = self.plexdb.season(season_id) if not season: @@ -406,8 +376,8 @@ class Episode(ItemBase, TvShowMixin): # Network share filename = playurl.rsplit("/", 1)[1] path = playurl.replace(filename, "") - parent_path_id = self.kodi_db.parent_path_id(path) - kodi_pathid = self.kodi_db.add_video_path( + parent_path_id = self.kodidb.parent_path_id(path) + kodi_pathid = self.kodidb.add_video_path( path, id_parent_path=parent_path_id) if do_indirect: # Set plugin path - do NOT use "intermediate" paths for the show @@ -418,99 +388,108 @@ class Episode(ItemBase, TvShowMixin): % (path, plex_id, v.PLEX_TYPE_EPISODE, filename)) playurl = filename # Root path tvshows/ already saved in Kodi DB - kodi_pathid = self.kodi_db.add_video_path(path) + kodi_pathid = self.kodidb.add_video_path(path) # add/retrieve kodi_pathid and fileid # if the path or file already exists, the calls return current value - kodi_fileid = self.kodi_db.add_file(filename, - kodi_pathid, - api.date_created()) + kodi_fileid = self.kodidb.add_file(filename, + kodi_pathid, + api.date_created()) # UPDATE THE EPISODE ##### if update_item: LOG.info("UPDATE episode plex_id: %s - %s", plex_id, api.title()) if kodi_fileid != old_kodi_fileid: - self.kodi_db.remove_file(old_kodi_fileid) - ratingid = self.kodi_db.get_ratingid(kodi_id, - v.KODI_TYPE_EPISODE) - self.kodi_db.update_ratings(kodi_id, - v.KODI_TYPE_EPISODE, - "default", - userdata['Rating'], - api.votecount(), - ratingid) + self.kodidb.remove_file(old_kodi_fileid) + ratingid = self.kodidb.get_ratingid(kodi_id, + v.KODI_TYPE_EPISODE) + self.kodidb.update_ratings(kodi_id, + v.KODI_TYPE_EPISODE, + "default", + userdata['Rating'], + api.votecount(), + ratingid) # update new uniqueid Kodi 17 - uniqueid = self.kodi_db.get_uniqueid(kodi_id, - v.KODI_TYPE_EPISODE) - self.kodi_db.update_uniqueid(kodi_id, - v.KODI_TYPE_EPISODE, - api.provider('tvdb'), - "tvdb", - uniqueid) - query = ''' - UPDATE episode - SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, - c10 = ?, c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?, - c18 = ?, c19 = ?, idFile=?, idSeason = ?, - userrating = ? - WHERE idEpisode = ? - ''' - self.kodicursor.execute( - query, (api.title(), api.plot(), ratingid, writer, - api.premiere_date(), api.runtime(), director, season_no, - episode_no, api.title(), airs_before_season, - airs_before_episode, playurl, kodi_pathid, kodi_fileid, - parent_id, userdata['UserRating'], kodi_id)) + uniqueid = self.kodidb.get_uniqueid(kodi_id, + v.KODI_TYPE_EPISODE) + self.kodidb.update_uniqueid(kodi_id, + v.KODI_TYPE_EPISODE, + api.provider('tvdb'), + "tvdb", + uniqueid) + self.kodidb.update_episode(api.title(), + api.plot(), + ratingid, + writer, + api.premiere_date(), + api.runtime(), + director, + season_no, + episode_no, + api.title(), + airs_before_season, + airs_before_episode, + playurl, + kodi_pathid, + kodi_fileid, + parent_id, + userdata['UserRating'], + kodi_id) # OR ADD THE EPISODE ##### else: LOG.info("ADD episode plex_id: %s - %s", plex_id, api.title()) # Create the episode entry - rating_id = self.kodi_db.get_ratingid(kodi_id, - v.KODI_TYPE_EPISODE) - self.kodi_db.add_ratings(rating_id, + rating_id = self.kodidb.get_ratingid(kodi_id, + v.KODI_TYPE_EPISODE) + self.kodidb.add_ratings(rating_id, + kodi_id, + v.KODI_TYPE_EPISODE, + "default", + userdata['Rating'], + api.votecount()) + # add new uniqueid Kodi 17 + uniqueid = self.kodidb.get_uniqueid(kodi_id, + v.KODI_TYPE_EPISODE) + self.kodidb.add_uniqueid(uniqueid, kodi_id, v.KODI_TYPE_EPISODE, - "default", - userdata['Rating'], - api.votecount()) - # add new uniqueid Kodi 17 - uniqueid = self.kodi_db.get_uniqueid(kodi_id, - v.KODI_TYPE_EPISODE) - self.kodi_db.add_uniqueid(uniqueid, - kodi_id, - v.KODI_TYPE_EPISODE, - api.provider('tvdb'), - "tvdb") - query = ''' - INSERT INTO episode( idEpisode, idFile, c00, c01, c03, c04, - c05, c09, c10, c12, c13, c14, idShow, c15, c16, c18, - c19, idSeason, userrating) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, - ?, ?) - ''' - self.kodicursor.execute( - query, (kodi_id, kodi_fileid, api.title(), api.plot(), rating_id, - writer, api.premiere_date(), api.runtime(), director, - season_no, episode_no, api.title(), grandparent_id, - airs_before_season, airs_before_episode, playurl, - kodi_pathid, parent_id, userdata['UserRating'])) + api.provider('tvdb'), + "tvdb") + self.kodidb.add_episode(kodi_id, + kodi_fileid, + api.title(), + api.plot(), + rating_id, + writer, + api.premiere_date(), + api.runtime(), + director, + season_no, + episode_no, + api.title(), + grandparent_id, + airs_before_season, + airs_before_episode, + playurl, + kodi_pathid, + parent_id, + userdata['UserRating']) - self.kodi_db.modify_people(kodi_id, - v.KODI_TYPE_EPISODE, - api.people_list()) - artwork.modify_artwork(api.artwork(), - kodi_id, - v.KODI_TYPE_EPISODE, - self.kodicursor) + self.kodidb.modify_people(kodi_id, + v.KODI_TYPE_EPISODE, + api.people_list()) + self.kodidb.modify_artwork(api.artwork(), + kodi_id, + v.KODI_TYPE_EPISODE) streams = api.mediastreams() - self.kodi_db.modify_streams(kodi_fileid, streams, api.runtime()) - self.kodi_db.set_resume(kodi_fileid, - api.resume_point(), - api.runtime(), - userdata['PlayCount'], - userdata['LastPlayedDate'], - None) # Do send None, we check here + self.kodidb.modify_streams(kodi_fileid, streams, api.runtime()) + self.kodidb.set_resume(kodi_fileid, + api.resume_point(), + api.runtime(), + userdata['PlayCount'], + userdata['LastPlayedDate'], + None) # Do send None, we check here if not state.DIRECT_PATHS: # need to set a SECOND file entry for a path without plex show id filename = api.file_name(force_first_media=True) @@ -519,16 +498,16 @@ class Episode(ItemBase, TvShowMixin): filename = ('%s%s/?plex_id=%s&plex_type=%s&mode=play&filename=%s' % (path, show_id, plex_id, v.PLEX_TYPE_EPISODE, filename)) - kodi_pathid = self.kodi_db.add_video_path(path) - kodi_fileid = self.kodi_db.add_file(filename, - kodi_pathid, - api.date_created()) - self.kodi_db.set_resume(kodi_fileid, - api.resume_point(), - api.runtime(), - userdata['PlayCount'], - userdata['LastPlayedDate'], - None) # Do send None - 2nd entry + kodi_pathid = self.kodidb.add_video_path(path) + kodi_fileid = self.kodidb.add_file(filename, + kodi_pathid, + api.date_created()) + self.kodidb.set_resume(kodi_fileid, + api.resume_point(), + api.runtime(), + userdata['PlayCount'], + userdata['LastPlayedDate'], + None) # Do send None - 2nd entry self.plexdb.add_episode(plex_id=plex_id, checksum=api.checksum(), section_id=section_id, diff --git a/resources/lib/kodi_db/__init__.py b/resources/lib/kodi_db/__init__.py new file mode 100644 index 00000000..7e12270f --- /dev/null +++ b/resources/lib/kodi_db/__init__.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, unicode_literals +from logging import getLogger + +from .video import KodiVideoDB +from .music import KodiMusicDB +from .texture import KodiTextureDB + +from .. import path_ops, utils, variables as v + +LOG = getLogger('PLEX.kodi_db') + + +def kodiid_from_filename(path, kodi_type=None, db_type=None): + """ + Returns kodi_id if we have an item in the Kodi video or audio database with + said path. Feed with either koditype, e.v. 'movie', 'song' or the DB + you want to poll ('video' or 'music') + Returns None, if not possible + """ + kodi_id = None + path = utils.try_decode(path) + try: + filename = path.rsplit('/', 1)[1] + path = path.rsplit('/', 1)[0] + '/' + except IndexError: + filename = path.rsplit('\\', 1)[1] + path = path.rsplit('\\', 1)[0] + '\\' + if kodi_type == v.KODI_TYPE_SONG or db_type == 'music': + with KodiMusicDB() as kodidb: + try: + kodi_id = kodidb.music_id_from_filename(filename, path) + except TypeError: + LOG.debug('No Kodi audio db element found for path %s', path) + else: + kodi_type = v.KODI_TYPE_SONG + else: + with KodiVideoDB() as kodidb: + try: + kodi_id, kodi_type = kodidb.video_id_from_filename(filename, + path) + except TypeError: + LOG.debug('No kodi video db element found for path %s', path) + return kodi_id, kodi_type + + +def setup_kodi_default_entries(): + """ + Makes sure that we retain the Kodi standard databases. E.g. that there + is a dummy artist with ID 1 + """ + if utils.settings('enableMusic') == 'true': + with KodiMusicDB() as kodidb: + kodidb.cursor.execute(''' + INSERT OR REPLACE INTO artist( + idArtist, + strArtist, + strMusicBrainzArtistID) + VALUES (?, ?, ?) + ''', (1, '[Missing Tag]', 'Artist Tag Missing')) + if v.KODIVERSION >= 18: + kodidb.cursor.execute(''' + INSERT OR REPLACE INTO versiontagscan( + idVersion, + iNeedsScan, + lastscanned) + VALUES (?, ?, ?) + ''', (v.DB_MUSIC_VERSION[v.KODIVERSION], + 0, + utils.unix_date_to_kodi(utils.unix_timestamp()))) + + +def reset_cached_images(): + LOG.info('Resetting cached artwork') + # Remove all existing textures first + path = path_ops.translate_path('special://thumbnails/') + if path_ops.exists(path): + path_ops.rmtree(path, ignore_errors=True) + paths = ('', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', + 'Video', 'plex') + for path in paths: + new_path = path_ops.translate_path('special://thumbnails/%s' % path) + path_ops.makedirs(path_ops.encode_path(new_path)) + with KodiTextureDB() as kodidb: + for row in kodidb.cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type=?', + ('table', )): + if row[0] != 'version': + kodidb.cursor.execute("DELETE FROM %s" % row[0]) + + +def wipe_dbs(music=True): + """ + Completely resets the Kodi databases 'video', 'texture' and 'music' (if + music sync is enabled) + """ + LOG.warn('Wiping Kodi databases!') + kinds = [KodiVideoDB, KodiTextureDB] + if music: + kinds.append(KodiMusicDB) + for db in kinds: + with db() as kodidb: + kodidb.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table'") + tables = kodidb.cursor.fetchall() + tables = [i[0] for i in tables] + if 'version' in tables: + tables.remove('version') + if 'versiontagscan' in tables: + tables.remove('versiontagscan') + for table in tables: + kodidb.cursor.execute('DELETE FROM %s' % table) + setup_kodi_default_entries() + # Make sure Kodi knows we wiped the databases + import xbmc + xbmc.executebuiltin('UpdateLibrary(video)') + if utils.settings('enableMusic') == 'true': + xbmc.executebuiltin('UpdateLibrary(music)') + + +KODIDB_FROM_PLEXTYPE = { + v.PLEX_TYPE_MOVIE: KodiVideoDB, + v.PLEX_TYPE_SHOW: KodiVideoDB, + v.PLEX_TYPE_SEASON: KodiVideoDB, + v.PLEX_TYPE_EPISODE: KodiVideoDB, + v.PLEX_TYPE_ARTIST: KodiMusicDB, + v.PLEX_TYPE_ALBUM: KodiMusicDB, + v.PLEX_TYPE_SONG: KodiMusicDB +} diff --git a/resources/lib/kodi_db/common.py b/resources/lib/kodi_db/common.py new file mode 100644 index 00000000..53300154 --- /dev/null +++ b/resources/lib/kodi_db/common.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, unicode_literals + +from .. import utils, path_ops + + +class KodiDBBase(object): + """ + Kodi database methods used for all types of items + """ + def __init__(self, texture_db=False, cursor=None, artcursor=None): + """ + Allows direct use with a cursor instead of context mgr + """ + self._texture_db = texture_db + self.cursor = cursor + self.artconn = None + self.artcursor = artcursor + + def __enter__(self): + self.kodiconn = utils.kodi_sql(self.db_kind) + self.cursor = self.kodiconn.cursor() + if self._texture_db: + self.artconn = utils.kodi_sql('texture') + self.artcursor = self.artconn.cursor() + return self + + def __exit__(self, e_typ, e_val, trcbak): + self.kodiconn.commit() + self.kodiconn.close() + if self._texture_db: + self.artconn.commit() + self.artconn.close() + + 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): + return (x[0] for x in + self.cursor.execute('SELECT url FROM art WHERE type == ?', + (kodi_type, ))) + + 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) + + 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): + for row in self.cursor.execute('SELECT url FROM art WHERE media_id = ? AND media_type = ?', + (kodi_id, kodi_type, )): + self.delete_cached_artwork(row[0]) + + 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, )) diff --git a/resources/lib/kodi_db/movies.py b/resources/lib/kodi_db/movies.py new file mode 100644 index 00000000..a2edaa27 --- /dev/null +++ b/resources/lib/kodi_db/movies.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, unicode_literals +from logging import getLogger + +from .video import KodiVideoDB + +LOG = getLogger('PLEX.kodi_db.movies') + + +class KodiMovieDB(KodiVideoDB): diff --git a/resources/lib/kodi_db/music.py b/resources/lib/kodi_db/music.py new file mode 100644 index 00000000..8b2d0ce3 --- /dev/null +++ b/resources/lib/kodi_db/music.py @@ -0,0 +1,247 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, unicode_literals +from logging import getLogger + +from . import common +from .. import utils, variables as v + +LOG = getLogger('PLEX.kodi_db.music') + + +class KodiMusicDB(common.KodiDBBase): + db_kind = 'music' + + def __enter__(self): + self.kodiconn = utils.kodi_sql('music') + self.cursor = self.kodiconn.cursor() + return self + + def add_music_path(self, path, hash_string=None): + """ + Add the path (unicode) to the music DB, if it does not exist already. + Returns the path id + Set hash_string to something unicode to set the strHash attribute + """ + # SQL won't return existing paths otherwise + path = '' if path is None else path + self.cursor.execute('SELECT idPath FROM path WHERE strPath = ?', + (path,)) + try: + pathid = self.cursor.fetchone()[0] + except TypeError: + self.cursor.execute("SELECT COALESCE(MAX(idPath),0) FROM path") + pathid = self.cursor.fetchone()[0] + 1 + self.cursor.execute(''' + INSERT INTO path(idPath, strPath, strHash) + VALUES (?, ?, ?) + ''', + (pathid, path, hash_string)) + return pathid + + def music_id_from_filename(self, filename, path): + """ + Returns the Kodi song_id from the Kodi music database or None if not + found OR something went wrong. + """ + self.cursor.execute('SELECT idPath FROM path WHERE strPath = ?', + (path,)) + path_ids = self.cursor.fetchall() + if len(path_ids) != 1: + LOG.debug('Found wrong number of path ids: %s for path %s, abort', + path_ids, path) + return + self.cursor.execute('SELECT idSong FROM song WHERE strFileName = ? AND idPath = ?', + (filename, path_ids[0][0])) + song_ids = self.cursor.fetchall() + if len(song_ids) != 1: + LOG.info('Found wrong number of songs %s, abort', song_ids) + return + return song_ids[0][0] + + def delete_song_from_song_artist(self, song_id): + """ + Deletes son from song_artist table and possibly orphaned roles + """ + self.cursor.execute('SELECT idArtist, idRole FROM song_artist WHERE idSong = ? LIMIT 1', + (song_id, )) + artist = self.cursor.fetchone() + if artist is None: + # No entry to begin with + return + # Delete the entry + self.cursor.execute('DELETE FROM song_artist WHERE idSong = ?', + (song_id, )) + # Check whether we need to delete orphaned roles + self.cursor.execute('SELECT idRole FROM song_artist WHERE idRole = ? LIMIT 1', + (artist[1], )) + if not self.cursor.fetchone(): + # Delete orphaned role + self.cursor.execute('DELETE FROM role WHERE idRole = ?', + (artist[1], )) + + def delete_album_from_discography(self, album_id): + """ + Removes the album with id album_id from the table discography + """ + # Need to get the album name as a string first! + self.cursor.execute('SELECT strAlbum, iYear FROM album WHERE idAlbum = ? LIMIT 1', + (album_id, )) + try: + name, year = self.cursor.fetchone() + except TypeError: + return + self.cursor.execute('SELECT idArtist FROM album_artist WHERE idAlbum = ? LIMIT 1', + (album_id, )) + artist = self.cursor.fetchone() + if not artist: + return + self.cursor.execute('DELETE FROM discography WHERE idArtist = ? AND strAlbum = ? AND strYear = ?', + (artist[0], name, year)) + + def delete_song_from_song_genre(self, song_id): + """ + Deletes the one entry with id song_id from the song_genre table. + Will also delete orphaned genres from genre table + """ + self.cursor.execute('SELECT idGenre FROM song_genre WHERE idSong = ?', + (song_id, )) + genres = self.cursor.fetchall() + self.cursor.execute('DELETE FROM song_genre WHERE idSong = ?', + (song_id, )) + # Check for orphaned genres in both song_genre and album_genre tables + for genre in genres: + self.cursor.execute('SELECT idGenre FROM song_genre WHERE idGenre = ? LIMIT 1', + (genre[0], )) + if not self.cursor.fetchone(): + self.cursor.execute('SELECT idGenre FROM album_genre WHERE idGenre = ? LIMIT 1', + (genre[0], )) + if not self.cursor.fetchone(): + self.cursor.execute('DELETE FROM genre WHERE idGenre = ?', + (genre[0], )) + + def delete_album_from_album_genre(self, album_id): + """ + Deletes the one entry with id album_id from the album_genre table. + Will also delete orphaned genres from genre table + """ + self.cursor.execute('SELECT idGenre FROM album_genre WHERE idAlbum = ?', + (album_id, )) + genres = self.cursor.fetchall() + self.cursor.execute('DELETE FROM album_genre WHERE idAlbum = ?', + (album_id, )) + # Check for orphaned genres in both album_genre and song_genre tables + for genre in genres: + self.cursor.execute('SELECT idGenre FROM album_genre WHERE idGenre = ? LIMIT 1', + (genre[0], )) + if not self.cursor.fetchone(): + self.cursor.execute('SELECT idGenre FROM song_genre WHERE idGenre = ? LIMIT 1', + (genre[0], )) + if not self.cursor.fetchone(): + self.cursor.execute('DELETE FROM genre WHERE idGenre = ?', + (genre[0], )) + + def add_album(self, name, musicbrainz): + """ + Adds a single album to the DB + """ + self.cursor.execute('SELECT idAlbum FROM album WHERE strMusicBrainzAlbumID = ?', + (musicbrainz, )) + try: + albumid = self.cursor.fetchone()[0] + except TypeError: + # Create the album + self.cursor.execute('SELECT COALESCE(MAX(idAlbum),0) FROM album') + albumid = self.cursor.fetchone()[0] + 1 + self.cursor.execute(''' + INSERT INTO album( + idAlbum, + strAlbum, + strMusicBrainzAlbumID, + strReleaseType) + VALUES (?, ?, ?, ?) + ''', (albumid, name, musicbrainz, 'album')) + return albumid + + def add_music_genres(self, kodiid, genres, mediatype): + """ + Adds a list of genres (list of unicode) for a certain Kodi item + """ + if mediatype == "album": + # Delete current genres for clean slate + self.cursor.execute('DELETE FROM album_genre WHERE idAlbum = ?', + (kodiid, )) + for genre in genres: + self.cursor.execute('SELECT idGenre FROM genre WHERE strGenre = ?', + (genre, )) + try: + genreid = self.cursor.fetchone()[0] + except TypeError: + # Create the genre + self.cursor.execute('SELECT COALESCE(MAX(idGenre),0) FROM genre') + genreid = self.cursor.fetchone()[0] + 1 + self.cursor.execute('INSERT INTO genre(idGenre, strGenre) VALUES(?, ?)', + (genreid, genre)) + self.cursor.execute(''' + INSERT OR REPLACE INTO album_genre( + idGenre, + idAlbum) + VALUES (?, ?) + ''', (genreid, kodiid)) + elif mediatype == "song": + # Delete current genres for clean slate + self.cursor.execute('DELETE FROM song_genre WHERE idSong = ?', + (kodiid, )) + for genre in genres: + self.cursor.execute('SELECT idGenre FROM genre WHERE strGenre = ?', + (genre, )) + try: + genreid = self.cursor.fetchone()[0] + except TypeError: + # Create the genre + self.cursor.execute('SELECT COALESCE(MAX(idGenre),0) FROM genre') + genreid = self.cursor.fetchone()[0] + 1 + self.cursor.execute('INSERT INTO genre(idGenre, strGenre) values(?, ?)', + (genreid, genre)) + self.cursor.execute(''' + INSERT OR REPLACE INTO song_genre( + idGenre, + idSong) + VALUES (?, ?) + ''', (genreid, kodiid)) + + def add_artist(self, name, musicbrainz): + """ + Adds a single artist's name to the db + """ + self.cursor.execute(''' + SELECT idArtist, strArtist + FROM artist + WHERE strMusicBrainzArtistID = ? + ''', (musicbrainz, )) + try: + result = self.cursor.fetchone() + artistid = result[0] + artistname = result[1] + except TypeError: + self.cursor.execute('SELECT idArtist FROM artist WHERE strArtist = ? COLLATE NOCASE', + (name, )) + try: + artistid = self.cursor.fetchone()[0] + except TypeError: + # Krypton has a dummy first entry idArtist: 1 strArtist: + # [Missing Tag] strMusicBrainzArtistID: Artist Tag Missing + self.cursor.execute('SELECT COALESCE(MAX(idArtist),1) FROM artist') + artistid = self.cursor.fetchone()[0] + 1 + self.cursor.execute(''' + INSERT INTO artist( + idArtist, + strArtist, + strMusicBrainzArtistID) + VALUES (?, ?, ?) + ''', (artistid, name, musicbrainz)) + else: + if artistname != name: + self.cursor.execute('UPDATE artist SET strArtist = ? WHERE idArtist = ?', + (name, artistid,)) + return artistid diff --git a/resources/lib/kodi_db/texture.py b/resources/lib/kodi_db/texture.py new file mode 100644 index 00000000..dfc4cdd7 --- /dev/null +++ b/resources/lib/kodi_db/texture.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import absolute_import, division, unicode_literals + +from . import common + + +class KodiTextureDB(common.KodiDBBase): + db_kind = 'texture' + + def url_not_yet_cached(self, url): + """ + Returns True if url has not yet been cached to the Kodi texture cache + """ + self.cursor.execute('SELECT url FROM texture WHERE url = ? LIMIT 1', + (url, )) + return self.cursor.fetchone() is None diff --git a/resources/lib/kodidb_functions.py b/resources/lib/kodi_db/video.py similarity index 65% rename from resources/lib/kodidb_functions.py rename to resources/lib/kodi_db/video.py index 24c1f386..f1ad0b95 100644 --- a/resources/lib/kodidb_functions.py +++ b/resources/lib/kodi_db/video.py @@ -1,52 +1,17 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -""" -Connect to the Kodi databases (video and music) and operate on them -""" from __future__ import absolute_import, division, unicode_literals from logging import getLogger from sqlite3 import IntegrityError -from . import artwork, utils, variables as v, state, path_ops +from . import common +from .. import path_ops, utils, variables as v, state -############################################################################### - -LOG = getLogger('PLEX.kodidb_functions') +LOG = getLogger('PLEX.kodi_db.video') -class GetKodiDB(object): - """ - Usage: with GetKodiDB(db_type) as kodi_db: - do stuff with kodi_db - - Parameters: - db_type: DB to open: 'video', 'music', 'plex', 'texture' - - On exiting "with" (no matter what), commits get automatically committed - and the db gets closed - """ - def __init__(self, db_type): - self.kodiconn = None - self.db_type = db_type - - def __enter__(self): - self.kodiconn = utils.kodi_sql(self.db_type) - kodi_db = KodiDBMethods(self.kodiconn.cursor()) - return kodi_db - - def __exit__(self, typus, value, traceback): - self.kodiconn.commit() - self.kodiconn.close() - - -class KodiDBMethods(object): - """ - Best used indirectly with another Class GetKodiDB: - with GetKodiDB(db_type) as kodi_db: - kodi_db.method() - """ - def __init__(self, cursor): - self.cursor = cursor +class KodiVideoDB(common.KodiDBBase): + db_kind = 'video' def setup_path_table(self): """ @@ -155,28 +120,6 @@ class KodiDBMethods(object): content, scraper, 1)) return pathid - def add_music_path(self, path, hash_string=None): - """ - Add the path (unicode) to the music DB, if it does not exist already. - Returns the path id - Set hash_string to something unicode to set the strHash attribute - """ - # SQL won't return existing paths otherwise - path = '' if path is None else path - self.cursor.execute('SELECT idPath FROM path WHERE strPath = ?', - (path,)) - try: - pathid = self.cursor.fetchone()[0] - except TypeError: - self.cursor.execute("SELECT COALESCE(MAX(idPath),0) FROM path") - pathid = self.cursor.fetchone()[0] + 1 - self.cursor.execute(''' - INSERT INTO path(idPath, strPath, strHash) - VALUES (?, ?, ?) - ''', - (pathid, path, hash_string)) - return pathid - def get_path(self, path): """ Returns the idPath from the path table for path [unicode] or None @@ -443,7 +386,7 @@ class KodiDBMethods(object): self.cursor.execute(query_actor_delete, (person[0],)) if kind == 'actor': # Delete any associated artwork - artwork.delete_artwork(person[0], 'actor', self.cursor) + self.delete_artwork(person[0], 'actor') # Save new people to Kodi DB by iterating over the remaining entries if kind == 'actor': query = 'INSERT INTO actor_link VALUES (?, ?, ?, ?, ?)' @@ -489,11 +432,10 @@ class KodiDBMethods(object): 'VALUES (?, ?)', (actor_id, name)) if art_url: - artwork.modify_art(art_url, - actor_id, - 'actor', - 'thumb', - self.cursor) + self.modify_art(art_url, + actor_id, + 'actor', + 'thumb') return actor_id def get_art(self, kodi_id, kodi_type): @@ -610,26 +552,6 @@ class KodiDBMethods(object): return return movie_id, typus - def music_id_from_filename(self, filename, path): - """ - Returns the Kodi song_id from the Kodi music database or None if not - found OR something went wrong. - """ - self.cursor.execute('SELECT idPath FROM path WHERE strPath = ?', - (path,)) - path_ids = self.cursor.fetchall() - if len(path_ids) != 1: - LOG.debug('Found wrong number of path ids: %s for path %s, abort', - path_ids, path) - return - self.cursor.execute('SELECT idSong FROM song WHERE strFileName = ? AND idPath = ?', - (filename, path_ids[0][0])) - song_ids = self.cursor.fetchall() - if len(song_ids) != 1: - LOG.info('Found wrong number of songs %s, abort', song_ids) - return - return song_ids[0][0] - def get_resume(self, file_id): """ Returns the first resume point in seconds (int) if found, else None for @@ -803,208 +725,6 @@ class KodiDBMethods(object): ''', (seasonid, showid, seasonnumber)) return seasonid - def add_artist(self, name, musicbrainz): - """ - Adds a single artist's name to the db - """ - self.cursor.execute(''' - SELECT idArtist, strArtist - FROM artist - WHERE strMusicBrainzArtistID = ? - ''', (musicbrainz, )) - try: - result = self.cursor.fetchone() - artistid = result[0] - artistname = result[1] - except TypeError: - self.cursor.execute('SELECT idArtist FROM artist WHERE strArtist = ? COLLATE NOCASE', - (name, )) - try: - artistid = self.cursor.fetchone()[0] - except TypeError: - # Krypton has a dummy first entry idArtist: 1 strArtist: - # [Missing Tag] strMusicBrainzArtistID: Artist Tag Missing - self.cursor.execute('SELECT COALESCE(MAX(idArtist),1) FROM artist') - artistid = self.cursor.fetchone()[0] + 1 - self.cursor.execute(''' - INSERT INTO artist( - idArtist, - strArtist, - strMusicBrainzArtistID) - VALUES (?, ?, ?) - ''', (artistid, name, musicbrainz)) - else: - if artistname != name: - self.cursor.execute('UPDATE artist SET strArtist = ? WHERE idArtist = ?', - (name, artistid,)) - return artistid - - def delete_song_from_song_artist(self, song_id): - """ - Deletes son from song_artist table and possibly orphaned roles - """ - self.cursor.execute('SELECT idArtist, idRole FROM song_artist WHERE idSong = ? LIMIT 1', - (song_id, )) - artist = self.cursor.fetchone() - if artist is None: - # No entry to begin with - return - # Delete the entry - self.cursor.execute('DELETE FROM song_artist WHERE idSong = ?', - (song_id, )) - # Check whether we need to delete orphaned roles - self.cursor.execute('SELECT idRole FROM song_artist WHERE idRole = ? LIMIT 1', - (artist[1], )) - if not self.cursor.fetchone(): - # Delete orphaned role - self.cursor.execute('DELETE FROM role WHERE idRole = ?', - (artist[1], )) - - def delete_album_from_discography(self, album_id): - """ - Removes the album with id album_id from the table discography - """ - # Need to get the album name as a string first! - self.cursor.execute('SELECT strAlbum, iYear FROM album WHERE idAlbum = ? LIMIT 1', - (album_id, )) - try: - name, year = self.cursor.fetchone() - except TypeError: - return - self.cursor.execute('SELECT idArtist FROM album_artist WHERE idAlbum = ? LIMIT 1', - (album_id, )) - artist = self.cursor.fetchone() - if not artist: - return - self.cursor.execute('DELETE FROM discography WHERE idArtist = ? AND strAlbum = ? AND strYear = ?', - (artist[0], name, year)) - - def delete_song_from_song_genre(self, song_id): - """ - Deletes the one entry with id song_id from the song_genre table. - Will also delete orphaned genres from genre table - """ - self.cursor.execute('SELECT idGenre FROM song_genre WHERE idSong = ?', - (song_id, )) - genres = self.cursor.fetchall() - self.cursor.execute('DELETE FROM song_genre WHERE idSong = ?', - (song_id, )) - # Check for orphaned genres in both song_genre and album_genre tables - for genre in genres: - self.cursor.execute('SELECT idGenre FROM song_genre WHERE idGenre = ? LIMIT 1', - (genre[0], )) - if not self.cursor.fetchone(): - self.cursor.execute('SELECT idGenre FROM album_genre WHERE idGenre = ? LIMIT 1', - (genre[0], )) - if not self.cursor.fetchone(): - self.cursor.execute('DELETE FROM genre WHERE idGenre = ?', - (genre[0], )) - - def delete_album_from_album_genre(self, album_id): - """ - Deletes the one entry with id album_id from the album_genre table. - Will also delete orphaned genres from genre table - """ - self.cursor.execute('SELECT idGenre FROM album_genre WHERE idAlbum = ?', - (album_id, )) - genres = self.cursor.fetchall() - self.cursor.execute('DELETE FROM album_genre WHERE idAlbum = ?', - (album_id, )) - # Check for orphaned genres in both album_genre and song_genre tables - for genre in genres: - self.cursor.execute('SELECT idGenre FROM album_genre WHERE idGenre = ? LIMIT 1', - (genre[0], )) - if not self.cursor.fetchone(): - self.cursor.execute('SELECT idGenre FROM song_genre WHERE idGenre = ? LIMIT 1', - (genre[0], )) - if not self.cursor.fetchone(): - self.cursor.execute('DELETE FROM genre WHERE idGenre = ?', - (genre[0], )) - - def add_album(self, name, musicbrainz): - """ - Adds a single album to the DB - """ - self.cursor.execute('SELECT idAlbum FROM album WHERE strMusicBrainzAlbumID = ?', - (musicbrainz, )) - try: - albumid = self.cursor.fetchone()[0] - except TypeError: - # Create the album - self.cursor.execute('SELECT COALESCE(MAX(idAlbum),0) FROM album') - albumid = self.cursor.fetchone()[0] + 1 - self.cursor.execute(''' - INSERT INTO album( - idAlbum, - strAlbum, - strMusicBrainzAlbumID, - strReleaseType) - VALUES (?, ?, ?, ?) - ''', (albumid, name, musicbrainz, 'album')) - return albumid - - def add_music_genres(self, kodiid, genres, mediatype): - """ - Adds a list of genres (list of unicode) for a certain Kodi item - """ - if mediatype == "album": - # Delete current genres for clean slate - self.cursor.execute('DELETE FROM album_genre WHERE idAlbum = ?', - (kodiid, )) - for genre in genres: - self.cursor.execute('SELECT idGenre FROM genre WHERE strGenre = ?', - (genre, )) - try: - genreid = self.cursor.fetchone()[0] - except TypeError: - # Create the genre - self.cursor.execute('SELECT COALESCE(MAX(idGenre),0) FROM genre') - genreid = self.cursor.fetchone()[0] + 1 - self.cursor.execute('INSERT INTO genre(idGenre, strGenre) VALUES(?, ?)', - (genreid, genre)) - self.cursor.execute(''' - INSERT OR REPLACE INTO album_genre( - idGenre, - idAlbum) - VALUES (?, ?) - ''', (genreid, kodiid)) - elif mediatype == "song": - # Delete current genres for clean slate - self.cursor.execute('DELETE FROM song_genre WHERE idSong = ?', - (kodiid, )) - for genre in genres: - self.cursor.execute('SELECT idGenre FROM genre WHERE strGenre = ?', - (genre, )) - try: - genreid = self.cursor.fetchone()[0] - except TypeError: - # Create the genre - self.cursor.execute('SELECT COALESCE(MAX(idGenre),0) FROM genre') - genreid = self.cursor.fetchone()[0] + 1 - self.cursor.execute('INSERT INTO genre(idGenre, strGenre) values(?, ?)', - (genreid, genre)) - self.cursor.execute(''' - INSERT OR REPLACE INTO song_genre( - idGenre, - idSong) - VALUES (?, ?) - ''', (genreid, kodiid)) - -# Krypton only stuff ############################## - - def update_userrating(self, kodi_id, kodi_type, userrating): - """ - Updates userrating for >=Krypton - """ - if kodi_type == v.KODI_TYPE_MOVIE: - identifier = 'idMovie' - elif kodi_type == v.KODI_TYPE_EPISODE: - identifier = 'idEpisode' - elif kodi_type == v.KODI_TYPE_SONG: - identifier = 'idSong' - self.cursor.execute('UPDATE %s SET userrating = ? WHERE ? = ?' % kodi_type, - (userrating, identifier, kodi_id)) - def add_uniqueid(self, *args): """ Feed with: @@ -1103,141 +823,151 @@ class KodiDBMethods(object): self.cursor.execute('DELETE FROM rating WHERE media_id = ? AND media_type = ?', (kodi_id, kodi_type)) - 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 new_show_id(self): + self.cursor.execute('SELECT COALESCE(MAX(idShow), 0) FROM tvshow') + return self.cursor.fetchone()[0] + 1 - def artwork_generator(self, kodi_type): - """ - """ - return (x[0] for x in - self.cursor.execute('SELECT url FROM art WHERE type == ?', - (kodi_type, ))) + def new_episode_id(self): + self.cursor.execute('SELECT COALESCE(MAX(idEpisode), 0) FROM episode') + return self.cursor.fetchone()[0] + 1 - def url_not_yet_cached(self, url): - """ - Returns True if url has not yet been cached to the Kodi texture cache - """ - self.cursor.execute('SELECT url FROM texture WHERE url == ? LIMIT 1', - (url, )) - return self.cursor.fetchone() is None + def add_episode(self, *args): + self.cursor.execute( + ''' + INSERT INTO episode( + idEpisode, + idFile, + c00, + c01, + c03, + c04, + c05, + c09, + c10, + c12, + c13, + c14, + idShow, + c15, + c16, + c18, + c19, + idSeason, + userrating) + VALUES + (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', (args)) + def update_episode(self, *args): + self.cursor.execute( + ''' + UPDATE episode + SET c00 = ?, + c01 = ?, + c03 = ?, + c04 = ?, + c05 = ?, + c09 = ?, + c10 = ?, + c12 = ?, + c13 = ?, + c14 = ?, + c15 = ?, + c16 = ?, + c18 = ?, + c19 = ?, + idFile=?, + idSeason = ?, + userrating = ? + WHERE idEpisode = ? + ''', (args)) -def kodiid_from_filename(path, kodi_type=None, db_type=None): - """ - Returns kodi_id if we have an item in the Kodi video or audio database with - said path. Feed with either koditype, e.v. 'movie', 'song' or the DB - you want to poll ('video' or 'music') - Returns None, if not possible - """ - kodi_id = None - path = utils.try_decode(path) - try: - filename = path.rsplit('/', 1)[1] - path = path.rsplit('/', 1)[0] + '/' - except IndexError: - filename = path.rsplit('\\', 1)[1] - path = path.rsplit('\\', 1)[0] + '\\' - if kodi_type == v.KODI_TYPE_SONG or db_type == 'music': - with GetKodiDB('music') as kodi_db: - try: - kodi_id = kodi_db.music_id_from_filename(filename, path) - except TypeError: - LOG.debug('No Kodi audio db element found for path %s', path) - else: - kodi_type = v.KODI_TYPE_SONG - else: - with GetKodiDB('video') as kodi_db: - try: - kodi_id, kodi_type = kodi_db.video_id_from_filename(filename, - path) - except TypeError: - LOG.debug('No kodi video db element found for path %s', path) - return kodi_id, kodi_type + def add_show(self, *args): + self.cursor.execute( + ''' + INSERT INTO tvshow( + idShow, + c00, + c01, + c04, + c05, + c08, + c09, + c12, + c13, + c14, + c15) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ''', (args)) + def update_show(self, *args): + self.cursor.execute( + ''' + UPDATE tvshow + SET c00 = ?, + c01 = ?, + c04 = ?, + c05 = ?, + c08 = ?, + c09 = ?, + c12 = ?, + c13 = ?, + c14 = ?, + c15 = ? + WHERE idShow = ? + ''', (args)) -def setup_kodi_default_entries(): - """ - Makes sure that we retain the Kodi standard databases. E.g. that there - is a dummy artist with ID 1 - """ - if utils.settings('enableMusic') == 'true': - with GetKodiDB('music') as kodi_db: - kodi_db.cursor.execute(''' - INSERT OR REPLACE INTO artist( - idArtist, - strArtist, - strMusicBrainzArtistID) - VALUES (?, ?, ?) - ''', (1, '[Missing Tag]', 'Artist Tag Missing')) - if v.KODIVERSION >= 18: - kodi_db.cursor.execute(''' - INSERT OR REPLACE INTO versiontagscan( - idVersion, - iNeedsScan, - lastscanned) - VALUES (?, ?, ?) - ''', (v.DB_MUSIC_VERSION[v.KODIVERSION], - 0, - utils.unix_date_to_kodi(utils.unix_timestamp()))) + def add_showlinkpath(self, kodi_id, kodi_pathid): + self.cursor.execute('INSERT INTO tvshowlinkpath(idShow, idPath) VALUES (?, ?)', + (kodi_id, kodi_pathid)) + def remove_show(self, kodi_id): + self.cursor.execute('DELETE FROM tvshow WHERE idShow = ?', (kodi_id,)) -def reset_cached_images(): - LOG.info('Resetting cached artwork') - # Remove all existing textures first - path = path_ops.translate_path('special://thumbnails/') - if path_ops.exists(path): - path_ops.rmtree(path, ignore_errors=True) - paths = ('', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'a', 'b', 'c', 'd', 'e', 'f', - 'Video', 'plex') - for path in paths: - new_path = path_ops.translate_path('special://thumbnails/%s' % path) - path_ops.makedirs(path_ops.encode_path(new_path)) - with GetKodiDB('texture') as kodi_db: - kodi_db.cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type=?', - ('table', )) - rows = kodi_db.cursor.fetchall() - for row in rows: - if row[0] != 'version': - kodi_db.cursor.execute("DELETE FROM %s" % row[0]) + def remove_season(self, kodi_id): + self.cursor.execute('DELETE FROM seasons WHERE idSeason = ?', + (kodi_id,)) + def remove_episode(self, kodi_id): + self.cursor.execute('DELETE FROM episode WHERE idEpisode = ?', + (kodi_id,)) -def wipe_dbs(music=True): - """ - Completely resets the Kodi databases 'video', 'texture' and 'music' (if - music sync is enabled) - """ - LOG.warn('Wiping Kodi databases!') - kinds = ['video', 'texture'] - if music: - kinds.append('music') - for db in kinds: - with GetKodiDB(db) as kodi_db: - kodi_db.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table'") - tables = kodi_db.cursor.fetchall() - tables = [i[0] for i in tables] - if 'version' in tables: - tables.remove('version') - if 'versiontagscan' in tables: - tables.remove('versiontagscan') - for table in tables: - kodi_db.cursor.execute('DELETE FROM %s' % table) - setup_kodi_default_entries() - # Make sure Kodi knows we wiped the databases - import xbmc - xbmc.executebuiltin('UpdateLibrary(video)') - if utils.settings('enableMusic') == 'true': - xbmc.executebuiltin('UpdateLibrary(music)') + def new_movie_id(self): + self.cursor.execute('SELECT COALESCE(MAX(idMovie), 0) FROM movie') + return self.cursor.fetchone()[0] + 1 + def add_movie(self, *args): + self.cursor.execute( + ''' + INSERT OR REPLACE INTO movie( + idMovie, + idFile, + c00, + c01, + c02, + c03, + c04, + c05, + c06, + c07, + c09, + c10, + c11, + c12, + c14, + c15, + c16, + c18, + c19, + c21, + c22, + c23, + premiered, + userrating) + VALUES + (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, + ?, ?, ?, ?) + ''', (args)) -KODIDB_FROM_PLEXTYPE = { - v.PLEX_TYPE_MOVIE: GetKodiDB('video'), - v.PLEX_TYPE_SHOW: GetKodiDB('video'), - v.PLEX_TYPE_SEASON: GetKodiDB('video'), - v.PLEX_TYPE_EPISODE: GetKodiDB('video'), - v.PLEX_TYPE_ARTIST: GetKodiDB('music'), - v.PLEX_TYPE_ALBUM: GetKodiDB('music'), - v.PLEX_TYPE_SONG: GetKodiDB('music') -} + def remove_movie(self, kodi_id): + self.cursor.execute('DELETE FROM movie WHERE idMovie = ?', (kodi_id,)) diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index 935ecf62..5e3ba73b 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -12,7 +12,7 @@ import xbmc from xbmcgui import Window from .plex_db import PlexDB -from . import kodidb_functions as kodidb +from . import kodi_db from . import utils from . import plex_functions as PF from .downloadutils import DownloadUtils as DU @@ -302,7 +302,7 @@ class KodiMonitor(xbmc.Monitor): plex_type = None # If using direct paths and starting playback from a widget if not kodi_id and kodi_type and path: - kodi_id, _ = kodidb.kodiid_from_filename(path, kodi_type) + kodi_id, _ = kodi_db.kodiid_from_filename(path, kodi_type) if kodi_id: with PlexDB() as plexdb: db_item = plexdb.item_by_kodi_id(kodi_id, kodi_type) @@ -559,8 +559,8 @@ def _record_playstate(status, ended): last_played = utils.unix_date_to_kodi(utils.unix_timestamp()) if playcount is None: LOG.debug('playcount not found, looking it up in the Kodi DB') - with kodidb.GetKodiDB('video') as kodi_db: - playcount = kodi_db.get_playcount(db_item['kodi_fileid']) + with kodi_db.KodiVideoDB() as kodidb: + playcount = kodidb.get_playcount(db_item['kodi_fileid']) playcount = 0 if playcount is None else playcount if time < v.IGNORE_SECONDS_AT_START: LOG.debug('Ignoring playback less than %s seconds', @@ -574,13 +574,13 @@ def _record_playstate(status, ended): v.MARK_PLAYED_AT) playcount += 1 time = 0 - with kodidb.GetKodiDB('video') as kodi_db: - kodi_db.set_resume(db_item['kodi_fileid'], - time, - totaltime, - playcount, - last_played, - status['plex_type']) + with kodi_db.KodiVideoDB() as kodidb: + kodidb.set_resume(db_item['kodi_fileid'], + time, + totaltime, + playcount, + last_played, + status['plex_type']) # Hack to force "in progress" widget to appear if it wasn't visible before if (state.FORCE_RELOAD_SKIN and xbmc.getCondVisibility('Window.IsVisible(Home.xml)')): @@ -600,9 +600,9 @@ def _clean_file_table(): """ LOG.debug('Start cleaning Kodi files table') xbmc.sleep(2000) - with kodidb.GetKodiDB('video') as kodi_db_1: - with kodidb.GetKodiDB('video') as kodi_db_2: - for file_id in kodi_db_1.obsolete_file_ids(): + with kodi_db.KodiVideoDB() as kodidb_1: + with kodi_db.KodiVideoDB() as kodidb_2: + for file_id in kodidb_1.obsolete_file_ids(): LOG.debug('Removing obsolete Kodi file_id %s', file_id) - kodi_db_2.remove_file(file_id, remove_orphans=False) + kodidb_2.remove_file(file_id, remove_orphans=False) LOG.debug('Done cleaning up Kodi file table') diff --git a/resources/lib/library_sync/fanart.py b/resources/lib/library_sync/fanart.py index 10a0a70c..7a553101 100644 --- a/resources/lib/library_sync/fanart.py +++ b/resources/lib/library_sync/fanart.py @@ -6,7 +6,8 @@ import xbmc from . import common from ..plex_api import API from ..plex_db import PlexDB -from .. import backgroundthread, utils, kodidb_functions as kodidb +from ..kodi_db import KodiVideoDB +from .. import backgroundthread, utils from .. import itemtypes, plex_functions as PF, variables as v, state @@ -87,9 +88,9 @@ def process_fanart(plex_id, plex_type, refresh=False): LOG.error('Could not get Kodi id for plex id %s', plex_id) return if not refresh: - with kodidb.GetKodiDB('video') as kodi_db: - artworks = kodi_db.get_art(db_item['kodi_id'], - db_item['kodi_type']) + with KodiVideoDB() as kodidb: + artworks = kodidb.get_art(db_item['kodi_id'], + db_item['kodi_type']) # Check if we even need to get additional art for key in v.ALL_KODI_ARTWORK: if key not in artworks: @@ -117,8 +118,8 @@ def process_fanart(plex_id, plex_type, refresh=False): if plex_type == v.PLEX_TYPE_MOVIE: for _, setname in api.collection_list(): LOG.debug('Getting artwork for movie set %s', setname) - with kodidb.GetKodiDB('video') as kodi_db: - setid = kodi_db.create_collection(setname) + with KodiVideoDB() as kodidb: + setid = kodidb.create_collection(setname) external_set_artwork = api.set_artwork() if external_set_artwork and PREFER_KODI_COLLECTION_ART: kodi_artwork = api.artwork(kodi_id=setid, diff --git a/resources/lib/library_sync/sections.py b/resources/lib/library_sync/sections.py index 2e4e8c13..1ffb5cb8 100644 --- a/resources/lib/library_sync/sections.py +++ b/resources/lib/library_sync/sections.py @@ -7,7 +7,7 @@ import copy from . import common, videonodes from ..utils import cast from ..plex_db import PlexDB -from .. import kodidb_functions as kodidb +from .. import kodi_db from .. import itemtypes from .. import plex_functions as PF, music, utils, state, variables as v @@ -58,10 +58,10 @@ def sync_from_pms(): # Backup old sections to delete them later, if needed (at the end # of this method, only unused sections will be left in old_sections) old_sections = list(plexdb.section_ids()) - with kodidb.GetKodiDB('video') as kodi_db: + with kodi_db.KodiVideoDB() as kodidb: for section in sections: _process_section(section, - kodi_db, + kodidb, plexdb, sorted_sections, old_sections, @@ -77,7 +77,7 @@ def sync_from_pms(): return True -def _process_section(section_xml, kodi_db, plexdb, sorted_sections, +def _process_section(section_xml, kodidb, plexdb, sorted_sections, old_sections, totalnodes): folder = section_xml.attrib plex_type = folder['type'] @@ -101,7 +101,7 @@ def _process_section(section_xml, kodi_db, plexdb, sorted_sections, current_tagid = section[3] except TypeError: LOG.info('Creating section id: %s in Plex database.', section_id) - tagid = kodi_db.create_tag(section_name) + tagid = kodidb.create_tag(section_name) # Create playlist for the video library if (section_name not in playlists and plex_type in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)): @@ -133,13 +133,13 @@ def _process_section(section_xml, kodi_db, plexdb, sorted_sections, if current_sectionname != section_name: LOG.info('section id: %s new sectionname: %s', section_id, section_name) - tagid = kodi_db.create_tag(section_name) + tagid = kodidb.create_tag(section_name) # Update view with new info plexdb.add_section(section_id, - section_name, - plex_type, - tagid) + section_name, + plex_type, + tagid) if plexdb.section_id_by_name(current_sectionname) is None: # The tag could be a combined view. Ensure there's @@ -177,7 +177,7 @@ def _process_section(section_xml, kodi_db, plexdb, sorted_sections, # Update items with new tag for item in plexdb.kodi_id_by_section(section_id): # Remove the "s" from viewtype for tags - kodi_db.update_tag( + kodidb.update_tag( current_tagid, tagid, item[0], current_sectiontype[:-1]) else: # Validate the playlist exists or recreate it @@ -214,24 +214,25 @@ def delete_sections(old_sections): with PlexDB() as plexdb: old_sections = [plexdb.section(x) for x in old_sections] LOG.info("Removing entire Plex library sections: %s", old_sections) - with kodidb.GetKodiDB() as kodi_db: + with kodi_db.KodiVideoDB() as kodidb: for section in old_sections: - if section[2] == v.KODI_TYPE_MOVIE: + if section[2] == v.KODI_TYPE_PHOTO: + # not synced + plexdb.remove_section(section[0]) + elif section[2] == v.KODI_TYPE_MOVIE: video_library_update = True context = itemtypes.Movie(plexdb=plexdb, - kodi_db=kodi_db) + kodidb=kodidb) elif section[2] == v.KODI_TYPE_SHOW: video_library_update = True context = itemtypes.Show(plexdb=plexdb, - kodi_db=kodi_db) - elif section[2] == v.KODI_TYPE_ARTIST: + kodidb=kodidb) + with kodi_db.KodiMusicDB() as kodidb: + for section in old_sections: + if section[2] == v.KODI_TYPE_ARTIST: music_library_update = True context = itemtypes.Artist(plexdb=plexdb, - kodi_db=kodi_db) - elif section[2] == v.KODI_TYPE_PHOTO: - # not synced - plexdb.remove_section(section[0]) - continue + kodidb=kodidb) for plex_id in plexdb.plexid_by_section(section[0]): context.remove(plex_id) # Only remove Plex entry if we've removed all items first diff --git a/resources/lib/library_sync/websocket.py b/resources/lib/library_sync/websocket.py index 798529f4..71189119 100644 --- a/resources/lib/library_sync/websocket.py +++ b/resources/lib/library_sync/websocket.py @@ -8,7 +8,7 @@ from .full_sync import PLAYLIST_SYNC_ENABLED from .fanart import SYNC_FANART, FanartTask from ..plex_api import API from ..plex_db import PlexDB -from .. import kodidb_functions as kodidb +from .. import kodi_db from .. import backgroundthread, playlists, plex_functions as PF, itemtypes from .. import artwork, utils, variables as v, state @@ -360,6 +360,6 @@ def cache_artwork(plex_id, plex_type, kodi_id=None, kodi_type=None): LOG.error('Could not retrieve Plex db info for %s', plex_id) return kodi_id, kodi_type = item['kodi_id'], item['kodi_type'] - with kodidb.KODIDB_FROM_PLEXTYPE[plex_type] as kodi_db: - for url in kodi_db.art_urls(kodi_id, kodi_type): + with kodi_db.KODIDB_FROM_PLEXTYPE[plex_type] as kodidb: + for url in kodidb.art_urls(kodi_id, kodi_type): artwork.cache_url(url) diff --git a/resources/lib/playback.py b/resources/lib/playback.py index a12c0df0..1360f187 100644 --- a/resources/lib/playback.py +++ b/resources/lib/playback.py @@ -13,7 +13,7 @@ from .plex_db import PlexDB from . import plex_functions as PF from . import utils from .downloadutils import DownloadUtils as DU -from . import kodidb_functions as kodidb +from .kodi_db import KodiVideoDB from . import playlist_func as PL from . import playqueue as PQ from . import json_rpc as js @@ -429,8 +429,8 @@ def _conclude_playback(playqueue, pos): with PlexDB() as plexdb: db_item = plexdb.item_by_id(item.plex_id, item.plex_type) file_id = db_item['kodi_fileid'] if db_item else None - with kodidb.GetKodiDB('video') as kodi_db: - item.offset = kodi_db.get_resume(file_id) + with KodiVideoDB() as kodidb: + item.offset = kodidb.get_resume(file_id) LOG.info('Resuming playback at %s', item.offset) if v.KODIVERSION >= 18 and api: # Kodi 18 Alpha 3 broke StartOffset diff --git a/resources/lib/playback_starter.py b/resources/lib/playback_starter.py index a4ce8cc9..324d3fe4 100644 --- a/resources/lib/playback_starter.py +++ b/resources/lib/playback_starter.py @@ -5,11 +5,11 @@ from logging import getLogger from threading import Thread from urlparse import parse_qsl +from .kodi_db import KodiVideoDB from . import playback from . import context_entry from . import json_rpc as js from . import pickler -from . import kodidb_functions as kodidb from . import state ############################################################################### @@ -47,8 +47,8 @@ class PlaybackStarter(Thread): resolve=resolve) elif mode == 'navigation': # e.g. when plugin://...tvshows is called for entire season - with kodidb.GetKodiDB('video') as kodi_db: - show_id = kodi_db.show_id_from_path(params.get('path')) + with KodiVideoDB() as kodidb: + show_id = kodidb.show_id_from_path(params.get('path')) if show_id: js.activate_window('videos', 'videodb://tvshows/titles/%s' % show_id) diff --git a/resources/lib/playlist_func.py b/resources/lib/playlist_func.py index 8dbf175a..bc715e98 100644 --- a/resources/lib/playlist_func.py +++ b/resources/lib/playlist_func.py @@ -11,7 +11,7 @@ from urlparse import parse_qsl, urlsplit from .plex_api import API from .plex_db import PlexDB from . import plex_functions as PF -from . import kodidb_functions as kodidb +from .kodi_db import kodiid_from_filename from .downloadutils import DownloadUtils as DU from . import utils from . import json_rpc as js @@ -360,8 +360,8 @@ def verify_kodi_item(plex_id, kodi_item): if (not state.DIRECT_PATHS and state.ENABLE_MUSIC and kodi_item.get('type') == v.KODI_TYPE_SONG and kodi_item['file'].startswith('http')): - kodi_item['id'], _ = kodidb.kodiid_from_filename(kodi_item['file'], - v.KODI_TYPE_SONG) + kodi_item['id'], _ = kodiid_from_filename(kodi_item['file'], + v.KODI_TYPE_SONG) LOG.debug('Detected song. Research results: %s', kodi_item) return kodi_item # Need more info since we don't have kodi_id nor type. Use file path. @@ -372,16 +372,16 @@ def verify_kodi_item(plex_id, kodi_item): raise PlaylistError LOG.debug('Starting research for Kodi id since we didnt get one: %s', kodi_item) - kodi_id, _ = kodidb.kodiid_from_filename(kodi_item['file'], - v.KODI_TYPE_MOVIE) + kodi_id, _ = kodiid_from_filename(kodi_item['file'], + v.KODI_TYPE_MOVIE) kodi_item['type'] = v.KODI_TYPE_MOVIE if kodi_id is None: - kodi_id, _ = kodidb.kodiid_from_filename(kodi_item['file'], - v.KODI_TYPE_EPISODE) + kodi_id, _ = kodiid_from_filename(kodi_item['file'], + v.KODI_TYPE_EPISODE) kodi_item['type'] = v.KODI_TYPE_EPISODE if kodi_id is None: - kodi_id, _ = kodidb.kodiid_from_filename(kodi_item['file'], - v.KODI_TYPE_SONG) + kodi_id, _ = kodiid_from_filename(kodi_item['file'], + v.KODI_TYPE_SONG) kodi_item['type'] = v.KODI_TYPE_SONG kodi_item['id'] = kodi_id kodi_item['type'] = None if kodi_id is None else kodi_item['type'] diff --git a/resources/lib/playlists/db.py b/resources/lib/playlists/db.py index 5cbd386f..3bb0e98c 100644 --- a/resources/lib/playlists/db.py +++ b/resources/lib/playlists/db.py @@ -9,7 +9,7 @@ from logging import getLogger from .common import Playlist, PlaylistError from ..plex_db import PlexDB -from .. import kodidb_functions as kodidb +from ..kodi_db import kodiid_from_filename from .. import path_ops, utils, variables as v ############################################################################### LOG = getLogger('PLEX.playlists.db') @@ -88,8 +88,8 @@ def m3u_to_plex_ids(playlist): plex_ids.append(plex_id) else: # Add-on paths not working, try direct - kodi_id, kodi_type = kodidb.kodiid_from_filename( - entry, db_type=playlist.kodi_type) + kodi_id, kodi_type = kodiid_from_filename(entry, + db_type=playlist.kodi_type) if not kodi_id: continue with PlexDB() as plexdb: diff --git a/resources/lib/plex_api.py b/resources/lib/plex_api.py index c8b0dbb0..8a7fb90f 100644 --- a/resources/lib/plex_api.py +++ b/resources/lib/plex_api.py @@ -38,13 +38,13 @@ from urlparse import parse_qsl from xbmcgui import ListItem from .plex_db import PlexDB +from .kodi_db import KodiVideoDB, KodiMusicDB from .utils import cast from .downloadutils import DownloadUtils as DU from . import clientinfo from . import utils from . import path_ops from . import plex_functions as PF -from . import kodidb_functions as kodidb from . import variables as v from . import state @@ -939,13 +939,13 @@ class API(object): else: return artworks # Grab artwork from the season - with kodidb.GetKodiDB('video') as kodi_db: - season_art = kodi_db.get_art(season_id, v.KODI_TYPE_SEASON) + with KodiVideoDB() as kodidb: + season_art = kodidb.get_art(season_id, v.KODI_TYPE_SEASON) for kodi_art in season_art: artworks['season.%s' % kodi_art] = season_art[kodi_art] # Grab more artwork from the show - with kodidb.GetKodiDB('video') as kodi_db: - show_art = kodi_db.get_art(show_id, v.KODI_TYPE_SHOW) + with KodiVideoDB() as kodidb: + show_art = kodidb.get_art(show_id, v.KODI_TYPE_SHOW) for kodi_art in show_art: artworks['tvshow.%s' % kodi_art] = show_art[kodi_art] return artworks @@ -953,11 +953,11 @@ class API(object): if kodi_id: # in Kodi database, potentially with additional e.g. clearart if self.plex_type() in v.PLEX_VIDEOTYPES: - with kodidb.GetKodiDB('video') as kodi_db: - return kodi_db.get_art(kodi_id, kodi_type) + with KodiVideoDB() as kodidb: + return kodidb.get_art(kodi_id, kodi_type) else: - with kodidb.GetKodiDB('music') as kodi_db: - return kodi_db.get_art(kodi_id, kodi_type) + with KodiMusicDB() as kodidb: + return kodidb.get_art(kodi_id, kodi_type) # Grab artwork from Plex # if self.plex_type() == v.PLEX_TYPE_EPISODE: diff --git a/resources/lib/sync.py b/resources/lib/sync.py index 49e1bdb6..6bd462eb 100644 --- a/resources/lib/sync.py +++ b/resources/lib/sync.py @@ -7,7 +7,7 @@ import xbmc from .downloadutils import DownloadUtils as DU from . import library_sync from . import backgroundthread, utils, path_ops, artwork, variables as v, state -from . import plex_db, kodidb_functions as kodidb +from . import plex_db, kodi_db LOG = getLogger('PLEX.sync') @@ -118,7 +118,7 @@ class Sync(backgroundthread.KillableThread): return # ask to reset all existing or not if utils.yesno_dialog('Image Texture Cache', utils.lang(39251)): - kodidb.reset_cached_images() + kodi_db.reset_cached_images() self.start_image_cache_thread() def on_library_scan_finished(self, successful): @@ -236,9 +236,9 @@ class Sync(backgroundthread.KillableThread): plex_db.initialize() # Hack to speed up look-ups for actors (giant table!) utils.create_kodi_db_indicees() - with kodidb.GetKodiDB('video') as kodi_db: + with kodi_db.KodiVideoDB() as kodidb: # Setup the paths for addon-paths (even when using direct paths) - kodi_db.setup_path_table() + kodidb.setup_path_table() while not self.isCanceled(): # In the event the server goes offline diff --git a/resources/lib/utils.py b/resources/lib/utils.py index 8fe22040..56c1c102 100644 --- a/resources/lib/utils.py +++ b/resources/lib/utils.py @@ -526,8 +526,7 @@ def wipe_database(): delete_playlists() # Clean up the video nodes delete_nodes() - from . import kodidb_functions - from . import plex_db + from . import kodi_db, plex_db # First get the paths to all synced playlists playlist_paths = [] try: @@ -545,7 +544,7 @@ def wipe_database(): # Plex DB completely empty yet. Wipe existing Kodi music only if we # expect to sync Plex music music = settings('enableMusic') == 'true' - kodidb_functions.wipe_dbs(music) + kodi_db.wipe_dbs(music) plex_db.wipe() plex_db.initialize() # Delete all synced playlists @@ -558,7 +557,7 @@ def wipe_database(): LOG.info("Resetting all cached artwork.") # Remove all cached artwork - kodidb_functions.reset_cached_images() + kodi_db.reset_cached_images() # reset the install run flag settings('SyncInstallRunDone', value="false") LOG.info('Wiping done')