Rewire llibrary sync, part 6

This commit is contained in:
croneter 2018-10-24 10:57:52 +02:00
parent 2f96749fc7
commit f520cebf66
11 changed files with 928 additions and 244 deletions

View file

@ -3,6 +3,18 @@
from __future__ import absolute_import, division, unicode_literals
from .movies import Movie
from .tvshows import Show, Season, Episode
from .music import Artist, Album, Song
from .. import variables as v
# Note: always use same order of URL arguments, NOT urlencode:
# plex_id=<plex_id>&plex_type=<plex_type>&mode=play
ITEMTYPE_FROM_PLEXTYPE = {
v.PLEX_TYPE_MOVIE: Movie,
v.PLEX_TYPE_SHOW: Show,
v.PLEX_TYPE_SEASON: Season,
v.PLEX_TYPE_EPISODE: Episode,
v.PLEX_TYPE_ARTIST: Artist,
v.PLEX_TYPE_ALBUM: Album,
v.PLEX_TYPE_SONG: Song
}

View file

@ -0,0 +1,544 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from .common import ItemBase, process_path
from ..plex_api import API
from .. import state, variables as v
LOG = getLogger('PLEX.music')
class MusicMixin(object):
def remove(self, plex_id, plex_type=None):
"""
Remove the entire TV shows object (show, season or episode) including
all associated entries from the Kodi DB.
"""
if plex_type is None:
entry = self.plex_db.episode(plex_id)
kodi_type = v.KODI_TYPE_EPISODE
if not entry:
entry = self.plex_db.season(plex_id)
kodi_type = v.KODI_TYPE_SEASON
if not entry:
entry = self.plex_db.show(plex_id)
kodi_type = v.KODI_TYPE_SHOW
else:
pass
if not entry:
LOG.debug('Cannot delete plex_id %s - not found in DB', plex_id)
return
kodi_id = entry[0]
file_id = entry[1]
parent_id = entry[3]
kodi_type = entry[4]
LOG.debug("Removing %s with kodi_id: %s file_id: %s parent_id: %s",
kodi_type, kodi_id, file_id, parent_id)
# Remove the plex reference
self.plex_db.removeItem(plex_id)
# EPISODE #####
if kodi_type == v.KODI_TYPE_EPISODE:
# Delete episode, verify season and tvshow
self.remove_episode(kodi_id, file_id)
# Season verification
season = self.plex_db.getItem_byKodiId(parent_id,
v.KODI_TYPE_SEASON)
if season is not None:
if not self.plex_db.getItem_byParentId(parent_id,
v.KODI_TYPE_EPISODE):
# No episode left for season - so delete the season
self.remove_season(parent_id)
self.plex_db.removeItem(season[0])
show = self.plex_db.getItem_byKodiId(season[1],
v.KODI_TYPE_SHOW)
if show is not None:
if not self.plex_db.getItem_byParentId(season[1],
v.KODI_TYPE_SEASON):
# No seasons for show left - so delete entire show
self.remove_show(season[1])
self.plex_db.removeItem(show[0])
else:
LOG.error('No show found in Plex DB for season %s', season)
else:
LOG.error('No season found in Plex DB!')
# SEASON #####
elif kodi_type == v.KODI_TYPE_SEASON:
# Remove episodes, season, verify tvshow
for episode in self.plex_db.getItem_byParentId(
kodi_id, v.KODI_TYPE_EPISODE):
self.remove_episode(episode[1], episode[2])
self.plex_db.removeItem(episode[0])
# Remove season
self.remove_season(kodi_id)
# Show verification
if not self.plex_db.getItem_byParentId(parent_id,
v.KODI_TYPE_SEASON):
# There's no other season left, delete the show
self.remove_show(parent_id)
self.plex_db.removeItem_byKodiId(parent_id, v.KODI_TYPE_SHOW)
# TVSHOW #####
elif kodi_type == v.KODI_TYPE_SHOW:
# Remove episodes, seasons and the tvshow itself
for season in self.plex_db.getItem_byParentId(kodi_id,
v.KODI_TYPE_SEASON):
for episode in self.plex_db.getItem_byParentId(
season[1], v.KODI_TYPE_EPISODE):
self.remove_episode(episode[1], episode[2])
self.plex_db.removeItem(episode[0])
self.remove_season(season[1])
self.plex_db.removeItem(season[0])
self.remove_show(kodi_id)
LOG.debug("Deleted %s %s from Kodi database", kodi_type, plex_id)
def remove_show(self, kodi_id):
"""
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)
self.artwork.delete_artwork(kodi_id,
v.KODI_TYPE_SHOW,
self.kodicursor)
self.kodicursor.execute("DELETE FROM tvshow WHERE idShow = ?",
(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)
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
"""
self.artwork.delete_artwork(kodi_id,
v.KODI_TYPE_SEASON,
self.kodicursor)
self.kodicursor.execute("DELETE FROM seasons WHERE idSeason = ?",
(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)
self.artwork.delete_artwork(kodi_id,
v.KODI_TYPE_EPISODE,
self.kodicursor)
self.kodicursor.execute("DELETE FROM episode WHERE idEpisode = ?",
(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)
LOG.debug("Removed episode: %s", kodi_id)
class Artist(ItemBase, MusicMixin):
"""
For Plex library-type artists
"""
def add_update(self, xml, section_name=None, section_id=None,
children=None):
"""
Process a single artist
"""
api = API(xml)
update_item = True
plex_id = api.plex_id()
LOG.debug('Adding show with plex_id %s', plex_id)
if not plex_id:
LOG.error("Cannot parse XML data for TV show: %s", xml.attrib)
return
show = self.plex_db.show(plex_id)
try:
kodi_id = show[3]
kodi_pathid = show[4]
except TypeError:
update_item = False
query = 'SELECT COALESCE(MAX(idShow), 0) FROM tvshow'
self.kodicursor.execute(query)
kodi_id = self.kodicursor.fetchone()[0] + 1
else:
# 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)
studios = api.music_studio_list()
studio = api.list_to_string(studios)
# GET THE FILE AND PATH #####
if state.DIRECT_PATHS:
# Direct paths is set the Kodi way
playurl = api.validate_playurl(api.tv_show_path(),
api.plex_type(),
folder=True)
if playurl is None:
return
path, toplevelpath = process_path(playurl)
toppathid = self.kodi_db.add_video_path(
toplevelpath,
content='tvshows',
scraper='metadata.local')
else:
# Set plugin path
toplevelpath = "plugin://%s.tvshows/" % v.ADDON_ID
path = "%s%s/" % (toplevelpath, plex_id)
# Do NOT set a parent id because addon-path cannot be "stacked"
toppathid = None
kodi_pathid = self.kodi_db.add_video_path(path,
date_added=api.date_created(),
id_parent_path=toppathid)
# UPDATE THE TVSHOW #####
if update_item:
LOG.info("UPDATE tvshow plex_id: %s - Title: %s",
plex_id, api.title())
# update new ratings Kodi 17
rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.update_ratings(kodi_id,
v.KODI_TYPE_SHOW,
"default",
api.audience_rating(),
api.votecount(),
rating_id)
# update new uniqueid Kodi 17
if api.provider('tvdb') is not None:
uniqueid = self.kodi_db.get_uniqueid(kodi_id,
v.KODI_TYPE_SHOW)
self.kodi_db.update_uniqueid(kodi_id,
v.KODI_TYPE_SHOW,
api.provider('tvdb'),
"unknown",
uniqueid)
else:
self.kodi_db.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))
# OR ADD THE TVSHOW #####
else:
LOG.info("ADD tvshow plex_id: %s - Title: %s",
plex_id, api.title())
# Link the path
query = "INSERT INTO tvshowlinkpath(idShow, idPath) values (?, ?)"
self.kodicursor.execute(query, (kodi_id, kodi_pathid))
rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
self.kodi_db.add_ratings(rating_id,
kodi_id,
v.KODI_TYPE_SHOW,
"default",
api.audience_rating(),
api.votecount())
if api.provider('tvdb') is not None:
uniqueid = self.kodi_db.get_uniqueid(kodi_id,
v.KODI_TYPE_SHOW)
self.kodi_db.add_uniqueid(uniqueid,
kodi_id,
v.KODI_TYPE_SHOW,
api.provider('tvdb'),
"unknown")
else:
uniqueid = -1
# Create the tvshow entry
query = '''
INSERT INTO tvshow(
idShow, c00, c01, c04, c05, c08, c09, c12, c13, c14,
c15)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
'''
self.kodicursor.execute(
query, (kodi_id, api.title(), api.plot(), rating_id,
api.premiere_date(), genre, api.title(), uniqueid,
api.content_rating(), studio, api.sorttitle()))
self.kodi_db.modify_people(kodi_id,
v.KODI_TYPE_SHOW,
api.people_list())
self.kodi_db.modify_genres(kodi_id, v.KODI_TYPE_SHOW, genres)
self.artwork.modify_artwork(api.artwork(),
kodi_id,
v.KODI_TYPE_SHOW,
self.kodicursor)
# Process studios
self.kodi_db.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.plex_db.add_show(plex_id=plex_id,
checksum=api.checksum(),
section_id=section_id,
kodi_id=kodi_id,
kodi_pathid=kodi_pathid,
last_sync=self.last_sync)
class Album(ItemBase, MusicMixin):
def add_update(self, xml, section_name=None, section_id=None,
children=None):
"""
Process a single album
"""
api = API(xml)
plex_id = api.plex_id()
LOG.debug('Adding season with plex_id %s', plex_id)
if not plex_id:
LOG.error('Error getting plex_id for season, skipping: %s',
xml.attrib)
return
show_id = api.parent_plex_id()
show = self.plex_db.show(show_id)
try:
parent_id = show[3]
except TypeError:
LOG.error('Could not find parent tv show for season %s. '
'Skipping season for now.', plex_id)
return
kodi_id = self.kodi_db.add_season(parent_id, api.season_number())
self.artwork.modify_artwork(api.artwork(),
kodi_id,
v.KODI_TYPE_SEASON,
self.kodicursor)
self.plex_db.add_season(plex_id=plex_id,
checksum=api.checksum(),
section_id=section_id,
show_id=show_id,
parent_id=parent_id,
kodi_id=kodi_id,
last_sync=self.last_sync)
class Song(ItemBase, MusicMixin):
def add_update(self, xml, section_name=None, section_id=None,
children=None):
"""
Process single song/track
"""
api = API(xml)
update_item = True
plex_id = api.plex_id()
LOG.debug('Adding episode with plex_id %s', plex_id)
if not plex_id:
LOG.error('Error getting plex_id for episode, skipping: %s',
xml.attrib)
return
entry = self.plex_db.item_by_id(plex_id)
try:
kodi_id = entry[0]
old_kodi_fileid = entry[1]
kodi_pathid = entry[2]
except TypeError:
update_item = False
query = 'SELECT COALESCE(MAX(idEpisode), 0) FROM episode'
self.kodicursor.execute(query)
kodi_id = self.kodicursor.fetchone()[0] + 1
else:
# 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'])
writer = api.list_to_string(peoples['Writer'])
userdata = api.userdata()
show_id, season_id, _, season_no, episode_no = api.episode_data()
if season_no is None:
season_no = -1
if episode_no is None:
episode_no = -1
airs_before_season = "-1"
airs_before_episode = "-1"
show = self.plex_db.show(show_id)
try:
grandparent_id = show[3]
except TypeError:
LOG.error("Parent tvshow now found, skip item")
return False
parent_id = self.kodi_db.add_season(grandparent_id, season_no)
# GET THE FILE AND PATH #####
do_indirect = not state.DIRECT_PATHS
if state.DIRECT_PATHS:
playurl = api.file_path(force_first_media=True)
if playurl is None:
do_indirect = True
else:
playurl = api.validate_playurl(playurl, v.PLEX_TYPE_EPISODE)
if "\\" in playurl:
# Local path
filename = playurl.rsplit("\\", 1)[1]
else:
# 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(
path, id_parent_path=parent_path_id)
if do_indirect:
# Set plugin path - do NOT use "intermediate" paths for the show
# as with direct paths!
filename = api.file_name(force_first_media=True)
path = 'plugin://%s.tvshows/%s/' % (v.ADDON_ID, show_id)
filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s'
% (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)
# add/retrieve kodi_pathid and fileid
# if the path or file already exists, the calls return current value
kodi_fileid = self.kodi_db.add_file(filename,
kodi_pathid,
api.date_created())
# UPDATE THE EPISODE #####
if update_item:
LOG.info("UPDATE episode plex_id: %s, Title: %s",
plex_id, api.title())
if kodi_fileid != old_kodi_fileid:
self.kodi_db.remove_file(old_kodi_fileid)
ratingid = self.kodi_db.get_ratingid(kodi_id,
v.KODI_TYPE_EPISODE)
self.kodi_db.update_ratings(kodi_id,
v.KODI_TYPE_EPISODE,
"default",
userdata['Rating'],
api.votecount(),
ratingid)
# update new uniqueid Kodi 17
uniqueid = self.kodi_db.get_uniqueid(kodi_id,
v.KODI_TYPE_EPISODE)
self.kodi_db.update_uniqueid(kodi_id,
v.KODI_TYPE_EPISODE,
api.provider('tvdb'),
"tvdb",
uniqueid)
query = '''
UPDATE episode
SET c00 = ?, c01 = ?, c03 = ?, c04 = ?, c05 = ?, c09 = ?,
c10 = ?, c12 = ?, c13 = ?, c14 = ?, c15 = ?, c16 = ?,
c18 = ?, c19 = ?, idFile=?, idSeason = ?,
userrating = ?
WHERE idEpisode = ?
'''
self.kodicursor.execute(
query, (api.title(), api.plot(), ratingid, writer,
api.premiere_date(), api.runtime(), director, season_no,
episode_no, api.title(), airs_before_season,
airs_before_episode, playurl, kodi_pathid, kodi_fileid,
parent_id, userdata['UserRating'], kodi_id))
# OR ADD THE EPISODE #####
else:
LOG.info("ADD episode plex_id: %s - Title: %s",
plex_id, api.title())
# Create the episode entry
rating_id = self.kodi_db.get_ratingid(kodi_id,
v.KODI_TYPE_EPISODE)
self.kodi_db.add_ratings(rating_id,
kodi_id,
v.KODI_TYPE_EPISODE,
"default",
userdata['Rating'],
api.votecount())
# add new uniqueid Kodi 17
uniqueid = self.kodi_db.get_uniqueid(kodi_id,
v.KODI_TYPE_EPISODE)
self.kodi_db.add_uniqueid(uniqueid,
kodi_id,
v.KODI_TYPE_EPISODE,
api.provider('tvdb'),
"tvdb")
query = '''
INSERT INTO episode( idEpisode, idFile, c00, c01, c03, c04,
c05, c09, c10, c12, c13, c14, idShow, c15, c16, c18,
c19, idSeason, userrating)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,
?, ?)
'''
self.kodicursor.execute(
query, (kodi_id, kodi_fileid, api.title(), api.plot(), rating_id,
writer, api.premiere_date(), api.runtime(), director,
season_no, episode_no, api.title(), grandparent_id,
airs_before_season, airs_before_episode, playurl,
kodi_pathid, parent_id, userdata['UserRating']))
self.kodi_db.modify_people(kodi_id,
v.KODI_TYPE_EPISODE,
api.people_list())
self.artwork.modify_artwork(api.artwork(),
kodi_id,
v.KODI_TYPE_EPISODE,
self.kodicursor)
streams = api.mediastreams()
self.kodi_db.modify_streams(kodi_fileid, streams, api.runtime())
self.kodi_db.set_resume(kodi_fileid,
api.resume_point(),
api.runtime(),
userdata['PlayCount'],
userdata['LastPlayedDate'],
None) # Do send None, we check here
if not state.DIRECT_PATHS:
# need to set a SECOND file entry for a path without plex show id
filename = api.file_name(force_first_media=True)
path = 'plugin://%s.tvshows/' % v.ADDON_ID
# Filename is exactly the same, WITH plex show id!
filename = ('%s%s/?plex_id=%s&plex_type=%s&mode=play&filename=%s'
% (path, show_id, plex_id, v.PLEX_TYPE_EPISODE,
filename))
kodi_pathid = self.kodi_db.add_video_path(path)
kodi_fileid = self.kodi_db.add_file(filename,
kodi_pathid,
api.date_created())
self.kodi_db.set_resume(kodi_fileid,
api.resume_point(),
api.runtime(),
userdata['PlayCount'],
userdata['LastPlayedDate'],
None) # Do send None - 2nd entry
self.plex_db.add_episode(plex_id=plex_id,
checksum=api.checksum(),
section_id=section_id,
show_id=show_id,
grandparent_id=grandparent_id,
season_id=season_id,
parent_id=parent_id,
kodi_id=kodi_id,
kodi_fileid=kodi_fileid,
kodi_pathid=kodi_pathid,
last_sync=self.last_sync)

