# -*- coding: utf-8 -*- from __future__ import absolute_import, division, unicode_literals from logging import getLogger from . import additional_metadata_tmdb from ..plex_db import PlexDB from .. import backgroundthread, utils from .. import variables as v, app logger = getLogger('PLEX.sync.metadata') BATCH_SIZE = 500 SUPPORTED_METADATA = { v.PLEX_TYPE_MOVIE: ( ('missing_trailers', additional_metadata_tmdb.process_trailers), ('missing_fanart', additional_metadata_tmdb.process_fanart), ), v.PLEX_TYPE_SHOW: ( ('missing_fanart', additional_metadata_tmdb.process_fanart), ), } class ProcessingNotDone(Exception): """Exception to detect whether we've completed our sync and did not have to abort or suspend.""" pass def processing_is_activated(item_getter): """Checks the PKC settings whether processing is even activated.""" if item_getter == 'missing_fanart': return utils.settings('FanartTV') == 'true' return True class MetadataThread(backgroundthread.KillableThread): """This will potentially take hours!""" def __init__(self, callback, refresh=False): self.callback = callback self.refresh = refresh super(MetadataThread, self).__init__() def should_suspend(self): return self._suspended or app.APP.is_playing_video def _process_in_batches(self, item_getter, processor, plex_type): offset = 0 while True: with PlexDB() as plexdb: # Keep DB connection open only for a short period of time! if self.refresh: # Simply grab every single item if we want to refresh func = plexdb.every_plex_id else: func = getattr(plexdb, item_getter) batch = list(func(plex_type, offset, BATCH_SIZE)) for plex_id in batch: # Do the actual, time-consuming processing if self.should_suspend() or self.should_cancel(): raise ProcessingNotDone() processor(plex_id, plex_type, self.refresh) if len(batch) < BATCH_SIZE: break offset += BATCH_SIZE def _loop(self): for plex_type in SUPPORTED_METADATA: for item_getter, processor in SUPPORTED_METADATA[plex_type]: if not processing_is_activated(item_getter): continue self._process_in_batches(item_getter, processor, plex_type) def _run(self): finished = False while not finished: try: self._loop() except ProcessingNotDone: finished = False else: finished = True if self.wait_while_suspended(): break logger.info('MetadataThread finished completely: %s', finished) self.callback(finished) def run(self): logger.info('Starting MetadataThread') app.APP.register_metadata_thread(self) try: self._run() except Exception: utils.ERROR(notify=True) finally: app.APP.deregister_metadata_thread(self) class ProcessMetadataTask(backgroundthread.Task): """This task will also be executed while library sync is suspended!""" def setup(self, plex_id, plex_type, refresh=False): self.plex_id = plex_id self.plex_type = plex_type self.refresh = refresh def run(self): if self.plex_type not in SUPPORTED_METADATA: return for item_getter, processor in SUPPORTED_METADATA[self.plex_type]: if self.should_cancel(): # Just don't process this item at all. Next full sync will # take care of it return if not processing_is_activated(item_getter): continue processor(self.plex_id, self.plex_type, self.refresh)