Rewire library sync, suspend sync during playback

- Fixes #451
This commit is contained in:
croneter 2018-04-17 20:18:25 +02:00
parent 88ef5f9eda
commit 678544d236
7 changed files with 592 additions and 625 deletions

View file

@ -136,6 +136,7 @@ class KodiMonitor(xbmc.Monitor):
LOG.debug("Method: %s Data: %s", method, data) LOG.debug("Method: %s Data: %s", method, data)
if method == "Player.OnPlay": if method == "Player.OnPlay":
state.SUSPEND_SYNC = True
self.PlayBackStart(data) self.PlayBackStart(data)
elif method == "Player.OnStop": elif method == "Player.OnStop":
# Should refresh our video nodes, e.g. on deck # Should refresh our video nodes, e.g. on deck
@ -149,6 +150,7 @@ class KodiMonitor(xbmc.Monitor):
_playback_cleanup(ended=True) _playback_cleanup(ended=True)
else: else:
_playback_cleanup() _playback_cleanup()
state.SUSPEND_SYNC = False
elif method == 'Playlist.OnAdd': elif method == 'Playlist.OnAdd':
self._playlist_onadd(data) self._playlist_onadd(data)
elif method == 'Playlist.OnRemove': elif method == 'Playlist.OnRemove':

View file

@ -12,15 +12,16 @@ import variables as v
############################################################################### ###############################################################################
log = getLogger("PLEX."+__name__) LOG = getLogger("PLEX." + __name__)
############################################################################### ###############################################################################
@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD', @thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD',
'DB_SCAN', 'DB_SCAN',
'STOP_SYNC']) 'STOP_SYNC',
class Process_Fanart_Thread(Thread): 'SUSPEND_SYNC'])
class ThreadedProcessFanart(Thread):
""" """
Threaded download of additional fanart in the background Threaded download of additional fanart in the background
@ -39,21 +40,10 @@ class Process_Fanart_Thread(Thread):
Thread.__init__(self) Thread.__init__(self)
def run(self): def run(self):
"""
Catch all exceptions and log them
"""
try:
self.__run()
except Exception as e:
log.error('Exception %s' % e)
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
def __run(self):
""" """
Do the work Do the work
""" """
log.debug("---===### Starting FanartSync ###===---") LOG.debug("---===### Starting FanartSync ###===---")
stopped = self.stopped stopped = self.stopped
suspended = self.suspended suspended = self.suspended
queue = self.queue queue = self.queue
@ -63,7 +53,7 @@ class Process_Fanart_Thread(Thread):
# Set in service.py # Set in service.py
if stopped(): if stopped():
# Abort was requested while waiting. We should exit # Abort was requested while waiting. We should exit
log.info("---===### Stopped FanartSync ###===---") LOG.info("---===### Stopped FanartSync ###===---")
return return
sleep(1000) sleep(1000)
# grabs Plex item from queue # grabs Plex item from queue
@ -73,15 +63,14 @@ class Process_Fanart_Thread(Thread):
sleep(200) sleep(200)
continue continue
log.debug('Get additional fanart for Plex id %s' % item['plex_id']) LOG.debug('Get additional fanart for Plex id %s', item['plex_id'])
with getattr(itemtypes, with getattr(itemtypes,
v.ITEMTYPE_FROM_PLEXTYPE[item['plex_type']])() as cls: v.ITEMTYPE_FROM_PLEXTYPE[item['plex_type']])() as item_type:
result = cls.getfanart(item['plex_id'], result = item_type.getfanart(item['plex_id'],
refresh=item['refresh']) refresh=item['refresh'])
if result is True: if result is True:
log.debug('Done getting fanart for Plex id %s' LOG.debug('Done getting fanart for Plex id %s', item['plex_id'])
% item['plex_id'])
with plexdb.Get_Plex_DB() as plex_db: with plexdb.Get_Plex_DB() as plex_db:
plex_db.set_fanart_synched(item['plex_id']) plex_db.set_fanart_synched(item['plex_id'])
queue.task_done() queue.task_done()
log.debug("---===### Stopped FanartSync ###===---") LOG.debug("---===### Stopped FanartSync ###===---")

