Rewire kodi database access

This commit is contained in:
croneter 2018-11-08 21:22:16 +01:00
parent 150229061b
commit a16eae143a
22 changed files with 1078 additions and 934 deletions

View file

@ -6,10 +6,10 @@ from urllib import quote_plus, unquote
import requests import requests
import xbmc import xbmc
from . import backgroundthread, path_ops, utils from .kodi_db import KodiVideoDB, KodiMusicDB, KodiTextureDB
from . import backgroundthread, utils
from . import state from . import state
###############################################################################
LOG = getLogger('PLEX.artwork') LOG = getLogger('PLEX.artwork')
# Disable annoying requests warnings # Disable annoying requests warnings
@ -26,8 +26,6 @@ IMAGE_CACHING_SUSPENDS = [
if not utils.settings('imageSyncDuringPlayback') == 'true': if not utils.settings('imageSyncDuringPlayback') == 'true':
IMAGE_CACHING_SUSPENDS.append(state.SUSPEND_SYNC) IMAGE_CACHING_SUSPENDS.append(state.SUSPEND_SYNC)
###############################################################################
def double_urlencode(text): def double_urlencode(text):
return quote_plus(quote_plus(text)) return quote_plus(quote_plus(text))
@ -53,18 +51,16 @@ class ImageCachingThread(backgroundthread.KillableThread):
@staticmethod @staticmethod
def _art_url_generator(): def _art_url_generator():
from . import kodidb_functions as kodidb for kind in (KodiVideoDB, KodiMusicDB):
for kind in ('video', 'music'): with kind() as kodidb:
with kodidb.GetKodiDB(kind) as kodi_db:
for kodi_type in ('poster', 'fanart'): 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 yield url
def missing_art_cache_generator(self): def missing_art_cache_generator(self):
from . import kodidb_functions as kodidb with KodiTextureDB() as kodidb:
with kodidb.GetKodiDB('texture') as kodi_db:
for url in self._art_url_generator(): for url in self._art_url_generator():
if kodi_db.url_not_yet_cached(url): if kodidb.url_not_yet_cached(url):
yield url yield url
def run(self): def run(self):
@ -126,74 +122,3 @@ def cache_url(url):
break break
# We did not even get a timeout # We did not even get a timeout
break 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,))

View file