View file

@ -16,84 +16,59 @@ class TvShowMixin(object):
Remove the entire TV shows object (show, season or episode) including
all associated entries from the Kodi DB.
"""
if plex_type is None:
entry = self.plex_db.episode(plex_id)
kodi_type = v.KODI_TYPE_EPISODE
if not entry:
entry = self.plex_db.season(plex_id)
kodi_type = v.KODI_TYPE_SEASON
if not entry:
entry = self.plex_db.show(plex_id)
kodi_type = v.KODI_TYPE_SHOW
else:
pass
if not entry:
db_item = self.plex_db.item_by_id(plex_id, plex_type)
if not db_item:
LOG.debug('Cannot delete plex_id %s - not found in DB', plex_id)
return
kodi_id = entry[0]
file_id = entry[1]
parent_id = entry[3]
kodi_type = entry[4]
LOG.debug("Removing %s with kodi_id: %s file_id: %s parent_id: %s",
kodi_type, kodi_id, file_id, parent_id)
LOG.debug('Removing %s %s with kodi_id: %s',
db_item['plex_type'], plex_id, db_item['kodi_id'])
# Remove the plex reference
self.plex_db.removeItem(plex_id)
self.plex_db.remove(plex_id, db_item['plex_type'])
# EPISODE #####
if kodi_type == v.KODI_TYPE_EPISODE:
if db_item['plex_type'] == v.PLEX_TYPE_EPISODE:
# Delete episode, verify season and tvshow
self.remove_episode(kodi_id, file_id)
self.remove_episode(db_item['kodi_id'], db_item['kodi_fileid'])
# Season verification
season = self.plex_db.getItem_byKodiId(parent_id,
v.KODI_TYPE_SEASON)
if season is not None:
if not self.plex_db.getItem_byParentId(parent_id,
v.KODI_TYPE_EPISODE):
# No episode left for season - so delete the season
self.remove_season(parent_id)
self.plex_db.removeItem(season[0])
show = self.plex_db.getItem_byKodiId(season[1],
v.KODI_TYPE_SHOW)
if show is not None:
if not self.plex_db.getItem_byParentId(season[1],
v.KODI_TYPE_SEASON):
# No seasons for show left - so delete entire show
self.remove_show(season[1])
self.plex_db.removeItem(show[0])
else:
LOG.error('No show found in Plex DB for season %s', season)
else:
LOG.error('No season found in Plex DB!')
# SEASON #####
elif kodi_type == v.KODI_TYPE_SEASON:
# Remove episodes, season, verify tvshow
for episode in self.plex_db.getItem_byParentId(
kodi_id, v.KODI_TYPE_EPISODE):
self.remove_episode(episode[1], episode[2])
self.plex_db.removeItem(episode[0])
# Remove season
self.remove_season(kodi_id)
if not self.plex_db.season_has_episodes(db_item['season_id']):
# No episode left for this season - so delete the season
self.remove_season(db_item['parent_id'])
self.plex_db.remove(db_item['season_id'], v.PLEX_TYPE_SEASON)
# Show verification
if not self.plex_db.getItem_byParentId(parent_id,
v.KODI_TYPE_SEASON):
# There's no other season left, delete the show
self.remove_show(parent_id)
self.plex_db.removeItem_byKodiId(parent_id, v.KODI_TYPE_SHOW)
if (not self.plex_db.show_has_seasons(db_item['show_id']) and
not self.plex_db.show_has_episodes(db_item['show_id'])):
# No seasons for show left - so delete entire show
self.remove_show(db_item['grandparent_id'])
self.plex_db.remove(db_item['show_id'], v.PLEX_TYPE_SHOW)
# SEASON #####
elif db_item['plex_type'] == v.PLEX_TYPE_SEASON:
# Remove episodes, season, verify tvshow
for episode in self.plex_db.episode_by_season(db_item['plex_id']):
self.remove_episode(episode['kodi_id'], episode['kodi_fileid'])
self.plex_db.remove(episode['plex_id'], v.PLEX_TYPE_EPISODE)
# Remove season
self.remove_season(db_item['kodi_id'])
# Show verification
if (not self.plex_db.show_has_seasons(db_item['show_id']) and
not self.plex_db.show_has_episodes(db_item['show_id'])):
# There's no other season or episode left, delete the show
self.remove_show(db_item['parent_id'])
self.plex_db.remove(db_item['show_id'], v.KODI_TYPE_SHOW)
# TVSHOW #####
elif kodi_type == v.KODI_TYPE_SHOW:
elif db_item['plex_type'] == v.PLEX_TYPE_SHOW:
# Remove episodes, seasons and the tvshow itself
for season in self.plex_db.getItem_byParentId(kodi_id,
v.KODI_TYPE_SEASON):
for episode in self.plex_db.getItem_byParentId(
season[1], v.KODI_TYPE_EPISODE):
self.remove_episode(episode[1], episode[2])
self.plex_db.removeItem(episode[0])
self.remove_season(season[1])
self.plex_db.removeItem(season[0])
self.remove_show(kodi_id)
for episode in self.plex_db.episode_by_show(db_item['plex_id']):
self.remove_episode(episode['kodi_id'],
episode['kodi_fileid'])
self.plex_db.remove(episode['plex_id'], v.PLEX_TYPE_EPISODE)
for season in self.plex_db.season_by_show(db_item['plex_id']):
self.remove_season(season['kodi_id'])
self.plex_db.remove(season['plex_id'], v.PLEX_TYPE_SEASON)
self.remove_show(db_item['kodi_id'])
LOG.debug("Deleted %s %s from Kodi database", kodi_type, plex_id)
LOG.debug('Deleted %s %s from all databases',
db_item['plex_type'], db_item['plex_id'])
def remove_show(self, kodi_id):
"""