View file

@ -11,20 +11,22 @@ import sync_info
############################################################################### ###############################################################################
log = getLogger("PLEX."+__name__) LOG = getLogger("PLEX." + __name__)
############################################################################### ###############################################################################
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD', 'STOP_SYNC']) @thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
class Threaded_Get_Metadata(Thread): 'STOP_SYNC',
'SUSPEND_SYNC'])
class ThreadedGetMetadata(Thread):
""" """
Threaded download of Plex XML metadata for a certain library item. Threaded download of Plex XML metadata for a certain library item.
Fills the out_queue with the downloaded etree XML objects Fills the out_queue with the downloaded etree XML objects
Input: Input:
queue Queue.Queue() object that you'll need to fill up queue Queue.Queue() object that you'll need to fill up
with Plex itemIds with plex_ids
out_queue Queue() object where this thread will store out_queue Queue() object where this thread will store
the downloaded metadata XMLs as etree objects the downloaded metadata XMLs as etree objects
""" """
@ -60,21 +62,10 @@ class Threaded_Get_Metadata(Thread):
self.out_queue.task_done() self.out_queue.task_done()
def run(self): def run(self):
"""
Catch all exceptions and log them
"""
try:
self.__run()
except Exception as e:
log.error('Exception %s' % e)
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
def __run(self):
""" """
Do the work Do the work
""" """
log.debug('Starting get metadata thread') LOG.debug('Starting get metadata thread')
# cache local variables because it's faster # cache local variables because it's faster
queue = self.queue queue = self.queue
out_queue = self.out_queue out_queue = self.out_queue
@ -88,11 +79,11 @@ class Threaded_Get_Metadata(Thread):
sleep(20) sleep(20)
continue continue
# Download Metadata # Download Metadata
xml = GetPlexMetadata(item['itemId']) xml = GetPlexMetadata(item['plex_id'])
if xml is None: if xml is None:
# Did not receive a valid XML - skip that item for now # Did not receive a valid XML - skip that item for now
log.error("Could not get metadata for %s. Skipping that item " LOG.error("Could not get metadata for %s. Skipping that item "
"for now" % item['itemId']) "for now", item['plex_id'])
# Increase BOTH counters - since metadata won't be processed # Increase BOTH counters - since metadata won't be processed
with sync_info.LOCK: with sync_info.LOCK:
sync_info.GET_METADATA_COUNT += 1 sync_info.GET_METADATA_COUNT += 1
@ -100,21 +91,21 @@ class Threaded_Get_Metadata(Thread):
queue.task_done() queue.task_done()
continue continue
elif xml == 401: elif xml == 401:
log.error('HTTP 401 returned by PMS. Too much strain? ' LOG.error('HTTP 401 returned by PMS. Too much strain? '
'Cancelling sync for now') 'Cancelling sync for now')
window('plex_scancrashed', value='401') window('plex_scancrashed', value='401')
# Kill remaining items in queue (for main thread to cont.) # Kill remaining items in queue (for main thread to cont.)
queue.task_done() queue.task_done()
break break
item['XML'] = xml item['xml'] = xml
if item.get('get_children') is True: if item.get('get_children') is True:
children_xml = GetAllPlexChildren(item['itemId']) children_xml = GetAllPlexChildren(item['plex_id'])
try: try:
children_xml[0].attrib children_xml[0].attrib
except (TypeError, IndexError, AttributeError): except (TypeError, IndexError, AttributeError):
log.error('Could not get children for Plex id %s' LOG.error('Could not get children for Plex id %s',
% item['itemId']) item['plex_id'])
item['children'] = [] item['children'] = []
else: else:
item['children'] = children_xml item['children'] = children_xml
@ -128,4 +119,4 @@ class Threaded_Get_Metadata(Thread):
queue.task_done() queue.task_done()
# Empty queue in case PKC was shut down (main thread hangs otherwise) # Empty queue in case PKC was shut down (main thread hangs otherwise)
self.terminate_now() self.terminate_now()
log.debug('Get metadata thread terminated') LOG.debug('Get metadata thread terminated')

