Rewire llibrary sync, part 3

This commit is contained in:
croneter 2018-10-21 16:56:13 +02:00
parent 3f4c43e373
commit e935b7c97b
12 changed files with 564 additions and 155 deletions

View file

@ -48,7 +48,7 @@ class ItemBase(object):
self.last_sync = last_sync
self.artwork = artwork.Artwork()
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.kodicursor = kodi_db.cursor if kodi_db else None
self.plex_db = plex_db

View file

@ -11,13 +11,23 @@ LOG = getLogger('PLEX.tvshows')
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
all associated entries from the Kodi DB.
"""
entry = self.plex_db.getItem_byId(plex_id)
if entry is None:
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]
@ -144,13 +154,12 @@ class Show(ItemBase, TvShowMixin):
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")
LOG.error("Cannot parse XML data for TV show: %s", xml.attrib)
return
update_item = True
entry = self.plex_db.getItem_byId(plex_id)
show = self.plex_db.show(plex_id)
try:
kodi_id = entry[0]
path_id = entry[2]
kodi_id = show[3]
kodi_pathid = show[4]
except TypeError:
update_item = False
query = 'SELECT COALESCE(MAX(idShow), 0) FROM tvshow'
@ -158,8 +167,8 @@ class Show(ItemBase, TvShowMixin):
kodi_id = self.kodicursor.fetchone()[0] + 1
else:
# Verification the item is still in Kodi
query = 'SELECT * FROM tvshow WHERE idShow = ?'
self.kodicursor.execute(query, (kodi_id,))
self.kodicursor.execute('SELECT * FROM tvshow WHERE idShow = ?',
(kodi_id,))
try:
self.kodicursor.fetchone()[0]
except TypeError:
@ -193,7 +202,7 @@ class Show(ItemBase, TvShowMixin):
# Do NOT set a parent id because addon-path cannot be "stacked"
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(),
id_parent_path=toppathid)
# UPDATE THE TVSHOW #####
@ -238,7 +247,7 @@ class Show(ItemBase, TvShowMixin):
plex_id, api.title())
# Link the path
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
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.extend([i for _, i in api.collection_list()])
self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_SHOW, tags)
self.plex_db.addReference(plex_id,
v.PLEX_TYPE_SHOW,
kodi_id,
v.KODI_TYPE_SHOW,
kodi_pathid=path_id,
checksum=api.checksum(),
view_id=section_id,
last_sync=self.last_sync)
self.plex_db.add_reference(plex_type=v.PLEX_TYPE_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 Season(ItemBase, TvShowMixin):
@ -304,34 +312,30 @@ class Season(ItemBase, TvShowMixin):
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')
LOG.error('Error getting plex_id for season, skipping: %s',
xml.attrib)
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:
show_id = entry[0]
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(show_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
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)
if update_item:
self.plex_db.updateReference(plex_id, api.checksum())
else:
self.plex_db.addReference(plex_id,
v.PLEX_TYPE_SEASON,
kodi_id,
v.KODI_TYPE_SEASON,
parent_id=show_id,
view_id=section_id,
checksum=api.checksum(),
last_sync=self.last_sync)
self.plex_db.add_reference(plex_type=v.PLEX_TYPE_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 Episode(ItemBase, TvShowMixin):
@ -345,13 +349,14 @@ class Episode(ItemBase, TvShowMixin):
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')
LOG.error('Error getting plex_id for episode, skipping: %s',
xml.attrib)
return
entry = self.plex_db.getItem_byId(plex_id)
entry = self.plex_db.item_by_id(plex_id)
try:
kodi_id = entry[0]
old_file_id = entry[1]
path_id = entry[2]
old_kodi_fileid = entry[1]
kodi_pathid = entry[2]
except TypeError:
update_item = False
query = 'SELECT COALESCE(MAX(idEpisode), 0) FROM episode'
@ -359,7 +364,7 @@ class Episode(ItemBase, TvShowMixin):
kodi_id = self.kodicursor.fetchone()[0] + 1
else:
# 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, ))
try:
self.kodicursor.fetchone()[0]
@ -373,23 +378,22 @@ class Episode(ItemBase, TvShowMixin):
director = api.list_to_string(peoples['Director'])
writer = api.list_to_string(peoples['Writer'])
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:
season = -1
if episode is None:
episode = -1
if season_no is None:
season_no = -1
if episode_no is None:
episode_no = -1
airs_before_season = "-1"
airs_before_episode = "-1"
# Get season id
show = self.plex_db.getItem_byId(series_id)
show = self.plex_db.show(show_id)
try:
show_id = show[0]
grandparent_id = show[3]
except TypeError:
LOG.error("Parent tvshow now found, skip item")
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 #####
do_indirect = not state.DIRECT_PATHS
@ -407,29 +411,31 @@ class Episode(ItemBase, TvShowMixin):
filename = playurl.rsplit("/", 1)[1]
path = playurl.replace(filename, "")
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)
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, series_id)
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
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
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 #####
if update_item:
LOG.info("UPDATE episode plex_id: %s, Title: %s",
plex_id, api.title())
if file_id != old_file_id:
self.kodi_db.remove_file(old_file_id)
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,
@ -456,12 +462,10 @@ class Episode(ItemBase, TvShowMixin):
'''
self.kodicursor.execute(
query, (api.title(), api.plot(), ratingid, writer,
api.premiere_date(), api.runtime(), director, season,
episode, api.title(), airs_before_season,
airs_before_episode, playurl, path_id, file_id,
season_id, userdata['UserRating'], kodi_id))
# Update parentid reference
self.plex_db.updateParentId(plex_id, season_id)
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:
@ -492,11 +496,11 @@ class Episode(ItemBase, TvShowMixin):
?, ?)
'''
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,
season, episode, api.title(), show_id,
season_no, episode_no, api.title(), grandparent_id,
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,
v.KODI_TYPE_EPISODE,
@ -506,8 +510,8 @@ class Episode(ItemBase, TvShowMixin):
v.KODI_TYPE_EPISODE,
self.kodicursor)
streams = api.mediastreams()
self.kodi_db.modify_streams(file_id, streams, api.runtime())
self.kodi_db.set_resume(file_id,
self.kodi_db.modify_streams(kodi_fileid, streams, api.runtime())
self.kodi_db.set_resume(kodi_fileid,
api.resume_point(),
api.runtime(),
userdata['PlayCount'],
@ -519,25 +523,27 @@ class Episode(ItemBase, TvShowMixin):
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, series_id, plex_id, v.PLEX_TYPE_EPISODE,
% (path, show_id, plex_id, v.PLEX_TYPE_EPISODE,
filename))
path_id = self.kodi_db.add_video_path(path)
file_id = self.kodi_db.add_file(filename,
path_id,
api.date_created())
self.kodi_db.set_resume(file_id,
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.addReference(plex_id,
v.PLEX_TYPE_EPISODE,
kodi_id,
v.KODI_TYPE_EPISODE,
kodi_file_id=file_id,
kodi_pathid=path_id,
parent_id=season_id,
checksum=api.checksum(),
view_id=section_id,
last_sync=self.last_sync)
self.plex_db.add_reference(plex_type=v.PLEX_TYPE_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

@ -30,9 +30,10 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
self.process_thread = None
self.last_sync = None
self.plex_db = None
self.plex_type = None
super(FullSync, self).__init__()
def process_item(self, xml_item, get_children):
def process_item(self, xml_item):
"""
Processes a single library item
"""
@ -42,7 +43,7 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
backgroundthread.BGThreader.addTask(
GetMetadataTask().setup(self.queue,
plex_id,
get_children))
self.get_children))
else:
if self.plex_db.check_checksum(
int('%s%s' % (xml_item['ratingKey'],
@ -50,20 +51,29 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
backgroundthread.BGThreader.addTask(
GetMetadataTask().setup(self.queue,
plex_id,
get_children))
self.get_children))
else:
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
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])
sections = (x for x in sections.SECTIONS if x['kodi_type'] == kind[1])
LOG.debug('Start processing %ss', self.plex_type)
sections = (x for x in sections.SECTIONS
if x['plex_type'] == self.plex_type)
for section in sections:
LOG.debug('Processing library section %s', section)
if self.isCanceled():
@ -71,10 +81,12 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
if not self.install_sync_done:
state.PATH_VERIFIED = False
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
queue_info = process_metadata.InitNewSection(
kind[2],
self.context,
utils.cast(int, iterator.get('totalSize', 0)),
utils.cast(unicode, iterator.get('librarySectionTitle')),
section['id'])
@ -82,32 +94,42 @@ class FullSync(backgroundthread.KillableThread, common.libsync_mixin):
for xml_item in iterator:
if self.isCanceled():
return False
self.process_item(xml_item, kind[3])
self.process_item(xml_item)
except RuntimeError:
LOG.error('Could not entirely process section %s', section)
continue
LOG.debug('Finished processing %s', kind[0])
LOG.debug('Finished processing %ss', self.plex_type)
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)
t = process_metadata.ProcessMetadata(self.queue, self.last_sync)
t.start()
kinds = [
('movies', v.KODI_TYPE_MOVIE, itemtypes.Movie, False),
('tv shows', v.KODI_TYPE_SHOW, itemtypes.Show, False),
('tv seasons', v.KODI_TYPE_SEASON, itemtypes.Season, False),
('tv shows', v.KODI_TYPE_SHOW, itemtypes.Show, False),
(v.PLEX_TYPE_MOVIE, itemtypes.Movie, False),
(v.PLEX_TYPE_SHOW, itemtypes.Show, False),
(v.PLEX_TYPE_SEASON, itemtypes.Season, 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:
if self.isCanceled() or not self.process_kind(kind):
return False
for kind in kinds:
# Setup our variables
self.plex_type = kind[0]
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
common.update_kodi_library(video=True, music=state.ENABLE_MUSIC)

View file

@ -54,10 +54,10 @@ def sync_from_pms():
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
# 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:
for section in sections:
_process_section(section,
@ -71,8 +71,8 @@ def sync_from_pms():
# Section has been deleted on the PMS
delete_sections(old_sections)
# update sections for all:
with plexdb.Get_Plex_DB() as plex_db:
SECTIONS = plex_db.list_section_info()
with plexdb.PlexDB() as plex_db:
SECTIONS = [plex_db.section_infos()]
utils.window('Plex.nodes.total', str(totalnodes))
LOG.info("Finished processing library sections: %s", SECTIONS)
return True
@ -209,7 +209,7 @@ def delete_sections(old_sections):
sound=False)
video_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]
LOG.info("Removing entire Plex library sections: %s", old_sections)
with kodidb.GetKodiDB() as kodi_db:

View file

@ -214,20 +214,6 @@ class LibrarySync(Thread):
"""
with plexdb.Get_Plex_DB() as plex_db:
# 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('''
CREATE TABLE IF NOT EXISTS sections(
section_id INTEGER PRIMARY KEY,
@ -236,6 +222,88 @@ class LibrarySync(Thread):
kodi_tagid 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('''
CREATE TABLE IF NOT EXISTS playlists(
plex_id INTEGER PRIMARY KEY ASC,

View file

@ -95,17 +95,17 @@ def _write_playlist_to_file(playlist, xml):
api = API(element)
append_season_episode = False
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:
season_id = int(season_id)
episode_id = int(episode_id)
season_no = int(season_no)
episode_no = int(episode_no)
except ValueError:
pass
else:
append_season_episode = True
if append_season_episode:
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()))
else:
# Only append the TV show name

View file

@ -260,9 +260,9 @@ class API(object):
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):
"""
@ -639,14 +639,14 @@ class API(object):
"""
Returns the 'parentRatingKey' as a string or None
"""
return self.item.get('parentRatingKey')
return cast(int, self.item.get('parentRatingKey'))
def grandparent_id(self):
"""
Returns the ratingKey for the corresponding grandparent, e.g. a TV show
for episodes, or None
"""
return self.item.get('grandparentRatingKey')
return cast(int, self.item.get('grandparentRatingKey'))
def grandparent_title(self):
"""
@ -661,16 +661,18 @@ class API(object):
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 season, Plex: 'parentIndex'
Episode number, Plex: 'index'
]
"""
return (self.item.get('grandparentRatingKey'),
self.item.get('grandparentTitle'),
self.item.get('parentIndex'),
self.item.get('index'))
return (cast(int, self.item.get('grandparentRatingKey')),
cast(int, self.item.get('parentRatingKey')),
cast(unicode, self.item.get('grandparentTitle')),
cast(int, self.item.get('parentIndex')),
cast(int, self.item.get('index')))
@staticmethod
def attach_plex_token_to_url(url):
@ -1609,7 +1611,7 @@ class API(object):
if typus == v.PLEX_TYPE_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)
episode = -1 if episode is None else int(episode)
metadata['episode'] = episode

View 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)

View 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()

View 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
"""

View file

@ -534,7 +534,8 @@ class DownloadGen(object):
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._pos = 0
self._exhausted = False
@ -542,17 +543,18 @@ class DownloadGen(object):
self.attrib = deepcopy(self.xml.attrib)
def _download_chunk(self):
args = {
self._args.update({
'X-Plex-Container-Size': CONTAINERSIZE,
'X-Plex-Container-Start': self._pos,
'sort': 'id'
}
self.xml = DU().downloadUrl(self._url, parameters=args)
'sort': 'id', # Entries are sorted by plex_id
'excludeAllLeaves': 1 # PMS wont attach a first summary child
})
self.xml = DU().downloadUrl(self._url, parameters=self._args)
try:
self.xml.attrib
except AttributeError:
LOG.error('Error while downloading chunks: %s, args: %s',
self._url, args)
self._url, self._args)
raise RuntimeError('Error while downloading chunks for %s'
% self._url)
@ -582,13 +584,31 @@ class DownloadGen(object):
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
"""
def __init__(self, section_id):
super(PlexSectionItems, self).__init__(
'{server}/library/sections/%s/all' % section_id)
super(Leaves, self).__init__(
'{server}/library/sections/%s/allLeaves' % section_id)
def DownloadChunks(url):

View file

@ -355,6 +355,18 @@ PLEX_TYPE_FROM_WEBSOCKET = {
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 = {
'poster': 'thumb',