View file

@ -3,3 +3,4 @@ from __future__ import absolute_import, division, unicode_literals
from .full_sync import start, PLAYLIST_SYNC_ENABLED
from .time import sync_pms_time
from .common import update_kodi_library

View file

@ -1,26 +1,23 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from threading import Thread
from Queue import Empty
import xbmc
from ..plex_api import API
from .. import utils, plexdb_functions as plexdb, kodidb_functions as kodidb
from ..plex_db import PlexDB
from .. import backgroundthread
from ..backgroundthread.Queue import Empty
from .. import utils, kodidb_functions as kodidb
from .. import itemtypes, artwork, plex_functions as PF, variables as v, state
###############################################################################
LOG = getLogger("PLEX." + __name__)
LOG = getLogger('PLEX.library_sync.fanart')
###############################################################################
@utils.thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
'DB_SCAN',
'STOP_SYNC',
'SUSPEND_SYNC'])
class ThreadedProcessFanart(Thread):
class ThreadedProcessFanart(backgroundthread.KillableThread):
"""
Threaded download of additional fanart in the background
@ -36,27 +33,57 @@ class ThreadedProcessFanart(Thread):
"""
def __init__(self, queue):
self.queue = queue
Thread.__init__(self)
super(ThreadedProcessFanart, self).__init__()
def isCanceled(self):
return xbmc.abortRequested or state.STOP_PKC
def isSuspended(self):
return (state.SUSPEND_LIBRARY_THREAD or
state.DB_SCAN or
state.STOP_SYNC or
state.SUSPEND_SYNC)
def run(self):
LOG.info('---===### Starting FanartSync ###===---')
try:
self._run()
except:
utils.ERROR(txt='FanartSync crashed', notify=True)
raise
LOG.info('---===### Stopping FanartSync ###===---')
def _run(self):
"""
Do the work
"""
LOG.debug("---===### Starting FanartSync ###===---")
while not self.stopped():
# First run through our already synced items in the Plex DB
for plex_type in (v.PLEX_TYPE_MOVIE, v.PLEX_TYPE_SHOW):
with PlexDB() as plexdb:
for plex_id in plexdb.fanart(plex_type):
if self.isCanceled():
break
while self.isSuspended():
if self.isCanceled():
break
xbmc.sleep(1000)
process_item(plexdb, {'plex_id': plex_id,
'plex_type': plex_type,
'refresh': False})
# Then keep checking the queue for new items
while not self.isCanceled():
# In the event the server goes offline
while self.suspended():
while self.isSuspended():
# Set in service.py
if self.stopped():
# Abort was requested while waiting. We should exit
LOG.debug("---===### Stopped FanartSync ###===---")
if self.isCanceled():
return
xbmc.sleep(1000)
# grabs Plex item from queue
try:
item = self.queue.get(block=False)
except Empty:
xbmc.sleep(200)
xbmc.sleep(1000)
continue
self.queue.task_done()
if isinstance(item, artwork.ArtworkSyncMessage):
@ -68,26 +95,23 @@ class ThreadedProcessFanart(Thread):
sound=False)
continue
LOG.debug('Get additional fanart for Plex id %s', item['plex_id'])
_process(item)
LOG.debug("---===### Stopped FanartSync ###===---")
with PlexDB() as plexdb:
process_item(plexdb, item)
def _process(item):
def process_item(plexdb, item):
done = False
try:
artworks = None
with plexdb.Get_Plex_DB() as plex_db:
db_item = plex_db.getItem_byId(item['plex_id'])
try:
kodi_id = db_item[0]
kodi_type = db_item[4]
except TypeError:
db_item = plexdb.item_by_id(item['plex_id'], item['plex_type'])
if not db_item:
LOG.error('Could not get Kodi id for plex id %s, abort getfanart',
item['plex_id'])
return
if item['refresh'] is False:
with kodidb.GetKodiDB('video') as kodi_db:
artworks = kodi_db.get_art(kodi_id, kodi_type)
artworks = kodi_db.get_art(db_item['kodi_id'],
db_item['kodi_type'])
# Check if we even need to get additional art
for key in v.ALL_KODI_ARTWORK:
if key not in artworks:
@ -111,9 +135,10 @@ def _process(item):
artworks = api.artwork()
# Get additional missing artwork from fanart artwork sites
artworks = api.fanart_artwork(artworks)
with getattr(itemtypes,
v.ITEMTYPE_FROM_PLEXTYPE[item['plex_type']])() as itm:
itm.set_fanart(artworks, kodi_id, kodi_type)
with itemtypes.ITEMTYPE_FROM_PLEXTYPE[item['plex_type']] as context:
context.set_fanart(artworks,
db_item['kodi_id'],
db_item['kodi_type'])
# Additional fanart for sets/collections
if api.plex_type() == v.PLEX_TYPE_MOVIE:
for _, setname in api.collection_list():
@ -128,14 +153,13 @@ def _process(item):
for art in kodi_artwork:
if art in external_set_artwork:
del external_set_artwork[art]
with itemtypes.Movies() as movie_db:
movie_db.artwork.modify_artwork(external_set_artwork,
setid,
v.KODI_TYPE_SET,
movie_db.kodicursor)
with itemtypes.Movie() as movie:
movie.artwork.modify_artwork(external_set_artwork,
setid,
v.KODI_TYPE_SET,
movie.kodicursor)
done = True
finally:
if done is True:
LOG.debug('Done getting fanart for Plex id %s', item['plex_id'])
with plexdb.Get_Plex_DB() as plex_db:
plex_db.set_fanart_synched(item['plex_id'])
plexdb.set_fanart_synced(item['plex_id'], item['plex_type'])