View file

@ -10,13 +10,15 @@ import itemtypes
import sync_info import sync_info
############################################################################### ###############################################################################
log = getLogger("PLEX."+__name__) LOG = getLogger("PLEX." + __name__)
############################################################################### ###############################################################################
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD', 'STOP_SYNC']) @thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
class Threaded_Process_Metadata(Thread): 'STOP_SYNC',
'SUSPEND_SYNC'])
class ThreadedProcessMetadata(Thread):
""" """
Not yet implemented for more than 1 thread - if ever. Only to be called by Not yet implemented for more than 1 thread - if ever. Only to be called by
ONE thread! ONE thread!
@ -25,12 +27,12 @@ class Threaded_Process_Metadata(Thread):
Input: Input:
queue: Queue.Queue() object that you'll need to fill up with queue: Queue.Queue() object that you'll need to fill up with
the downloaded XML eTree objects the downloaded XML eTree objects
item_type: as used to call functions in itemtypes.py e.g. 'Movies' => item_class: as used to call functions in itemtypes.py e.g. 'Movies' =>
itemtypes.Movies() itemtypes.Movies()
""" """
def __init__(self, queue, item_type): def __init__(self, queue, item_class):
self.queue = queue self.queue = queue
self.item_type = item_type self.item_class = item_class
Thread.__init__(self) Thread.__init__(self)
def terminate_now(self): def terminate_now(self):
@ -49,23 +51,12 @@ class Threaded_Process_Metadata(Thread):
self.queue.task_done() self.queue.task_done()
def run(self): def run(self):
"""
Catch all exceptions and log them
"""
try:
self.__run()
except Exception as e:
log.error('Exception %s' % e)
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
def __run(self):
""" """
Do the work Do the work
""" """
log.debug('Processing thread started') LOG.debug('Processing thread started')
# Constructs the method name, e.g. itemtypes.Movies # Constructs the method name, e.g. itemtypes.Movies
item_fct = getattr(itemtypes, self.item_type) item_fct = getattr(itemtypes, self.item_class)
# cache local variables because it's faster # cache local variables because it's faster
queue = self.queue queue = self.queue
stopped = self.stopped stopped = self.stopped
@ -79,24 +70,19 @@ class Threaded_Process_Metadata(Thread):
continue continue
# Do the work # Do the work
item_method = getattr(item_class, item['method']) item_method = getattr(item_class, item['method'])
if item.get('children') is not None: if item.get('children'):
item_method(item['XML'][0], item_method(item['xml'][0],
viewtag=item['viewName'], viewtag=item['view_name'],
viewid=item['viewId'], viewid=item['view_id'],
children=item['children']) children=item['children'])
else: else:
item_method(item['XML'][0], item_method(item['xml'][0],
viewtag=item['viewName'], viewtag=item['view_name'],
viewid=item['viewId']) viewid=item['view_id'])
# Keep track of where we are at # Keep track of where we are at
try:
log.debug('found child: %s'
% item['children'].attrib)
except:
pass
with sync_info.LOCK: with sync_info.LOCK:
sync_info.PROCESS_METADATA_COUNT += 1 sync_info.PROCESS_METADATA_COUNT += 1
sync_info.PROCESSING_VIEW_NAME = item['title'] sync_info.PROCESSING_VIEW_NAME = item['title']
queue.task_done() queue.task_done()
self.terminate_now() self.terminate_now()
log.debug('Processing thread terminated') LOG.debug('Processing thread terminated')

View file

