Rewire llibrary sync, part 3
This commit is contained in:
parent
3f4c43e373
commit
e935b7c97b
12 changed files with 564 additions and 155 deletions
|
@ -48,7 +48,7 @@ class ItemBase(object):
|
||||||
self.last_sync = last_sync
|
self.last_sync = last_sync
|
||||||
self.artwork = artwork.Artwork()
|
self.artwork = artwork.Artwork()
|
||||||
self.plexconn = None
|
self.plexconn = None
|
||||||
self.plexcursor = plex_db.plexcursor if plex_db else None
|
self.plexcursor = plex_db.cursor if plex_db else None
|
||||||
self.kodiconn = None
|
self.kodiconn = None
|
||||||
self.kodicursor = kodi_db.cursor if kodi_db else None
|
self.kodicursor = kodi_db.cursor if kodi_db else None
|
||||||
self.plex_db = plex_db
|
self.plex_db = plex_db
|
||||||
|
|
|
@ -11,13 +11,23 @@ LOG = getLogger('PLEX.tvshows')
|
||||||
|
|
||||||
|
|
||||||
class TvShowMixin(object):
|
class TvShowMixin(object):
|
||||||
def remove(self, plex_id):
|
def remove(self, plex_id, plex_type=None):
|
||||||
"""
|
"""
|
||||||
Remove the entire TV shows object (show, season or episode) including
|
Remove the entire TV shows object (show, season or episode) including
|
||||||
all associated entries from the Kodi DB.
|
all associated entries from the Kodi DB.
|
||||||
"""
|
"""
|
||||||
entry = self.plex_db.getItem_byId(plex_id)
|
if plex_type is None:
|
||||||
if entry 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)
|
LOG.debug('Cannot delete plex_id %s - not found in DB', plex_id)
|
||||||
return
|
return
|
||||||
kodi_id = entry[0]
|
kodi_id = entry[0]
|
||||||
|
@ -144,13 +154,12 @@ class Show(ItemBase, TvShowMixin):
|
||||||
plex_id = api.plex_id()
|
plex_id = api.plex_id()
|
||||||
LOG.debug('Adding show with plex_id %s', plex_id)
|
LOG.debug('Adding show with plex_id %s', plex_id)
|
||||||
if not plex_id:
|
if not plex_id:
|
||||||
LOG.error("Cannot parse XML data for TV show")
|
LOG.error("Cannot parse XML data for TV show: %s", xml.attrib)
|
||||||
return
|
return
|
||||||
update_item = True
|
show = self.plex_db.show(plex_id)
|
||||||
entry = self.plex_db.getItem_byId(plex_id)
|
|
||||||
try:
|
try:
|
||||||
kodi_id = entry[0]
|
kodi_id = show[3]
|
||||||
path_id = entry[2]
|
kodi_pathid = show[4]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
update_item = False
|
update_item = False
|
||||||
query = 'SELECT COALESCE(MAX(idShow), 0) FROM tvshow'
|
query = 'SELECT COALESCE(MAX(idShow), 0) FROM tvshow'
|
||||||
|
@ -158,8 +167,8 @@ class Show(ItemBase, TvShowMixin):
|
||||||
kodi_id = self.kodicursor.fetchone()[0] + 1
|
kodi_id = self.kodicursor.fetchone()[0] + 1
|
||||||
else:
|
else:
|
||||||
# Verification the item is still in Kodi
|
# Verification the item is still in Kodi
|
||||||
query = 'SELECT * FROM tvshow WHERE idShow = ?'
|
self.kodicursor.execute('SELECT * FROM tvshow WHERE idShow = ?',
|
||||||
self.kodicursor.execute(query, (kodi_id,))
|
(kodi_id,))
|
||||||
try:
|
try:
|
||||||
self.kodicursor.fetchone()[0]
|
self.kodicursor.fetchone()[0]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -193,7 +202,7 @@ class Show(ItemBase, TvShowMixin):
|
||||||
# Do NOT set a parent id because addon-path cannot be "stacked"
|
# Do NOT set a parent id because addon-path cannot be "stacked"
|
||||||
toppathid = None
|
toppathid = None
|
||||||
|
|
||||||
path_id = self.kodi_db.add_video_path(path,
|
kodi_pathid = self.kodi_db.add_video_path(path,
|
||||||
date_added=api.date_created(),
|
date_added=api.date_created(),
|
||||||
id_parent_path=toppathid)
|
id_parent_path=toppathid)
|
||||||
# UPDATE THE TVSHOW #####
|
# UPDATE THE TVSHOW #####
|
||||||
|
@ -238,7 +247,7 @@ class Show(ItemBase, TvShowMixin):
|
||||||
plex_id, api.title())
|
plex_id, api.title())
|
||||||
# Link the path
|
# Link the path
|
||||||
query = "INSERT INTO tvshowlinkpath(idShow, idPath) values (?, ?)"
|
query = "INSERT INTO tvshowlinkpath(idShow, idPath) values (?, ?)"
|
||||||
self.kodicursor.execute(query, (kodi_id, path_id))
|
self.kodicursor.execute(query, (kodi_id, kodi_pathid))
|
||||||
# Create the reference in plex table
|
# Create the reference in plex table
|
||||||
|
|
||||||
rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
|
rating_id = self.kodi_db.get_ratingid(kodi_id, v.KODI_TYPE_SHOW)
|
||||||
|
@ -284,14 +293,13 @@ class Show(ItemBase, TvShowMixin):
|
||||||
tags = [section_name]
|
tags = [section_name]
|
||||||
tags.extend([i for _, i in api.collection_list()])
|
tags.extend([i for _, i in api.collection_list()])
|
||||||
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags)
|
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags)
|
||||||
self.plex_db.addReference(plex_id,
|
self.plex_db.add_reference(plex_type=v.PLEX_TYPE_SHOW,
|
||||||
v.PLEX_TYPE_SHOW,
|
plex_id=plex_id,
|
||||||
kodi_id,
|
checksum=api.checksum(),
|
||||||
v.KODI_TYPE_SHOW,
|
section_id=section_id,
|
||||||
kodi_pathid=path_id,
|
kodi_id=kodi_id,
|
||||||
checksum=api.checksum(),
|
kodi_pathid=kodi_pathid,
|
||||||
view_id=section_id,
|
last_sync=self.last_sync)
|
||||||
last_sync=self.last_sync)
|
|
||||||
|
|
||||||
|
|
||||||
class Season(ItemBase, TvShowMixin):
|
class Season(ItemBase, TvShowMixin):
|
||||||
|
@ -304,34 +312,30 @@ class Season(ItemBase, TvShowMixin):
|
||||||
plex_id = api.plex_id()
|
plex_id = api.plex_id()
|
||||||
LOG.debug('Adding season with plex_id %s', plex_id)
|
LOG.debug('Adding season with plex_id %s', plex_id)
|
||||||
if not plex_id:
|
if not plex_id:
|
||||||
LOG.error('Error getting plex_id for season, skipping')
|
LOG.error('Error getting plex_id for season, skipping: %s',
|
||||||
|
xml.attrib)
|
||||||
return
|
return
|
||||||
entry = self.plex_db.getItem_byId(api.parent_plex_id())
|
show_id = api.parent_plex_id()
|
||||||
|
show = self.plex_db.show(show_id)
|
||||||
try:
|
try:
|
||||||
show_id = entry[0]
|
parent_id = show[3]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
LOG.error('Could not find parent tv show for season %s. '
|
LOG.error('Could not find parent tv show for season %s. '
|
||||||
'Skipping season for now.', plex_id)
|
'Skipping season for now.', plex_id)
|
||||||
return
|
return
|
||||||
kodi_id = self.kodi_db.add_season(show_id, api.season_number())
|
kodi_id = self.kodi_db.add_season(parent_id, api.season_number())
|
||||||
# Check whether Season already exists
|
|
||||||
entry = self.plex_db.getItem_byId(plex_id)
|
|
||||||
update_item = False if entry is None else True
|
|
||||||
self.artwork.modify_artwork(api.artwork(),
|
self.artwork.modify_artwork(api.artwork(),
|
||||||
kodi_id,
|
kodi_id,
|
||||||
v.KODI_TYPE_SEASON,
|
v.KODI_TYPE_SEASON,
|
||||||
self.kodicursor)
|
self.kodicursor)
|
||||||
if update_item:
|
self.plex_db.add_reference(plex_type=v.PLEX_TYPE_SEASON,
|
||||||
self.plex_db.updateReference(plex_id, api.checksum())
|
plex_id=plex_id,
|
||||||
else:
|
checksum=api.checksum(),
|
||||||
self.plex_db.addReference(plex_id,
|
section_id=section_id,
|
||||||
v.PLEX_TYPE_SEASON,
|
show_id=show_id,
|
||||||
kodi_id,
|
parent_id=parent_id,
|
||||||
v.KODI_TYPE_SEASON,
|
kodi_id=kodi_id,
|
||||||
parent_id=show_id,
|
last_sync=self.last_sync)
|
||||||
view_id=section_id,
|
|
||||||
checksum=api.checksum(),
|
|
||||||
last_sync=self.last_sync)
|
|
||||||
|
|
||||||
|
|
||||||
class Episode(ItemBase, TvShowMixin):
|
class Episode(ItemBase, TvShowMixin):
|
||||||
|
@ -345,13 +349,14 @@ class Episode(ItemBase, TvShowMixin):
|
||||||
plex_id = api.plex_id()
|
plex_id = api.plex_id()
|
||||||
LOG.debug('Adding episode with plex_id %s', plex_id)
|
LOG.debug('Adding episode with plex_id %s', plex_id)
|
||||||
if not plex_id:
|
if not plex_id:
|
||||||
LOG.error('Error getting plex_id for episode, skipping')
|
LOG.error('Error getting plex_id for episode, skipping: %s',
|
||||||
|
xml.attrib)
|
||||||
return
|
return
|
||||||
entry = self.plex_db.getItem_byId(plex_id)
|
entry = self.plex_db.item_by_id(plex_id)
|
||||||
try:
|
try:
|
||||||
kodi_id = entry[0]
|
kodi_id = entry[0]
|
||||||
old_file_id = entry[1]
|
old_kodi_fileid = entry[1]
|
||||||
path_id = entry[2]
|
kodi_pathid = entry[2]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
update_item = False
|
update_item = False
|
||||||
query = 'SELECT COALESCE(MAX(idEpisode), 0) FROM episode'
|
query = 'SELECT COALESCE(MAX(idEpisode), 0) FROM episode'
|
||||||
|
@ -359,7 +364,7 @@ class Episode(ItemBase, TvShowMixin):
|
||||||
kodi_id = self.kodicursor.fetchone()[0] + 1
|
kodi_id = self.kodicursor.fetchone()[0] + 1
|
||||||
else:
|
else:
|
||||||
# Verification the item is still in Kodi
|
# Verification the item is still in Kodi
|
||||||
query = 'SELECT * FROM episode WHERE idEpisode = ?'
|
query = 'SELECT * FROM episode WHERE idEpisode = ? LIMIT 1'
|
||||||
self.kodicursor.execute(query, (kodi_id, ))
|
self.kodicursor.execute(query, (kodi_id, ))
|
||||||
try:
|
try:
|
||||||
self.kodicursor.fetchone()[0]
|
self.kodicursor.fetchone()[0]
|
||||||
|
@ -373,23 +378,22 @@ class Episode(ItemBase, TvShowMixin):
|
||||||
director = api.list_to_string(peoples['Director'])
|
director = api.list_to_string(peoples['Director'])
|
||||||
writer = api.list_to_string(peoples['Writer'])
|
writer = api.list_to_string(peoples['Writer'])
|
||||||
userdata = api.userdata()
|
userdata = api.userdata()
|
||||||
series_id, _, season, episode = api.episode_data()
|
show_id, season_id, _, season_no, episode_no = api.episode_data()
|
||||||
|
|
||||||
if season is None:
|
if season_no is None:
|
||||||
season = -1
|
season_no = -1
|
||||||
if episode is None:
|
if episode_no is None:
|
||||||
episode = -1
|
episode_no = -1
|
||||||
airs_before_season = "-1"
|
airs_before_season = "-1"
|
||||||
airs_before_episode = "-1"
|
airs_before_episode = "-1"
|
||||||
|
|
||||||
# Get season id
|
show = self.plex_db.show(show_id)
|
||||||
show = self.plex_db.getItem_byId(series_id)
|
|
||||||
try:
|
try:
|
||||||
show_id = show[0]
|
grandparent_id = show[3]
|
||||||
except TypeError:
|
except TypeError:
|
||||||
LOG.error("Parent tvshow now found, skip item")
|
LOG.error("Parent tvshow now found, skip item")
|
||||||
return False
|
return False
|
||||||
season_id = self.kodi_db.add_season(show_id, season)
|
parent_id = self.kodi_db.add_season(grandparent_id, season_no)
|
||||||
|
|
||||||
# GET THE FILE AND PATH #####
|
# GET THE FILE AND PATH #####
|
||||||
do_indirect = not state.DIRECT_PATHS
|
do_indirect = not state.DIRECT_PATHS
|
||||||
|
@ -407,29 +411,31 @@ class Episode(ItemBase, TvShowMixin):
|
||||||
filename = playurl.rsplit("/", 1)[1]
|
filename = playurl.rsplit("/", 1)[1]
|
||||||
path = playurl.replace(filename, "")
|
path = playurl.replace(filename, "")
|
||||||
parent_path_id = self.kodi_db.parent_path_id(path)
|
parent_path_id = self.kodi_db.parent_path_id(path)
|
||||||
path_id = self.kodi_db.add_video_path(
|
kodi_pathid = self.kodi_db.add_video_path(
|
||||||
path, id_parent_path=parent_path_id)
|
path, id_parent_path=parent_path_id)
|
||||||
if do_indirect:
|
if do_indirect:
|
||||||
# Set plugin path - do NOT use "intermediate" paths for the show
|
# Set plugin path - do NOT use "intermediate" paths for the show
|
||||||
# as with direct paths!
|
# as with direct paths!
|
||||||
filename = api.file_name(force_first_media=True)
|
filename = api.file_name(force_first_media=True)
|
||||||
path = 'plugin://%s.tvshows/%s/' % (v.ADDON_ID, series_id)
|
path = 'plugin://%s.tvshows/%s/' % (v.ADDON_ID, show_id)
|
||||||
filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s'
|
filename = ('%s?plex_id=%s&plex_type=%s&mode=play&filename=%s'
|
||||||
% (path, plex_id, v.PLEX_TYPE_EPISODE, filename))
|
% (path, plex_id, v.PLEX_TYPE_EPISODE, filename))
|
||||||
playurl = filename
|
playurl = filename
|
||||||
# Root path tvshows/ already saved in Kodi DB
|
# Root path tvshows/ already saved in Kodi DB
|
||||||
path_id = self.kodi_db.add_video_path(path)
|
kodi_pathid = self.kodi_db.add_video_path(path)
|
||||||
|
|
||||||
# add/retrieve path_id and fileid
|
# add/retrieve kodi_pathid and fileid
|
||||||
# if the path or file already exists, the calls return current value
|
# if the path or file already exists, the calls return current value
|
||||||
file_id = self.kodi_db.add_file(filename, path_id, api.date_created())
|
kodi_fileid = self.kodi_db.add_file(filename,
|
||||||
|
kodi_pathid,
|
||||||
|
api.date_created())
|
||||||
|
|
||||||
# UPDATE THE EPISODE #####
|
# UPDATE THE EPISODE #####
|
||||||
if update_item:
|
if update_item:
|
||||||
LOG.info("UPDATE episode plex_id: %s, Title: %s",
|
LOG.info("UPDATE episode plex_id: %s, Title: %s",
|
||||||
plex_id, api.title())
|
plex_id, api.title())
|
||||||
if file_id != old_file_id:
|
if kodi_fileid != old_kodi_fileid:
|
||||||
self.kodi_db.remove_file(old_file_id)
|
self.kodi_db.remove_file(old_kodi_fileid)
|
||||||
ratingid = self.kodi_db.get_ratingid(kodi_id,
|
ratingid = self.kodi_db.get_ratingid(kodi_id,
|
||||||
v.KODI_TYPE_EPISODE)
|
v.KODI_TYPE_EPISODE)
|
||||||
self.kodi_db.update_ratings(kodi_id,
|
self.kodi_db.update_ratings(kodi_id,
|
||||||
|
@ -456,12 +462,10 @@ class Episode(ItemBase, TvShowMixin):
|
||||||
'''
|
'''
|
||||||
self.kodicursor.execute(
|
self.kodicursor.execute(
|
||||||
query, (api.title(), api.plot(), ratingid, writer,
|
query, (api.title(), api.plot(), ratingid, writer,
|
||||||
api.premiere_date(), api.runtime(), director, season,
|
api.premiere_date(), api.runtime(), director, season_no,
|
||||||
episode, api.title(), airs_before_season,
|
episode_no, api.title(), airs_before_season,
|
||||||
airs_before_episode, playurl, path_id, file_id,
|
airs_before_episode, playurl, kodi_pathid, kodi_fileid,
|
||||||
season_id, userdata['UserRating'], kodi_id))
|
parent_id, userdata['UserRating'], kodi_id))
|
||||||
# Update parentid reference
|
|
||||||
self.plex_db.updateParentId(plex_id, season_id)
|
|
||||||
|
|
||||||
# OR ADD THE EPISODE #####
|
# OR ADD THE EPISODE #####
|
||||||
else:
|
else:
|
||||||
|
@ -492,11 +496,11 @@ class Episode(ItemBase, TvShowMixin):
|
||||||
?, ?)
|
?, ?)
|
||||||
'''
|
'''
|
||||||
self.kodicursor.execute(
|
self.kodicursor.execute(
|
||||||
query, (kodi_id, file_id, api.title(), api.plot(), rating_id,
|
query, (kodi_id, kodi_fileid, api.title(), api.plot(), rating_id,
|
||||||
writer, api.premiere_date(), api.runtime(), director,
|
writer, api.premiere_date(), api.runtime(), director,
|
||||||
season, episode, api.title(), show_id,
|
season_no, episode_no, api.title(), grandparent_id,
|
||||||
airs_before_season, airs_before_episode, playurl,
|
airs_before_season, airs_before_episode, playurl,
|
||||||
path_id, season_id, userdata['UserRating']))
|
kodi_pathid, parent_id, userdata['UserRating']))
|
||||||
|
|
||||||
self.kodi_db.modify_people(kodi_id,
|
self.kodi_db.modify_people(kodi_id,
|
||||||
v.KODI_TYPE_EPISODE,
|
v.KODI_TYPE_EPISODE,
|
||||||
|
@ -506,8 +510,8 @@ class Episode(ItemBase, TvShowMixin):
|
||||||
v.KODI_TYPE_EPISODE,
|
v.KODI_TYPE_EPISODE,
|
||||||
self.kodicursor)
|
self.kodicursor)
|
||||||
streams = api.mediastreams()
|
streams = api.mediastreams()
|
||||||
self.kodi_db.modify_streams(file_id, streams, api.runtime())
|
self.kodi_db.modify_streams(kodi_fileid, streams, api.runtime())
|
||||||
self.kodi_db.set_resume(file_id,
|
self.kodi_db.set_resume(kodi_fileid,
|
||||||
api.resume_point(),
|
api.resume_point(),
|
||||||
api.runtime(),
|
api.runtime(),
|
||||||
userdata['PlayCount'],
|
userdata['PlayCount'],
|
||||||
|
@ -519,25 +523,27 @@ class Episode(ItemBase, TvShowMixin):
|
||||||
path = 'plugin://%s.tvshows/' % v.ADDON_ID
|
path = 'plugin://%s.tvshows/' % v.ADDON_ID
|
||||||
# Filename is exactly the same, WITH plex show id!
|
# Filename is exactly the same, WITH plex show id!
|
||||||
filename = ('%s%s/?plex_id=%s&plex_type=%s&mode=play&filename=%s'
|
filename = ('%s%s/?plex_id=%s&plex_type=%s&mode=play&filename=%s'
|
||||||
% (path, series_id, plex_id, v.PLEX_TYPE_EPISODE,
|
% (path, show_id, plex_id, v.PLEX_TYPE_EPISODE,
|
||||||
filename))
|
filename))
|
||||||
path_id = self.kodi_db.add_video_path(path)
|
kodi_pathid = self.kodi_db.add_video_path(path)
|
||||||
file_id = self.kodi_db.add_file(filename,
|
kodi_fileid = self.kodi_db.add_file(filename,
|
||||||
path_id,
|
kodi_pathid,
|
||||||
api.date_created())
|
api.date_created())
|
||||||
self.kodi_db.set_resume(file_id,
|
self.kodi_db.set_resume(kodi_fileid,
|
||||||
api.resume_point(),
|
api.resume_point(),
|
||||||
api.runtime(),
|
api.runtime(),
|
||||||
userdata['PlayCount'],
|
userdata['PlayCount'],
|
||||||
userdata['LastPlayedDate'],
|
userdata['LastPlayedDate'],
|
||||||
None) # Do send None - 2nd entry
|
None) # Do send None - 2nd entry
|
||||||
self.plex_db.addReference(plex_id,
|
self.plex_db.add_reference(plex_type=v.PLEX_TYPE_EPISODE,
|
||||||
v.PLEX_TYPE_EPISODE,
|
plex_id=plex_id,
|
||||||
kodi_id,
|
checksum=api.checksum(),
|
||||||
v.KODI_TYPE_EPISODE,
|
section_id=section_id,
|
||||||
kodi_file_id=file_id,
|
show_id=show_id,
|
||||||
kodi_pathid=path_id,
|
grandparent_id=grandparent_id,
|
||||||
parent_id=season_id,
|
season_id=season_id,
|
||||||
checksum=api.checksum(),
|
parent_id=parent_id,
|
||||||
view_id=section_id,
|
kodi_id=kodi_id,
|
||||||
last_sync=self.last_sync)
|
kodi_fileid=kodi_fileid,
|
||||||
|
kodi_pathid=kodi_pathid,
|
||||||
|
last_sync=self.last_sync)
|
||||||
|
|
|
@ -30,9 +30,10 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
||||||
self.process_thread = None
|
self.process_thread = None
|
||||||
self.last_sync = None
|
self.last_sync = None
|
||||||
self.plex_db = None
|
self.plex_db = None
|
||||||
|
self.plex_type = None
|
||||||
super(FullSync, self).__init__()
|
super(FullSync, self).__init__()
|
||||||
|
|
||||||
def process_item(self, xml_item, get_children):
|
def process_item(self, xml_item):
|
||||||
"""
|
"""
|
||||||
Processes a single library item
|
Processes a single library item
|
||||||
"""
|
"""
|
||||||
|
@ -42,7 +43,7 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
||||||
backgroundthread.BGThreader.addTask(
|
backgroundthread.BGThreader.addTask(
|
||||||
GetMetadataTask().setup(self.queue,
|
GetMetadataTask().setup(self.queue,
|
||||||
plex_id,
|
plex_id,
|
||||||
get_children))
|
self.get_children))
|
||||||
else:
|
else:
|
||||||
if self.plex_db.check_checksum(
|
if self.plex_db.check_checksum(
|
||||||
int('%s%s' % (xml_item['ratingKey'],
|
int('%s%s' % (xml_item['ratingKey'],
|
||||||
|
@ -50,20 +51,29 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
||||||
backgroundthread.BGThreader.addTask(
|
backgroundthread.BGThreader.addTask(
|
||||||
GetMetadataTask().setup(self.queue,
|
GetMetadataTask().setup(self.queue,
|
||||||
plex_id,
|
plex_id,
|
||||||
get_children))
|
self.get_children))
|
||||||
else:
|
else:
|
||||||
self.plex_db.update_last_sync(plex_id, self.last_sync)
|
self.plex_db.update_last_sync(plex_id, self.last_sync)
|
||||||
|
|
||||||
|
def process_delete(self):
|
||||||
|
"""
|
||||||
|
Removes all the items that have NOT been updated (last_sync timestamp)
|
||||||
|
is different
|
||||||
|
"""
|
||||||
|
with self.context() as c:
|
||||||
|
for plex_id in self.plex_db.plex_id_by_last_sync(self.plex_type,
|
||||||
|
self.last_sync):
|
||||||
|
if self.isCanceled():
|
||||||
|
return
|
||||||
|
c.remove(plex_id, plex_type=self.plex_type)
|
||||||
|
|
||||||
@utils.log_time
|
@utils.log_time
|
||||||
def process_kind(self, kind):
|
def process_kind(self):
|
||||||
"""
|
"""
|
||||||
kind is a tuple: (<name as unicode>,
|
|
||||||
kodi_type,
|
|
||||||
<itemtype class>,
|
|
||||||
get_children)
|
|
||||||
"""
|
"""
|
||||||
LOG.debug('Start processing %s', kind[0])
|
LOG.debug('Start processing %ss', self.plex_type)
|
||||||
sections = (x for x in sections.SECTIONS if x['kodi_type'] == kind[1])
|
sections = (x for x in sections.SECTIONS
|
||||||
|
if x['plex_type'] == self.plex_type)
|
||||||
for section in sections:
|
for section in sections:
|
||||||
LOG.debug('Processing library section %s', section)
|
LOG.debug('Processing library section %s', section)
|
||||||
if self.isCanceled():
|
if self.isCanceled():
|
||||||
|
@ -71,10 +81,12 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
||||||
if not self.install_sync_done:
|
if not self.install_sync_done:
|
||||||
state.PATH_VERIFIED = False
|
state.PATH_VERIFIED = False
|
||||||
try:
|
try:
|
||||||
iterator = PF.PlexSectionItems(section['id'])
|
iterator = PF.SectionItems(
|
||||||
|
section['id'],
|
||||||
|
{'type': v.PLEX_TYPE_NUMBER_FROM_PLEX_TYPE[self.plex_type]})
|
||||||
# Tell the processing thread about this new section
|
# Tell the processing thread about this new section
|
||||||
queue_info = process_metadata.InitNewSection(
|
queue_info = process_metadata.InitNewSection(
|
||||||
kind[2],
|
self.context,
|
||||||
utils.cast(int, iterator.get('totalSize', 0)),
|
utils.cast(int, iterator.get('totalSize', 0)),
|
||||||
utils.cast(unicode, iterator.get('librarySectionTitle')),
|
utils.cast(unicode, iterator.get('librarySectionTitle')),
|
||||||
section['id'])
|
section['id'])
|
||||||
|
@ -82,32 +94,42 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
|
||||||
for xml_item in iterator:
|
for xml_item in iterator:
|
||||||
if self.isCanceled():
|
if self.isCanceled():
|
||||||
return False
|
return False
|
||||||
self.process_item(xml_item, kind[3])
|
self.process_item(xml_item)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
LOG.error('Could not entirely process section %s', section)
|
LOG.error('Could not entirely process section %s', section)
|
||||||
continue
|
continue
|
||||||
LOG.debug('Finished processing %s', kind[0])
|
|
||||||
|
LOG.debug('Finished processing %ss', self.plex_type)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def full_library_sync(self, new_items_only=False):
|
def full_library_sync(self):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
process = [self.plex_movies, self.plex_tv_show]
|
|
||||||
if state.ENABLE_MUSIC:
|
|
||||||
process.append(self.plex_music)
|
|
||||||
self.queue = backgroundthread.Queue.Queue(maxsize=200)
|
self.queue = backgroundthread.Queue.Queue(maxsize=200)
|
||||||
t = process_metadata.ProcessMetadata(self.queue, self.last_sync)
|
t = process_metadata.ProcessMetadata(self.queue, self.last_sync)
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
kinds = [
|
kinds = [
|
||||||
('movies', v.KODI_TYPE_MOVIE, itemtypes.Movie, False),
|
(v.PLEX_TYPE_MOVIE, itemtypes.Movie, False),
|
||||||
('tv shows', v.KODI_TYPE_SHOW, itemtypes.Show, False),
|
(v.PLEX_TYPE_SHOW, itemtypes.Show, False),
|
||||||
('tv seasons', v.KODI_TYPE_SEASON, itemtypes.Season, False),
|
(v.PLEX_TYPE_SEASON, itemtypes.Season, False),
|
||||||
('tv shows', v.KODI_TYPE_SHOW, itemtypes.Show, False),
|
(v.PLEX_TYPE_EPISODE, itemtypes.Episode, False),
|
||||||
|
(v.PLEX_TYPE_ARTIST, itemtypes.Artist, False),
|
||||||
|
(v.PLEX_TYPE_ALBUM, itemtypes.Album, True),
|
||||||
|
(v.PLEX_TYPE_SONG, itemtypes.Song, False),
|
||||||
]
|
]
|
||||||
try:
|
for kind in kinds:
|
||||||
for kind in kinds:
|
# Setup our variables
|
||||||
if self.isCanceled() or not self.process_kind(kind):
|
self.plex_type = kind[0]
|
||||||
return False
|
self.context = kind[1]
|
||||||
|
self.get_children = kind[2]
|
||||||
|
# Now do the heavy lifting
|
||||||
|
if self.isCanceled() or not self.process_kind():
|
||||||
|
return False
|
||||||
|
if not self.new_items_only:
|
||||||
|
# Delete movies that are not on Plex anymore - do this only once
|
||||||
|
self.process_delete()
|
||||||
|
|
||||||
|
|
||||||
# Let kodi update the views in any case, since we're doing a full sync
|
# Let kodi update the views in any case, since we're doing a full sync
|
||||||
common.update_kodi_library(video=True, music=state.ENABLE_MUSIC)
|
common.update_kodi_library(video=True, music=state.ENABLE_MUSIC)
|
||||||
|
|
|
@ -54,10 +54,10 @@ def sync_from_pms():
|
||||||
|
|
||||||
VNODES.clearProperties()
|
VNODES.clearProperties()
|
||||||
|
|
||||||
with plexdb.Get_Plex_DB() as plex_db:
|
with plexdb.PlexDB() as plex_db:
|
||||||
# Backup old sections to delete them later, if needed (at the end
|
# Backup old sections to delete them later, if needed (at the end
|
||||||
# of this method, only unused sections will be left in old_sections)
|
# of this method, only unused sections will be left in old_sections)
|
||||||
old_sections = plex_db.sections()
|
old_sections = [plex_db.section_ids()]
|
||||||
with kodidb.GetKodiDB('video') as kodi_db:
|
with kodidb.GetKodiDB('video') as kodi_db:
|
||||||
for section in sections:
|
for section in sections:
|
||||||
_process_section(section,
|
_process_section(section,
|
||||||
|
@ -71,8 +71,8 @@ def sync_from_pms():
|
||||||
# Section has been deleted on the PMS
|
# Section has been deleted on the PMS
|
||||||
delete_sections(old_sections)
|
delete_sections(old_sections)
|
||||||
# update sections for all:
|
# update sections for all:
|
||||||
with plexdb.Get_Plex_DB() as plex_db:
|
with plexdb.PlexDB() as plex_db:
|
||||||
SECTIONS = plex_db.list_section_info()
|
SECTIONS = [plex_db.section_infos()]
|
||||||
utils.window('Plex.nodes.total', str(totalnodes))
|
utils.window('Plex.nodes.total', str(totalnodes))
|
||||||
LOG.info("Finished processing library sections: %s", SECTIONS)
|
LOG.info("Finished processing library sections: %s", SECTIONS)
|
||||||
return True
|
return True
|
||||||
|
@ -209,7 +209,7 @@ def delete_sections(old_sections):
|
||||||
sound=False)
|
sound=False)
|
||||||
video_library_update = False
|
video_library_update = False
|
||||||
music_library_update = False
|
music_library_update = False
|
||||||
with plexdb.Get_Plex_DB() as plex_db:
|
with plexdb.PlexDB() as plex_db:
|
||||||
old_sections = [plex_db.section_by_id(x) for x in old_sections]
|
old_sections = [plex_db.section_by_id(x) for x in old_sections]
|
||||||
LOG.info("Removing entire Plex library sections: %s", old_sections)
|
LOG.info("Removing entire Plex library sections: %s", old_sections)
|
||||||
with kodidb.GetKodiDB() as kodi_db:
|
with kodidb.GetKodiDB() as kodi_db:
|
||||||
|
|
|
@ -214,20 +214,6 @@ class LibrarySync(Thread):
|
||||||
"""
|
"""
|
||||||
with plexdb.Get_Plex_DB() as plex_db:
|
with plexdb.Get_Plex_DB() as plex_db:
|
||||||
# Create the tables for the plex database
|
# Create the tables for the plex database
|
||||||
plex_db.plexcursor.execute('''
|
|
||||||
CREATE TABLE IF NOT EXISTS plex(
|
|
||||||
plex_id INTEGER PRIMARY KEY ASC,
|
|
||||||
section_id INTEGER,
|
|
||||||
plex_type TEXT,
|
|
||||||
kodi_type TEXT,
|
|
||||||
kodi_id INTEGER,
|
|
||||||
kodi_fileid INTEGER,
|
|
||||||
kodi_pathid INTEGER,
|
|
||||||
parent_id INTEGER,
|
|
||||||
checksum INTEGER UNIQUE,
|
|
||||||
fanart_synced INTEGER,
|
|
||||||
last_sync INTEGER)
|
|
||||||
''')
|
|
||||||
plex_db.plexcursor.execute('''
|
plex_db.plexcursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS sections(
|
CREATE TABLE IF NOT EXISTS sections(
|
||||||
section_id INTEGER PRIMARY KEY,
|
section_id INTEGER PRIMARY KEY,
|
||||||
|
@ -236,6 +222,88 @@ class LibrarySync(Thread):
|
||||||
kodi_tagid INTEGER,
|
kodi_tagid INTEGER,
|
||||||
sync_to_kodi INTEGER)
|
sync_to_kodi INTEGER)
|
||||||
''')
|
''')
|
||||||
|
plex_db.plexcursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS movie(
|
||||||
|
plex_id INTEGER PRIMARY KEY ASC,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
kodi_id INTEGER,
|
||||||
|
kodi_fileid INTEGER,
|
||||||
|
kodi_pathid INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER)
|
||||||
|
''')
|
||||||
|
plex_db.plexcursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS show(
|
||||||
|
plex_id INTEGER PRIMARY KEY ASC,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
kodi_id INTEGER,
|
||||||
|
kodi_pathid INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER)
|
||||||
|
''')
|
||||||
|
plex_db.plexcursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS season(
|
||||||
|
plex_id INTEGER PRIMARY KEY,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
show_id INTEGER, # plex_id of the parent show
|
||||||
|
parent_id INTEGER, # kodi_id of the parent show
|
||||||
|
kodi_id INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER)
|
||||||
|
''')
|
||||||
|
plex_db.plexcursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS episode(
|
||||||
|
plex_id INTEGER PRIMARY KEY,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
show_id INTEGER, # plex_id of the parent show
|
||||||
|
grandparent_id INTEGER, # kodi_id of the parent show
|
||||||
|
season_id INTEGER, # plex_id of the parent season
|
||||||
|
parent_id INTEGER, # kodi_id of the parent season
|
||||||
|
kodi_id INTEGER,
|
||||||
|
kodi_fileid INTEGER,
|
||||||
|
kodi_pathid INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER)
|
||||||
|
''')
|
||||||
|
plex_db.plexcursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS artist(
|
||||||
|
plex_id INTEGER PRIMARY KEY ASC,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
kodi_id INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER)
|
||||||
|
''')
|
||||||
|
plex_db.plexcursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS album(
|
||||||
|
plex_id INTEGER PRIMARY KEY,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
artist_id INTEGER, # plex_id of the parent artist
|
||||||
|
parent_id INTEGER, # kodi_id of the parent artist
|
||||||
|
kodi_id INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER)
|
||||||
|
''')
|
||||||
|
plex_db.plexcursor.execute('''
|
||||||
|
CREATE TABLE IF NOT EXISTS track(
|
||||||
|
plex_id INTEGER PRIMARY KEY,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
artist_id INTEGER, # plex_id of the parent artist
|
||||||
|
grandparent_id INTEGER, # kodi_id of the parent artist
|
||||||
|
album_id INTEGER, # plex_id of the parent album
|
||||||
|
parent_id INTEGER, # kodi_id of the parent album
|
||||||
|
kodi_id INTEGER,
|
||||||
|
kodi_fileid INTEGER,
|
||||||
|
kodi_pathid INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER)
|
||||||
|
''')
|
||||||
plex_db.plexcursor.execute('''
|
plex_db.plexcursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS playlists(
|
CREATE TABLE IF NOT EXISTS playlists(
|
||||||
plex_id INTEGER PRIMARY KEY ASC,
|
plex_id INTEGER PRIMARY KEY ASC,
|
||||||
|
|
|
@ -95,17 +95,17 @@ def _write_playlist_to_file(playlist, xml):
|
||||||
api = API(element)
|
api = API(element)
|
||||||
append_season_episode = False
|
append_season_episode = False
|
||||||
if api.plex_type() == v.PLEX_TYPE_EPISODE:
|
if api.plex_type() == v.PLEX_TYPE_EPISODE:
|
||||||
_, show, season_id, episode_id = api.episode_data()
|
_, _, show, season_no, episode_no = api.episode_data()
|
||||||
try:
|
try:
|
||||||
season_id = int(season_id)
|
season_no = int(season_no)
|
||||||
episode_id = int(episode_id)
|
episode_no = int(episode_no)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
append_season_episode = True
|
append_season_episode = True
|
||||||
if append_season_episode:
|
if append_season_episode:
|
||||||
text += ('#EXTINF:%s,%s S%.2dE%.2d - %s\n%s\n'
|
text += ('#EXTINF:%s,%s S%.2dE%.2d - %s\n%s\n'
|
||||||
% (api.runtime(), show, season_id, episode_id,
|
% (api.runtime(), show, season_no, episode_no,
|
||||||
api.title(), api.path()))
|
api.title(), api.path()))
|
||||||
else:
|
else:
|
||||||
# Only append the TV show name
|
# Only append the TV show name
|
||||||
|
|
|
@ -260,9 +260,9 @@ class API(object):
|
||||||
|
|
||||||
def season_number(self):
|
def season_number(self):
|
||||||
"""
|
"""
|
||||||
Returns the 'index' of an PMS XML reply. Depicts e.g. season number.
|
Returns the 'index' of an XML reply as int. Depicts e.g. season number.
|
||||||
"""
|
"""
|
||||||
return self.item.get('index')
|
return cast(int, self.item.get('index'))
|
||||||
|
|
||||||
def date_created(self):
|
def date_created(self):
|
||||||
"""
|
"""
|
||||||
|
@ -639,14 +639,14 @@ class API(object):
|
||||||
"""
|
"""
|
||||||
Returns the 'parentRatingKey' as a string or None
|
Returns the 'parentRatingKey' as a string or None
|
||||||
"""
|
"""
|
||||||
return self.item.get('parentRatingKey')
|
return cast(int, self.item.get('parentRatingKey'))
|
||||||
|
|
||||||
def grandparent_id(self):
|
def grandparent_id(self):
|
||||||
"""
|
"""
|
||||||
Returns the ratingKey for the corresponding grandparent, e.g. a TV show
|
Returns the ratingKey for the corresponding grandparent, e.g. a TV show
|
||||||
for episodes, or None
|
for episodes, or None
|
||||||
"""
|
"""
|
||||||
return self.item.get('grandparentRatingKey')
|
return cast(int, self.item.get('grandparentRatingKey'))
|
||||||
|
|
||||||
def grandparent_title(self):
|
def grandparent_title(self):
|
||||||
"""
|
"""
|
||||||
|
@ -661,16 +661,18 @@ class API(object):
|
||||||
|
|
||||||
Output: for the corresponding the TV show and season:
|
Output: for the corresponding the TV show and season:
|
||||||
[
|
[
|
||||||
TV show key, Plex: 'grandparentRatingKey'
|
TV show ID, Plex: 'grandparentRatingKey'
|
||||||
|
TV season ID, Plex: 'grandparentRatingKey'
|
||||||
TV show title, Plex: 'grandparentTitle'
|
TV show title, Plex: 'grandparentTitle'
|
||||||
TV show season, Plex: 'parentIndex'
|
TV show season, Plex: 'parentIndex'
|
||||||
Episode number, Plex: 'index'
|
Episode number, Plex: 'index'
|
||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
return (self.item.get('grandparentRatingKey'),
|
return (cast(int, self.item.get('grandparentRatingKey')),
|
||||||
self.item.get('grandparentTitle'),
|
cast(int, self.item.get('parentRatingKey')),
|
||||||
self.item.get('parentIndex'),
|
cast(unicode, self.item.get('grandparentTitle')),
|
||||||
self.item.get('index'))
|
cast(int, self.item.get('parentIndex')),
|
||||||
|
cast(int, self.item.get('index')))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def attach_plex_token_to_url(url):
|
def attach_plex_token_to_url(url):
|
||||||
|
@ -1609,7 +1611,7 @@ class API(object):
|
||||||
|
|
||||||
if typus == v.PLEX_TYPE_EPISODE:
|
if typus == v.PLEX_TYPE_EPISODE:
|
||||||
metadata['mediatype'] = 'episode'
|
metadata['mediatype'] = 'episode'
|
||||||
_, show, season, episode = self.episode_data()
|
_, _, show, season, episode = self.episode_data()
|
||||||
season = -1 if season is None else int(season)
|
season = -1 if season is None else int(season)
|
||||||
episode = -1 if episode is None else int(episode)
|
episode = -1 if episode is None else int(episode)
|
||||||
metadata['episode'] = episode
|
metadata['episode'] = episode
|
||||||
|
|
43
resources/lib/plex_db/__init__.py
Normal file
43
resources/lib/plex_db/__init__.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
|
||||||
|
from .common import PlexDBBase
|
||||||
|
from .tvshows import
|
||||||
|
from .. import utils, variables as v
|
||||||
|
|
||||||
|
|
||||||
|
class PlexDB(object):
|
||||||
|
"""
|
||||||
|
Usage: with PlexDB() as plex_db:
|
||||||
|
plex_db.do_something()
|
||||||
|
|
||||||
|
On exiting "with" (no matter what), commits get automatically committed
|
||||||
|
and the db gets closed
|
||||||
|
"""
|
||||||
|
def __init__(self, kind=None):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.plexconn = utils.kodi_sql('plex')
|
||||||
|
if kind is None:
|
||||||
|
func = PlexDBBase
|
||||||
|
return func(self.plexconn.cursor())
|
||||||
|
|
||||||
|
def __exit__(self, type, value, traceback):
|
||||||
|
self.plexconn.commit()
|
||||||
|
self.plexconn.close()
|
||||||
|
|
||||||
|
|
||||||
|
def wipe_dbs():
|
||||||
|
"""
|
||||||
|
Completely resets the Plex database
|
||||||
|
"""
|
||||||
|
query = "SELECT name FROM sqlite_master WHERE type = 'table'"
|
||||||
|
with PlexDB() as plex_db:
|
||||||
|
plex_db.plexcursor.execute(query)
|
||||||
|
tables = plex_db.plexcursor.fetchall()
|
||||||
|
tables = [i[0] for i in tables]
|
||||||
|
for table in tables:
|
||||||
|
delete_query = 'DELETE FROM %s' % table
|
||||||
|
plex_db.plexcursor.execute(delete_query)
|
112
resources/lib/plex_db/common.py
Normal file
112
resources/lib/plex_db/common.py
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
|
||||||
|
|
||||||
|
class PlexDB(object):
|
||||||
|
"""
|
||||||
|
Methods used for all types of items
|
||||||
|
"""
|
||||||
|
def __init__(self, cursor):
|
||||||
|
self.cursor = cursor
|
||||||
|
|
||||||
|
def section_ids(self):
|
||||||
|
"""
|
||||||
|
Returns an iterator for section Plex ids for all sections
|
||||||
|
"""
|
||||||
|
self.cursor.execute('SELECT section_id FROM sections')
|
||||||
|
return (x[0] for x in self.cursor)
|
||||||
|
|
||||||
|
def section_infos(self):
|
||||||
|
"""
|
||||||
|
Returns an iterator for dicts for all Plex libraries:
|
||||||
|
{
|
||||||
|
'section_id'
|
||||||
|
'section_name'
|
||||||
|
'plex_type'
|
||||||
|
'kodi_tagid'
|
||||||
|
'sync_to_kodi'
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
self.cursor.execute('SELECT * FROM sections')
|
||||||
|
return ({'section_id': x[0],
|
||||||
|
'section_name': x[1],
|
||||||
|
'plex_type': x[2],
|
||||||
|
'kodi_tagid': x[3],
|
||||||
|
'sync_to_kodi': x[4]} for x in self.cursor)
|
||||||
|
|
||||||
|
def section_by_id(self, section_id):
|
||||||
|
"""
|
||||||
|
For section_id, returns tuple (or None)
|
||||||
|
(section_id,
|
||||||
|
section_name,
|
||||||
|
plex_type,
|
||||||
|
kodi_tagid,
|
||||||
|
sync_to_kodi)
|
||||||
|
"""
|
||||||
|
self.cursor.execute('SELECT * FROM sections WHERE section_id = ? LIMIT 1',
|
||||||
|
(section_id, ))
|
||||||
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
|
def section_id_by_name(self, section_name):
|
||||||
|
"""
|
||||||
|
Returns the section_id for section_name (or None)
|
||||||
|
"""
|
||||||
|
self.cursor.execute('SELECT section_id FROM sections WHERE section_name = ? LIMIT 1,'
|
||||||
|
(section_name, ))
|
||||||
|
try:
|
||||||
|
return self.cursor.fetchone()[0]
|
||||||
|
except TypeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_section(self, section_id, section_name, plex_type, kodi_tagid,
|
||||||
|
sync_to_kodi=True):
|
||||||
|
"""
|
||||||
|
Appends a Plex section to the Plex sections table
|
||||||
|
sync=False: Plex library won't be synced to Kodi
|
||||||
|
"""
|
||||||
|
query = '''
|
||||||
|
INSERT INTO sections(
|
||||||
|
section_id, section_name, plex_type, kodi_tagid, sync_to_kodi)
|
||||||
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
'''
|
||||||
|
self.cursor.execute(query,
|
||||||
|
(section_id,
|
||||||
|
section_name,
|
||||||
|
plex_type,
|
||||||
|
kodi_tagid,
|
||||||
|
sync_to_kodi))
|
||||||
|
|
||||||
|
def update_section(self, section_name, kodi_tagid, section_id):
|
||||||
|
"""
|
||||||
|
Updates the section_id with section_name and kodi_tagid
|
||||||
|
"""
|
||||||
|
query = '''
|
||||||
|
UPDATE sections
|
||||||
|
SET section_name = ?, kodi_tagid = ?
|
||||||
|
WHERE section_id = ?
|
||||||
|
'''
|
||||||
|
self.cursor.execute(query, (section_name, kodi_tagid, section_id))
|
||||||
|
|
||||||
|
def remove_section(self, section_id):
|
||||||
|
"""
|
||||||
|
Removes the Plex db entry for the section with section_id
|
||||||
|
"""
|
||||||
|
self.cursor.execute('DELETE FROM sections WHERE section_id = ?',
|
||||||
|
(section_id, ))
|
||||||
|
|
||||||
|
def item_by_id(self, plex_id):
|
||||||
|
"""
|
||||||
|
For plex_id, returns the tuple
|
||||||
|
(kodi_id, kodi_fileid, kodi_pathid, parent_id, kodi_type, plex_type)
|
||||||
|
|
||||||
|
None if not found
|
||||||
|
"""
|
||||||
|
query = '''
|
||||||
|
SELECT kodi_id, kodi_fileid, kodi_pathid, parent_id, kodi_type,
|
||||||
|
plex_type
|
||||||
|
FROM plex WHERE plex_id = ?
|
||||||
|
LIMIT 1
|
||||||
|
'''
|
||||||
|
self.cursor.execute(query, (plex_id,))
|
||||||
|
return self.cursor.fetchone()
|
124
resources/lib/plex_db/tvshows.py
Normal file
124
resources/lib/plex_db/tvshows.py
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
|
||||||
|
from . import common
|
||||||
|
from .. import variables as v
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
class PlexDB(common.PlexDB):
|
||||||
|
def add_reference(self, plex_type=None, plex_id=None, checksum=None,
|
||||||
|
section_id=None, show_id=None, grandparent_id=None,
|
||||||
|
season_id=None, parent_id=None, kodi_id=None,
|
||||||
|
kodi_fileid=None, kodi_pathid=None, last_sync=None):
|
||||||
|
"""
|
||||||
|
Appends or replaces an entry into the plex table
|
||||||
|
"""
|
||||||
|
if plex_type == v.PLEX_TYPE_EPISODE:
|
||||||
|
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)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
'''
|
||||||
|
self.plexcursor.execute(
|
||||||
|
query,
|
||||||
|
(plex_id, checksum, section_id, show_id, grandparent_id,
|
||||||
|
season_id, parent_id, kodi_id, kodi_fileid, kodi_pathid,
|
||||||
|
0, last_sync))
|
||||||
|
elif plex_type == v.PLEX_TYPE_SEASON:
|
||||||
|
query = '''
|
||||||
|
INSERT OR REPLACE INTO season(
|
||||||
|
plex_id, checksum, section_id, show_id, parent_id,
|
||||||
|
kodi_id, fanart_synced, last_sync)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
'''
|
||||||
|
self.plexcursor.execute(
|
||||||
|
query,
|
||||||
|
(plex_id, checksum, section_id, show_id, parent_id,
|
||||||
|
kodi_id, 0, last_sync))
|
||||||
|
elif plex_type == v.PLEX_TYPE_SHOW:
|
||||||
|
query = '''
|
||||||
|
INSERT OR REPLACE INTO show(
|
||||||
|
plex_id, checksum, section_id, kodi_id, kodi_pathid,
|
||||||
|
fanart_synced, last_sync)
|
||||||
|
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||||
|
'''
|
||||||
|
self.plexcursor.execute(
|
||||||
|
query,
|
||||||
|
(plex_id, checksum, section_id, kodi_id, kodi_pathid, 0,
|
||||||
|
last_sync))
|
||||||
|
|
||||||
|
def show(self, plex_id):
|
||||||
|
"""
|
||||||
|
Returns the show info as a tuple for the TV show with plex_id:
|
||||||
|
plex_id INTEGER PRIMARY KEY ASC,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
kodi_id INTEGER,
|
||||||
|
kodi_pathid INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER
|
||||||
|
"""
|
||||||
|
self.cursor.execute('SELECT * FROM show WHERE plex_id = ?',
|
||||||
|
(plex_id, ))
|
||||||
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
|
def season(self, plex_id):
|
||||||
|
"""
|
||||||
|
Returns the show info as a tuple for the TV show with plex_id:
|
||||||
|
plex_id INTEGER PRIMARY KEY,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
show_id INTEGER, # plex_id of the parent show
|
||||||
|
parent_id INTEGER, # kodi_id of the parent show
|
||||||
|
kodi_id INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER
|
||||||
|
"""
|
||||||
|
self.cursor.execute('SELECT * FROM season WHERE plex_id = ?',
|
||||||
|
(plex_id, ))
|
||||||
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
|
def episode(self, plex_id):
|
||||||
|
"""
|
||||||
|
Returns the show info as a tuple for the TV show with plex_id:
|
||||||
|
plex_id INTEGER PRIMARY KEY,
|
||||||
|
checksum INTEGER UNIQUE,
|
||||||
|
section_id INTEGER,
|
||||||
|
show_id INTEGER, # plex_id of the parent show
|
||||||
|
grandparent_id INTEGER, # kodi_id of the parent show
|
||||||
|
season_id INTEGER, # plex_id of the parent season
|
||||||
|
parent_id INTEGER, # kodi_id of the parent season
|
||||||
|
kodi_id INTEGER,
|
||||||
|
kodi_fileid INTEGER,
|
||||||
|
kodi_pathid INTEGER,
|
||||||
|
fanart_synced INTEGER,
|
||||||
|
last_sync INTEGER
|
||||||
|
"""
|
||||||
|
self.cursor.execute('SELECT * FROM episode WHERE plex_id = ?',
|
||||||
|
(plex_id, ))
|
||||||
|
return self.cursor.fetchone()
|
||||||
|
|
||||||
|
def plex_id_by_last_sync(self, plex_type, last_sync):
|
||||||
|
"""
|
||||||
|
Returns an iterator for all items where the last_sync is NOT identical
|
||||||
|
"""
|
||||||
|
self.cursor.execute('SELECT plex_id FROM ? WHERE last_sync <> ?',
|
||||||
|
(plex_type, last_sync, ))
|
||||||
|
return (x[0] for x in self.cursor)
|
||||||
|
|
||||||
|
def shows_plex_id_section_id(self):
|
||||||
|
"""
|
||||||
|
Iterator for tuples (plex_id, section_id) of all our TV shows
|
||||||
|
"""
|
||||||
|
self.cursor.execute('SELECT plex_id, section_id FROM show')
|
||||||
|
return self.cursor
|
||||||
|
|
||||||
|
def update_last_sync(self, plex_type, plex_id, last_sync):
|
||||||
|
"""
|
||||||
|
Sets a new timestamp for plex_id
|
||||||
|
"""
|
|
@ -534,7 +534,8 @@ class DownloadGen(object):
|
||||||
|
|
||||||
Yields XML etree children or raises RuntimeError
|
Yields XML etree children or raises RuntimeError
|
||||||
"""
|
"""
|
||||||
def __init__(self, url):
|
def __init__(self, url, args=None):
|
||||||
|
self._args = args or {}
|
||||||
self._url = url
|
self._url = url
|
||||||
self._pos = 0
|
self._pos = 0
|
||||||
self._exhausted = False
|
self._exhausted = False
|
||||||
|
@ -542,17 +543,18 @@ class DownloadGen(object):
|
||||||
self.attrib = deepcopy(self.xml.attrib)
|
self.attrib = deepcopy(self.xml.attrib)
|
||||||
|
|
||||||
def _download_chunk(self):
|
def _download_chunk(self):
|
||||||
args = {
|
self._args.update({
|
||||||
'X-Plex-Container-Size': CONTAINERSIZE,
|
'X-Plex-Container-Size': CONTAINERSIZE,
|
||||||
'X-Plex-Container-Start': self._pos,
|
'X-Plex-Container-Start': self._pos,
|
||||||
'sort': 'id'
|
'sort': 'id', # Entries are sorted by plex_id
|
||||||
}
|
'excludeAllLeaves': 1 # PMS wont attach a first summary child
|
||||||
self.xml = DU().downloadUrl(self._url, parameters=args)
|
})
|
||||||
|
self.xml = DU().downloadUrl(self._url, parameters=self._args)
|
||||||
try:
|
try:
|
||||||
self.xml.attrib
|
self.xml.attrib
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
LOG.error('Error while downloading chunks: %s, args: %s',
|
LOG.error('Error while downloading chunks: %s, args: %s',
|
||||||
self._url, args)
|
self._url, self._args)
|
||||||
raise RuntimeError('Error while downloading chunks for %s'
|
raise RuntimeError('Error while downloading chunks for %s'
|
||||||
% self._url)
|
% self._url)
|
||||||
|
|
||||||
|
@ -582,13 +584,31 @@ class DownloadGen(object):
|
||||||
return self.attrib.get(key, default)
|
return self.attrib.get(key, default)
|
||||||
|
|
||||||
|
|
||||||
class PlexSectionItems(DownloadGen):
|
class SectionItems(DownloadGen):
|
||||||
|
"""
|
||||||
|
Iterator object to get all items of a Plex library section
|
||||||
|
"""
|
||||||
|
def __init__(self, section_id, args):
|
||||||
|
super(SectionItems, self).__init__(
|
||||||
|
'{server}/library/sections/%s/all' % section_id, args)
|
||||||
|
|
||||||
|
|
||||||
|
class Children(DownloadGen):
|
||||||
|
"""
|
||||||
|
Iterator object to get all items of a Plex library section
|
||||||
|
"""
|
||||||
|
def __init__(self, plex_id):
|
||||||
|
super(Children, self).__init__(
|
||||||
|
'{server}/library/metadata/%s/children' % plex_id)
|
||||||
|
|
||||||
|
|
||||||
|
class Leaves(DownloadGen):
|
||||||
"""
|
"""
|
||||||
Iterator object to get all items of a Plex library section
|
Iterator object to get all items of a Plex library section
|
||||||
"""
|
"""
|
||||||
def __init__(self, section_id):
|
def __init__(self, section_id):
|
||||||
super(PlexSectionItems, self).__init__(
|
super(Leaves, self).__init__(
|
||||||
'{server}/library/sections/%s/all' % section_id)
|
'{server}/library/sections/%s/allLeaves' % section_id)
|
||||||
|
|
||||||
|
|
||||||
def DownloadChunks(url):
|
def DownloadChunks(url):
|
||||||
|
|
|
@ -355,6 +355,18 @@ PLEX_TYPE_FROM_WEBSOCKET = {
|
||||||
15: 'playlist'
|
15: 'playlist'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PLEX_TYPE_NUMBER_FROM_PLEX_TYPE = {
|
||||||
|
PLEX_TYPE_MOVIE: 1,
|
||||||
|
PLEX_TYPE_SHOW: 2,
|
||||||
|
PLEX_TYPE_SEASON: 3,
|
||||||
|
PLEX_TYPE_EPISODE: 4,
|
||||||
|
PLEX_TYPE_ARTIST: 8,
|
||||||
|
PLEX_TYPE_ALBUM: 9,
|
||||||
|
PLEX_TYPE_SONG: 10,
|
||||||
|
PLEX_TYPE_CLIP: 12,
|
||||||
|
'playlist': 15
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
KODI_TO_PLEX_ARTWORK = {
|
KODI_TO_PLEX_ARTWORK = {
|
||||||
'poster': 'thumb',
|
'poster': 'thumb',
|
||||||
|
|
Loading…
Reference in a new issue