View file

@ -38,6 +38,38 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
self.processing_thread = None
super(FullSync, self).__init__()
def plex_update_watched(self, viewId, item_class, lastViewedAt=None,
updatedAt=None):
"""
YET to implement
Updates plex elements' view status ('watched' or 'unwatched') and
also updates resume times.
This is done by downloading one XML for ALL elements with viewId
"""
if self.new_items_only is False:
# Only do this once for fullsync: the first run where new items are
# added to Kodi
return
xml = PF.GetAllPlexLeaves(viewId,
lastViewedAt=lastViewedAt,
updatedAt=updatedAt)
# Return if there are no items in PMS reply - it's faster
try:
xml[0].attrib
except (TypeError, AttributeError, IndexError):
LOG.error('Error updating watch status. Could not get viewId: '
'%s of item_class %s with lastViewedAt: %s, updatedAt: '
'%s', viewId, item_class, lastViewedAt, updatedAt)
return
if item_class in ('Movies', 'TVShows'):
self.update_kodi_video_library = True
elif item_class == 'Music':
self.update_kodi_music_library = True
with getattr(itemtypes, item_class)() as itemtype:
itemtype.updateUserdata(xml)
def process_item(self, xml_item):
"""
Processes a single library item
@ -94,7 +126,8 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
self.context,
utils.cast(int, iterator.get('totalSize', 0)),
utils.cast(unicode, iterator.get('librarySectionTitle')),
section['id'])
section['id'],
utils.cast(unicode, section['viewGroup']))
self.queue.put(queue_info)
for xml_item in iterator:
if self.isCanceled():

View file

@ -4,7 +4,7 @@ from logging import getLogger
import xbmcgui
from . import common
from .. import utils, backgroundthread
from .. import backgroundthread, utils, variables as v
LOG = getLogger('PLEX.library_sync.process_metadata')
@ -17,11 +17,12 @@ class InitNewSection(object):
context: itemtypes.Movie, itemtypes.Episode, etc.
"""
def __init__(self, context, total_number_of_items, section_name,
section_id):
section_id, plex_type):
self.context = context
self.total = total_number_of_items
self.name = section_name
self.id = section_id
self.plex_type = plex_type
class ProcessMetadata(backgroundthread.KillableThread, common.libsync_mixin):
@ -59,6 +60,8 @@ class ProcessMetadata(backgroundthread.KillableThread, common.libsync_mixin):
self.section_name,
'%s/%s: %s'
% (self.current, self.total, self.title))
common.update_kodi_library(video=self.is_video,
music=not self.is_video)
def run(self):
"""
@ -80,6 +83,7 @@ class ProcessMetadata(backgroundthread.KillableThread, common.libsync_mixin):
self.current = 0
self.total = section.total
self.section_name = section.name
self.is_video = section.plex_type in v.PLEX_VIDEO_TYPES
with section.context(self.last_sync) as context:
while self.isCanceled() is False:
# grabs item from queue. This will block!

View file

@ -1,74 +0,0 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from threading import Thread, Lock
from xbmc import sleep
from xbmcgui import DialogProgressBG
from .. import utils
###############################################################################
LOG = getLogger("PLEX." + __name__)
GET_METADATA_COUNT = 0
PROCESS_METADATA_COUNT = 0
PROCESSING_VIEW_NAME = ''
LOCK = Lock()
###############################################################################
@utils.thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
'STOP_SYNC',
'SUSPEND_SYNC'])
class ThreadedShowSyncInfo(Thread):
"""
Threaded class to show the Kodi statusbar of the metadata download.
Input:
total: Total number of items to get
item_type:
"""
def __init__(self, total, item_type):
self.total = total
self.item_type = item_type
Thread.__init__(self)
def run(self):
"""
Do the work
"""
LOG.debug('Show sync info thread started')
# cache local variables because it's faster
total = self.total
dialog = DialogProgressBG('dialoglogProgressBG')
dialog.create("%s %s: %s %s"
% (utils.lang(39714),
self.item_type,
unicode(total),
utils.lang(39715)))
total = 2 * total
total_progress = 0
while not self.stopped():
with LOCK:
get_progress = GET_METADATA_COUNT
process_progress = PROCESS_METADATA_COUNT
view_name = PROCESSING_VIEW_NAME
total_progress = get_progress + process_progress
try:
percentage = int(float(total_progress) / float(total) * 100.0)
except ZeroDivisionError:
percentage = 0
dialog.update(percentage,
message="%s %s. %s %s: %s"
% (get_progress,
utils.lang(39712),
process_progress,
utils.lang(39713),
view_name))
# Sleep for x milliseconds
sleep(200)
dialog.close()
LOG.debug('Show sync info thread terminated')

View file

@ -3,6 +3,7 @@
from __future__ import absolute_import, division, unicode_literals
from logging import getLogger
from random import shuffle
import Queue
import xbmc
from . import library_sync
@ -39,8 +40,12 @@ class LibrarySync(backgroundthread.KillableThread):
self.session_keys = {}
self.sync_successful = False
self.last_full_sync = 0
self.fanartqueue = Queue.Queue()
self.fanartthread = fanart.ThreadedProcessFanart(self.fanartqueue)
if utils.settings('FanartTV') == 'true':
self.fanartqueue = Queue.Queue()
self.fanartthread = library_sync.fanart.ThreadedProcessFanart(self.fanartqueue)
else:
self.fanartqueue = None
self.fanartthread = None
# How long should we wait at least to process new/changed PMS items?
# Show sync dialog even if user deactivated?
self.force_dialog = False
@ -92,36 +97,6 @@ class LibrarySync(backgroundthread.KillableThread):
message=message,
icon='{error}')
def plex_update_watched(self, viewId, item_class, lastViewedAt=None,
updatedAt=None):
"""
Updates plex elements' view status ('watched' or 'unwatched') and
also updates resume times.
This is done by downloading one XML for ALL elements with viewId
"""
if self.new_items_only is False:
# Only do this once for fullsync: the first run where new items are
# added to Kodi
return
xml = PF.GetAllPlexLeaves(viewId,
lastViewedAt=lastViewedAt,
updatedAt=updatedAt)
# Return if there are no items in PMS reply - it's faster
try:
xml[0].attrib
except (TypeError, AttributeError, IndexError):
LOG.error('Error updating watch status. Could not get viewId: '
'%s of item_class %s with lastViewedAt: %s, updatedAt: '
'%s', viewId, item_class, lastViewedAt, updatedAt)
return
if item_class in ('Movies', 'TVShows'):
self.update_kodi_video_library = True
elif item_class == 'Music':
self.update_kodi_music_library = True
with getattr(itemtypes, item_class)() as itemtype:
itemtype.updateUserdata(xml)
def process_message(self, message):
"""
processes json.loads() messages from websocket. Triage what we need to
@ -207,8 +182,8 @@ class LibrarySync(backgroundthread.KillableThread):
delete_list)
# Let Kodi know of the change
if self.update_kodi_video_library or self.update_kodi_music_library:
update_library(video=self.update_kodi_video_library,
music=self.update_kodi_music_library)
library_sync.update_library(video=self.update_kodi_video_library,
music=self.update_kodi_music_library)
self.update_kodi_video_library = False
self.update_kodi_music_library = False