@ -6,8 +6,8 @@ from ntpath import dirname
from ..plex_api import API from ..plex_api import API
from ..plex_db import PlexDB from ..plex_db import PlexDB
from .. import kodidb_functions as kodidb from ..kodi_db import KodiVideoDB
from .. import artwork, utils from .. import utils
LOG = getLogger('PLEX.itemtypes.common') LOG = getLogger('PLEX.itemtypes.common')
@ -39,14 +39,16 @@ class ItemBase(object):
Input: Input:
kodiType: optional argument; e.g. 'video' or 'music' 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.last_sync = last_sync
self.plexconn = None self.plexconn = None
self.plexcursor = plexdb.cursor if plexdb else None self.plexcursor = plexdb.cursor if plexdb else None
self.kodiconn = 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.plexdb = plexdb
self.kodi_db = kodi_db self.kodidb = kodidb
def __enter__(self): def __enter__(self):
""" """
@ -56,8 +58,12 @@ class ItemBase(object):
self.plexcursor = self.plexconn.cursor() self.plexcursor = self.plexconn.cursor()
self.kodiconn = utils.kodi_sql('video') self.kodiconn = utils.kodi_sql('video')
self.kodicursor = self.kodiconn.cursor() self.kodicursor = self.kodiconn.cursor()
self.artconn = utils.kodi_sql('texture')
self.artcursor = self.artconn.cursor()
self.plexdb = PlexDB(self.plexcursor) 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 return self
def __exit__(self, exc_type, exc_val, exc_tb): def __exit__(self, exc_type, exc_val, exc_tb):
@ -66,18 +72,19 @@ class ItemBase(object):
""" """
self.plexconn.commit() self.plexconn.commit()
self.kodiconn.commit() self.kodiconn.commit()
self.artconn.commit()
self.plexconn.close() self.plexconn.close()
self.kodiconn.close() self.kodiconn.close()
self.artconn.close()
return self return self
def set_fanart(self, artworks, kodi_id, kodi_type): def set_fanart(self, artworks, kodi_id, kodi_type):
""" """
Writes artworks [dict containing only set artworks] to the Kodi art DB Writes artworks [dict containing only set artworks] to the Kodi art DB
""" """
artwork.modify_artwork(artworks, self.kodidb.modify_artwork(artworks,
kodi_id, kodi_id,
kodi_type, kodi_type)
self.kodicursor)
def update_userdata(self, xml_element, plex_type): def update_userdata(self, xml_element, plex_type):
""" """
@ -93,13 +100,13 @@ class ItemBase(object):
# Grab the user's viewcount, resume points etc. from PMS' answer # Grab the user's viewcount, resume points etc. from PMS' answer
userdata = api.userdata() userdata = api.userdata()
# Write to Kodi DB # Write to Kodi DB
self.kodi_db.set_resume(db_item['kodi_fileid'], self.kodidb.set_resume(db_item['kodi_fileid'],
userdata['Resume'], userdata['Resume'],
userdata['Runtime'], userdata['Runtime'],
userdata['PlayCount'], userdata['PlayCount'],
userdata['LastPlayedDate'], userdata['LastPlayedDate'],
plex_type) plex_type)
self.kodi_db.update_userrating(db_item['kodi_id'], self.kodidb.update_userrating(db_item['kodi_id'],
db_item['kodi_type'], db_item['kodi_type'],
userdata['UserRating']) userdata['UserRating'])
@ -118,7 +125,7 @@ class ItemBase(object):
view_count = 1 view_count = 1
resume = 0 resume = 0
# Do the actual update # Do the actual update
self.kodi_db.set_resume(kodi_fileid, self.kodidb.set_resume(kodi_fileid,
resume, resume,
duration, duration,
view_count, view_count,

View file

@ -5,7 +5,7 @@ from logging import getLogger
from .common import ItemBase from .common import ItemBase
from ..plex_api import API 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') LOG = getLogger('PLEX.movies')
@ -20,32 +20,20 @@ class Movie(ItemBase):
Process single movie Process single movie
""" """
api = API(xml) api = API(xml)
update_item = True
plex_id = api.plex_id() plex_id = api.plex_id()
# Cannot parse XML, abort # Cannot parse XML, abort
if not plex_id: if not plex_id:
LOG.error('Cannot parse XML data for movie: %s', xml.attrib) LOG.error('Cannot parse XML data for movie: %s', xml.attrib)
return return
movie = self.plexdb.movie(plex_id) movie = self.plexdb.movie(plex_id)
try: if movie:
update_item = True
kodi_id = movie['kodi_id'] kodi_id = movie['kodi_id']
old_kodi_fileid = movie['kodi_fileid'] old_kodi_fileid = movie['kodi_fileid']
kodi_pathid = movie['kodi_pathid'] 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: 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 update_item = False
LOG.info("kodi_id: %s missing from Kodi, repairing the entry.", kodi_id = self.kodidb.new_movie_id()
kodi_id)
userdata = api.userdata() userdata = api.userdata()
playcount = userdata['PlayCount'] playcount = userdata['PlayCount']
@ -80,7 +68,7 @@ class Movie(ItemBase):
# Network share # Network share
filename = playurl.rsplit("/", 1)[1] filename = playurl.rsplit("/", 1)[1]
path = playurl.replace(filename, "") path = playurl.replace(filename, "")
kodi_pathid = self.kodi_db.add_video_path(path, kodi_pathid = self.kodidb.add_video_path(path,
content='movies', content='movies',
scraper='metadata.local') scraper='metadata.local')
if do_indirect: if do_indirect:
@ -90,19 +78,19 @@ class Movie(ItemBase):
filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s' filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s'
% (path, plex_id, v.PLEX_TYPE_MOVIE, filename)) % (path, plex_id, v.PLEX_TYPE_MOVIE, filename))
playurl = 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, file_id = self.kodidb.add_file(filename,
kodi_pathid, kodi_pathid,
api.date_created()) api.date_created())
if update_item: if update_item:
LOG.info('UPDATE movie plex_id: %s - %s', plex_id, api.title()) LOG.info('UPDATE movie plex_id: %s - %s', plex_id, api.title())
if file_id != old_kodi_fileid: if file_id != old_kodi_fileid:
self.kodi_db.remove_file(old_kodi_fileid) self.kodidb.remove_file(old_kodi_fileid)
rating_id = self.kodi_db.get_ratingid(kodi_id, rating_id = self.kodidb.get_ratingid(kodi_id,
v.KODI_TYPE_MOVIE) v.KODI_TYPE_MOVIE)
self.kodi_db.update_ratings(kodi_id, self.kodidb.update_ratings(kodi_id,
v.KODI_TYPE_MOVIE, v.KODI_TYPE_MOVIE,
"default", "default",
rating, rating,
@ -110,30 +98,30 @@ class Movie(ItemBase):
rating_id) rating_id)
# update new uniqueid Kodi 17 # update new uniqueid Kodi 17
if api.provider('imdb') is not None: if api.provider('imdb') is not None:
uniqueid = self.kodi_db.get_uniqueid(kodi_id, uniqueid = self.kodidb.get_uniqueid(kodi_id,
v.KODI_TYPE_MOVIE) v.KODI_TYPE_MOVIE)
self.kodi_db.update_uniqueid(kodi_id, self.kodidb.update_uniqueid(kodi_id,
v.KODI_TYPE_MOVIE, v.KODI_TYPE_MOVIE,
api.provider('imdb'), api.provider('imdb'),
"imdb", "imdb",
uniqueid) uniqueid)
else: else:
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_MOVIE) self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_MOVIE)
uniqueid = -1 uniqueid = -1
else: else:
LOG.info("ADD movie plex_id: %s - %s", plex_id, title) LOG.info("ADD movie plex_id: %s - %s", plex_id, title)
rating_id = self.kodi_db.get_ratingid(kodi_id, rating_id = self.kodidb.get_ratingid(kodi_id,
v.KODI_TYPE_MOVIE) v.KODI_TYPE_MOVIE)
self.kodi_db.add_ratings(rating_id, self.kodidb.add_ratings(rating_id,
kodi_id, kodi_id,
v.KODI_TYPE_MOVIE, v.KODI_TYPE_MOVIE,
"default", "default",
rating, rating,
api.votecount()) api.votecount())
if api.provider('imdb') is not None: if api.provider('imdb') is not None:
uniqueid = self.kodi_db.get_uniqueid(kodi_id, uniqueid = self.kodidb.get_uniqueid(kodi_id,
v.KODI_TYPE_MOVIE) v.KODI_TYPE_MOVIE)
self.kodi_db.add_uniqueid(uniqueid, self.kodidb.add_uniqueid(uniqueid,
kodi_id, kodi_id,
v.KODI_TYPE_MOVIE, v.KODI_TYPE_MOVIE,
api.provider('imdb'), api.provider('imdb'),
@ -142,42 +130,48 @@ class Movie(ItemBase):
uniqueid = -1 uniqueid = -1
# Update Kodi's main entry # Update Kodi's main entry
query = ''' self.kodidb.add_movie(kodi_id,
INSERT OR REPLACE INTO movie(idMovie, idFile, c00, c01, c02, c03, file_id,
c04, c05, c06, c07, c09, c10, c11, c12, c14, c15, c16, title,
c18, c19, c21, c22, c23, premiered, userrating) api.plot(),
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, api.shortplot(),
?, ?, ?, ?, ?, ?, ?) api.tagline(),
''' api.votecount(),
self.kodicursor.execute( rating_id,
query, api.list_to_string(people['Writer']),
(kodi_id, file_id, title, api.plot(), api.shortplot(), api.year(),
api.tagline(), api.votecount(), rating_id, uniqueid,
api.list_to_string(people['Writer']), api.year(), api.sorttitle(),
uniqueid, api.sorttitle(), runtime, api.content_rating(), runtime,
api.list_to_string(genres), api.list_to_string(people['Director']), api.content_rating(),
title, api.list_to_string(studios), api.trailer(), api.list_to_string(genres),
api.list_to_string(countries), playurl, kodi_pathid, api.list_to_string(people['Director']),
api.premiere_date(), userdata['UserRating'])) 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.kodidb.modify_countries(kodi_id, v.KODI_TYPE_MOVIE, countries)
self.kodi_db.modify_people(kodi_id, self.kodidb.modify_people(kodi_id,
v.KODI_TYPE_MOVIE, v.KODI_TYPE_MOVIE,
api.people_list()) api.people_list())
self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_MOVIE, genres) self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_MOVIE, genres)
artwork.modify_artwork(api.artwork(), self.kodidb.modify_artwork(api.artwork(),
kodi_id, kodi_id,
v.KODI_TYPE_MOVIE, v.KODI_TYPE_MOVIE)
self.kodicursor) self.kodidb.modify_streams(file_id, api.mediastreams(), runtime)
self.kodi_db.modify_streams(file_id, api.mediastreams(), runtime) self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_MOVIE, studios)
self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_MOVIE, studios)
tags = [section_name] tags = [section_name]
if collections: if collections:
for plex_set_id, set_name in collections: for plex_set_id, set_name in collections:
tags.append(set_name) tags.append(set_name)
# Add any sets from Plex collection tags # Add any sets from Plex collection tags
kodi_set_id = self.kodi_db.create_collection(set_name) kodi_set_id = self.kodidb.create_collection(set_name)
self.kodi_db.assign_collection(kodi_set_id, kodi_id) self.kodidb.assign_collection(kodi_set_id, kodi_id)
if children is None: if children is None:
# e.g. when added via websocket # e.g. when added via websocket
LOG.debug('Costly looking up Plex collection %s: %s', LOG.debug('Costly looking up Plex collection %s: %s',
@ -199,13 +193,12 @@ class Movie(ItemBase):
set_api = API(children[plex_set_id][0]) set_api = API(children[plex_set_id][0])
else: else:
continue continue
artwork.modify_artwork(set_api.artwork(), self.kodidb.modify_artwork(set_api.artwork(),
kodi_set_id, kodi_set_id,
v.KODI_TYPE_SET, v.KODI_TYPE_SET)
self.kodicursor) self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_MOVIE, tags)
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_MOVIE, tags)
# Process playstate # Process playstate
self.kodi_db.set_resume(file_id, self.kodidb.set_resume(file_id,
resume, resume,
runtime, runtime,
playcount, playcount,
@ -238,19 +231,18 @@ class Movie(ItemBase):
# Remove the plex reference # Remove the plex reference
self.plexdb.remove(plex_id, v.PLEX_TYPE_MOVIE) self.plexdb.remove(plex_id, v.PLEX_TYPE_MOVIE)
# Remove artwork # Remove artwork
artwork.delete_artwork(kodi_id, kodi_type, self.self.kodicursor) self.kodidb.delete_artwork(kodi_id, kodi_type)
set_id = self.kodi_db.get_set_id(kodi_id) set_id = self.kodidb.get_set_id(kodi_id)
self.kodi_db.modify_countries(kodi_id, kodi_type) self.kodidb.modify_countries(kodi_id, kodi_type)
self.kodi_db.modify_people(kodi_id, kodi_type) self.kodidb.modify_people(kodi_id, kodi_type)
self.kodi_db.modify_genres(kodi_id, kodi_type) self.kodidb.modify_genres(kodi_id, kodi_type)
self.kodi_db.modify_studios(kodi_id, kodi_type) self.kodidb.modify_studios(kodi_id, kodi_type)
self.kodi_db.modify_tags(kodi_id, kodi_type) self.kodidb.modify_tags(kodi_id, kodi_type)
# Delete kodi movie and file # Delete kodi movie and file
self.kodi_db.remove_file(file_id) self.kodidb.remove_file(file_id)
self.self.kodicursor.execute('DELETE FROM movie WHERE idMovie = ?', self.kodidb.remove_movie(kodi_id)
(kodi_id,))
if set_id: if set_id:
self.kodi_db.delete_possibly_empty_set(set_id) self.kodidb.delete_possibly_empty_set(set_id)
self.kodi_db.remove_uniqueid(kodi_id, kodi_type) self.kodidb.remove_uniqueid(kodi_id, kodi_type)
self.kodi_db.remove_ratings(kodi_id, kodi_type) self.kodidb.remove_ratings(kodi_id, kodi_type)
LOG.debug('Deleted movie %s from kodi database', plex_id) LOG.debug('Deleted movie %s from kodi database', plex_id)

View file

@ -6,8 +6,8 @@ from logging import getLogger
from .common import ItemBase from .common import ItemBase
from ..plex_api import API from ..plex_api import API
from ..plex_db import PlexDB from ..plex_db import PlexDB
from .. import artwork, kodidb_functions as kodidb from ..kodi_db import KodiMusicDB
from .. import plex_functions as PF, utils, state, variables as v from .. import artwork, plex_functions as PF, utils, state, variables as v
LOG = getLogger('PLEX.music') LOG = getLogger('PLEX.music')
@ -21,8 +21,12 @@ class MusicMixin(object):
self.plexcursor = self.plexconn.cursor() self.plexcursor = self.plexconn.cursor()
self.kodiconn = utils.kodi_sql('music') self.kodiconn = utils.kodi_sql('music')
self.kodicursor = self.kodiconn.cursor() self.kodicursor = self.kodiconn.cursor()
self.artconn = utils.kodi_sql('texture')
self.artcursor = self.artconn.cursor()
self.plexdb = PlexDB(self.plexcursor) 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 return self
def remove(self, plex_id, plex_type=None): def remove(self, plex_id, plex_type=None):
@ -93,7 +97,7 @@ class MusicMixin(object):
path_id = self.kodicursor.fetchone()[0] path_id = self.kodicursor.fetchone()[0]
except TypeError: except TypeError:
pass 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 = ?', self.kodicursor.execute('DELETE FROM song WHERE idSong = ?',
(kodi_id, )) (kodi_id, ))
# Check whether we have orphaned path entries # Check whether we have orphaned path entries
@ -103,7 +107,7 @@ class MusicMixin(object):
self.kodicursor.execute('DELETE FROM path WHERE idPath = ?', self.kodicursor.execute('DELETE FROM path WHERE idPath = ?',
(path_id, )) (path_id, ))
if v.KODIVERSION < 18: 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 = ?' query = 'DELETE FROM albuminfosong WHERE idAlbumInfoSong = ?'
self.kodicursor.execute(query, (kodi_id, )) self.kodicursor.execute(query, (kodi_id, ))
artwork.delete_artwork(kodi_id, v.KODI_TYPE_SONG, self.kodicursor) artwork.delete_artwork(kodi_id, v.KODI_TYPE_SONG, self.kodicursor)
@ -112,9 +116,9 @@ class MusicMixin(object):
''' '''
Remove an album Remove an album
''' '''
self.kodi_db.delete_album_from_discography(kodi_id) self.kodidb.delete_album_from_discography(kodi_id)
if v.KODIVERSION < 18: 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 = ?' query = 'DELETE FROM albuminfosong WHERE idAlbumInfo = ?'
self.kodicursor.execute(query, (kodi_id, )) self.kodicursor.execute(query, (kodi_id, ))
self.kodicursor.execute('DELETE FROM album_artist WHERE idAlbum = ?', self.kodicursor.execute('DELETE FROM album_artist WHERE idAlbum = ?',
@ -183,7 +187,7 @@ class Artist(MusicMixin, ItemBase):
# multiple times. # multiple times.
# Kodi doesn't allow that. In case that happens we just merge the # Kodi doesn't allow that. In case that happens we just merge the
# artist entries. # 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 # Create the reference in plex table
query = ''' query = '''
UPDATE artist UPDATE artist
@ -279,7 +283,7 @@ class Album(MusicMixin, ItemBase):
# OR ADD THE ALBUM ##### # OR ADD THE ALBUM #####
else: else:
LOG.info("ADD album plex_id: %s - Name: %s", plex_id, name) 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 # Process the album info
if v.KODIVERSION >= 18: if v.KODIVERSION >= 18:
# Kodi Leia # Kodi Leia
@ -360,7 +364,7 @@ class Album(MusicMixin, ItemBase):
self.kodicursor.execute(query, self.kodicursor.execute(query,
(artist_id, name, api.year())) (artist_id, name, api.year()))
if v.KODIVERSION < 18: if v.KODIVERSION < 18:
self.kodi_db.add_music_genres(kodi_id, self.kodidb.add_music_genres(kodi_id,
genres, genres,
v.KODI_TYPE_ALBUM) v.KODI_TYPE_ALBUM)
# Update artwork # Update artwork
@ -379,7 +383,7 @@ class Album(MusicMixin, ItemBase):
if scan_children: if scan_children:
context = Song(self.last_sync, context = Song(self.last_sync,
plexdb=self.plexdb, plexdb=self.plexdb,
kodi_db=self.kodi_db) kodidb=self.kodidb)
for song in children: for song in children:
context.add_update(song, context.add_update(song,
section_name=section_name, section_name=section_name,
@ -426,7 +430,7 @@ class Song(MusicMixin, ItemBase):
LOG.error('Grandparent tvartist %s xml download failed for %s', LOG.error('Grandparent tvartist %s xml download failed for %s',
artist_id, xml.attrib) artist_id, xml.attrib)
return 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_xml[0], section_name, section_id)
artist = self.plexdb.artist(artist_id) artist = self.plexdb.artist(artist_id)
if not artist: if not artist:
@ -463,7 +467,7 @@ class Song(MusicMixin, ItemBase):
LOG.error('Parent album %s xml download failed for %s', LOG.error('Parent album %s xml download failed for %s',
album_id, xml.attrib) album_id, xml.attrib)
return 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_xml[0], section_name, section_id)
album = self.plexdb.album(album_id) album = self.plexdb.album(album_id)
if not album: if not album:
@ -615,7 +619,7 @@ class Song(MusicMixin, ItemBase):
else: else:
LOG.info("ADD song plex_id: %s - %s", plex_id, title) LOG.info("ADD song plex_id: %s - %s", plex_id, title)
# Add path # 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 # Create the song entry
if v.KODIVERSION >= 18: if v.KODIVERSION >= 18:
# Kodi Leia # Kodi Leia
@ -730,7 +734,7 @@ class Song(MusicMixin, ItemBase):
(grandparent_id, kodi_id, 1, 0, artist_name)) (grandparent_id, kodi_id, 1, 0, artist_name))
# Add genres # Add genres
if 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() artworks = api.artwork()
artwork.modify_artwork(artworks, artwork.modify_artwork(artworks,
kodi_id, kodi_id,

View file

@ -5,7 +5,7 @@ from logging import getLogger
from .common import ItemBase, process_path from .common import ItemBase, process_path
from ..plex_api import API 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') LOG = getLogger('PLEX.tvshows')
@ -74,38 +74,35 @@ class TvShowMixin(object):
""" """
Remove a TV show, and only the show, no seasons or episodes Remove a TV show, and only the show, no seasons or episodes
""" """
self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_SHOW) self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_SHOW) self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW) self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_SHOW)
artwork.delete_artwork(kodi_id, v.KODI_TYPE_SHOW, self.kodicursor) self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_SHOW)
self.kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?", self.kodidb.remove_show(kodi_id)
(kodi_id,))
if v.KODIVERSION >= 17: if v.KODIVERSION >= 17:
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW) self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_SHOW) self.kodidb.remove_ratings(kodi_id, v.KODI_TYPE_SHOW)
LOG.debug("Removed tvshow: %s", kodi_id) LOG.debug("Removed tvshow: %s", kodi_id)
def remove_season(self, kodi_id): def remove_season(self, kodi_id):
""" """
Remove a season, and only a season, not the show or episodes Remove a season, and only a season, not the show or episodes
""" """
artwork.delete_artwork(kodi_id, v.KODI_TYPE_SEASON, self.kodicursor) self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_SEASON)
self.kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?", self.kodidb.remove_season(kodi_id)
(kodi_id,))
LOG.debug("Removed season: %s", kodi_id) LOG.debug("Removed season: %s", kodi_id)
def remove_episode(self, kodi_id, file_id): def remove_episode(self, kodi_id, file_id):
""" """
Remove an episode, and episode only from the Kodi DB (not Plex DB) 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.kodidb.modify_people(kodi_id, v.KODI_TYPE_EPISODE)
self.kodi_db.remove_file(file_id, plex_type=v.PLEX_TYPE_EPISODE) self.kodidb.remove_file(file_id, plex_type=v.PLEX_TYPE_EPISODE)
artwork.delete_artwork(kodi_id, v.KODI_TYPE_EPISODE, self.kodicursor) self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_EPISODE)
self.kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?", self.kodidb.remove_episode(kodi_id)
(kodi_id,))
if v.KODIVERSION >= 17: if v.KODIVERSION >= 17:
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE) self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE)
self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_EPISODE) self.kodidb.remove_ratings(kodi_id, v.KODI_TYPE_EPISODE)
LOG.debug("Removed episode: %s", kodi_id) LOG.debug("Removed episode: %s", kodi_id)
@ -126,23 +123,11 @@ class Show(ItemBase, TvShowMixin):
show = self.plexdb.show(plex_id) show = self.plexdb.show(plex_id)
if not show: if not show:
update_item = False update_item = False
query = 'SELECT COALESCE(MAX(idShow), 0) FROM tvshow' kodi_id = self.kodidb.new_show_id()
self.kodicursor.execute(query)
kodi_id = self.kodicursor.fetchone()[0] + 1
else: else:
update_item = True update_item = True
kodi_id = show['kodi_id'] kodi_id = show['kodi_id']
kodi_pathid = show['kodi_pathid'] 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() genres = api.genre_list()
genre = api.list_to_string(genres) genre = api.list_to_string(genres)
@ -158,7 +143,7 @@ class Show(ItemBase, TvShowMixin):
if playurl is None: if playurl is None:
return return
path, toplevelpath = process_path(playurl) path, toplevelpath = process_path(playurl)
toppathid = self.kodi_db.add_video_path( toppathid = self.kodidb.add_video_path(
toplevelpath, toplevelpath,
content='tvshows', content='tvshows',
scraper='metadata.local') scraper='metadata.local')
@ -169,15 +154,15 @@ class Show(ItemBase, TvShowMixin):
# Do NOT set a parent id because addon-path cannot be "stacked" # Do NOT set a parent id because addon-path cannot be "stacked"
toppathid = None toppathid = None
kodi_pathid = self.kodi_db.add_video_path(path, kodi_pathid = self.kodidb.add_video_path(path,
date_added=api.date_created(), date_added=api.date_created(),
id_parent_path=toppathid) id_parent_path=toppathid)
# UPDATE THE TVSHOW ##### # UPDATE THE TVSHOW #####
if update_item: if update_item:
LOG.info("UPDATE tvshow plex_id: %s - %s", plex_id, api.title()) LOG.info("UPDATE tvshow plex_id: %s - %s", plex_id, api.title())
# update new ratings Kodi 17 # update new ratings Kodi 17
rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW) rating_id = self.kodidb.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.update_ratings(kodi_id, self.kodidb.update_ratings(kodi_id,
v.KODI_TYPE_SHOW, v.KODI_TYPE_SHOW,
"default", "default",
api.audience_rating(), api.audience_rating(),
@ -185,45 +170,44 @@ class Show(ItemBase, TvShowMixin):
rating_id) rating_id)
# update new uniqueid Kodi 17 # update new uniqueid Kodi 17
if api.provider('tvdb') is not None: if api.provider('tvdb') is not None:
uniqueid = self.kodi_db.get_uniqueid(kodi_id, uniqueid = self.kodidb.get_uniqueid(kodi_id,
v.KODI_TYPE_SHOW) v.KODI_TYPE_SHOW)
self.kodi_db.update_uniqueid(kodi_id, self.kodidb.update_uniqueid(kodi_id,
v.KODI_TYPE_SHOW, v.KODI_TYPE_SHOW,
api.provider('tvdb'), api.provider('tvdb'),
"unknown", "unknown",
uniqueid) uniqueid)
else: else:
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW) self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
uniqueid = -1 uniqueid = -1
# Update the tvshow entry # Update the tvshow entry
query = ''' self.kodidb.update_show(api.title(),
UPDATE tvshow api.plot(),
SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?, rating_id,
c12 = ?, c13 = ?, c14 = ?, c15 = ? api.premiere_date(),
WHERE idShow = ? genre,
''' api.title(),
self.kodicursor.execute( uniqueid,
query, (api.title(), api.plot(), rating_id, api.content_rating(),
api.premiere_date(), genre, api.title(), uniqueid, studio,
api.content_rating(), studio, api.sorttitle(), api.sorttitle(),
kodi_id)) kodi_id)
# OR ADD THE TVSHOW ##### # OR ADD THE TVSHOW #####
else: else:
LOG.info("ADD tvshow plex_id: %s - %s", plex_id, api.title()) LOG.info("ADD tvshow plex_id: %s - %s", plex_id, api.title())
# Link the path # Link the path
query = "INSERT INTO tvshowlinkpath(idShow, idPath) values (?, ?)" self.kodidb.add_showlinkpath(kodi_id, kodi_pathid)
self.kodicursor.execute(query, (kodi_id, kodi_pathid)) rating_id = self.kodidb.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW) self.kodidb.add_ratings(rating_id,
self.kodi_db.add_ratings(rating_id,
kodi_id, kodi_id,
v.KODI_TYPE_SHOW, v.KODI_TYPE_SHOW,
"default", "default",
api.audience_rating(), api.audience_rating(),
api.votecount()) api.votecount())
if api.provider('tvdb') is not None: if api.provider('tvdb') is not None:
uniqueid = self.kodi_db.get_uniqueid(kodi_id, uniqueid = self.kodidb.get_uniqueid(kodi_id,
v.KODI_TYPE_SHOW) v.KODI_TYPE_SHOW)
self.kodi_db.add_uniqueid(uniqueid, self.kodidb.add_uniqueid(uniqueid,
kodi_id, kodi_id,
v.KODI_TYPE_SHOW, v.KODI_TYPE_SHOW,
api.provider('tvdb'), api.provider('tvdb'),
@ -231,31 +215,30 @@ class Show(ItemBase, TvShowMixin):
else: else:
uniqueid = -1 uniqueid = -1
# Create the tvshow entry # Create the tvshow entry
query = ''' self.kodidb.add_show(kodi_id,
INSERT INTO tvshow( api.title(),
idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14, api.plot(),
c15) rating_id,
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) api.premiere_date(),
''' genre,
self.kodicursor.execute( api.title(),
query, (kodi_id, api.title(), api.plot(), rating_id, uniqueid,
api.premiere_date(), genre, api.title(), uniqueid, api.content_rating(),
api.content_rating(), studio, api.sorttitle())) studio,
api.sorttitle())
self.kodi_db.modify_people(kodi_id, self.kodidb.modify_people(kodi_id,
v.KODI_TYPE_SHOW, v.KODI_TYPE_SHOW,
api.people_list()) api.people_list())
self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_SHOW, genres) self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_SHOW, genres)
artwork.modify_artwork(api.artwork(), self.kodidb.modify_artwork(api.artwork(),
kodi_id, kodi_id,
v.KODI_TYPE_SHOW, v.KODI_TYPE_SHOW)
self.kodicursor)
# Process studios # 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 # Process tags: view, PMS collection tags
tags = [section_name] tags = [section_name]
tags.extend([i for _, i in api.collection_list()]) 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, self.plexdb.add_show(plex_id=plex_id,
checksum=api.checksum(), checksum=api.checksum(),
section_id=section_id, section_id=section_id,
@ -287,18 +270,17 @@ class Season(ItemBase, TvShowMixin):
except (TypeError, IndexError, AttributeError): except (TypeError, IndexError, AttributeError):
LOG.error("Parent tvshow %s xml download failed", show_id) LOG.error("Parent tvshow %s xml download failed", show_id)
return False 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_xml[0], section_name, section_id)
show = self.plexdb.show(show_id) show = self.plexdb.show(show_id)
if not show: if not show:
LOG.error('Still could not find parent tv show %s', show_id) LOG.error('Still could not find parent tv show %s', show_id)
return return
parent_id = show['kodi_id'] parent_id = show['kodi_id']
kodi_id = self.kodi_db.add_season(parent_id, api.season_number()) kodi_id = self.kodidb.add_season(parent_id, api.season_number())
artwork.modify_artwork(api.artwork(), self.kodidb.modify_artwork(api.artwork(),
kodi_id, kodi_id,
v.KODI_TYPE_SEASON, v.KODI_TYPE_SEASON)
self.kodicursor)
self.plexdb.add_season(plex_id=plex_id, self.plexdb.add_season(plex_id=plex_id,
checksum=api.checksum(), checksum=api.checksum(),
section_id=section_id, section_id=section_id,
@ -323,24 +305,12 @@ class Episode(ItemBase, TvShowMixin):
episode = self.plexdb.episode(plex_id) episode = self.plexdb.episode(plex_id)
if not episode: if not episode:
update_item = False update_item = False
query = 'SELECT COALESCE(MAX(idEpisode), 0) FROM episode' kodi_id = self.kodidb.new_episode_id()
self.kodicursor.execute(query)
kodi_id = self.kodicursor.fetchone()[0] + 1
else: else:
update_item = True update_item = True
kodi_id = episode['kodi_id'] kodi_id = episode['kodi_id']
old_kodi_fileid = episode['kodi_fileid'] old_kodi_fileid = episode['kodi_fileid']
kodi_pathid = episode['kodi_pathid'] 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() peoples = api.people()
director = api.list_to_string(peoples['Director']) director = api.list_to_string(peoples['Director'])
@ -365,7 +335,7 @@ class Episode(ItemBase, TvShowMixin):
except (TypeError, IndexError, AttributeError): except (TypeError, IndexError, AttributeError):
LOG.error("Grandparent tvshow %s xml download failed", show_id) LOG.error("Grandparent tvshow %s xml download failed", show_id)
return False 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_xml[0], section_name, section_id)
show = self.plexdb.show(show_id) show = self.plexdb.show(show_id)
if not show: if not show:
@ -383,7 +353,7 @@ class Episode(ItemBase, TvShowMixin):
except (TypeError, IndexError, AttributeError): except (TypeError, IndexError, AttributeError):
LOG.error("Parent season %s xml download failed", season_id) LOG.error("Parent season %s xml download failed", season_id)
return False 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_xml[0], section_name, section_id)
season = self.plexdb.season(season_id) season = self.plexdb.season(season_id)
if not season: if not season:
@ -406,8 +376,8 @@ class Episode(ItemBase, TvShowMixin):
# Network share # Network share
filename = playurl.rsplit("/", 1)[1] filename = playurl.rsplit("/", 1)[1]
path = playurl.replace(filename, "") path = playurl.replace(filename, "")
parent_path_id = self.kodi_db.parent_path_id(path) parent_path_id = self.kodidb.parent_path_id(path)
kodi_pathid = self.kodi_db.add_video_path( kodi_pathid = self.kodidb.add_video_path(
path, id_parent_path=parent_path_id) path, id_parent_path=parent_path_id)
if do_indirect: if do_indirect:
# Set plugin path - do NOT use "intermediate" paths for the show # Set plugin path - do NOT use "intermediate" paths for the show
@ -418,11 +388,11 @@ class Episode(ItemBase, TvShowMixin):
% (path, plex_id, v.PLEX_TYPE_EPISODE, filename)) % (path, plex_id, v.PLEX_TYPE_EPISODE, filename))
playurl = filename playurl = filename
# Root path tvshows/ already saved in Kodi DB # 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 # add/retrieve kodi_pathid and fileid
# if the path or file already exists, the calls return current value # if the path or file already exists, the calls return current value
kodi_fileid = self.kodi_db.add_file(filename, kodi_fileid = self.kodidb.add_file(filename,
kodi_pathid, kodi_pathid,
api.date_created()) api.date_created())
@ -430,82 +400,91 @@ class Episode(ItemBase, TvShowMixin):
if update_item: if update_item:
LOG.info("UPDATE episode plex_id: %s - %s", plex_id, api.title()) LOG.info("UPDATE episode plex_id: %s - %s", plex_id, api.title())
if kodi_fileid != old_kodi_fileid: if kodi_fileid != old_kodi_fileid:
self.kodi_db.remove_file(old_kodi_fileid) self.kodidb.remove_file(old_kodi_fileid)
ratingid = self.kodi_db.get_ratingid(kodi_id, ratingid = self.kodidb.get_ratingid(kodi_id,
v.KODI_TYPE_EPISODE) v.KODI_TYPE_EPISODE)
self.kodi_db.update_ratings(kodi_id, self.kodidb.update_ratings(kodi_id,
v.KODI_TYPE_EPISODE, v.KODI_TYPE_EPISODE,
"default", "default",
userdata['Rating'], userdata['Rating'],
api.votecount(), api.votecount(),
ratingid) ratingid)
# update new uniqueid Kodi 17 # update new uniqueid Kodi 17
uniqueid = self.kodi_db.get_uniqueid(kodi_id, uniqueid = self.kodidb.get_uniqueid(kodi_id,
v.KODI_TYPE_EPISODE) v.KODI_TYPE_EPISODE)
self.kodi_db.update_uniqueid(kodi_id, self.kodidb.update_uniqueid(kodi_id,
v.KODI_TYPE_EPISODE, v.KODI_TYPE_EPISODE,
api.provider('tvdb'), api.provider('tvdb'),
"tvdb", "tvdb",
uniqueid) uniqueid)
query = ''' self.kodidb.update_episode(api.title(),
UPDATE episode api.plot(),
SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?, ratingid,
c10 = ?, c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?, writer,
c18 = ?, c19 = ?, idFile=?, idSeason = ?, api.premiere_date(),
userrating = ? api.runtime(),
WHERE idEpisode = ? director,
''' season_no,
self.kodicursor.execute( episode_no,
query, (api.title(), api.plot(), ratingid, writer, api.title(),
api.premiere_date(), api.runtime(), director, season_no, airs_before_season,
episode_no, api.title(), airs_before_season, airs_before_episode,
airs_before_episode, playurl, kodi_pathid, kodi_fileid, playurl,
parent_id, userdata['UserRating'], kodi_id)) kodi_pathid,
kodi_fileid,
parent_id,
userdata['UserRating'],
kodi_id)
# OR ADD THE EPISODE ##### # OR ADD THE EPISODE #####
else: else:
LOG.info("ADD episode plex_id: %s - %s", plex_id, api.title()) LOG.info("ADD episode plex_id: %s - %s", plex_id, api.title())
# Create the episode entry # Create the episode entry
rating_id = self.kodi_db.get_ratingid(kodi_id, rating_id = self.kodidb.get_ratingid(kodi_id,
v.KODI_TYPE_EPISODE) v.KODI_TYPE_EPISODE)
self.kodi_db.add_ratings(rating_id, self.kodidb.add_ratings(rating_id,
kodi_id, kodi_id,
v.KODI_TYPE_EPISODE, v.KODI_TYPE_EPISODE,
"default", "default",
userdata['Rating'], userdata['Rating'],
api.votecount()) api.votecount())
# add new uniqueid Kodi 17 # add new uniqueid Kodi 17
uniqueid = self.kodi_db.get_uniqueid(kodi_id, uniqueid = self.kodidb.get_uniqueid(kodi_id,
v.KODI_TYPE_EPISODE) v.KODI_TYPE_EPISODE)
self.kodi_db.add_uniqueid(uniqueid, self.kodidb.add_uniqueid(uniqueid,
kodi_id, kodi_id,
v.KODI_TYPE_EPISODE, v.KODI_TYPE_EPISODE,
api.provider('tvdb'), api.provider('tvdb'),
"tvdb") "tvdb")
query = ''' self.kodidb.add_episode(kodi_id,
INSERT INTO episode( idEpisode, idFile, c00, c01, c03, c04, kodi_fileid,
c05, c09, c10, c12, c13, c14, idShow, c15, c16, c18, api.title(),
c19, idSeason, userrating) api.plot(),
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, rating_id,
?, ?) writer,
''' api.premiere_date(),
self.kodicursor.execute( api.runtime(),
query, (kodi_id, kodi_fileid, api.title(), api.plot(), rating_id, director,
writer, api.premiere_date(), api.runtime(), director, season_no,
season_no, episode_no, api.title(), grandparent_id, episode_no,
airs_before_season, airs_before_episode, playurl, api.title(),
kodi_pathid, parent_id, userdata['UserRating'])) grandparent_id,
airs_before_season,
airs_before_episode,
playurl,
kodi_pathid,
parent_id,
userdata['UserRating'])
self.kodi_db.modify_people(kodi_id, self.kodidb.modify_people(kodi_id,
v.KODI_TYPE_EPISODE, v.KODI_TYPE_EPISODE,
api.people_list()) api.people_list())
artwork.modify_artwork(api.artwork(), self.kodidb.modify_artwork(api.artwork(),
kodi_id, kodi_id,
v.KODI_TYPE_EPISODE, v.KODI_TYPE_EPISODE)
self.kodicursor)
streams = api.mediastreams() streams = api.mediastreams()
self.kodi_db.modify_streams(kodi_fileid, streams, api.runtime()) self.kodidb.modify_streams(kodi_fileid, streams, api.runtime())
self.kodi_db.set_resume(kodi_fileid, self.kodidb.set_resume(kodi_fileid,
api.resume_point(), api.resume_point(),
api.runtime(), api.runtime(),
userdata['PlayCount'], userdata['PlayCount'],
@ -519,11 +498,11 @@ class Episode(ItemBase, TvShowMixin):
filename = ('%s%s/?plex_id=%s&plex_type=%s&mode=play&filename=%s' filename = ('%s%s/?plex_id=%s&plex_type=%s&mode=play&filename=%s'
% (path, show_id, plex_id, v.PLEX_TYPE_EPISODE, % (path, show_id, plex_id, v.PLEX_TYPE_EPISODE,
filename)) filename))
kodi_pathid = self.kodi_db.add_video_path(path) kodi_pathid = self.kodidb.add_video_path(path)
kodi_fileid = self.kodi_db.add_file(filename, kodi_fileid = self.kodidb.add_file(filename,
kodi_pathid, kodi_pathid,
api.date_created()) api.date_created())
self.kodi_db.set_resume(kodi_fileid, self.kodidb.set_resume(kodi_fileid,
api.resume_point(), api.resume_point(),
api.runtime(), api.runtime(),
userdata['PlayCount'], userdata['PlayCount'],

View file

@ -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, <kodi_type> 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
}

View file

@ -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, ))

View file

@ -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):

