From 0db29dd568c8b101d657bfe091f12cadcbe8fb74 Mon Sep 17 00:00:00 2001 From: croneter Date: Wed, 7 Nov 2018 10:37:32 +0100 Subject: [PATCH] Get movie set information asynchronously --- resources/lib/itemtypes/movies.py | 42 +++++++------ resources/lib/library_sync/full_sync.py | 5 +- resources/lib/library_sync/get_metadata.py | 70 ++++++++++++++++++++-- resources/lib/plex_api.py | 18 ++++-- 4 files changed, 107 insertions(+), 28 deletions(-) diff --git a/resources/lib/itemtypes/movies.py b/resources/lib/itemtypes/movies.py index f340216e..4c654624 100644 --- a/resources/lib/itemtypes/movies.py +++ b/resources/lib/itemtypes/movies.py @@ -173,28 +173,36 @@ class Movie(ItemBase): self.kodi_db.modify_studios(kodi_id, v.KODI_TYPE_MOVIE, studios) tags = [section_name] if collections: - collections_match = api.collections_match() for plex_set_id, set_name in collections: tags.append(set_name) # Add any sets from Plex collection tags kodi_set_id = self.kodi_db.create_collection(set_name) self.kodi_db.assign_collection(kodi_set_id, kodi_id) - for index, coll_plex_id in collections_match: - # Get Plex artwork for collections - a pain - if index == plex_set_id: - set_xml = PF.GetPlexMetadata(coll_plex_id) - try: - set_xml.attrib - except AttributeError: - LOG.error('Could not get set metadata %s', - coll_plex_id) - continue - set_api = API(set_xml[0]) - artwork.modify_artwork(set_api.artwork(), - kodi_set_id, - v.KODI_TYPE_SET, - self.kodicursor) - break + if children is None: + # e.g. when added via websocket + LOG.debug('Costly looking up Plex collection %s: %s', + plex_set_id, set_name) + for index, coll_plex_id in api.collections_match(): + # Get Plex artwork for collections - a pain + if index == plex_set_id: + set_xml = PF.GetPlexMetadata(coll_plex_id) + try: + set_xml.attrib + except AttributeError: + LOG.error('Could not get set metadata %s', + coll_plex_id) + continue + set_api = API(set_xml[0]) + break + elif plex_set_id in children: + # Provided by get_metadata thread + set_api = API(children[plex_set_id][0]) + else: + continue + artwork.modify_artwork(set_api.artwork(), + kodi_set_id, + v.KODI_TYPE_SET, + self.kodicursor) self.kodi_db.modify_tags(kodi_id, v.KODI_TYPE_MOVIE, tags) # Process playstate self.kodi_db.set_resume(file_id, diff --git a/resources/lib/library_sync/full_sync.py b/resources/lib/library_sync/full_sync.py index 38fbbebe..2f3e8586 100644 --- a/resources/lib/library_sync/full_sync.py +++ b/resources/lib/library_sync/full_sync.py @@ -3,7 +3,7 @@ from __future__ import absolute_import, division, unicode_literals from logging import getLogger -from .get_metadata import GetMetadataTask +from .get_metadata import GetMetadataTask, reset_collections from . import common, process_metadata, sections from .. import utils, backgroundthread, variables as v, state from .. import plex_functions as PF, itemtypes @@ -56,7 +56,7 @@ class FullSync(common.libsync_mixin): self.current_sync) return task = GetMetadataTask() - task.setup(self.queue, plex_id, self.get_children) + task.setup(self.queue, plex_id, self.plex_type, self.get_children) self.threader.addTask(task) def process_delete(self): @@ -119,6 +119,7 @@ class FullSync(common.libsync_mixin): continue LOG.debug('Waiting for processing thread to finish section') self.queue.join() + reset_collections() try: # Sync playstate of every item iterator = PF.SectionItems(section['section_id'], diff --git a/resources/lib/library_sync/get_metadata.py b/resources/lib/library_sync/get_metadata.py index 5d421d4d..4de1388d 100644 --- a/resources/lib/library_sync/get_metadata.py +++ b/resources/lib/library_sync/get_metadata.py @@ -3,13 +3,28 @@ from __future__ import absolute_import, division, unicode_literals from logging import getLogger from . import common -from .. import plex_functions as PF, backgroundthread, utils +from ..plex_api import API +from .. import plex_functions as PF, backgroundthread, utils, variables as v -############################################################################### LOG = getLogger("PLEX." + __name__) -############################################################################### +LOCK = backgroundthread.threading.Lock() +# List of tuples: (collection index [as in an item's metadata with "Collection +# id"], collection plex id) +COLLECTION_MATCH = None +# Dict with entries of the form : +COLLECTION_XMLS = {} + + +def reset_collections(): + """ + Collections seem unique to Plex sections + """ + global LOCK, COLLECTION_MATCH, COLLECTION_XMLS + with LOCK: + COLLECTION_MATCH = None + COLLECTION_XMLS = {} class GetMetadataTask(backgroundthread.Task, common.libsync_mixin): @@ -21,11 +36,47 @@ class GetMetadataTask(backgroundthread.Task, common.libsync_mixin): queue Queue.Queue() object where this thread will store the downloaded metadata XMLs as etree objects """ - def setup(self, queue, plex_id, get_children=False): + def setup(self, queue, plex_id, plex_type, get_children=False): self.queue = queue self.plex_id = plex_id + self.plex_type = plex_type self.get_children = get_children + def _collections(self, item): + global COLLECTION_MATCH, COLLECTION_XMLS + api = API(item['xml'][0]) + if not COLLECTION_MATCH: + COLLECTION_MATCH = PF.collections(api.library_section_id()) + if not COLLECTION_MATCH: + LOG.error('Could not download collections') + return + # Extract what we need to know + COLLECTION_MATCH = \ + [(utils.cast(int, x.get('index')), + utils.cast(int, x.get('ratingKey'))) for x in COLLECTION_MATCH] + item['children'] = {} + for plex_set_id, set_name in api.collection_list(): + if self.isCanceled(): + return + if plex_set_id not in COLLECTION_XMLS: + # Get Plex metadata for collections - a pain + for index, collection_plex_id in COLLECTION_MATCH: + if index == plex_set_id: + collection_xml = PF.GetPlexMetadata(collection_plex_id) + try: + collection_xml[0].attrib + except (TypeError, IndexError, AttributeError): + LOG.error('Could not get collection %s %s', + collection_plex_id, set_name) + continue + COLLECTION_XMLS[plex_set_id] = collection_xml + break + else: + LOG.error('Did not find Plex collection %s %s', + plex_set_id, set_name) + continue + item['children'][plex_set_id] = COLLECTION_XMLS[plex_set_id] + def run(self): """ Do the work @@ -47,6 +98,17 @@ class GetMetadataTask(backgroundthread.Task, common.libsync_mixin): 'Cancelling sync for now') utils.window('plex_scancrashed', value='401') return + if not self.isCanceled() and self.plex_type == v.PLEX_TYPE_MOVIE: + # Check for collections/sets + collections = False + for child in item['xml'][0]: + if child.tag == 'Collection': + collections = True + break + if collections: + global LOCK + with LOCK: + self._collections(item) if not self.isCanceled() and self.get_children: children_xml = PF.GetAllPlexChildren(self.plex_id) try: diff --git a/resources/lib/plex_api.py b/resources/lib/plex_api.py index 2bada185..4bc0245f 100644 --- a/resources/lib/plex_api.py +++ b/resources/lib/plex_api.py @@ -67,6 +67,7 @@ class API(object): # which media part in the XML response shall we look at? self.part = 0 self.mediastream = None + self.collections = None self.server = utils.window('pms_server') def set_part_number(self, number=None): @@ -345,7 +346,8 @@ class API(object): collections = [] for child in self.item: if child.tag == 'Collection': - collections.append((child.get('id'), child.get('tag'))) + collections.append((cast(int, child.get('id')), + child.get('tag'))) return collections def people(self): @@ -1270,10 +1272,16 @@ class API(object): current item's Plex library sectin Pass in the collection id of e.g. the movie's metadata """ - xml = PF.collections(self.library_section_id()) - if xml is None: - return [] - return [(i.get('index'), i.get('ratingKey')) for i in xml] + if self.collections is None: + self.collections = PF.collections(self.library_section_id()) + if self.collections is None: + LOG.error('Could not download collections for %s', + self.library_section_id()) + return [] + self.collections = \ + [(utils.cast(int, x.get('index')), + utils.cast(int, x.get('ratingKey'))) for x in self.collections] + return self.collections def set_artwork(self): """