View file

@ -2,12 +2,12 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, unicode_literals
from . import utils
from . import utils, variables as v
class PlexDBBase(object):
"""
Methods used for all types of items
Plex database methods used for all types of items
"""
def __init__(self, cursor=None):
# Allows us to use this class with a cursor instead of context mgr
@ -22,6 +22,111 @@ class PlexDBBase(object):
self.plexconn.commit()
self.plexconn.close()
def item_by_id(self, plex_id, plex_type=None):
"""
Returns the following dict or None if not found
{
plex_id
plex_type
kodi_id
kodi_type
}
for plex_id. Supply with the correct plex_type to speed up lookup
"""
answ = None
if plex_type == v.PLEX_TYPE_MOVIE:
entry = self.movie(plex_id)
if entry:
answ = self.entry_to_movie(entry)
elif plex_type == v.PLEX_TYPE_EPISODE:
entry = self.episode(plex_id)
if entry:
answ = self.entry_to_episode(entry)
elif plex_type == v.PLEX_TYPE_SHOW:
entry = self.show(plex_id)
if entry:
answ = self.entry_to_show(entry)
elif plex_type == v.PLEX_TYPE_SEASON:
entry = self.season(plex_id)
if entry:
answ = self.entry_to_season(entry)
else:
# SLOW - lookup plex_id in all our tables
for kind in (v.PLEX_TYPE_MOVIE,
v.PLEX_TYPE_SHOW,
v.PLEX_TYPE_EPISODE,
v.PLEX_TYPE_SEASON):
method = getattr(self, kind)
entry = method(plex_id)
if entry:
method = getattr(self, 'entry_to_%s' % kind)
answ = method(entry)
break
return answ
@staticmethod
def entry_to_movie(entry):
return {
'plex_type': v.PLEX_TYPE_MOVIE,
'kodi_type': v.KODI_TYPE_MOVIE,
'plex_id': entry[0],
'checksum': entry[1],
'section_id': entry[2],
'kodi_id': entry[3],
'kodi_fileid': entry[4],
'kodi_pathid': entry[5],
'fanart_synced': entry[6],
'last_sync': entry[7]
}
@staticmethod
def entry_to_episode(entry):
return {
'plex_type': v.PLEX_TYPE_EPISODE,
'kodi_type': v.KODI_TYPE_EPISODE,
'plex_id': entry[0],
'checksum': entry[1],
'section_id': entry[2],
'show_id': entry[3],
'grandparent_id': entry[4],
'season_id': entry[5],
'parent_id': entry[6],
'kodi_id': entry[7],
'kodi_fileid': entry[8],
'kodi_pathid': entry[9],
'fanart_synced': entry[10],
'last_sync': entry[11]
}
@staticmethod
def entry_to_show(entry):
return {
'plex_type': v.PLEX_TYPE_SHOW,
'kodi_type': v.KODI_TYPE_SHOW,
'plex_id': entry[0],
'checksum': entry[1],
'section_id': entry[2],
'kodi_id': entry[3],
'kodi_pathid': entry[4],
'fanart_synced': entry[5],
'last_sync': entry[6]
}
@staticmethod
def entry_to_season(entry):
return {
'plex_type': v.PLEX_TYPE_SEASON,
'kodi_type': v.KODI_TYPE_SEASON,
'plex_id': entry[0],
'checksum': entry[1],
'section_id': entry[2],
'show_id': entry[3],
'parent_id': entry[4],
'kodi_id': entry[5],
'fanart_synced': entry[6],
'last_sync': entry[7]
}
def section_ids(self):
"""
Returns an iterator for section Plex ids for all sections
@ -118,14 +223,30 @@ class PlexDBBase(object):
query = 'DELETE FROM ? WHERE plex_id = ?' % plex_type
self.cursor.execute(query, (plex_id, ))
def fanart(self, plex_type):
"""
Returns an iterator for plex_type for all plex_id, where fanart_synced
has not yet been set to 1
"""
query = 'SELECT plex_id from %s WHERE fanart_synced = 0' % plex_type
self.cursor.execute(query)
return (x[0] for x in self.cursor)
def set_fanart_synced(self, plex_id, plex_type):
"""
Toggles fanart_synced to 1 for plex_id
"""
query = 'UPDATE %s SET fanart_synced = 1 WHERE plex_id = ?' % plex_type
self.cursor.execute(query, (plex_id, ))
def initialize():
"""
Run once during startup to verify that plex db exists.
Run once upon PKC startup to verify that plex db exists.
"""
with PlexDBBase() as plex_db:
with PlexDBBase() as plexdb:
# Create the tables for the plex database
plex_db.cursor.execute('''
plexdb.cursor.execute('''
CREATE TABLE IF NOT EXISTS sections(
section_id INTEGER PRIMARY KEY,
section_name TEXT,
@ -133,9 +254,9 @@ def initialize():
kodi_tagid INTEGER,
sync_to_kodi INTEGER)
''')
plex_db.cursor.execute('''
plexdb.cursor.execute('''
CREATE TABLE IF NOT EXISTS movie(
plex_id INTEGER PRIMARY KEY ASC,
plex_id INTEGER PRIMARY KEY,
checksum INTEGER UNIQUE,
section_id INTEGER,
kodi_id INTEGER,
@ -144,9 +265,9 @@ def initialize():
fanart_synced INTEGER,
last_sync INTEGER)
''')
plex_db.cursor.execute('''
plexdb.cursor.execute('''
CREATE TABLE IF NOT EXISTS show(
plex_id INTEGER PRIMARY KEY ASC,
plex_id INTEGER PRIMARY KEY,
checksum INTEGER UNIQUE,
section_id INTEGER,
kodi_id INTEGER,
@ -154,7 +275,7 @@ def initialize():
fanart_synced INTEGER,
last_sync INTEGER)
''')
plex_db.cursor.execute('''
plexdb.cursor.execute('''
CREATE TABLE IF NOT EXISTS season(
plex_id INTEGER PRIMARY KEY,
checksum INTEGER UNIQUE,
@ -165,7 +286,7 @@ def initialize():
fanart_synced INTEGER,
last_sync INTEGER)
''')
plex_db.cursor.execute('''
plexdb.cursor.execute('''
CREATE TABLE IF NOT EXISTS episode(
plex_id INTEGER PRIMARY KEY,
checksum INTEGER UNIQUE,
@ -180,16 +301,16 @@ def initialize():
fanart_synced INTEGER,
last_sync INTEGER)
''')
plex_db.cursor.execute('''
plexdb.cursor.execute('''
CREATE TABLE IF NOT EXISTS artist(
plex_id INTEGER PRIMARY KEY ASC,
plex_id INTEGER PRIMARY KEY,
checksum INTEGER UNIQUE,
section_id INTEGER,
kodi_id INTEGER,
fanart_synced INTEGER,
last_sync INTEGER)
''')
plex_db.cursor.execute('''
plexdb.cursor.execute('''
CREATE TABLE IF NOT EXISTS album(
plex_id INTEGER PRIMARY KEY,
checksum INTEGER UNIQUE,
@ -200,7 +321,7 @@ def initialize():
fanart_synced INTEGER,
last_sync INTEGER)
''')
plex_db.cursor.execute('''
plexdb.cursor.execute('''
CREATE TABLE IF NOT EXISTS track(
plex_id INTEGER PRIMARY KEY,
checksum INTEGER UNIQUE,
@ -215,17 +336,15 @@ def initialize():
fanart_synced INTEGER,
last_sync INTEGER)
''')
plex_db.cursor.execute('''
plexdb.cursor.execute('''
CREATE TABLE IF NOT EXISTS playlists(
plex_id INTEGER PRIMARY KEY ASC,
plex_id INTEGER PRIMARY KEY,
plex_name TEXT,
plex_updatedat INTEGER,
kodi_path TEXT,
kodi_type TEXT,
kodi_hash TEXT)
''')
# Create an index for actors to speed up sync
utils.create_actor_db_index()
def wipe():
@ -233,10 +352,10 @@ def wipe():
Completely resets the Plex database
"""
query = "SELECT name FROM sqlite_master WHERE type = 'table'"
with PlexDBBase() as plex_db:
plex_db.cursor.execute(query)
tables = plex_db.cursor.fetchall()
with PlexDBBase() as plexdb:
plexdb.cursor.execute(query)
tables = plexdb.cursor.fetchall()
tables = [i[0] for i in tables]
for table in tables:
delete_query = 'DELETE FROM %s' % table
plex_db.cursor.execute(delete_query)
plexdb.cursor.execute(delete_query)