@ -2,14 +2,14 @@
from logging import getLogger from logging import getLogger
from threading import Thread, Lock from threading import Thread, Lock
from xbmc import sleep, Player from xbmc import sleep
from xbmcgui import DialogProgressBG from xbmcgui import DialogProgressBG
from utils import thread_methods, language as lang from utils import thread_methods, language as lang
############################################################################### ###############################################################################
log = getLogger("PLEX."+__name__) LOG = getLogger("PLEX." + __name__)
GET_METADATA_COUNT = 0 GET_METADATA_COUNT = 0
PROCESS_METADATA_COUNT = 0 PROCESS_METADATA_COUNT = 0
@ -19,8 +19,10 @@ LOCK = Lock()
############################################################################### ###############################################################################
@thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD', 'STOP_SYNC']) @thread_methods(add_stops=['SUSPEND_LIBRARY_THREAD',
class Threaded_Show_Sync_Info(Thread): 'STOP_SYNC',
'SUSPEND_SYNC'])
class ThreadedShowSyncInfo(Thread):
""" """
Threaded class to show the Kodi statusbar of the metadata download. Threaded class to show the Kodi statusbar of the metadata download.
@ -34,38 +36,26 @@ class Threaded_Show_Sync_Info(Thread):
Thread.__init__(self) Thread.__init__(self)
def run(self): def run(self):
"""
Catch all exceptions and log them
"""
try:
self.__run()
except Exception as e:
log.error('Exception %s' % e)
import traceback
log.error("Traceback:\n%s" % traceback.format_exc())
def __run(self):
""" """
Do the work Do the work
""" """
log.debug('Show sync info thread started') LOG.debug('Show sync info thread started')
# cache local variables because it's faster # cache local variables because it's faster
total = self.total total = self.total
dialog = DialogProgressBG('dialoglogProgressBG') dialog = DialogProgressBG('dialoglogProgressBG')
dialog.create("%s %s: %s %s" dialog.create("%s %s: %s %s"
% (lang(39714), self.item_type, str(total), lang(39715))) % (lang(39714), self.item_type, str(total), lang(39715)))
player = Player()
total = 2 * total total = 2 * total
totalProgress = 0 total_progress = 0
while self.stopped() is False and not player.isPlaying(): while not self.stopped():
with LOCK: with LOCK:
get_progress = GET_METADATA_COUNT get_progress = GET_METADATA_COUNT
process_progress = PROCESS_METADATA_COUNT process_progress = PROCESS_METADATA_COUNT
viewName = PROCESSING_VIEW_NAME view_name = PROCESSING_VIEW_NAME
totalProgress = get_progress + process_progress total_progress = get_progress + process_progress
try: try:
percentage = int(float(totalProgress) / float(total)*100.0) percentage = int(float(total_progress) / float(total)*100.0)
except ZeroDivisionError: except ZeroDivisionError:
percentage = 0 percentage = 0
dialog.update(percentage, dialog.update(percentage,
@ -74,8 +64,8 @@ class Threaded_Show_Sync_Info(Thread):
lang(39712), lang(39712),
process_progress, process_progress,
lang(39713), lang(39713),
viewName)) view_name))
# Sleep for x milliseconds # Sleep for x milliseconds
sleep(200) sleep(200)
dialog.close() dialog.close()
log.debug('Show sync info thread terminated') LOG.debug('Show sync info thread terminated')

File diff suppressed because it is too large Load diff

View file

@ -10,6 +10,9 @@ STOP_PKC = False
SUSPEND_LIBRARY_THREAD = False SUSPEND_LIBRARY_THREAD = False
# Set if user decided to cancel sync # Set if user decided to cancel sync
STOP_SYNC = False STOP_SYNC = False
# Set e.g. during media playback if PKC should not do any syncs. Will NOT
# suspend synching of playstate progress
SUSPEND_SYNC = False
# Could we access the paths? # Could we access the paths?
PATH_VERIFIED = False PATH_VERIFIED = False
# Set if a Plex-Kodi DB sync is being done - along with # Set if a Plex-Kodi DB sync is being done - along with
@ -36,8 +39,6 @@ FORCE_RELOAD_SKIN = True
# Stemming from the PKC settings.xml # Stemming from the PKC settings.xml
# Shall we show Kodi dialogs when synching? # Shall we show Kodi dialogs when synching?
SYNC_DIALOG = True SYNC_DIALOG = True
# Have we already checked the Kodi DB on consistency?
KODI_DB_CHECKED = False
# Is synching of Plex music enabled? # Is synching of Plex music enabled?
ENABLE_MUSIC = True ENABLE_MUSIC = True
# How often shall we sync? # How often shall we sync?