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 xbmc
from . import backgroundthread, path_ops, utils
from .kodi_db import KodiVideoDB, KodiMusicDB, KodiTextureDB
from . import backgroundthread, utils
from . import state
###############################################################################
LOG = getLogger('PLEX.artwork')
# Disable annoying requests warnings
@ -26,8 +26,6 @@ IMAGE_CACHING_SUSPENDS = [
if not utils.settings('imageSyncDuringPlayback') == 'true':
IMAGE_CACHING_SUSPENDS.append(state.SUSPEND_SYNC)
###############################################################################
def double_urlencode(text):
return quote_plus(quote_plus(text))
@ -53,18 +51,16 @@ class ImageCachingThread(backgroundthread.KillableThread):
@staticmethod
def _art_url_generator():
from . import kodidb_functions as kodidb
for kind in ('video', 'music'):
with kodidb.GetKodiDB(kind) as kodi_db:
for kind in (KodiVideoDB, KodiMusicDB):
with kind() as kodidb:
for kodi_type in ('poster', 'fanart'):
for url in kodi_db.artwork_generator(kodi_type):
for url in kodidb.artwork_generator(kodi_type):
yield url
def missing_art_cache_generator(self):
from . import kodidb_functions as kodidb
with kodidb.GetKodiDB('texture') as kodi_db:
with KodiTextureDB() as kodidb:
for url in self._art_url_generator():
if kodi_db.url_not_yet_cached(url):
if kodidb.url_not_yet_cached(url):
yield url
def run(self):
@ -126,74 +122,3 @@ def cache_url(url):
break
# We did not even get a timeout
break
def modify_artwork(artworks, kodi_id, kodi_type, cursor):
"""
Pass in an artworks dict (see PlexAPI) to set an items artwork.
"""
for kodi_art, url in artworks.iteritems():
modify_art(url, kodi_id, kodi_type, kodi_art, cursor)
def modify_art(url, kodi_id, kodi_type, kodi_art, cursor):
"""
Adds or modifies the artwork of kind kodi_art (e.g. 'poster') in the
Kodi art table for item kodi_id/kodi_type. Will also cache everything
except actor portraits.
"""
query = '''
SELECT url FROM art
WHERE media_id = ? AND media_type = ? AND type = ?
LIMIT 1
'''
cursor.execute(query, (kodi_id, kodi_type, kodi_art,))
try:
# Update the artwork
old_url = cursor.fetchone()[0]
except TypeError:
# Add the artwork
query = '''
INSERT INTO art(media_id, media_type, type, url)
VALUES (?, ?, ?, ?)
'''
cursor.execute(query, (kodi_id, kodi_type, kodi_art, url))
else:
if url == old_url:
# Only cache artwork if it changed
return
delete_cached_artwork(old_url)
query = '''
UPDATE art SET url = ?
WHERE media_id = ? AND media_type = ? AND type = ?
'''
cursor.execute(query, (url, kodi_id, kodi_type, kodi_art))
def delete_artwork(kodiId, mediaType, cursor):
cursor.execute('SELECT url FROM art WHERE media_id = ? AND media_type = ?',
(kodiId, mediaType, ))
for row in cursor.fetchall():
delete_cached_artwork(row[0])
def delete_cached_artwork(url):
"""
Deleted the cached artwork with path url (if it exists)
"""
from . import kodidb_functions as kodidb
with kodidb.GetKodiDB('texture') as kodi_db:
try:
kodi_db.cursor.execute("SELECT cachedurl FROM texture WHERE url=? LIMIT 1",
(url, ))
cachedurl = kodi_db.cursor.fetchone()[0]
except TypeError:
# Could not find cached url
pass
else:
# Delete thumbnail as well as the entry
path = path_ops.translate_path("special://thumbnails/%s"
% cachedurl)
if path_ops.exists(path):
path_ops.rmtree(path, ignore_errors=True)
kodi_db.cursor.execute("DELETE FROM texture WHERE url = ?", (url,))

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ from logging import getLogger
from .common import ItemBase, process_path
from ..plex_api import API
from .. import artwork, plex_functions as PF, state, variables as v
from .. import plex_functions as PF, state, variables as v
LOG = getLogger('PLEX.tvshows')
@ -74,38 +74,35 @@ class TvShowMixin(object):
"""
Remove a TV show, and only the show, no seasons or episodes
"""
self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW)
artwork.delete_artwork(kodi_id, v.KODI_TYPE_SHOW, self.kodicursor)
self.kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?",
(kodi_id,))
self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_SHOW)
self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_SHOW)
self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_SHOW)
self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_SHOW)
self.kodidb.remove_show(kodi_id)
if v.KODIVERSION >= 17:
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_SHOW)
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
self.kodidb.remove_ratings(kodi_id, v.KODI_TYPE_SHOW)
LOG.debug("Removed tvshow: %s", kodi_id)
def remove_season(self, kodi_id):
"""
Remove a season, and only a season, not the show or episodes
"""
artwork.delete_artwork(kodi_id, v.KODI_TYPE_SEASON, self.kodicursor)
self.kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?",
(kodi_id,))
self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_SEASON)
self.kodidb.remove_season(kodi_id)
LOG.debug("Removed season: %s", kodi_id)
def remove_episode(self, kodi_id, file_id):
"""
Remove an episode, and episode only from the Kodi DB (not Plex DB)
"""
self.kodi_db.modify_people(kodi_id, v.KODI_TYPE_EPISODE)
self.kodi_db.remove_file(file_id, plex_type=v.PLEX_TYPE_EPISODE)
artwork.delete_artwork(kodi_id, v.KODI_TYPE_EPISODE, self.kodicursor)
self.kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?",
(kodi_id,))
self.kodidb.modify_people(kodi_id, v.KODI_TYPE_EPISODE)
self.kodidb.remove_file(file_id, plex_type=v.PLEX_TYPE_EPISODE)
self.kodidb.delete_artwork(kodi_id, v.KODI_TYPE_EPISODE)
self.kodidb.remove_episode(kodi_id)
if v.KODIVERSION >= 17:
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE)
self.kodi_db.remove_ratings(kodi_id, v.KODI_TYPE_EPISODE)
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_EPISODE)
self.kodidb.remove_ratings(kodi_id, v.KODI_TYPE_EPISODE)
LOG.debug("Removed episode: %s", kodi_id)
@ -126,23 +123,11 @@ class Show(ItemBase, TvShowMixin):
show = self.plexdb.show(plex_id)
if not show:
update_item = False
query = 'SELECT COALESCE(MAX(idShow), 0) FROM tvshow'
self.kodicursor.execute(query)
kodi_id = self.kodicursor.fetchone()[0] + 1
kodi_id = self.kodidb.new_show_id()
else:
update_item = True
kodi_id = show['kodi_id']
kodi_pathid = show['kodi_pathid']
# Verification the item is still in Kodi
self.kodicursor.execute('SELECT * FROM tvshow WHERE idShow = ?',
(kodi_id,))
try:
self.kodicursor.fetchone()[0]
except TypeError:
# item is not found, let's recreate it.
update_item = False
LOG.info("idShow: %s missing from Kodi, repairing the entry.",
kodi_id)
genres = api.genre_list()
genre = api.list_to_string(genres)
@ -158,7 +143,7 @@ class Show(ItemBase, TvShowMixin):
if playurl is None:
return
path, toplevelpath = process_path(playurl)
toppathid = self.kodi_db.add_video_path(
toppathid = self.kodidb.add_video_path(
toplevelpath,
content='tvshows',
scraper='metadata.local')
@ -169,15 +154,15 @@ class Show(ItemBase, TvShowMixin):
# Do NOT set a parent id because addon-path cannot be "stacked"
toppathid = None
kodi_pathid = self.kodi_db.add_video_path(path,
kodi_pathid = self.kodidb.add_video_path(path,
date_added=api.date_created(),
id_parent_path=toppathid)
# UPDATE THE TVSHOW #####
if update_item:
LOG.info("UPDATE tvshow plex_id: %s - %s", plex_id, api.title())
# update new ratings Kodi 17
rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.update_ratings(kodi_id,
rating_id = self.kodidb.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
self.kodidb.update_ratings(kodi_id,
v.KODI_TYPE_SHOW,
"default",
api.audience_rating(),
@ -185,45 +170,44 @@ class Show(ItemBase, TvShowMixin):
rating_id)
# update new uniqueid Kodi 17
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)
self.kodi_db.update_uniqueid(kodi_id,
self.kodidb.update_uniqueid(kodi_id,
v.KODI_TYPE_SHOW,
api.provider('tvdb'),
"unknown",
uniqueid)
else:
self.kodi_db.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
self.kodidb.remove_uniqueid(kodi_id, v.KODI_TYPE_SHOW)
uniqueid = -1
# Update the tvshow entry
query = '''
UPDATE tvshow
SET c00 = ?, c01 = ?, c04 = ?, c05 = ?, c08 = ?, c09 = ?,
c12 = ?, c13 = ?, c14 = ?, c15 = ?
WHERE idShow = ?
'''
self.kodicursor.execute(
query, (api.title(), api.plot(), rating_id,
api.premiere_date(), genre, api.title(), uniqueid,
api.content_rating(), studio, api.sorttitle(),
kodi_id))
self.kodidb.update_show(api.title(),
api.plot(),
rating_id,
api.premiere_date(),
genre,
api.title(),
uniqueid,
api.content_rating(),
studio,
api.sorttitle(),
kodi_id)
# OR ADD THE TVSHOW #####
else:
LOG.info("ADD tvshow plex_id: %s - %s", plex_id, api.title())
# Link the path
query = "INSERT INTO tvshowlinkpath(idShow, idPath) values (?, ?)"
self.kodicursor.execute(query, (kodi_id, kodi_pathid))
rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.add_ratings(rating_id,
self.kodidb.add_showlinkpath(kodi_id, kodi_pathid)
rating_id = self.kodidb.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
self.kodidb.add_ratings(rating_id,
kodi_id,
v.KODI_TYPE_SHOW,
"default",
api.audience_rating(),
api.votecount())
if api.provider('tvdb') is not None:
uniqueid = self.kodi_db.get_uniqueid(kodi_id,
uniqueid = self.kodidb.get_uniqueid(kodi_id,
v.KODI_TYPE_SHOW)
self.kodi_db.add_uniqueid(uniqueid,
self.kodidb.add_uniqueid(uniqueid,
kodi_id,
v.KODI_TYPE_SHOW,
api.provider('tvdb'),
@ -231,31 +215,30 @@ class Show(ItemBase, TvShowMixin):
else:
uniqueid = -1
# Create the tvshow entry
query = '''
INSERT INTO tvshow(
idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14,
c15)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
'''
self.kodicursor.execute(
query, (kodi_id, api.title(), api.plot(), rating_id,
api.premiere_date(), genre, api.title(), uniqueid,
api.content_rating(), studio, api.sorttitle()))
self.kodi_db.modify_people(kodi_id,
self.kodidb.add_show(kodi_id,
api.title(),
api.plot(),
rating_id,
api.premiere_date(),
genre,
api.title(),
uniqueid,
api.content_rating(),
studio,
api.sorttitle())
self.kodidb.modify_people(kodi_id,
v.KODI_TYPE_SHOW,
api.people_list())
self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_SHOW, genres)
artwork.modify_artwork(api.artwork(),
self.kodidb.modify_genres(kodi_id, v.KODI_TYPE_SHOW, genres)
self.kodidb.modify_artwork(api.artwork(),
kodi_id,
v.KODI_TYPE_SHOW,
self.kodicursor)
v.KODI_TYPE_SHOW)
# Process studios
self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_SHOW, studios)
self.kodidb.modify_studios(kodi_id, v.KODI_TYPE_SHOW, studios)
# Process tags: view, PMS collection tags
tags = [section_name]
tags.extend([i for _, i in api.collection_list()])
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags)
self.kodidb.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags)
self.plexdb.add_show(plex_id=plex_id,
checksum=api.checksum(),
section_id=section_id,
@ -287,18 +270,17 @@ class Season(ItemBase, TvShowMixin):
except (TypeError, IndexError, AttributeError):
LOG.error("Parent tvshow %s xml download failed", show_id)
return False
Show(self.last_sync, plexdb=self.plexdb, kodi_db=self.kodi_db).add_update(
Show(self.last_sync, plexdb=self.plexdb, kodidb=self.kodidb).add_update(
show_xml[0], section_name, section_id)
show = self.plexdb.show(show_id)
if not show:
LOG.error('Still could not find parent tv show %s', show_id)
return
parent_id = show['kodi_id']
kodi_id = self.kodi_db.add_season(parent_id, api.season_number())
artwork.modify_artwork(api.artwork(),
kodi_id = self.kodidb.add_season(parent_id, api.season_number())
self.kodidb.modify_artwork(api.artwork(),
kodi_id,
v.KODI_TYPE_SEASON,
self.kodicursor)
v.KODI_TYPE_SEASON)
self.plexdb.add_season(plex_id=plex_id,
checksum=api.checksum(),
section_id=section_id,
@ -323,24 +305,12 @@ class Episode(ItemBase, TvShowMixin):
episode = self.plexdb.episode(plex_id)
if not episode:
update_item = False
query = 'SELECT COALESCE(MAX(idEpisode), 0) FROM episode'
self.kodicursor.execute(query)
kodi_id = self.kodicursor.fetchone()[0] + 1
kodi_id = self.kodidb.new_episode_id()
else:
update_item = True
kodi_id = episode['kodi_id']
old_kodi_fileid = episode['kodi_fileid']
kodi_pathid = episode['kodi_pathid']
# Verification the item is still in Kodi
query = 'SELECT * FROM episode WHERE idEpisode = ? LIMIT 1'
self.kodicursor.execute(query, (kodi_id, ))
try:
self.kodicursor.fetchone()[0]
except TypeError:
# item is not found, let's recreate it.
update_item = False
LOG.info('idEpisode %s missing from Kodi, repairing entry.',
kodi_id)
peoples = api.people()
director = api.list_to_string(peoples['Director'])
@ -365,7 +335,7 @@ class Episode(ItemBase, TvShowMixin):
except (TypeError, IndexError, AttributeError):
LOG.error("Grandparent tvshow %s xml download failed", show_id)
return False
Show(self.last_sync, plexdb=self.plexdb, kodi_db=self.kodi_db).add_update(
Show(self.last_sync, plexdb=self.plexdb, kodidb=self.kodidb).add_update(
show_xml[0], section_name, section_id)
show = self.plexdb.show(show_id)
if not show:
@ -383,7 +353,7 @@ class Episode(ItemBase, TvShowMixin):
except (TypeError, IndexError, AttributeError):
LOG.error("Parent season %s xml download failed", season_id)
return False
Season(self.last_sync, plexdb=self.plexdb, kodi_db=self.kodi_db).add_update(
Season(self.last_sync, plexdb=self.plexdb, kodidb=self.kodidb).add_update(
season_xml[0], section_name, section_id)
season = self.plexdb.season(season_id)
if not season:
@ -406,8 +376,8 @@ class Episode(ItemBase, TvShowMixin):
# Network share
filename = playurl.rsplit("/", 1)[1]
path = playurl.replace(filename, "")
parent_path_id = self.kodi_db.parent_path_id(path)
kodi_pathid = self.kodi_db.add_video_path(
parent_path_id = self.kodidb.parent_path_id(path)
kodi_pathid = self.kodidb.add_video_path(
path, id_parent_path=parent_path_id)
if do_indirect:
# Set plugin path - do NOT use "intermediate" paths for the show
@ -418,11 +388,11 @@ class Episode(ItemBase, TvShowMixin):
% (path, plex_id, v.PLEX_TYPE_EPISODE, filename))
playurl = filename
# Root path tvshows/ already saved in Kodi DB
kodi_pathid = self.kodi_db.add_video_path(path)
kodi_pathid = self.kodidb.add_video_path(path)
# add/retrieve kodi_pathid and fileid
# if the path or file already exists, the calls return current value
kodi_fileid = self.kodi_db.add_file(filename,
kodi_fileid = self.kodidb.add_file(filename,
kodi_pathid,
api.date_created())
@ -430,82 +400,91 @@ class Episode(ItemBase, TvShowMixin):
if update_item:
LOG.info("UPDATE episode plex_id: %s - %s", plex_id, api.title())
if kodi_fileid != old_kodi_fileid:
self.kodi_db.remove_file(old_kodi_fileid)
ratingid = self.kodi_db.get_ratingid(kodi_id,
self.kodidb.remove_file(old_kodi_fileid)
ratingid = self.kodidb.get_ratingid(kodi_id,
v.KODI_TYPE_EPISODE)
self.kodi_db.update_ratings(kodi_id,
self.kodidb.update_ratings(kodi_id,
v.KODI_TYPE_EPISODE,
"default",
userdata['Rating'],
api.votecount(),
ratingid)
# update new uniqueid Kodi 17
uniqueid = self.kodi_db.get_uniqueid(kodi_id,
uniqueid = self.kodidb.get_uniqueid(kodi_id,
v.KODI_TYPE_EPISODE)
self.kodi_db.update_uniqueid(kodi_id,
self.kodidb.update_uniqueid(kodi_id,
v.KODI_TYPE_EPISODE,
api.provider('tvdb'),
"tvdb",
uniqueid)
query = '''
UPDATE episode
SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?,
c10 = ?, c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?,
c18 = ?, c19 = ?, idFile=?, idSeason = ?,
userrating = ?
WHERE idEpisode = ?
'''
self.kodicursor.execute(
query, (api.title(), api.plot(), ratingid, writer,
api.premiere_date(), api.runtime(), director, season_no,
episode_no, api.title(), airs_before_season,
airs_before_episode, playurl, kodi_pathid, kodi_fileid,
parent_id, userdata['UserRating'], kodi_id))
self.kodidb.update_episode(api.title(),
api.plot(),
ratingid,
writer,
api.premiere_date(),
api.runtime(),
director,
season_no,
episode_no,
api.title(),
airs_before_season,
airs_before_episode,
playurl,
kodi_pathid,
kodi_fileid,
parent_id,
userdata['UserRating'],
kodi_id)
# OR ADD THE EPISODE #####
else:
LOG.info("ADD episode plex_id: %s - %s", plex_id, api.title())
# Create the episode entry
rating_id = self.kodi_db.get_ratingid(kodi_id,
rating_id = self.kodidb.get_ratingid(kodi_id,
v.KODI_TYPE_EPISODE)
self.kodi_db.add_ratings(rating_id,
self.kodidb.add_ratings(rating_id,
kodi_id,
v.KODI_TYPE_EPISODE,
"default",
userdata['Rating'],
api.votecount())
# add new uniqueid Kodi 17
uniqueid = self.kodi_db.get_uniqueid(kodi_id,
uniqueid = self.kodidb.get_uniqueid(kodi_id,
v.KODI_TYPE_EPISODE)
self.kodi_db.add_uniqueid(uniqueid,
self.kodidb.add_uniqueid(uniqueid,
kodi_id,
v.KODI_TYPE_EPISODE,
api.provider('tvdb'),
"tvdb")
query = '''
INSERT INTO episode( idEpisode, idFile, c00, c01, c03, c04,
c05, c09, c10, c12, c13, c14, idShow, c15, c16, c18,
c19, idSeason, userrating)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?)
'''
self.kodicursor.execute(
query, (kodi_id, kodi_fileid, api.title(), api.plot(), rating_id,
writer, api.premiere_date(), api.runtime(), director,
season_no, episode_no, api.title(), grandparent_id,
airs_before_season, airs_before_episode, playurl,
kodi_pathid, parent_id, userdata['UserRating']))
self.kodidb.add_episode(kodi_id,
kodi_fileid,
api.title(),
api.plot(),
rating_id,
writer,
api.premiere_date(),
api.runtime(),
director,
season_no,
episode_no,
api.title(),
grandparent_id,
airs_before_season,
airs_before_episode,
playurl,
kodi_pathid,
parent_id,
userdata['UserRating'])
self.kodi_db.modify_people(kodi_id,
self.kodidb.modify_people(kodi_id,
v.KODI_TYPE_EPISODE,
api.people_list())
artwork.modify_artwork(api.artwork(),
self.kodidb.modify_artwork(api.artwork(),
kodi_id,
v.KODI_TYPE_EPISODE,
self.kodicursor)
v.KODI_TYPE_EPISODE)
streams = api.mediastreams()
self.kodi_db.modify_streams(kodi_fileid, streams, api.runtime())
self.kodi_db.set_resume(kodi_fileid,
self.kodidb.modify_streams(kodi_fileid, streams, api.runtime())
self.kodidb.set_resume(kodi_fileid,
api.resume_point(),
api.runtime(),
userdata['PlayCount'],
@ -519,11 +498,11 @@ class Episode(ItemBase, TvShowMixin):
filename = ('%s%s/?plex_id=%s&plex_type=%s&mode=play&filename=%s'
% (path, show_id, plex_id, v.PLEX_TYPE_EPISODE,
filename))
kodi_pathid = self.kodi_db.add_video_path(path)
kodi_fileid = self.kodi_db.add_file(filename,
kodi_pathid = self.kodidb.add_video_path(path)
kodi_fileid = self.kodidb.add_file(filename,
kodi_pathid,
api.date_created())
self.kodi_db.set_resume(kodi_fileid,
self.kodidb.set_resume(kodi_fileid,
api.resume_point(),
api.runtime(),
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
# -*- coding: utf-8 -*-
"""
Connect to the Kodi databases (video and music) and operate on them
"""
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from sqlite3 import IntegrityError
from . import artwork, utils, variables as v, state, path_ops
from . import common
from .. import path_ops, utils, variables as v, state
###############################################################################
LOG = getLogger('PLEX.kodidb_functions')
LOG = getLogger('PLEX.kodi_db.video')
class GetKodiDB(object):
"""
Usage: with GetKodiDB(db_type) as kodi_db:
do stuff with kodi_db
Parameters:
db_type: DB to open: 'video', 'music', 'plex', 'texture'
On exiting "with" (no matter what), commits get automatically committed
and the db gets closed
"""
def __init__(self, db_type):
self.kodiconn = None
self.db_type = db_type
def __enter__(self):
self.kodiconn = utils.kodi_sql(self.db_type)
kodi_db = KodiDBMethods(self.kodiconn.cursor())
return kodi_db
def __exit__(self, typus, value, traceback):
self.kodiconn.commit()
self.kodiconn.close()
class KodiDBMethods(object):
"""
Best used indirectly with another Class GetKodiDB:
with GetKodiDB(db_type) as kodi_db:
kodi_db.method()
"""
def __init__(self, cursor):
self.cursor = cursor
class KodiVideoDB(common.KodiDBBase):
db_kind = 'video'
def setup_path_table(self):
"""
@ -155,28 +120,6 @@ class KodiDBMethods(object):
content, scraper, 1))
return pathid
def add_music_path(self, path, hash_string=None):
"""
Add the path (unicode) to the music DB, if it does not exist already.
Returns the path id
Set hash_string to something unicode to set the strHash attribute
"""
# SQL won't return existing paths otherwise
path = '' if path is None else path
self.cursor.execute('SELECT idPath FROM path WHERE strPath = ?',
(path,))
try:
pathid = self.cursor.fetchone()[0]
except TypeError:
self.cursor.execute("SELECT COALESCE(MAX(idPath),0) FROM path")
pathid = self.cursor.fetchone()[0] + 1
self.cursor.execute('''
INSERT INTO path(idPath, strPath, strHash)
VALUES (?, ?, ?)
''',
(pathid, path, hash_string))
return pathid
def get_path(self, path):
"""
Returns the idPath from the path table for path [unicode] or None
@ -443,7 +386,7 @@ class KodiDBMethods(object):
self.cursor.execute(query_actor_delete, (person[0],))
if kind == 'actor':
# Delete any associated artwork
artwork.delete_artwork(person[0], 'actor', self.cursor)
self.delete_artwork(person[0], 'actor')
# Save new people to Kodi DB by iterating over the remaining entries
if kind == 'actor':
query = 'INSERT INTO actor_link VALUES (?, ?, ?, ?, ?)'
@ -489,11 +432,10 @@ class KodiDBMethods(object):
'VALUES (?, ?)',
(actor_id, name))
if art_url:
artwork.modify_art(art_url,
self.modify_art(art_url,
actor_id,
'actor',
'thumb',
self.cursor)
'thumb')
return actor_id
def get_art(self, kodi_id, kodi_type):
@ -610,26 +552,6 @@ class KodiDBMethods(object):
return
return movie_id, typus
def music_id_from_filename(self, filename, path):
"""
Returns the Kodi song_id from the Kodi music database or None if not
found OR something went wrong.
"""
self.cursor.execute('SELECT idPath FROM path WHERE strPath = ?',
(path,))
path_ids = self.cursor.fetchall()
if len(path_ids) != 1:
LOG.debug('Found wrong number of path ids: %s for path %s, abort',
path_ids, path)
return
self.cursor.execute('SELECT idSong FROM song WHERE strFileName = ? AND idPath = ?',
(filename, path_ids[0][0]))
song_ids = self.cursor.fetchall()
if len(song_ids) != 1:
LOG.info('Found wrong number of songs %s, abort', song_ids)
return
return song_ids[0][0]
def get_resume(self, file_id):
"""
Returns the first resume point in seconds (int) if found, else None for
@ -803,208 +725,6 @@ class KodiDBMethods(object):
''', (seasonid, showid, seasonnumber))
return seasonid
def add_artist(self, name, musicbrainz):
"""
Adds a single artist's name to the db
"""
self.cursor.execute('''
SELECT idArtist, strArtist
FROM artist
WHERE strMusicBrainzArtistID = ?
''', (musicbrainz, ))
try:
result = self.cursor.fetchone()
artistid = result[0]
artistname = result[1]
except TypeError:
self.cursor.execute('SELECT idArtist FROM artist WHERE strArtist = ? COLLATE NOCASE',
(name, ))
try:
artistid = self.cursor.fetchone()[0]
except TypeError:
# Krypton has a dummy first entry idArtist: 1 strArtist:
# [Missing Tag] strMusicBrainzArtistID: Artist Tag Missing
self.cursor.execute('SELECT COALESCE(MAX(idArtist),1) FROM artist')
artistid = self.cursor.fetchone()[0] + 1
self.cursor.execute('''
INSERT INTO artist(
idArtist,
strArtist,
strMusicBrainzArtistID)
VALUES (?, ?, ?)
''', (artistid, name, musicbrainz))
else:
if artistname != name:
self.cursor.execute('UPDATE artist SET strArtist = ? WHERE idArtist = ?',
(name, artistid,))
return artistid
def delete_song_from_song_artist(self, song_id):
"""
Deletes son from song_artist table and possibly orphaned roles
"""
self.cursor.execute('SELECT idArtist, idRole FROM song_artist WHERE idSong = ? LIMIT 1',
(song_id, ))
artist = self.cursor.fetchone()
if artist is None:
# No entry to begin with
return
# Delete the entry
self.cursor.execute('DELETE FROM song_artist WHERE idSong = ?',
(song_id, ))
# Check whether we need to delete orphaned roles
self.cursor.execute('SELECT idRole FROM song_artist WHERE idRole = ? LIMIT 1',
(artist[1], ))
if not self.cursor.fetchone():
# Delete orphaned role
self.cursor.execute('DELETE FROM role WHERE idRole = ?',
(artist[1], ))
def delete_album_from_discography(self, album_id):
"""
Removes the album with id album_id from the table discography
"""
# Need to get the album name as a string first!
self.cursor.execute('SELECT strAlbum, iYear FROM album WHERE idAlbum = ? LIMIT 1',
(album_id, ))
try:
name, year = self.cursor.fetchone()
except TypeError:
return
self.cursor.execute('SELECT idArtist FROM album_artist WHERE idAlbum = ? LIMIT 1',
(album_id, ))
artist = self.cursor.fetchone()
if not artist:
return
self.cursor.execute('DELETE FROM discography WHERE idArtist = ? AND strAlbum = ? AND strYear = ?',
(artist[0], name, year))
def delete_song_from_song_genre(self, song_id):
"""
Deletes the one entry with id song_id from the song_genre table.
Will also delete orphaned genres from genre table
"""
self.cursor.execute('SELECT idGenre FROM song_genre WHERE idSong = ?',
(song_id, ))
genres = self.cursor.fetchall()
self.cursor.execute('DELETE FROM song_genre WHERE idSong = ?',
(song_id, ))
# Check for orphaned genres in both song_genre and album_genre tables
for genre in genres:
self.cursor.execute('SELECT idGenre FROM song_genre WHERE idGenre = ? LIMIT 1',
(genre[0], ))
if not self.cursor.fetchone():
self.cursor.execute('SELECT idGenre FROM album_genre WHERE idGenre = ? LIMIT 1',
(genre[0], ))
if not self.cursor.fetchone():
self.cursor.execute('DELETE FROM genre WHERE idGenre = ?',
(genre[0], ))
def delete_album_from_album_genre(self, album_id):
"""
Deletes the one entry with id album_id from the album_genre table.
Will also delete orphaned genres from genre table
"""
self.cursor.execute('SELECT idGenre FROM album_genre WHERE idAlbum = ?',
(album_id, ))
genres = self.cursor.fetchall()
self.cursor.execute('DELETE FROM album_genre WHERE idAlbum = ?',
(album_id, ))
# Check for orphaned genres in both album_genre and song_genre tables
for genre in genres:
self.cursor.execute('SELECT idGenre FROM album_genre WHERE idGenre = ? LIMIT 1',
(genre[0], ))
if not self.cursor.fetchone():
self.cursor.execute('SELECT idGenre FROM song_genre WHERE idGenre = ? LIMIT 1',
(genre[0], ))
if not self.cursor.fetchone():
self.cursor.execute('DELETE FROM genre WHERE idGenre = ?',
(genre[0], ))
def add_album(self, name, musicbrainz):
"""
Adds a single album to the DB
"""
self.cursor.execute('SELECT idAlbum FROM album WHERE strMusicBrainzAlbumID = ?',
(musicbrainz, ))
try:
albumid = self.cursor.fetchone()[0]
except TypeError:
# Create the album
self.cursor.execute('SELECT COALESCE(MAX(idAlbum),0) FROM album')
albumid = self.cursor.fetchone()[0] + 1
self.cursor.execute('''
INSERT INTO album(
idAlbum,
strAlbum,
strMusicBrainzAlbumID,
strReleaseType)
VALUES (?, ?, ?, ?)
''', (albumid, name, musicbrainz, 'album'))
return albumid
def add_music_genres(self, kodiid, genres, mediatype):
"""
Adds a list of genres (list of unicode) for a certain Kodi item
"""
if mediatype == "album":
# Delete current genres for clean slate
self.cursor.execute('DELETE FROM album_genre WHERE idAlbum = ?',
(kodiid, ))
for genre in genres:
self.cursor.execute('SELECT idGenre FROM genre WHERE strGenre = ?',
(genre, ))
try:
genreid = self.cursor.fetchone()[0]
except TypeError:
# Create the genre
self.cursor.execute('SELECT COALESCE(MAX(idGenre),0) FROM genre')
genreid = self.cursor.fetchone()[0] + 1
self.cursor.execute('INSERT INTO genre(idGenre, strGenre) VALUES(?, ?)',
(genreid, genre))
self.cursor.execute('''
INSERT OR REPLACE INTO album_genre(
idGenre,
idAlbum)
VALUES (?, ?)
''', (genreid, kodiid))
elif mediatype == "song":
# Delete current genres for clean slate
self.cursor.execute('DELETE FROM song_genre WHERE idSong = ?',
(kodiid, ))
for genre in genres:
self.cursor.execute('SELECT idGenre FROM genre WHERE strGenre = ?',
(genre, ))
try:
genreid = self.cursor.fetchone()[0]
except TypeError:
# Create the genre
self.cursor.execute('SELECT COALESCE(MAX(idGenre),0) FROM genre')
genreid = self.cursor.fetchone()[0] + 1
self.cursor.execute('INSERT INTO genre(idGenre, strGenre) values(?, ?)',
(genreid, genre))
self.cursor.execute('''
INSERT OR REPLACE INTO song_genre(
idGenre,
idSong)
VALUES (?, ?)
''', (genreid, kodiid))
# Krypton only stuff ##############################
def update_userrating(self, kodi_id, kodi_type, userrating):
"""
Updates userrating for >=Krypton
"""
if kodi_type == v.KODI_TYPE_MOVIE:
identifier = 'idMovie'
elif kodi_type == v.KODI_TYPE_EPISODE:
identifier = 'idEpisode'
elif kodi_type == v.KODI_TYPE_SONG:
identifier = 'idSong'
self.cursor.execute('UPDATE %s SET userrating = ? WHERE ? = ?' % kodi_type,
(userrating, identifier, kodi_id))
def add_uniqueid(self, *args):
"""
Feed with:
@ -1103,141 +823,151 @@ class KodiDBMethods(object):
self.cursor.execute('DELETE FROM rating WHERE media_id = ? AND media_type = ?',
(kodi_id, kodi_type))
def art_urls(self, kodi_id, kodi_type):
return (x[0] for x in
self.cursor.execute('SELECT url FROM art WHERE media_id = ? AND media_type = ?',
(kodi_id, kodi_type)))
def new_show_id(self):
self.cursor.execute('SELECT COALESCE(MAX(idShow), 0) FROM tvshow')
return self.cursor.fetchone()[0] + 1
def artwork_generator(self, kodi_type):
"""
"""
return (x[0] for x in
self.cursor.execute('SELECT url FROM art WHERE type == ?',
(kodi_type, )))
def new_episode_id(self):
self.cursor.execute('SELECT COALESCE(MAX(idEpisode), 0) FROM episode')
return self.cursor.fetchone()[0] + 1
def url_not_yet_cached(self, url):
"""
Returns True if url has not yet been cached to the Kodi texture cache
"""
self.cursor.execute('SELECT url FROM texture WHERE url == ? LIMIT 1',
(url, ))
return self.cursor.fetchone() is None
def add_episode(self, *args):
self.cursor.execute(
'''
INSERT INTO episode(
idEpisode,
idFile,
c00,
c01,
c03,
c04,
c05,
c09,
c10,
c12,
c13,
c14,
idShow,
c15,
c16,
c18,
c19,
idSeason,
userrating)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
def update_episode(self, *args):
self.cursor.execute(
'''
UPDATE episode
SET c00 = ?,
c01 = ?,
c03 = ?,
c04 = ?,
c05 = ?,
c09 = ?,
c10 = ?,
c12 = ?,
c13 = ?,
c14 = ?,
c15 = ?,
c16 = ?,
c18 = ?,
c19 = ?,
idFile=?,
idSeason = ?,
userrating = ?
WHERE idEpisode = ?
''', (args))
def kodiid_from_filename(path, kodi_type=None, db_type=None):
"""
Returns kodi_id if we have an item in the Kodi video or audio database with
said path. Feed with either koditype, e.v. 'movie', 'song' or the DB
you want to poll ('video' or 'music')
Returns None, <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 GetKodiDB('music') as kodi_db:
try:
kodi_id = kodi_db.music_id_from_filename(filename, path)
except TypeError:
LOG.debug('No Kodi audio db element found for path %s', path)
else:
kodi_type = v.KODI_TYPE_SONG
else:
with GetKodiDB('video') as kodi_db:
try:
kodi_id, kodi_type = kodi_db.video_id_from_filename(filename,
path)
except TypeError:
LOG.debug('No kodi video db element found for path %s', path)
return kodi_id, kodi_type
def add_show(self, *args):
self.cursor.execute(
'''
INSERT INTO tvshow(
idShow,
c00,
c01,
c04,
c05,
c08,
c09,
c12,
c13,
c14,
c15)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (args))
def update_show(self, *args):
self.cursor.execute(
'''
UPDATE tvshow
SET c00 = ?,
c01 = ?,
c04 = ?,
c05 = ?,
c08 = ?,
c09 = ?,
c12 = ?,
c13 = ?,
c14 = ?,
c15 = ?
WHERE idShow = ?
''', (args))
def setup_kodi_default_entries():
"""
Makes sure that we retain the Kodi standard databases. E.g. that there
is a dummy artist with ID 1
"""
if utils.settings('enableMusic') == 'true':
with GetKodiDB('music') as kodi_db:
kodi_db.cursor.execute('''
INSERT OR REPLACE INTO artist(
idArtist,
strArtist,
strMusicBrainzArtistID)
VALUES (?, ?, ?)
''', (1, '[Missing Tag]', 'Artist Tag Missing'))
if v.KODIVERSION >= 18:
kodi_db.cursor.execute('''
INSERT OR REPLACE INTO versiontagscan(
idVersion,
iNeedsScan,
lastscanned)
VALUES (?, ?, ?)
''', (v.DB_MUSIC_VERSION[v.KODIVERSION],
0,
utils.unix_date_to_kodi(utils.unix_timestamp())))
def add_showlinkpath(self, kodi_id, kodi_pathid):
self.cursor.execute('INSERT INTO tvshowlinkpath(idShow, idPath) VALUES (?, ?)',
(kodi_id, kodi_pathid))
def remove_show(self, kodi_id):
self.cursor.execute('DELETE FROM tvshow WHERE idShow = ?', (kodi_id,))
def reset_cached_images():
LOG.info('Resetting cached artwork')
# Remove all existing textures first
path = path_ops.translate_path('special://thumbnails/')
if path_ops.exists(path):
path_ops.rmtree(path, ignore_errors=True)
paths = ('', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f',
'Video', 'plex')
for path in paths:
new_path = path_ops.translate_path('special://thumbnails/%s' % path)
path_ops.makedirs(path_ops.encode_path(new_path))
with GetKodiDB('texture') as kodi_db:
kodi_db.cursor.execute('SELECT tbl_name FROM sqlite_master WHERE type=?',
('table', ))
rows = kodi_db.cursor.fetchall()
for row in rows:
if row[0] != 'version':
kodi_db.cursor.execute("DELETE FROM %s" % row[0])
def remove_season(self, kodi_id):
self.cursor.execute('DELETE FROM seasons WHERE idSeason = ?',
(kodi_id,))
def remove_episode(self, kodi_id):
self.cursor.execute('DELETE FROM episode WHERE idEpisode = ?',
(kodi_id,))
def wipe_dbs(music=True):
"""
Completely resets the Kodi databases 'video', 'texture' and 'music' (if
music sync is enabled)
"""
LOG.warn('Wiping Kodi databases!')
kinds = ['video', 'texture']
if music:
kinds.append('music')
for db in kinds:
with GetKodiDB(db) as kodi_db:
kodi_db.cursor.execute("SELECT name FROM sqlite_master WHERE type = 'table'")
tables = kodi_db.cursor.fetchall()
tables = [i[0] for i in tables]
if 'version' in tables:
tables.remove('version')
if 'versiontagscan' in tables:
tables.remove('versiontagscan')
for table in tables:
kodi_db.cursor.execute('DELETE FROM %s' % table)
setup_kodi_default_entries()
# Make sure Kodi knows we wiped the databases
import xbmc
xbmc.executebuiltin('UpdateLibrary(video)')
if utils.settings('enableMusic') == 'true':
xbmc.executebuiltin('UpdateLibrary(music)')
def new_movie_id(self):
self.cursor.execute('SELECT COALESCE(MAX(idMovie), 0) FROM movie')
return self.cursor.fetchone()[0] + 1
def add_movie(self, *args):
self.cursor.execute(
'''
INSERT OR REPLACE INTO movie(
idMovie,
idFile,
c00,
c01,
c02,
c03,
c04,
c05,
c06,
c07,
c09,
c10,
c11,
c12,
c14,
c15,
c16,
c18,
c19,
c21,
c22,
c23,
premiered,
userrating)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?, ?, ?)
''', (args))
KODIDB_FROM_PLEXTYPE = {
v.PLEX_TYPE_MOVIE: GetKodiDB('video'),
v.PLEX_TYPE_SHOW: GetKodiDB('video'),
v.PLEX_TYPE_SEASON: GetKodiDB('video'),
v.PLEX_TYPE_EPISODE: GetKodiDB('video'),
v.PLEX_TYPE_ARTIST: GetKodiDB('music'),
v.PLEX_TYPE_ALBUM: GetKodiDB('music'),
v.PLEX_TYPE_SONG: GetKodiDB('music')
}
def remove_movie(self, kodi_id):
self.cursor.execute('DELETE FROM movie WHERE idMovie = ?', (kodi_id,))

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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