View file

@ -11,8 +11,13 @@ class TVShows(object):
"""
query = '''
INSERT OR REPLACE INTO show(
plex_id, checksum, section_id, kodi_id, kodi_pathid,
fanart_synced, last_sync)
plex_id
checksum
section_id
kodi_id
kodi_pathid
fanart_synced
last_sync)
VALUES (?, ?, ?, ?, ?, ?, ?)
'''
self.plexcursor.execute(
@ -27,8 +32,14 @@ class TVShows(object):
"""
query = '''
INSERT OR REPLACE INTO season(
plex_id, checksum, section_id, show_id, parent_id,
kodi_id, fanart_synced, last_sync)
plex_id
checksum
section_id
show_id
parent_id
kodi_id
fanart_synced
last_sync)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
'''
self.plexcursor.execute(
@ -45,9 +56,18 @@ class TVShows(object):
"""
query = '''
INSERT OR REPLACE INTO episode(
plex_id, checksum, section_id, show_id, grandparent_id,
season_id, parent_id, kodi_id, kodi_fileid, kodi_pathid,
fanart_synced, last_sync)
plex_id
checksum
section_id
show_id
grandparent_id
season_id
parent_id
kodi_id
kodi_fileid
kodi_pathid
fanart_synced
last_sync)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
'''
self.plexcursor.execute(
@ -106,3 +126,54 @@ class TVShows(object):
self.cursor.execute('SELECT * FROM episode WHERE plex_id = ? LIMIT 1',
(plex_id, ))
return self.cursor.fetchone()
def season_has_episodes(self, plex_id):
"""
Returns True if there are episodes left for the season with plex_id
"""
self.cursor.execute('SELECT plex_id FROM episode WHERE season_id = ? LIMIT 1',
(plex_id, ))
return self.cursor.fetchone() is not None
def show_has_seasons(self, plex_id):
"""
Returns True if there are seasons left for the show with plex_id
"""
self.cursor.execute('SELECT plex_id FROM season WHERE show_id = ? LIMIT 1',
(plex_id, ))
return self.cursor.fetchone() is not None
def show_has_episodes(self, plex_id):
"""
Returns True if there are episodes left for the show with plex_id
"""
self.cursor.execute('SELECT plex_id FROM episode WHERE show_id = ? LIMIT 1',
(plex_id, ))
return self.cursor.fetchone() is not None
def episode_by_season(self, plex_id):
"""
Returns an iterator for all episodes that have a parent season_id with
a value of plex_id
"""
self.cursor.execute('SELECT * FROM episode WHERE season_id = ?',
(plex_id, ))
return (self.entry_to_episode(x) for x in self.cursor)
def episode_by_show(self, plex_id):
"""
Returns an iterator for all episodes that have a grandparent show_id
with a value of plex_id
"""
self.cursor.execute('SELECT * FROM episode WHERE show_id = ?',
(plex_id, ))
return (self.entry_to_episode(x) for x in self.cursor)
def season_by_show(self, plex_id):
"""
Returns an iterator for all seasons that have a parent show_id
with a value of plex_id
"""
self.cursor.execute('SELECT * FROM season WHERE show_id = ?',
(plex_id, ))
return (self.entry_to_season(x) for x in self.cursor)