View file

@ -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

View file

@ -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

View file

@ -1,52 +1,17 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""
Connect to the Kodi databases (video and music) and operate on them
"""
from __future__ import absolute_import, division, unicode_literals from __future__ import absolute_import, division, unicode_literals
from logging import getLogger from logging import getLogger
from sqlite3 import IntegrityError 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.kodi_db.video')
LOG = getLogger('PLEX.kodidb_functions')
class GetKodiDB(object): class KodiVideoDB(common.KodiDBBase):
""" db_kind = 'video'
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
def setup_path_table(self): def setup_path_table(self):
""" """
@ -155,28 +120,6 @@ class KodiDBMethods(object):
content, scraper, 1)) content, scraper, 1))
return pathid 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): def get_path(self, path):
""" """
Returns the idPath from the path table for path [unicode] or None 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],)) self.cursor.execute(query_actor_delete, (person[0],))
if kind == 'actor': if kind == 'actor':
# Delete any associated artwork # 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 # Save new people to Kodi DB by iterating over the remaining entries
if kind == 'actor': if kind == 'actor':
query = 'INSERT INTO actor_link VALUES (?, ?, ?, ?, ?)' query = 'INSERT INTO actor_link VALUES (?, ?, ?, ?, ?)'
@ -489,11 +432,10 @@ class KodiDBMethods(object):
'VALUES (?, ?)', 'VALUES (?, ?)',
(actor_id, name)) (actor_id, name))
if art_url: if art_url:
artwork.modify_art(art_url, self.modify_art(art_url,
actor_id, actor_id,
'actor', 'actor',
'thumb', 'thumb')
self.cursor)
return actor_id return actor_id
def get_art(self, kodi_id, kodi_type): def get_art(self, kodi_id, kodi_type):
@ -610,26 +552,6 @@ class KodiDBMethods(object):
return return
return movie_id, typus 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): def get_resume(self, file_id):
""" """
Returns the first resume point in seconds (int) if found, else None for Returns the first resume point in seconds (int) if found, else None for
@ -803,208 +725,6 @@ class KodiDBMethods(object):
''', (seasonid, showid, seasonnumber)) ''', (seasonid, showid, seasonnumber))
return seasonid 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): def add_uniqueid(self, *args):
""" """
Feed with: Feed with:
@ -1103,141 +823,151 @@ class KodiDBMethods(object):
self.cursor.execute('DELETE FROM rating WHERE media_id = ? AND media_type = ?', self.cursor.execute('DELETE FROM rating WHERE media_id = ? AND media_type = ?',
(kodi_id, kodi_type)) (kodi_id, kodi_type))
def art_urls(self, kodi_id, kodi_type): def new_show_id(self):
return (x[0] for x in self.cursor.execute('SELECT COALESCE(MAX(idShow), 0) FROM tvshow')
self.cursor.execute('SELECT url FROM art WHERE media_id = ? AND media_type = ?', return self.cursor.fetchone()[0] + 1
(kodi_id, kodi_type)))
def artwork_generator(self, kodi_type): def new_episode_id(self):
""" self.cursor.execute('SELECT COALESCE(MAX(idEpisode), 0) FROM episode')
""" return self.cursor.fetchone()[0] + 1
return (x[0] for x in
self.cursor.execute('SELECT url FROM art WHERE type == ?',
(kodi_type, )))
def url_not_yet_cached(self, url): def add_episode(self, *args):
""" self.cursor.execute(
Returns True if url has not yet been cached to the Kodi texture cache '''
""" INSERT INTO episode(
self.cursor.execute('SELECT url FROM texture WHERE url == ? LIMIT 1', idEpisode,
(url, )) idFile,
return self.cursor.fetchone() is None 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): def add_show(self, *args):
""" self.cursor.execute(
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 INSERT INTO tvshow(
you want to poll ('video' or 'music') idShow,
Returns None, <kodi_type> if not possible c00,
""" c01,
kodi_id = None c04,
path = utils.try_decode(path) c05,
try: c08,
filename = path.rsplit('/', 1)[1] c09,
path = path.rsplit('/', 1)[0] + '/' c12,
except IndexError: c13,
filename = path.rsplit('\\', 1)[1] c14,
path = path.rsplit('\\', 1)[0] + '\\' c15)
if kodi_type == v.KODI_TYPE_SONG or db_type == 'music': VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
with GetKodiDB('music') as kodi_db: ''', (args))
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 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(): def add_showlinkpath(self, kodi_id, kodi_pathid):
""" self.cursor.execute('INSERT INTO tvshowlinkpath(idShow, idPath) VALUES (?, ?)',
Makes sure that we retain the Kodi standard databases. E.g. that there (kodi_id, kodi_pathid))
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 remove_show(self, kodi_id):
self.cursor.execute('DELETE FROM tvshow WHERE idShow = ?', (kodi_id,))
def reset_cached_images(): def remove_season(self, kodi_id):
LOG.info('Resetting cached artwork') self.cursor.execute('DELETE FROM seasons WHERE idSeason = ?',
# Remove all existing textures first (kodi_id,))
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_episode(self, kodi_id):
self.cursor.execute('DELETE FROM episode WHERE idEpisode = ?',
(kodi_id,))
def wipe_dbs(music=True): def new_movie_id(self):
""" self.cursor.execute('SELECT COALESCE(MAX(idMovie), 0) FROM movie')
Completely resets the Kodi databases 'video', 'texture' and 'music' (if return self.cursor.fetchone()[0] + 1
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 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 = { def remove_movie(self, kodi_id):
v.PLEX_TYPE_MOVIE: GetKodiDB('video'), self.cursor.execute('DELETE FROM movie WHERE idMovie = ?', (kodi_id,))
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')
}

View file

@ -12,7 +12,7 @@ import xbmc
from xbmcgui import Window from xbmcgui import Window
from .plex_db import PlexDB from .plex_db import PlexDB
from . import kodidb_functions as kodidb from . import kodi_db
from . import utils from . import utils
from . import plex_functions as PF from . import plex_functions as PF
from .downloadutils import DownloadUtils as DU from .downloadutils import DownloadUtils as DU
@ -302,7 +302,7 @@ class KodiMonitor(xbmc.Monitor):
plex_type = None plex_type = None
# If using direct paths and starting playback from a widget # If using direct paths and starting playback from a widget
if not kodi_id and kodi_type and path: 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: if kodi_id:
with PlexDB() as plexdb: with PlexDB() as plexdb:
db_item = plexdb.item_by_kodi_id(kodi_id, kodi_type) 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()) last_played = utils.unix_date_to_kodi(utils.unix_timestamp())
if playcount is None: if playcount is None:
LOG.debug('playcount not found, looking it up in the Kodi DB') LOG.debug('playcount not found, looking it up in the Kodi DB')
with kodidb.GetKodiDB('video') as kodi_db: with kodi_db.KodiVideoDB() as kodidb:
playcount = kodi_db.get_playcount(db_item['kodi_fileid']) playcount = kodidb.get_playcount(db_item['kodi_fileid'])
playcount = 0 if playcount is None else playcount playcount = 0 if playcount is None else playcount
if time < v.IGNORE_SECONDS_AT_START: if time < v.IGNORE_SECONDS_AT_START:
LOG.debug('Ignoring playback less than %s seconds', LOG.debug('Ignoring playback less than %s seconds',
@ -574,8 +574,8 @@ def _record_playstate(status, ended):
v.MARK_PLAYED_AT) v.MARK_PLAYED_AT)
playcount += 1 playcount += 1
time = 0 time = 0
with kodidb.GetKodiDB('video') as kodi_db: with kodi_db.KodiVideoDB() as kodidb:
kodi_db.set_resume(db_item['kodi_fileid'], kodidb.set_resume(db_item['kodi_fileid'],
time, time,
totaltime, totaltime,
playcount, playcount,
@ -600,9 +600,9 @@ def _clean_file_table():
""" """
LOG.debug('Start cleaning Kodi files table') LOG.debug('Start cleaning Kodi files table')
xbmc.sleep(2000) xbmc.sleep(2000)
with kodidb.GetKodiDB('video') as kodi_db_1: with kodi_db.KodiVideoDB() as kodidb_1:
with kodidb.GetKodiDB('video') as kodi_db_2: with kodi_db.KodiVideoDB() as kodidb_2:
for file_id in kodi_db_1.obsolete_file_ids(): for file_id in kodidb_1.obsolete_file_ids():
LOG.debug('Removing obsolete Kodi file_id %s', file_id) 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') LOG.debug('Done cleaning up Kodi file table')

View file

@ -6,7 +6,8 @@ import xbmc
from . import common from . import common
from ..plex_api import API from ..plex_api import API
from ..plex_db import PlexDB 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 from .. import itemtypes, plex_functions as PF, variables as v, state
@ -87,8 +88,8 @@ def process_fanart(plex_id, plex_type, refresh=False):
LOG.error('Could not get Kodi id for plex id %s', plex_id) LOG.error('Could not get Kodi id for plex id %s', plex_id)
return return
if not refresh: if not refresh:
with kodidb.GetKodiDB('video') as kodi_db: with KodiVideoDB() as kodidb:
artworks = kodi_db.get_art(db_item['kodi_id'], artworks = kodidb.get_art(db_item['kodi_id'],
db_item['kodi_type']) db_item['kodi_type'])
# Check if we even need to get additional art # Check if we even need to get additional art
for key in v.ALL_KODI_ARTWORK: for key in v.ALL_KODI_ARTWORK:
@ -117,8 +118,8 @@ def process_fanart(plex_id, plex_type, refresh=False):
if plex_type == v.PLEX_TYPE_MOVIE: if plex_type == v.PLEX_TYPE_MOVIE:
for _, setname in api.collection_list(): for _, setname in api.collection_list():
LOG.debug('Getting artwork for movie set %s', setname) LOG.debug('Getting artwork for movie set %s', setname)
with kodidb.GetKodiDB('video') as kodi_db: with KodiVideoDB() as kodidb:
setid = kodi_db.create_collection(setname) setid = kodidb.create_collection(setname)
external_set_artwork = api.set_artwork() external_set_artwork = api.set_artwork()
if external_set_artwork and PREFER_KODI_COLLECTION_ART: if external_set_artwork and PREFER_KODI_COLLECTION_ART:
kodi_artwork = api.artwork(kodi_id=setid, kodi_artwork = api.artwork(kodi_id=setid,

View file

@ -7,7 +7,7 @@ import copy
from . import common, videonodes from . import common, videonodes
from ..utils import cast from ..utils import cast
from ..plex_db import PlexDB from ..plex_db import PlexDB
from .. import kodidb_functions as kodidb from .. import kodi_db
from .. import itemtypes from .. import itemtypes
from .. import plex_functions as PF, music, utils, state, variables as v 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 # Backup old sections to delete them later, if needed (at the end
# of this method, only unused sections will be left in old_sections) # of this method, only unused sections will be left in old_sections)
old_sections = list(plexdb.section_ids()) old_sections = list(plexdb.section_ids())
with kodidb.GetKodiDB('video') as kodi_db: with kodi_db.KodiVideoDB() as kodidb:
for section in sections: for section in sections:
_process_section(section, _process_section(section,
kodi_db, kodidb,
plexdb, plexdb,
sorted_sections, sorted_sections,
old_sections, old_sections,
@ -77,7 +77,7 @@ def sync_from_pms():
return True return True
def _process_section(section_xml, kodi_db, plexdb, sorted_sections, def _process_section(section_xml, kodidb, plexdb, sorted_sections,
old_sections, totalnodes): old_sections, totalnodes):
folder = section_xml.attrib folder = section_xml.attrib
plex_type = folder['type'] plex_type = folder['type']
@ -101,7 +101,7 @@ def _process_section(section_xml, kodi_db, plexdb, sorted_sections,
current_tagid = section[3] current_tagid = section[3]
except TypeError: except TypeError:
LOG.info('Creating section id: %s in Plex database.', section_id) 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 # Create playlist for the video library
if (section_name not in playlists and if (section_name not in playlists and
plex_type in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)): plex_type in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW)):
@ -133,7 +133,7 @@ def _process_section(section_xml, kodi_db, plexdb, sorted_sections,
if current_sectionname != section_name: if current_sectionname != section_name:
LOG.info('section id: %s new sectionname: %s', LOG.info('section id: %s new sectionname: %s',
section_id, section_name) section_id, section_name)
tagid = kodi_db.create_tag(section_name) tagid = kodidb.create_tag(section_name)
# Update view with new info # Update view with new info
plexdb.add_section(section_id, plexdb.add_section(section_id,
@ -177,7 +177,7 @@ def _process_section(section_xml, kodi_db, plexdb, sorted_sections,
# Update items with new tag # Update items with new tag
for item in plexdb.kodi_id_by_section(section_id): for item in plexdb.kodi_id_by_section(section_id):
# Remove the "s" from viewtype for tags # Remove the "s" from viewtype for tags
kodi_db.update_tag( kodidb.update_tag(
current_tagid, tagid, item[0], current_sectiontype[:-1]) current_tagid, tagid, item[0], current_sectiontype[:-1])
else: else:
# Validate the playlist exists or recreate it # Validate the playlist exists or recreate it
@ -214,24 +214,25 @@ def delete_sections(old_sections):
with PlexDB() as plexdb: with PlexDB() as plexdb:
old_sections = [plexdb.section(x) for x in old_sections] old_sections = [plexdb.section(x) for x in old_sections]
LOG.info("Removing entire Plex library sections: %s", 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: 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 video_library_update = True
context = itemtypes.Movie(plexdb=plexdb, context = itemtypes.Movie(plexdb=plexdb,
kodi_db=kodi_db) kodidb=kodidb)
elif section[2] == v.KODI_TYPE_SHOW: elif section[2] == v.KODI_TYPE_SHOW:
video_library_update = True video_library_update = True
context = itemtypes.Show(plexdb=plexdb, context = itemtypes.Show(plexdb=plexdb,
kodi_db=kodi_db) kodidb=kodidb)
elif section[2] == v.KODI_TYPE_ARTIST: with kodi_db.KodiMusicDB() as kodidb:
for section in old_sections:
if section[2] == v.KODI_TYPE_ARTIST:
music_library_update = True music_library_update = True
context = itemtypes.Artist(plexdb=plexdb, context = itemtypes.Artist(plexdb=plexdb,
kodi_db=kodi_db) kodidb=kodidb)
elif section[2] == v.KODI_TYPE_PHOTO:
# not synced
plexdb.remove_section(section[0])
continue
for plex_id in plexdb.plexid_by_section(section[0]): for plex_id in plexdb.plexid_by_section(section[0]):
context.remove(plex_id) context.remove(plex_id)
# Only remove Plex entry if we've removed all items first # Only remove Plex entry if we've removed all items first

View file

@ -8,7 +8,7 @@ from .full_sync import PLAYLIST_SYNC_ENABLED
from .fanart import SYNC_FANART, FanartTask from .fanart import SYNC_FANART, FanartTask
from ..plex_api import API from ..plex_api import API
from ..plex_db import PlexDB 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 backgroundthread, playlists, plex_functions as PF, itemtypes
from .. import artwork, utils, variables as v, state 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) LOG.error('Could not retrieve Plex db info for %s', plex_id)
return return
kodi_id, kodi_type = item['kodi_id'], item['kodi_type'] kodi_id, kodi_type = item['kodi_id'], item['kodi_type']
with kodidb.KODIDB_FROM_PLEXTYPE[plex_type] as kodi_db: with kodi_db.KODIDB_FROM_PLEXTYPE[plex_type] as kodidb:
for url in kodi_db.art_urls(kodi_id, kodi_type): for url in kodidb.art_urls(kodi_id, kodi_type):
artwork.cache_url(url) artwork.cache_url(url)

View file

@ -13,7 +13,7 @@ from .plex_db import PlexDB
from . import plex_functions as PF from . import plex_functions as PF
from . import utils from . import utils
from .downloadutils import DownloadUtils as DU 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 playlist_func as PL
from . import playqueue as PQ from . import playqueue as PQ
from . import json_rpc as js from . import json_rpc as js
@ -429,8 +429,8 @@ def _conclude_playback(playqueue, pos):
with PlexDB() as plexdb: with PlexDB() as plexdb:
db_item = plexdb.item_by_id(item.plex_id, item.plex_type) db_item = plexdb.item_by_id(item.plex_id, item.plex_type)
file_id = db_item['kodi_fileid'] if db_item else None file_id = db_item['kodi_fileid'] if db_item else None
with kodidb.GetKodiDB('video') as kodi_db: with KodiVideoDB() as kodidb:
item.offset = kodi_db.get_resume(file_id) item.offset = kodidb.get_resume(file_id)
LOG.info('Resuming playback at %s', item.offset) LOG.info('Resuming playback at %s', item.offset)
if v.KODIVERSION >= 18 and api: if v.KODIVERSION >= 18 and api:
# Kodi 18 Alpha 3 broke StartOffset # Kodi 18 Alpha 3 broke StartOffset

View file

@ -5,11 +5,11 @@ from logging import getLogger
from threading import Thread from threading import Thread
from urlparse import parse_qsl from urlparse import parse_qsl
from .kodi_db import KodiVideoDB
from . import playback from . import playback
from . import context_entry from . import context_entry
from . import json_rpc as js from . import json_rpc as js
from . import pickler from . import pickler
from . import kodidb_functions as kodidb
from . import state from . import state
############################################################################### ###############################################################################
@ -47,8 +47,8 @@ class PlaybackStarter(Thread):
resolve=resolve) resolve=resolve)
elif mode == 'navigation': elif mode == 'navigation':
# e.g. when plugin://...tvshows is called for entire season # e.g. when plugin://...tvshows is called for entire season
with kodidb.GetKodiDB('video') as kodi_db: with KodiVideoDB() as kodidb:
show_id = kodi_db.show_id_from_path(params.get('path')) show_id = kodidb.show_id_from_path(params.get('path'))
if show_id: if show_id:
js.activate_window('videos', js.activate_window('videos',
'videodb://tvshows/titles/%s' % show_id) 'videodb://tvshows/titles/%s' % show_id)

View file

@ -11,7 +11,7 @@ from urlparse import parse_qsl, urlsplit
from .plex_api import API from .plex_api import API
from .plex_db import PlexDB from .plex_db import PlexDB
from . import plex_functions as PF 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 .downloadutils import DownloadUtils as DU
from . import utils from . import utils
from . import json_rpc as js from . import json_rpc as js
@ -360,7 +360,7 @@ def verify_kodi_item(plex_id, kodi_item):
if (not state.DIRECT_PATHS and state.ENABLE_MUSIC and if (not state.DIRECT_PATHS and state.ENABLE_MUSIC and
kodi_item.get('type') == v.KODI_TYPE_SONG and kodi_item.get('type') == v.KODI_TYPE_SONG and
kodi_item['file'].startswith('http')): kodi_item['file'].startswith('http')):
kodi_item['id'], _ = kodidb.kodiid_from_filename(kodi_item['file'], kodi_item['id'], _ = kodiid_from_filename(kodi_item['file'],
v.KODI_TYPE_SONG) v.KODI_TYPE_SONG)
LOG.debug('Detected song. Research results: %s', kodi_item) LOG.debug('Detected song. Research results: %s', kodi_item)
return kodi_item return kodi_item
@ -372,15 +372,15 @@ def verify_kodi_item(plex_id, kodi_item):
raise PlaylistError raise PlaylistError
LOG.debug('Starting research for Kodi id since we didnt get one: %s', LOG.debug('Starting research for Kodi id since we didnt get one: %s',
kodi_item) kodi_item)
kodi_id, _ = kodidb.kodiid_from_filename(kodi_item['file'], kodi_id, _ = kodiid_from_filename(kodi_item['file'],
v.KODI_TYPE_MOVIE) v.KODI_TYPE_MOVIE)
kodi_item['type'] = v.KODI_TYPE_MOVIE kodi_item['type'] = v.KODI_TYPE_MOVIE
if kodi_id is None: if kodi_id is None:
kodi_id, _ = kodidb.kodiid_from_filename(kodi_item['file'], kodi_id, _ = kodiid_from_filename(kodi_item['file'],
v.KODI_TYPE_EPISODE) v.KODI_TYPE_EPISODE)
kodi_item['type'] = v.KODI_TYPE_EPISODE kodi_item['type'] = v.KODI_TYPE_EPISODE
if kodi_id is None: if kodi_id is None:
kodi_id, _ = kodidb.kodiid_from_filename(kodi_item['file'], kodi_id, _ = kodiid_from_filename(kodi_item['file'],
v.KODI_TYPE_SONG) v.KODI_TYPE_SONG)
kodi_item['type'] = v.KODI_TYPE_SONG kodi_item['type'] = v.KODI_TYPE_SONG
kodi_item['id'] = kodi_id kodi_item['id'] = kodi_id

View file

@ -9,7 +9,7 @@ from logging import getLogger
from .common import Playlist, PlaylistError from .common import Playlist, PlaylistError
from ..plex_db import PlexDB 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 from .. import path_ops, utils, variables as v
############################################################################### ###############################################################################
LOG = getLogger('PLEX.playlists.db') LOG = getLogger('PLEX.playlists.db')
@ -88,8 +88,8 @@ def m3u_to_plex_ids(playlist):
plex_ids.append(plex_id) plex_ids.append(plex_id)
else: else:
# Add-on paths not working, try direct # Add-on paths not working, try direct
kodi_id, kodi_type = kodidb.kodiid_from_filename( kodi_id, kodi_type = kodiid_from_filename(entry,
entry, db_type=playlist.kodi_type) db_type=playlist.kodi_type)
if not kodi_id: if not kodi_id:
continue continue
with PlexDB() as plexdb: with PlexDB() as plexdb:

View file

@ -38,13 +38,13 @@ from urlparse import parse_qsl
from xbmcgui import ListItem from xbmcgui import ListItem
from .plex_db import PlexDB from .plex_db import PlexDB
from .kodi_db import KodiVideoDB, KodiMusicDB
from .utils import cast from .utils import cast
from .downloadutils import DownloadUtils as DU from .downloadutils import DownloadUtils as DU
from . import clientinfo from . import clientinfo
from . import utils from . import utils
from . import path_ops from . import path_ops
from . import plex_functions as PF from . import plex_functions as PF
from . import kodidb_functions as kodidb
from . import variables as v from . import variables as v
from . import state from . import state
@ -939,13 +939,13 @@ class API(object):
else: else:
return artworks return artworks
# Grab artwork from the season # Grab artwork from the season
with kodidb.GetKodiDB('video') as kodi_db: with KodiVideoDB() as kodidb:
season_art = kodi_db.get_art(season_id, v.KODI_TYPE_SEASON) season_art = kodidb.get_art(season_id, v.KODI_TYPE_SEASON)
for kodi_art in season_art: for kodi_art in season_art:
artworks['season.%s' % kodi_art] = season_art[kodi_art] artworks['season.%s' % kodi_art] = season_art[kodi_art]
# Grab more artwork from the show # Grab more artwork from the show
with kodidb.GetKodiDB('video') as kodi_db: with KodiVideoDB() as kodidb:
show_art = kodi_db.get_art(show_id, v.KODI_TYPE_SHOW) show_art = kodidb.get_art(show_id, v.KODI_TYPE_SHOW)
for kodi_art in show_art: for kodi_art in show_art:
artworks['tvshow.%s' % kodi_art] = show_art[kodi_art] artworks['tvshow.%s' % kodi_art] = show_art[kodi_art]
return artworks return artworks
@ -953,11 +953,11 @@ class API(object):
if kodi_id: if kodi_id:
# in Kodi database, potentially with additional e.g. clearart # in Kodi database, potentially with additional e.g. clearart
if self.plex_type() in v.PLEX_VIDEOTYPES: if self.plex_type() in v.PLEX_VIDEOTYPES:
with kodidb.GetKodiDB('video') as kodi_db: with KodiVideoDB() as kodidb:
return kodi_db.get_art(kodi_id, kodi_type) return kodidb.get_art(kodi_id, kodi_type)
else: else:
with kodidb.GetKodiDB('music') as kodi_db: with KodiMusicDB() as kodidb:
return kodi_db.get_art(kodi_id, kodi_type) return kodidb.get_art(kodi_id, kodi_type)
# Grab artwork from Plex # Grab artwork from Plex
# if self.plex_type() == v.PLEX_TYPE_EPISODE: # if self.plex_type() == v.PLEX_TYPE_EPISODE:

View file

@ -7,7 +7,7 @@ import xbmc
from .downloadutils import DownloadUtils as DU from .downloadutils import DownloadUtils as DU
from . import library_sync from . import library_sync
from . import backgroundthread, utils, path_ops, artwork, variables as v, state 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') LOG = getLogger('PLEX.sync')
@ -118,7 +118,7 @@ class Sync(backgroundthread.KillableThread):
return return
# ask to reset all existing or not # ask to reset all existing or not
if utils.yesno_dialog('Image Texture Cache', utils.lang(39251)): if utils.yesno_dialog('Image Texture Cache', utils.lang(39251)):
kodidb.reset_cached_images() kodi_db.reset_cached_images()
self.start_image_cache_thread() self.start_image_cache_thread()
def on_library_scan_finished(self, successful): def on_library_scan_finished(self, successful):
@ -236,9 +236,9 @@ class Sync(backgroundthread.KillableThread):
plex_db.initialize() plex_db.initialize()
# Hack to speed up look-ups for actors (giant table!) # Hack to speed up look-ups for actors (giant table!)
utils.create_kodi_db_indicees() 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) # Setup the paths for addon-paths (even when using direct paths)
kodi_db.setup_path_table() kodidb.setup_path_table()
while not self.isCanceled(): while not self.isCanceled():
# In the event the server goes offline # In the event the server goes offline

View file

@ -526,8 +526,7 @@ def wipe_database():
delete_playlists() delete_playlists()
# Clean up the video nodes # Clean up the video nodes
delete_nodes() delete_nodes()
from . import kodidb_functions from . import kodi_db, plex_db
from . import plex_db
# First get the paths to all synced playlists # First get the paths to all synced playlists
playlist_paths = [] playlist_paths = []
try: try:
@ -545,7 +544,7 @@ def wipe_database():
# Plex DB completely empty yet. Wipe existing Kodi music only if we # Plex DB completely empty yet. Wipe existing Kodi music only if we
# expect to sync Plex music # expect to sync Plex music
music = settings('enableMusic') == 'true' music = settings('enableMusic') == 'true'
kodidb_functions.wipe_dbs(music) kodi_db.wipe_dbs(music)
plex_db.wipe() plex_db.wipe()
plex_db.initialize() plex_db.initialize()
# Delete all synced playlists # Delete all synced playlists
@ -558,7 +557,7 @@ def wipe_database():
LOG.info("Resetting all cached artwork.") LOG.info("Resetting all cached artwork.")
# Remove all cached artwork # Remove all cached artwork
kodidb_functions.reset_cached_images() kodi_db.reset_cached_images()
# reset the install run flag # reset the install run flag
settings('SyncInstallRunDone', value="false") settings('SyncInstallRunDone', value="false")
LOG.info('Wiping done') LOG.info('Wiping done')