Zentrally register threads and introduce a way to wait for their suspension
This commit is contained in:
parent
63201db07d
commit
1787e51c7c
22 changed files with 381 additions and 254 deletions
|
@ -10,12 +10,22 @@ LOG = getLogger('PLEX.account')
|
||||||
|
|
||||||
class Account(object):
|
class Account(object):
|
||||||
def __init__(self, entrypoint=False):
|
def __init__(self, entrypoint=False):
|
||||||
|
self.plex_login = None
|
||||||
|
self.plex_login_id = None
|
||||||
|
self.plex_username = None
|
||||||
|
self.plex_user_id = None
|
||||||
|
self.plex_token = None
|
||||||
|
self.pms_token = None
|
||||||
|
self.avatar = None
|
||||||
|
self.myplexlogin = None
|
||||||
|
self.restricted_user = None
|
||||||
|
self.force_login = None
|
||||||
|
self._session = None
|
||||||
|
self.authenticated = False
|
||||||
if entrypoint:
|
if entrypoint:
|
||||||
self.load_entrypoint()
|
self.load_entrypoint()
|
||||||
else:
|
else:
|
||||||
self.authenticated = False
|
|
||||||
utils.window('plex_authenticated', clear=True)
|
utils.window('plex_authenticated', clear=True)
|
||||||
self._session = None
|
|
||||||
self.load()
|
self.load()
|
||||||
|
|
||||||
def set_authenticated(self):
|
def set_authenticated(self):
|
||||||
|
|
|
@ -1,27 +1,32 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
|
from logging import getLogger
|
||||||
import Queue
|
import Queue
|
||||||
from threading import Lock, RLock
|
from threading import Lock, RLock
|
||||||
|
|
||||||
|
import xbmc
|
||||||
|
|
||||||
from .. import utils
|
from .. import utils
|
||||||
|
|
||||||
|
LOG = getLogger('PLEX.app')
|
||||||
|
|
||||||
|
|
||||||
class App(object):
|
class App(object):
|
||||||
"""
|
"""
|
||||||
This class is used to store variables across PKC modules
|
This class is used to store variables across PKC modules
|
||||||
"""
|
"""
|
||||||
def __init__(self, entrypoint=False):
|
def __init__(self, entrypoint=False):
|
||||||
|
self.fetch_pms_item_number = None
|
||||||
|
self.force_reload_skin = None
|
||||||
if entrypoint:
|
if entrypoint:
|
||||||
self.load_entrypoint()
|
self.load_entrypoint()
|
||||||
else:
|
else:
|
||||||
self.load()
|
self.load()
|
||||||
# Quit PKC?
|
# Quit PKC?
|
||||||
self.stop_pkc = False
|
self.stop_pkc = False
|
||||||
# Shall we completely suspend PKC and our threads?
|
# This will suspend the main thread also
|
||||||
self.suspend = False
|
self.suspend = False
|
||||||
# Shall we only suspend threads?
|
|
||||||
self._suspend_threads = False
|
|
||||||
# Need to lock all methods and functions messing with Plex Companion subscribers
|
# Need to lock all methods and functions messing with Plex Companion subscribers
|
||||||
self.lock_subscriber = RLock()
|
self.lock_subscriber = RLock()
|
||||||
# Need to lock everything messing with Kodi/PKC playqueues
|
# Need to lock everything messing with Kodi/PKC playqueues
|
||||||
|
@ -38,6 +43,127 @@ class App(object):
|
||||||
self.monitor = None
|
self.monitor = None
|
||||||
# xbmc.Player() instance
|
# xbmc.Player() instance
|
||||||
self.player = None
|
self.player = None
|
||||||
|
# All thread instances
|
||||||
|
self.threads = []
|
||||||
|
# Instance of FanartThread()
|
||||||
|
self.fanart_thread = None
|
||||||
|
# Instance of ImageCachingThread()
|
||||||
|
self.caching_thread = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_playing(self):
|
||||||
|
return self.player.isPlaying()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_playing_video(self):
|
||||||
|
return self.player.isPlayingVideo()
|
||||||
|
|
||||||
|
def register_fanart_thread(self, thread):
|
||||||
|
self.fanart_thread = thread
|
||||||
|
self.threads.append(thread)
|
||||||
|
|
||||||
|
def deregister_fanart_thread(self, thread):
|
||||||
|
self.fanart_thread = None
|
||||||
|
self.threads.remove(thread)
|
||||||
|
|
||||||
|
def suspend_fanart_thread(self, block=True):
|
||||||
|
try:
|
||||||
|
self.fanart_thread.suspend(block=block)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def resume_fanart_thread(self):
|
||||||
|
try:
|
||||||
|
self.fanart_thread.resume()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def register_caching_thread(self, thread):
|
||||||
|
self.caching_thread = thread
|
||||||
|
self.threads.append(thread)
|
||||||
|
|
||||||
|
def deregister_caching_thread(self, thread):
|
||||||
|
self.caching_thread = None
|
||||||
|
self.threads.remove(thread)
|
||||||
|
|
||||||
|
def suspend_caching_thread(self, block=True):
|
||||||
|
try:
|
||||||
|
self.caching_thread.suspend(block=block)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def resume_caching_thread(self):
|
||||||
|
try:
|
||||||
|
self.caching_thread.resume()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def register_thread(self, thread):
|
||||||
|
"""
|
||||||
|
Hit with thread [backgroundthread.Killablethread instance] to register
|
||||||
|
any and all threads
|
||||||
|
"""
|
||||||
|
self.threads.append(thread)
|
||||||
|
|
||||||
|
def deregister_thread(self, thread):
|
||||||
|
"""
|
||||||
|
Sync thread has done it's work and is e.g. about to die
|
||||||
|
"""
|
||||||
|
self.threads.remove(thread)
|
||||||
|
|
||||||
|
def suspend_threads(self, block=True):
|
||||||
|
"""
|
||||||
|
Suspend all threads' activity with or without blocking.
|
||||||
|
Returns True only if PKC shutdown requested
|
||||||
|
"""
|
||||||
|
LOG.debug('Suspending threads: %s', self.threads)
|
||||||
|
for thread in self.threads:
|
||||||
|
thread.suspend()
|
||||||
|
if block:
|
||||||
|
while True:
|
||||||
|
for thread in self.threads:
|
||||||
|
if not thread.suspend_reached:
|
||||||
|
LOG.debug('Waiting for thread to suspend: %s', thread)
|
||||||
|
if self.monitor.waitForAbort(0.1):
|
||||||
|
return True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return xbmc.abortRequested
|
||||||
|
|
||||||
|
def resume_threads(self, block=True):
|
||||||
|
"""
|
||||||
|
Resume all thread activity with or without blocking.
|
||||||
|
Returns True only if PKC shutdown requested
|
||||||
|
"""
|
||||||
|
LOG.debug('Resuming threads: %s', self.threads)
|
||||||
|
for thread in self.threads:
|
||||||
|
thread.resume()
|
||||||
|
if block:
|
||||||
|
while True:
|
||||||
|
for thread in self.threads:
|
||||||
|
if thread.suspend_reached:
|
||||||
|
LOG.debug('Waiting for thread to resume: %s', thread)
|
||||||
|
if self.monitor.waitForAbort(0.1):
|
||||||
|
return True
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
return xbmc.abortRequested
|
||||||
|
|
||||||
|
def stop_threads(self, block=True):
|
||||||
|
"""
|
||||||
|
Stop all threads. Will block until all threads are stopped
|
||||||
|
Will NOT quit if PKC should exit!
|
||||||
|
"""
|
||||||
|
LOG.debug('Killing threads: %s', self.threads)
|
||||||
|
for thread in self.threads:
|
||||||
|
thread.abort()
|
||||||
|
if block:
|
||||||
|
while self.threads:
|
||||||
|
LOG.debug('Waiting for threads to exit: %s', self.threads)
|
||||||
|
if xbmc.sleep(100):
|
||||||
|
return True
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
# Number of items to fetch and display in widgets
|
# Number of items to fetch and display in widgets
|
||||||
|
@ -48,11 +174,3 @@ class App(object):
|
||||||
|
|
||||||
def load_entrypoint(self):
|
def load_entrypoint(self):
|
||||||
self.fetch_pms_item_number = int(utils.settings('fetch_pms_item_number'))
|
self.fetch_pms_item_number = int(utils.settings('fetch_pms_item_number'))
|
||||||
|
|
||||||
@property
|
|
||||||
def suspend_threads(self):
|
|
||||||
return self._suspend_threads or self.suspend
|
|
||||||
|
|
||||||
@suspend_threads.setter
|
|
||||||
def suspend_threads(self, value):
|
|
||||||
self._suspend_threads = value
|
|
||||||
|
|
|
@ -10,13 +10,25 @@ LOG = getLogger('PLEX.connection')
|
||||||
|
|
||||||
class Connection(object):
|
class Connection(object):
|
||||||
def __init__(self, entrypoint=False):
|
def __init__(self, entrypoint=False):
|
||||||
|
self.verify_ssl_cert = None
|
||||||
|
self.ssl_cert_path = None
|
||||||
|
self.machine_identifier = None
|
||||||
|
self.server_name = None
|
||||||
|
self.https = None
|
||||||
|
self.host = None
|
||||||
|
self.port = None
|
||||||
|
self.server = None
|
||||||
|
self.online = False
|
||||||
|
self.webserver_host = None
|
||||||
|
self.webserver_port = None
|
||||||
|
self.webserver_username = None
|
||||||
|
self.webserver_password = None
|
||||||
|
|
||||||
if entrypoint:
|
if entrypoint:
|
||||||
self.load_entrypoint()
|
self.load_entrypoint()
|
||||||
else:
|
else:
|
||||||
self.load_webserver()
|
self.load_webserver()
|
||||||
self.load()
|
self.load()
|
||||||
# TODO: Delete
|
|
||||||
self.pms_server = None
|
|
||||||
# Token passed along, e.g. if playback initiated by Plex Companion. Might be
|
# Token passed along, e.g. if playback initiated by Plex Companion. Might be
|
||||||
# another user playing something! Token identifies user
|
# another user playing something! Token identifies user
|
||||||
self.plex_transient_token = None
|
self.plex_transient_token = None
|
||||||
|
@ -57,7 +69,6 @@ class Connection(object):
|
||||||
self.server = 'https://%s:%s' % (self.host, self.port)
|
self.server = 'https://%s:%s' % (self.host, self.port)
|
||||||
else:
|
else:
|
||||||
self.server = 'http://%s:%s' % (self.host, self.port)
|
self.server = 'http://%s:%s' % (self.host, self.port)
|
||||||
utils.window('pms_server', value=self.server)
|
|
||||||
self.online = False
|
self.online = False
|
||||||
LOG.debug('Set server %s (%s) to %s',
|
LOG.debug('Set server %s (%s) to %s',
|
||||||
self.server_name, self.machine_identifier, self.server)
|
self.server_name, self.machine_identifier, self.server)
|
||||||
|
@ -85,4 +96,3 @@ class Connection(object):
|
||||||
self.host = None
|
self.host = None
|
||||||
self.port = None
|
self.port = None
|
||||||
self.server = None
|
self.server = None
|
||||||
utils.window('pms_server', clear=True)
|
|
||||||
|
|
|
@ -19,34 +19,71 @@ def remove_trailing_slash(path):
|
||||||
|
|
||||||
class Sync(object):
|
class Sync(object):
|
||||||
def __init__(self, entrypoint=False):
|
def __init__(self, entrypoint=False):
|
||||||
self.load()
|
# Direct Paths (True) or Addon Paths (False)?
|
||||||
|
self.direct_paths = None
|
||||||
|
# Is synching of Plex music enabled?
|
||||||
|
self.enable_music = None
|
||||||
|
# Do we sync artwork from the PMS to Kodi?
|
||||||
|
self.artwork = None
|
||||||
|
# Path remapping mechanism (e.g. smb paths)
|
||||||
|
# Do we replace \\myserver\path to smb://myserver/path?
|
||||||
|
self.replace_smb_path = None
|
||||||
|
# Do we generally remap?
|
||||||
|
self.remap_path = None
|
||||||
|
self.force_transcode_pix = None
|
||||||
|
# Mappings for REMAP_PATH:
|
||||||
|
self.remapSMBmovieOrg = None
|
||||||
|
self.remapSMBmovieNew = None
|
||||||
|
self.remapSMBtvOrg = None
|
||||||
|
self.remapSMBtvNew = None
|
||||||
|
self.remapSMBmusicOrg = None
|
||||||
|
self.remapSMBmusicNew = None
|
||||||
|
self.remapSMBphotoOrg = None
|
||||||
|
self.remapSMBphotoNew = None
|
||||||
|
# Escape path?
|
||||||
|
self.escape_path = None
|
||||||
|
# Shall we replace custom user ratings with the number of versions available?
|
||||||
|
self.indicate_media_versions = None
|
||||||
|
# Will sync movie trailer differently: either play trailer directly or show
|
||||||
|
# all the Plex extras for the user to choose
|
||||||
|
self.show_extras_instead_of_playing_trailer = None
|
||||||
|
# Only sync specific Plex playlists to Kodi?
|
||||||
|
self.sync_specific_plex_playlists = None
|
||||||
|
# Only sync specific Kodi playlists to Plex?
|
||||||
|
self.sync_specific_kodi_playlists = None
|
||||||
|
# Shall we show Kodi dialogs when synching?
|
||||||
|
self.sync_dialog = None
|
||||||
|
|
||||||
|
# How often shall we sync?
|
||||||
|
self.full_sync_intervall = None
|
||||||
|
# Background Sync disabled?
|
||||||
|
self.background_sync_disabled = None
|
||||||
|
# How long shall we wait with synching a new item to make sure Plex got all
|
||||||
|
# metadata?
|
||||||
|
self.backgroundsync_saftymargin = None
|
||||||
|
# How many threads to download Plex metadata on sync?
|
||||||
|
self.sync_thread_number = None
|
||||||
|
|
||||||
|
# Shall Kodi show dialogs for syncing/caching images? (e.g. images left
|
||||||
|
# to sync)
|
||||||
|
self.image_sync_notifications = None
|
||||||
|
|
||||||
# Do we need to run a special library scan?
|
# Do we need to run a special library scan?
|
||||||
self.run_lib_scan = None
|
self.run_lib_scan = None
|
||||||
# Set if user decided to cancel sync
|
# Set if user decided to cancel sync
|
||||||
self.stop_sync = False
|
self.stop_sync = False
|
||||||
# Set during media playback if PKC should not do any syncs. Will NOT
|
|
||||||
# suspend synching of playstate progress
|
|
||||||
self.suspend_sync = False
|
|
||||||
# Could we access the paths?
|
# Could we access the paths?
|
||||||
self.path_verified = False
|
self.path_verified = False
|
||||||
# Set if a Plex-Kodi DB sync is being done - along with
|
|
||||||
# window('plex_dbScan') set to 'true'
|
self.load()
|
||||||
self.db_scan = False
|
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
# Direct Paths (True) or Addon Paths (False)?
|
|
||||||
self.direct_paths = utils.settings('useDirectPaths') == '1'
|
self.direct_paths = utils.settings('useDirectPaths') == '1'
|
||||||
# Is synching of Plex music enabled?
|
|
||||||
self.enable_music = utils.settings('enableMusic') == 'true'
|
self.enable_music = utils.settings('enableMusic') == 'true'
|
||||||
# Do we sync artwork from the PMS to Kodi?
|
|
||||||
self.artwork = utils.settings('usePlexArtwork') == 'true'
|
self.artwork = utils.settings('usePlexArtwork') == 'true'
|
||||||
# Path remapping mechanism (e.g. smb paths)
|
|
||||||
# Do we replace \\myserver\path to smb://myserver/path?
|
|
||||||
self.replace_smb_path = utils.settings('replaceSMB') == 'true'
|
self.replace_smb_path = utils.settings('replaceSMB') == 'true'
|
||||||
# Do we generally remap?
|
|
||||||
self.remap_path = utils.settings('remapSMB') == 'true'
|
self.remap_path = utils.settings('remapSMB') == 'true'
|
||||||
self.force_transcode_pix = utils.settings('force_transcode_pix') == 'true'
|
self.force_transcode_pix = utils.settings('force_transcode_pix') == 'true'
|
||||||
# Mappings for REMAP_PATH:
|
|
||||||
self.remapSMBmovieOrg = remove_trailing_slash(utils.settings('remapSMBmovieOrg'))
|
self.remapSMBmovieOrg = remove_trailing_slash(utils.settings('remapSMBmovieOrg'))
|
||||||
self.remapSMBmovieNew = remove_trailing_slash(utils.settings('remapSMBmovieNew'))
|
self.remapSMBmovieNew = remove_trailing_slash(utils.settings('remapSMBmovieNew'))
|
||||||
self.remapSMBtvOrg = remove_trailing_slash(utils.settings('remapSMBtvOrg'))
|
self.remapSMBtvOrg = remove_trailing_slash(utils.settings('remapSMBtvOrg'))
|
||||||
|
@ -55,30 +92,16 @@ class Sync(object):
|
||||||
self.remapSMBmusicNew = remove_trailing_slash(utils.settings('remapSMBmusicNew'))
|
self.remapSMBmusicNew = remove_trailing_slash(utils.settings('remapSMBmusicNew'))
|
||||||
self.remapSMBphotoOrg = remove_trailing_slash(utils.settings('remapSMBphotoOrg'))
|
self.remapSMBphotoOrg = remove_trailing_slash(utils.settings('remapSMBphotoOrg'))
|
||||||
self.remapSMBphotoNew = remove_trailing_slash(utils.settings('remapSMBphotoNew'))
|
self.remapSMBphotoNew = remove_trailing_slash(utils.settings('remapSMBphotoNew'))
|
||||||
# Escape path?
|
|
||||||
self.escape_path = utils.settings('escapePath') == 'true'
|
self.escape_path = utils.settings('escapePath') == 'true'
|
||||||
# Shall we replace custom user ratings with the number of versions available?
|
|
||||||
self.indicate_media_versions = utils.settings('indicate_media_versions') == "true"
|
self.indicate_media_versions = utils.settings('indicate_media_versions') == "true"
|
||||||
# Will sync movie trailer differently: either play trailer directly or show
|
|
||||||
# all the Plex extras for the user to choose
|
|
||||||
self.show_extras_instead_of_playing_trailer = utils.settings('showExtrasInsteadOfTrailer') == 'true'
|
self.show_extras_instead_of_playing_trailer = utils.settings('showExtrasInsteadOfTrailer') == 'true'
|
||||||
# Only sync specific Plex playlists to Kodi?
|
|
||||||
self.sync_specific_plex_playlists = utils.settings('syncSpecificPlexPlaylists') == 'true'
|
self.sync_specific_plex_playlists = utils.settings('syncSpecificPlexPlaylists') == 'true'
|
||||||
# Only sync specific Kodi playlists to Plex?
|
|
||||||
self.sync_specific_kodi_playlists = utils.settings('syncSpecificKodiPlaylists') == 'true'
|
self.sync_specific_kodi_playlists = utils.settings('syncSpecificKodiPlaylists') == 'true'
|
||||||
# Shall we show Kodi dialogs when synching?
|
|
||||||
self.sync_dialog = utils.settings('dbSyncIndicator') == 'true'
|
self.sync_dialog = utils.settings('dbSyncIndicator') == 'true'
|
||||||
|
|
||||||
# How often shall we sync?
|
|
||||||
self.full_sync_intervall = int(utils.settings('fullSyncInterval')) * 60
|
self.full_sync_intervall = int(utils.settings('fullSyncInterval')) * 60
|
||||||
# Background Sync disabled?
|
|
||||||
self.background_sync_disabled = utils.settings('enableBackgroundSync') == 'false'
|
self.background_sync_disabled = utils.settings('enableBackgroundSync') == 'false'
|
||||||
# How long shall we wait with synching a new item to make sure Plex got all
|
|
||||||
# metadata?
|
|
||||||
self.backgroundsync_saftymargin = int(utils.settings('backgroundsync_saftyMargin'))
|
self.backgroundsync_saftymargin = int(utils.settings('backgroundsync_saftyMargin'))
|
||||||
# How many threads to download Plex metadata on sync?
|
|
||||||
self.sync_thread_number = int(utils.settings('syncThreadNumber'))
|
self.sync_thread_number = int(utils.settings('syncThreadNumber'))
|
||||||
|
|
||||||
# Shall Kodi show dialogs for syncing/caching images? (e.g. images left
|
|
||||||
# to sync)
|
|
||||||
self.image_sync_notifications = utils.settings('imageSyncNotifications') == 'true'
|
self.image_sync_notifications = utils.settings('imageSyncNotifications') == 'true'
|
||||||
|
|
|
@ -18,8 +18,6 @@ requests.packages.urllib3.disable_warnings()
|
||||||
TIMEOUT = (35.1, 35.1)
|
TIMEOUT = (35.1, 35.1)
|
||||||
BATCH_SIZE = 500
|
BATCH_SIZE = 500
|
||||||
|
|
||||||
IMAGE_CACHING_SUSPENDS = []
|
|
||||||
|
|
||||||
|
|
||||||
def double_urlencode(text):
|
def double_urlencode(text):
|
||||||
return quote_plus(quote_plus(text))
|
return quote_plus(quote_plus(text))
|
||||||
|
@ -30,8 +28,14 @@ def double_urldecode(text):
|
||||||
|
|
||||||
|
|
||||||
class ImageCachingThread(backgroundthread.KillableThread):
|
class ImageCachingThread(backgroundthread.KillableThread):
|
||||||
|
def __init__(self):
|
||||||
|
super(ImageCachingThread, self).__init__()
|
||||||
|
self.suspend_points = [self._suspended]
|
||||||
|
if not utils.settings('imageSyncDuringPlayback') == 'true':
|
||||||
|
self.suspend_points.append(app.APP.is_playing_video)
|
||||||
|
|
||||||
def isSuspended(self):
|
def isSuspended(self):
|
||||||
return any(IMAGE_CACHING_SUSPENDS)
|
return any(self.suspend_points)
|
||||||
|
|
||||||
def _url_generator(self, kind, kodi_type):
|
def _url_generator(self, kind, kodi_type):
|
||||||
"""
|
"""
|
||||||
|
@ -60,11 +64,13 @@ class ImageCachingThread(backgroundthread.KillableThread):
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
LOG.info("---===### Starting ImageCachingThread ###===---")
|
LOG.info("---===### Starting ImageCachingThread ###===---")
|
||||||
|
app.APP.register_caching_thread(self)
|
||||||
try:
|
try:
|
||||||
self._run()
|
self._run()
|
||||||
except Exception:
|
except Exception:
|
||||||
utils.ERROR()
|
utils.ERROR()
|
||||||
finally:
|
finally:
|
||||||
|
app.APP.deregister_caching_thread(self)
|
||||||
LOG.info("---===### Stopped ImageCachingThread ###===---")
|
LOG.info("---===### Stopped ImageCachingThread ###===---")
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
|
@ -74,14 +80,8 @@ class ImageCachingThread(backgroundthread.KillableThread):
|
||||||
for kind in kinds:
|
for kind in kinds:
|
||||||
for kodi_type in ('poster', 'fanart'):
|
for kodi_type in ('poster', 'fanart'):
|
||||||
for url in self._url_generator(kind, kodi_type):
|
for url in self._url_generator(kind, kodi_type):
|
||||||
if self.isCanceled():
|
if self.wait_while_suspended():
|
||||||
return
|
return
|
||||||
while self.isSuspended():
|
|
||||||
# Set in service.py
|
|
||||||
if self.isCanceled():
|
|
||||||
# Abort was requested while waiting. We should exit
|
|
||||||
return
|
|
||||||
app.APP.monitor.waitForAbort(1)
|
|
||||||
cache_url(url)
|
cache_url(url)
|
||||||
# Toggles Image caching completed to Yes
|
# Toggles Image caching completed to Yes
|
||||||
utils.settings('plex_status_image_caching', value=utils.lang(107))
|
utils.settings('plex_status_image_caching', value=utils.lang(107))
|
||||||
|
|
|
@ -77,7 +77,10 @@ class KillableThread(threading.Thread):
|
||||||
|
|
||||||
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
|
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
|
||||||
self._canceled = False
|
self._canceled = False
|
||||||
|
# Set to True to set the thread to suspended
|
||||||
self._suspended = False
|
self._suspended = False
|
||||||
|
# Thread will return True only if suspended state is reached
|
||||||
|
self.suspend_reached = False
|
||||||
super(KillableThread, self).__init__(group, target, name, args, kwargs)
|
super(KillableThread, self).__init__(group, target, name, args, kwargs)
|
||||||
|
|
||||||
def isCanceled(self):
|
def isCanceled(self):
|
||||||
|
@ -94,11 +97,16 @@ class KillableThread(threading.Thread):
|
||||||
"""
|
"""
|
||||||
self._canceled = True
|
self._canceled = True
|
||||||
|
|
||||||
def suspend(self):
|
def suspend(self, block=False):
|
||||||
"""
|
"""
|
||||||
Call to suspend this thread
|
Call to suspend this thread
|
||||||
"""
|
"""
|
||||||
self._suspended = True
|
self._suspended = True
|
||||||
|
if block:
|
||||||
|
while not self.suspend_reached:
|
||||||
|
LOG.debug('Waiting for thread to suspend: %s', self)
|
||||||
|
if app.APP.monitor.waitForAbort(0.1):
|
||||||
|
return
|
||||||
|
|
||||||
def resume(self):
|
def resume(self):
|
||||||
"""
|
"""
|
||||||
|
@ -106,6 +114,25 @@ class KillableThread(threading.Thread):
|
||||||
"""
|
"""
|
||||||
self._suspended = False
|
self._suspended = False
|
||||||
|
|
||||||
|
def wait_while_suspended(self):
|
||||||
|
"""
|
||||||
|
Blocks until thread is not suspended anymore or the thread should
|
||||||
|
exit.
|
||||||
|
Returns True only if the thread should exit (=isCanceled())
|
||||||
|
"""
|
||||||
|
while self.isSuspended():
|
||||||
|
try:
|
||||||
|
self.suspend_reached = True
|
||||||
|
# Set in service.py
|
||||||
|
if self.isCanceled():
|
||||||
|
# Abort was requested while waiting. We should exit
|
||||||
|
return True
|
||||||
|
if app.APP.monitor.waitForAbort(0.1):
|
||||||
|
return True
|
||||||
|
finally:
|
||||||
|
self.suspend_reached = False
|
||||||
|
return self.isCanceled()
|
||||||
|
|
||||||
def isSuspended(self):
|
def isSuspended(self):
|
||||||
"""
|
"""
|
||||||
Returns True if the thread is suspended
|
Returns True if the thread is suspended
|
||||||
|
|
|
@ -54,7 +54,8 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
"""
|
"""
|
||||||
LOG.debug('PKC settings change detected')
|
LOG.debug('PKC settings change detected')
|
||||||
# Assume that the user changed something so we can try to reconnect
|
# Assume that the user changed something so we can try to reconnect
|
||||||
app.APP.suspend = False
|
# app.APP.suspend = False
|
||||||
|
# app.APP.resume_threads(block=False)
|
||||||
|
|
||||||
def onNotification(self, sender, method, data):
|
def onNotification(self, sender, method, data):
|
||||||
"""
|
"""
|
||||||
|
@ -69,7 +70,6 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
self.hack_replay = None
|
self.hack_replay = None
|
||||||
|
|
||||||
if method == "Player.OnPlay":
|
if method == "Player.OnPlay":
|
||||||
app.SYNC.suspend_sync = True
|
|
||||||
with app.APP.lock_playqueues:
|
with app.APP.lock_playqueues:
|
||||||
self.PlayBackStart(data)
|
self.PlayBackStart(data)
|
||||||
elif method == "Player.OnStop":
|
elif method == "Player.OnStop":
|
||||||
|
@ -87,7 +87,6 @@ class KodiMonitor(xbmc.Monitor):
|
||||||
else:
|
else:
|
||||||
with app.APP.lock_playqueues:
|
with app.APP.lock_playqueues:
|
||||||
_playback_cleanup()
|
_playback_cleanup()
|
||||||
app.SYNC.suspend_sync = False
|
|
||||||
elif method == 'Playlist.OnAdd':
|
elif method == 'Playlist.OnAdd':
|
||||||
if 'item' in data and data['item'].get('type') == v.KODI_TYPE_SHOW:
|
if 'item' in data and data['item'].get('type') == v.KODI_TYPE_SHOW:
|
||||||
# Hitting the "browse" button on tv show info dialog
|
# Hitting the "browse" button on tv show info dialog
|
||||||
|
|
|
@ -3,24 +3,38 @@
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
import xbmc
|
import xbmc
|
||||||
|
|
||||||
from .. import app, utils, variables as v
|
from .. import utils, variables as v
|
||||||
|
|
||||||
PLAYLIST_SYNC_ENABLED = (v.DEVICE != 'Microsoft UWP' and
|
PLAYLIST_SYNC_ENABLED = (v.DEVICE != 'Microsoft UWP' and
|
||||||
utils.settings('enablePlaylistSync') == 'true')
|
utils.settings('enablePlaylistSync') == 'true')
|
||||||
|
|
||||||
|
|
||||||
class libsync_mixin(object):
|
|
||||||
def isCanceled(self):
|
|
||||||
return (self._canceled or app.APP.stop_pkc or app.SYNC.stop_sync or
|
|
||||||
app.APP.suspend_threads or app.SYNC.suspend_sync)
|
|
||||||
|
|
||||||
|
|
||||||
class fullsync_mixin(object):
|
class fullsync_mixin(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._canceled = False
|
||||||
|
|
||||||
|
def abort(self):
|
||||||
|
"""Hit method to terminate the thread"""
|
||||||
|
self._canceled = True
|
||||||
|
# Let's NOT suspend sync threads but immediately terminate them
|
||||||
|
suspend = abort
|
||||||
|
|
||||||
|
@property
|
||||||
|
def suspend_reached(self):
|
||||||
|
"""Since we're not suspending, we'll never set it to True"""
|
||||||
|
return False
|
||||||
|
|
||||||
|
@suspend_reached.setter
|
||||||
|
def suspend_reached(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def resume(self):
|
||||||
|
"""Obsolete since we're not suspending"""
|
||||||
|
pass
|
||||||
|
|
||||||
def isCanceled(self):
|
def isCanceled(self):
|
||||||
return (self._canceled or
|
"""Check whether we should exit this thread"""
|
||||||
app.APP.stop_pkc or
|
return self._canceled
|
||||||
app.SYNC.stop_sync or
|
|
||||||
app.APP.suspend_threads)
|
|
||||||
|
|
||||||
|
|
||||||
def update_kodi_library(video=True, music=True):
|
def update_kodi_library(video=True, music=True):
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
from __future__ import absolute_import, division, unicode_literals
|
from __future__ import absolute_import, division, unicode_literals
|
||||||
from logging import getLogger
|
from logging import getLogger
|
||||||
|
|
||||||
from . import common
|
|
||||||
from ..plex_api import API
|
from ..plex_api import API
|
||||||
from ..plex_db import PlexDB
|
from ..plex_db import PlexDB
|
||||||
from ..kodi_db import KodiVideoDB
|
from ..kodi_db import KodiVideoDB
|
||||||
|
@ -19,13 +18,6 @@ PREFER_KODI_COLLECTION_ART = utils.settings('PreferKodiCollectionArt') == 'false
|
||||||
BATCH_SIZE = 500
|
BATCH_SIZE = 500
|
||||||
|
|
||||||
|
|
||||||
def suspends():
|
|
||||||
return (app.APP.suspend_threads or
|
|
||||||
app.SYNC.stop_sync or
|
|
||||||
app.SYNC.db_scan or
|
|
||||||
app.SYNC.suspend_sync)
|
|
||||||
|
|
||||||
|
|
||||||
class FanartThread(backgroundthread.KillableThread):
|
class FanartThread(backgroundthread.KillableThread):
|
||||||
"""
|
"""
|
||||||
This will potentially take hours!
|
This will potentially take hours!
|
||||||
|
@ -36,16 +28,19 @@ class FanartThread(backgroundthread.KillableThread):
|
||||||
super(FanartThread, self).__init__()
|
super(FanartThread, self).__init__()
|
||||||
|
|
||||||
def isSuspended(self):
|
def isSuspended(self):
|
||||||
return suspends()
|
return self._suspended or app.APP.is_playing_video
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
LOG.info('Starting FanartThread')
|
||||||
|
app.APP.register_fanart_thread(self)
|
||||||
try:
|
try:
|
||||||
self._run_internal()
|
self._run_internal()
|
||||||
except Exception:
|
except Exception:
|
||||||
utils.ERROR(notify=True)
|
utils.ERROR(notify=True)
|
||||||
|
finally:
|
||||||
|
app.APP.deregister_fanart_thread(self)
|
||||||
|
|
||||||
def _run_internal(self):
|
def _run_internal(self):
|
||||||
LOG.info('Starting FanartThread')
|
|
||||||
finished = False
|
finished = False
|
||||||
try:
|
try:
|
||||||
for typus in SUPPORTED_TYPES:
|
for typus in SUPPORTED_TYPES:
|
||||||
|
@ -63,12 +58,8 @@ class FanartThread(backgroundthread.KillableThread):
|
||||||
BATCH_SIZE))
|
BATCH_SIZE))
|
||||||
for plex_id in batch:
|
for plex_id in batch:
|
||||||
# Do the actual, time-consuming processing
|
# Do the actual, time-consuming processing
|
||||||
if self.isCanceled():
|
if self.wait_while_suspended():
|
||||||
return
|
return
|
||||||
if self.isSuspended():
|
|
||||||
if self.isCanceled():
|
|
||||||
return
|
|
||||||
app.APP.monitor.waitForAbort(1)
|
|
||||||
process_fanart(plex_id, typus, self.refresh)
|
process_fanart(plex_id, typus, self.refresh)
|
||||||
if len(batch) < BATCH_SIZE:
|
if len(batch) < BATCH_SIZE:
|
||||||
break
|
break
|
||||||
|
@ -80,7 +71,7 @@ class FanartThread(backgroundthread.KillableThread):
|
||||||
self.callback(finished)
|
self.callback(finished)
|
||||||
|
|
||||||
|
|
||||||
class FanartTask(common.libsync_mixin, backgroundthread.Task):
|
class FanartTask(backgroundthread.Task):
|
||||||
"""
|
"""
|
||||||
This task will also be executed while library sync is suspended!
|
This task will also be executed while library sync is suspended!
|
||||||
"""
|
"""
|
||||||
|
@ -154,11 +145,7 @@ def process_fanart(plex_id, plex_type, refresh=False):
|
||||||
setid,
|
setid,
|
||||||
v.KODI_TYPE_SET)
|
v.KODI_TYPE_SET)
|
||||||
done = True
|
done = True
|
||||||
except utils.OperationalError:
|
|
||||||
# Caused if we reset the Plex database and this function has not yet
|
|
||||||
# returned
|
|
||||||
pass
|
|
||||||
finally:
|
finally:
|
||||||
if done is True and not suspends():
|
if done is True:
|
||||||
with PlexDB() as plexdb:
|
with PlexDB() as plexdb:
|
||||||
plexdb.set_fanart_synced(plex_id, plex_type)
|
plexdb.set_fanart_synced(plex_id, plex_type)
|
||||||
|
|
|
@ -44,7 +44,6 @@ class FullSync(common.fullsync_mixin):
|
||||||
"""
|
"""
|
||||||
repair=True: force sync EVERY item
|
repair=True: force sync EVERY item
|
||||||
"""
|
"""
|
||||||
self._canceled = False
|
|
||||||
self.repair = repair
|
self.repair = repair
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.queue = None
|
self.queue = None
|
||||||
|
@ -85,7 +84,7 @@ class FullSync(common.fullsync_mixin):
|
||||||
'%s (%s)' % (self.section_name, self.section_type_text),
|
'%s (%s)' % (self.section_name, self.section_type_text),
|
||||||
'%s %s/%s'
|
'%s %s/%s'
|
||||||
% (self.title, self.current, self.total))
|
% (self.title, self.current, self.total))
|
||||||
if app.APP.player.isPlayingVideo():
|
if app.APP.is_playing_video:
|
||||||
self.dialog.close()
|
self.dialog.close()
|
||||||
self.dialog = None
|
self.dialog = None
|
||||||
|
|
||||||
|
@ -394,14 +393,22 @@ class FullSync(common.fullsync_mixin):
|
||||||
LOG.debug('Done deleting')
|
LOG.debug('Done deleting')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@utils.log_time
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
app.APP.register_thread(self)
|
||||||
|
try:
|
||||||
|
self._run()
|
||||||
|
finally:
|
||||||
|
app.APP.deregister_thread(self)
|
||||||
|
LOG.info('Done full_sync')
|
||||||
|
|
||||||
|
@utils.log_time
|
||||||
|
def _run(self):
|
||||||
self.current_sync = timing.plex_now()
|
self.current_sync = timing.plex_now()
|
||||||
# Delete playlist and video node files from Kodi
|
# Delete playlist and video node files from Kodi
|
||||||
utils.delete_playlists()
|
utils.delete_playlists()
|
||||||
utils.delete_nodes()
|
utils.delete_nodes()
|
||||||
# Get latest Plex libraries and build playlist and video node files
|
# Get latest Plex libraries and build playlist and video node files
|
||||||
if not sections.sync_from_pms():
|
if not sections.sync_from_pms(self):
|
||||||
return
|
return
|
||||||
self.successful = True
|
self.successful = True
|
||||||
try:
|
try:
|
||||||
|
@ -436,7 +443,6 @@ class FullSync(common.fullsync_mixin):
|
||||||
icon='{error}')
|
icon='{error}')
|
||||||
if self.callback:
|
if self.callback:
|
||||||
self.callback(self.successful)
|
self.callback(self.successful)
|
||||||
LOG.info('Done full_sync')
|
|
||||||
|
|
||||||
|
|
||||||
def start(show_dialog, repair=False, callback=None):
|
def start(show_dialog, repair=False, callback=None):
|
||||||
|
|
|
@ -18,16 +18,23 @@ VNODES = videonodes.VideoNodes()
|
||||||
PLAYLISTS = {}
|
PLAYLISTS = {}
|
||||||
NODES = {}
|
NODES = {}
|
||||||
SECTIONS = []
|
SECTIONS = []
|
||||||
|
# Need a way to interrupt
|
||||||
|
IS_CANCELED = None
|
||||||
|
|
||||||
|
|
||||||
def isCanceled():
|
def sync_from_pms(parent_self):
|
||||||
return app.APP.stop_pkc or app.APP.suspend_threads or app.SYNC.stop_sync
|
|
||||||
|
|
||||||
|
|
||||||
def sync_from_pms():
|
|
||||||
"""
|
"""
|
||||||
Sync the Plex library sections
|
Sync the Plex library sections
|
||||||
"""
|
"""
|
||||||
|
global IS_CANCELED
|
||||||
|
IS_CANCELED = parent_self.isCanceled
|
||||||
|
try:
|
||||||
|
return _sync_from_pms()
|
||||||
|
finally:
|
||||||
|
IS_CANCELED = None
|
||||||
|
|
||||||
|
|
||||||
|
def _sync_from_pms():
|
||||||
sections = PF.get_plex_sections()
|
sections = PF.get_plex_sections()
|
||||||
try:
|
try:
|
||||||
sections.attrib
|
sections.attrib
|
||||||
|
@ -226,7 +233,7 @@ def _delete_kodi_db_items(section_id, section_type):
|
||||||
with kodi_context(texture_db=True) as kodidb:
|
with kodi_context(texture_db=True) as kodidb:
|
||||||
typus = context(None, plexdb=plexdb, kodidb=kodidb)
|
typus = context(None, plexdb=plexdb, kodidb=kodidb)
|
||||||
for plex_id in plex_ids:
|
for plex_id in plex_ids:
|
||||||
if isCanceled():
|
if IS_CANCELED():
|
||||||
return False
|
return False
|
||||||
typus.remove(plex_id)
|
typus.remove(plex_id)
|
||||||
if len(plex_ids) < BATCH_SIZE:
|
if len(plex_ids) < BATCH_SIZE:
|
||||||
|
|
|
@ -23,10 +23,6 @@ WEBSOCKET_MESSAGES = []
|
||||||
PLAYSTATE_SESSIONS = {}
|
PLAYSTATE_SESSIONS = {}
|
||||||
|
|
||||||
|
|
||||||
def interrupt_processing():
|
|
||||||
return app.APP.stop_pkc or app.APP.suspend_threads or app.SYNC.stop_sync
|
|
||||||
|
|
||||||
|
|
||||||
def multi_delete(input_list, delete_list):
|
def multi_delete(input_list, delete_list):
|
||||||
"""
|
"""
|
||||||
Deletes the list items of input_list at the positions in delete_list
|
Deletes the list items of input_list at the positions in delete_list
|
||||||
|
@ -81,9 +77,6 @@ def process_websocket_messages():
|
||||||
update_kodi_video_library, update_kodi_music_library = False, False
|
update_kodi_video_library, update_kodi_music_library = False, False
|
||||||
delete_list = []
|
delete_list = []
|
||||||
for i, message in enumerate(WEBSOCKET_MESSAGES):
|
for i, message in enumerate(WEBSOCKET_MESSAGES):
|
||||||
if interrupt_processing():
|
|
||||||
# Chances are that Kodi gets shut down
|
|
||||||
break
|
|
||||||
if message['state'] == 9:
|
if message['state'] == 9:
|
||||||
successful, video, music = process_delete_message(message)
|
successful, video, music = process_delete_message(message)
|
||||||
elif now - message['timestamp'] < app.SYNC.backgroundsync_saftymargin:
|
elif now - message['timestamp'] < app.SYNC.backgroundsync_saftymargin:
|
||||||
|
|
|
@ -533,7 +533,7 @@ def threaded_playback(kodi_playlist, startpos, offset):
|
||||||
app.APP.player.play(kodi_playlist, None, False, startpos)
|
app.APP.player.play(kodi_playlist, None, False, startpos)
|
||||||
if offset and offset != '0':
|
if offset and offset != '0':
|
||||||
i = 0
|
i = 0
|
||||||
while not app.APP.player.isPlaying():
|
while not app.APP.is_playing:
|
||||||
app.APP.monitor.waitForAbort(0.1)
|
app.APP.monitor.waitForAbort(0.1)
|
||||||
i += 1
|
i += 1
|
||||||
if i > 100:
|
if i > 100:
|
||||||
|
|
|
@ -43,7 +43,7 @@ IGNORE_PLEX_PLAYLIST_CHANGE = list()
|
||||||
|
|
||||||
|
|
||||||
def isCanceled():
|
def isCanceled():
|
||||||
return app.APP.stop_pkc or app.SYNC.stop_sync or app.APP.suspend_threads
|
return app.APP.stop_pkc or app.SYNC.stop_sync
|
||||||
|
|
||||||
|
|
||||||
def kodi_playlist_monitor():
|
def kodi_playlist_monitor():
|
||||||
|
|
|
@ -97,12 +97,6 @@ class PlayqueueMonitor(backgroundthread.KillableThread):
|
||||||
(playlist) are swapped. This is what this monitor is for. Don't replace
|
(playlist) are swapped. This is what this monitor is for. Don't replace
|
||||||
this mechanism till Kodi's implementation of playlists has improved
|
this mechanism till Kodi's implementation of playlists has improved
|
||||||
"""
|
"""
|
||||||
def isSuspended(self):
|
|
||||||
"""
|
|
||||||
Returns True if the thread is suspended
|
|
||||||
"""
|
|
||||||
return self._suspended or app.APP.suspend_threads
|
|
||||||
|
|
||||||
def _compare_playqueues(self, playqueue, new):
|
def _compare_playqueues(self, playqueue, new):
|
||||||
"""
|
"""
|
||||||
Used to poll the Kodi playqueue and update the Plex playqueue if needed
|
Used to poll the Kodi playqueue and update the Plex playqueue if needed
|
||||||
|
@ -193,11 +187,17 @@ class PlayqueueMonitor(backgroundthread.KillableThread):
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
LOG.info("----===## Starting PlayqueueMonitor ##===----")
|
LOG.info("----===## Starting PlayqueueMonitor ##===----")
|
||||||
|
app.APP.register_thread(self)
|
||||||
|
try:
|
||||||
|
self._run()
|
||||||
|
finally:
|
||||||
|
app.APP.deregister_thread(self)
|
||||||
|
LOG.info("----===## PlayqueueMonitor stopped ##===----")
|
||||||
|
|
||||||
|
def _run(self):
|
||||||
while not self.isCanceled():
|
while not self.isCanceled():
|
||||||
while self.isSuspended():
|
if self.wait_while_suspended():
|
||||||
if self.isCanceled():
|
return
|
||||||
break
|
|
||||||
app.APP.monitor.waitForAbort(1)
|
|
||||||
with app.APP.lock_playqueues:
|
with app.APP.lock_playqueues:
|
||||||
for playqueue in PLAYQUEUES:
|
for playqueue in PLAYQUEUES:
|
||||||
kodi_pl = js.playlist_get_items(playqueue.playlistid)
|
kodi_pl = js.playlist_get_items(playqueue.playlistid)
|
||||||
|
@ -212,4 +212,3 @@ class PlayqueueMonitor(backgroundthread.KillableThread):
|
||||||
self._compare_playqueues(playqueue, kodi_pl)
|
self._compare_playqueues(playqueue, kodi_pl)
|
||||||
playqueue.old_kodi_pl = list(kodi_pl)
|
playqueue.old_kodi_pl = list(kodi_pl)
|
||||||
app.APP.monitor.waitForAbort(0.2)
|
app.APP.monitor.waitForAbort(0.2)
|
||||||
LOG.info("----===## PlayqueueMonitor stopped ##===----")
|
|
||||||
|
|
|
@ -1769,7 +1769,7 @@ class API(object):
|
||||||
if force_check is False:
|
if force_check is False:
|
||||||
# Validate the path is correct with user intervention
|
# Validate the path is correct with user intervention
|
||||||
if self.ask_to_validate(path):
|
if self.ask_to_validate(path):
|
||||||
app.SYNC.stop_sync = True
|
app.APP.stop_threads(block=False)
|
||||||
path = None
|
path = None
|
||||||
app.SYNC.path_verified = True
|
app.SYNC.path_verified = True
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -80,12 +80,6 @@ class PlexCompanion(backgroundthread.KillableThread):
|
||||||
self.subscription_manager = None
|
self.subscription_manager = None
|
||||||
super(PlexCompanion, self).__init__()
|
super(PlexCompanion, self).__init__()
|
||||||
|
|
||||||
def isSuspended(self):
|
|
||||||
"""
|
|
||||||
Returns True if the thread is suspended
|
|
||||||
"""
|
|
||||||
return self._suspended or app.APP.suspend
|
|
||||||
|
|
||||||
def _process_alexa(self, data):
|
def _process_alexa(self, data):
|
||||||
xml = PF.GetPlexMetadata(data['key'])
|
xml = PF.GetPlexMetadata(data['key'])
|
||||||
try:
|
try:
|
||||||
|
@ -245,6 +239,7 @@ class PlexCompanion(backgroundthread.KillableThread):
|
||||||
"""
|
"""
|
||||||
Ensure that sockets will be closed no matter what
|
Ensure that sockets will be closed no matter what
|
||||||
"""
|
"""
|
||||||
|
app.APP.register_thread(self)
|
||||||
try:
|
try:
|
||||||
self._run()
|
self._run()
|
||||||
finally:
|
finally:
|
||||||
|
@ -257,6 +252,7 @@ class PlexCompanion(backgroundthread.KillableThread):
|
||||||
self.httpd.socket.close()
|
self.httpd.socket.close()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
app.APP.deregister_thread(self)
|
||||||
LOG.info("----===## Plex Companion stopped ##===----")
|
LOG.info("----===## Plex Companion stopped ##===----")
|
||||||
|
|
||||||
def _run(self):
|
def _run(self):
|
||||||
|
@ -303,10 +299,8 @@ class PlexCompanion(backgroundthread.KillableThread):
|
||||||
# If we are not authorized, sleep
|
# If we are not authorized, sleep
|
||||||
# Otherwise, we trigger a download which leads to a
|
# Otherwise, we trigger a download which leads to a
|
||||||
# re-authorizations
|
# re-authorizations
|
||||||
while self.isSuspended():
|
if self.wait_while_suspended():
|
||||||
if self.isCanceled():
|
|
||||||
break
|
break
|
||||||
app.APP.monitor.waitForAbort(1)
|
|
||||||
try:
|
try:
|
||||||
message_count += 1
|
message_count += 1
|
||||||
if httpd:
|
if httpd:
|
||||||
|
|
|
@ -134,7 +134,7 @@ class MyHandler(BaseHTTPRequestHandler):
|
||||||
CLIENT_DICT[self.client_address[0]] = []
|
CLIENT_DICT[self.client_address[0]] = []
|
||||||
tracker = CLIENT_DICT[self.client_address[0]]
|
tracker = CLIENT_DICT[self.client_address[0]]
|
||||||
tracker.append(self.client_address[1])
|
tracker.append(self.client_address[1])
|
||||||
while (not app.APP.player.isPlaying() and
|
while (not app.APP.is_playing and
|
||||||
not app.APP.monitor.abortRequested() and
|
not app.APP.monitor.abortRequested() and
|
||||||
sub_mgr.stop_sent_to_web and not
|
sub_mgr.stop_sent_to_web and not
|
||||||
(len(tracker) >= 4 and
|
(len(tracker) >= 4 and
|
||||||
|
|
|
@ -7,7 +7,7 @@ import xbmc
|
||||||
import xbmcgui
|
import xbmcgui
|
||||||
|
|
||||||
from . import utils, clientinfo, timing
|
from . import utils, clientinfo, timing
|
||||||
from . import initialsetup, artwork
|
from . import initialsetup
|
||||||
from . import kodimonitor
|
from . import kodimonitor
|
||||||
from . import sync
|
from . import sync
|
||||||
from . import websocket_client
|
from . import websocket_client
|
||||||
|
@ -27,9 +27,8 @@ LOG = logging.getLogger("PLEX.service")
|
||||||
###############################################################################
|
###############################################################################
|
||||||
|
|
||||||
WINDOW_PROPERTIES = (
|
WINDOW_PROPERTIES = (
|
||||||
"plex_dbScan", "pms_token", "plex_token", "pms_server",
|
"pms_token", "plex_token", "plex_authenticated", "plex_restricteduser",
|
||||||
"plex_authenticated", "plex_restricteduser", "plex_allows_mediaDeletion",
|
"plex_allows_mediaDeletion", "plexkodiconnect.command", "plex_result")
|
||||||
"plexkodiconnect.command", "plex_result")
|
|
||||||
|
|
||||||
# "Start from beginning", "Play from beginning"
|
# "Start from beginning", "Play from beginning"
|
||||||
STRINGS = (utils.try_encode(utils.lang(12021)),
|
STRINGS = (utils.try_encode(utils.lang(12021)),
|
||||||
|
@ -126,9 +125,10 @@ class Service():
|
||||||
# Alert the user and suppress future warning
|
# Alert the user and suppress future warning
|
||||||
if app.CONN.online:
|
if app.CONN.online:
|
||||||
# PMS was online before
|
# PMS was online before
|
||||||
app.CONN.online = False
|
|
||||||
app.APP.suspend_threads = True
|
|
||||||
LOG.warn("Plex Media Server went offline")
|
LOG.warn("Plex Media Server went offline")
|
||||||
|
app.CONN.online = False
|
||||||
|
app.APP.suspend_threads()
|
||||||
|
LOG.debug('Threads suspended')
|
||||||
if utils.settings('show_pms_offline') == 'true':
|
if utils.settings('show_pms_offline') == 'true':
|
||||||
utils.dialog('notification',
|
utils.dialog('notification',
|
||||||
utils.lang(33001),
|
utils.lang(33001),
|
||||||
|
@ -165,7 +165,7 @@ class Service():
|
||||||
if app.ACCOUNT.authenticated:
|
if app.ACCOUNT.authenticated:
|
||||||
# Server got offline when we were authenticated.
|
# Server got offline when we were authenticated.
|
||||||
# Hence resume threads
|
# Hence resume threads
|
||||||
app.APP.suspend_threads = False
|
app.APP.resume_threads()
|
||||||
app.CONN.online = True
|
app.CONN.online = True
|
||||||
finally:
|
finally:
|
||||||
self.connection_check_running = False
|
self.connection_check_running = False
|
||||||
|
@ -175,22 +175,10 @@ class Service():
|
||||||
Ensures that lib sync threads are suspended; signs out user
|
Ensures that lib sync threads are suspended; signs out user
|
||||||
"""
|
"""
|
||||||
LOG.info('Log-out requested')
|
LOG.info('Log-out requested')
|
||||||
app.APP.suspend_threads = True
|
app.APP.suspend_threads()
|
||||||
i = 0
|
LOG.info('Successfully suspended threads')
|
||||||
while app.SYNC.db_scan:
|
|
||||||
i += 1
|
|
||||||
app.APP.monitor.waitForAbort(0.1)
|
|
||||||
if i > 150:
|
|
||||||
LOG.error('Could not stop library sync, aborting log-out')
|
|
||||||
# Failed to reset PMS and plex.tv connects. Try to restart Kodi
|
|
||||||
utils.messageDialog(utils.lang(29999), utils.lang(39208))
|
|
||||||
# Resuming threads, just in case
|
|
||||||
app.APP.suspend_threads = False
|
|
||||||
return False
|
|
||||||
LOG.info('Successfully stopped library sync')
|
|
||||||
app.ACCOUNT.log_out()
|
app.ACCOUNT.log_out()
|
||||||
LOG.info('User has been logged out')
|
LOG.info('User has been logged out')
|
||||||
return True
|
|
||||||
|
|
||||||
def choose_pms_server(self, manual=False):
|
def choose_pms_server(self, manual=False):
|
||||||
LOG.info("Choosing PMS server requested, starting")
|
LOG.info("Choosing PMS server requested, starting")
|
||||||
|
@ -202,15 +190,16 @@ class Service():
|
||||||
if not server:
|
if not server:
|
||||||
LOG.info('We did not connect to a new PMS, aborting')
|
LOG.info('We did not connect to a new PMS, aborting')
|
||||||
return False
|
return False
|
||||||
LOG.info("User chose server %s", server['name'])
|
LOG.info("User chose server %s with url %s",
|
||||||
if server['machineIdentifier'] == app.CONN.machine_identifier:
|
server['name'], server['baseURL'])
|
||||||
|
if (server['machineIdentifier'] == app.CONN.machine_identifier and
|
||||||
|
server['baseURL'] == app.CONN.server):
|
||||||
LOG.info('User chose old PMS to connect to')
|
LOG.info('User chose old PMS to connect to')
|
||||||
return False
|
return False
|
||||||
# Save changes to to file
|
# Save changes to to file
|
||||||
self.setup.save_pms_settings(server['baseURL'], server['token'])
|
self.setup.save_pms_settings(server['baseURL'], server['token'])
|
||||||
self.setup.write_pms_to_settings(server)
|
self.setup.write_pms_to_settings(server)
|
||||||
if not self.log_out():
|
self.log_out()
|
||||||
return False
|
|
||||||
# Wipe Kodi and Plex database as well as playlists and video nodes
|
# Wipe Kodi and Plex database as well as playlists and video nodes
|
||||||
utils.wipe_database()
|
utils.wipe_database()
|
||||||
app.CONN.load()
|
app.CONN.load()
|
||||||
|
@ -220,12 +209,13 @@ class Service():
|
||||||
self.welcome_msg = False
|
self.welcome_msg = False
|
||||||
# Force a full sync
|
# Force a full sync
|
||||||
app.SYNC.run_lib_scan = 'full'
|
app.SYNC.run_lib_scan = 'full'
|
||||||
|
# Enable the main loop to continue
|
||||||
|
app.APP.suspend = False
|
||||||
LOG.info("Choosing new PMS complete")
|
LOG.info("Choosing new PMS complete")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def switch_plex_user(self):
|
def switch_plex_user(self):
|
||||||
if not self.log_out():
|
self.log_out()
|
||||||
return False
|
|
||||||
# First remove playlists of old user
|
# First remove playlists of old user
|
||||||
utils.delete_playlists()
|
utils.delete_playlists()
|
||||||
# Remove video nodes
|
# Remove video nodes
|
||||||
|
@ -234,6 +224,8 @@ class Service():
|
||||||
# Force full sync after login
|
# Force full sync after login
|
||||||
utils.settings('lastfullsync', value='0')
|
utils.settings('lastfullsync', value='0')
|
||||||
app.SYNC.run_lib_scan = 'full'
|
app.SYNC.run_lib_scan = 'full'
|
||||||
|
# Enable the main loop to display user selection dialog
|
||||||
|
app.APP.suspend = False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def toggle_plex_tv(self):
|
def toggle_plex_tv(self):
|
||||||
|
@ -246,6 +238,8 @@ class Service():
|
||||||
if self.setup.plex_tv_sign_in():
|
if self.setup.plex_tv_sign_in():
|
||||||
self.setup.write_credentials_to_settings()
|
self.setup.write_credentials_to_settings()
|
||||||
app.ACCOUNT.load()
|
app.ACCOUNT.load()
|
||||||
|
# Enable the main loop to continue
|
||||||
|
app.APP.suspend = False
|
||||||
|
|
||||||
def authenticate(self):
|
def authenticate(self):
|
||||||
"""
|
"""
|
||||||
|
@ -265,22 +259,19 @@ class Service():
|
||||||
icon='{plex}',
|
icon='{plex}',
|
||||||
time=2000,
|
time=2000,
|
||||||
sound=False)
|
sound=False)
|
||||||
app.APP.suspend_threads = False
|
app.APP.resume_threads()
|
||||||
self.auth_running = False
|
self.auth_running = False
|
||||||
|
|
||||||
def enter_new_pms_address(self):
|
def enter_new_pms_address(self):
|
||||||
server = self.setup.enter_new_pms_address()
|
server = self.setup.enter_new_pms_address()
|
||||||
if not server:
|
if not server:
|
||||||
return
|
return
|
||||||
if not self.log_out():
|
self.log_out()
|
||||||
return False
|
|
||||||
# Save changes to to file
|
# Save changes to to file
|
||||||
self.setup.save_pms_settings(server['baseURL'], server['token'])
|
self.setup.save_pms_settings(server['baseURL'], server['token'])
|
||||||
self.setup.write_pms_to_settings(server)
|
self.setup.write_pms_to_settings(server)
|
||||||
if not v.KODIVERSION >= 18:
|
if not v.KODIVERSION >= 18:
|
||||||
utils.settings('sslverify', value='false')
|
utils.settings('sslverify', value='false')
|
||||||
if not self.log_out():
|
|
||||||
return False
|
|
||||||
# Wipe Kodi and Plex database as well as playlists and video nodes
|
# Wipe Kodi and Plex database as well as playlists and video nodes
|
||||||
utils.wipe_database()
|
utils.wipe_database()
|
||||||
app.CONN.load()
|
app.CONN.load()
|
||||||
|
@ -290,7 +281,9 @@ class Service():
|
||||||
self.welcome_msg = False
|
self.welcome_msg = False
|
||||||
# Force a full sync
|
# Force a full sync
|
||||||
app.SYNC.run_lib_scan = 'full'
|
app.SYNC.run_lib_scan = 'full'
|
||||||
LOG.info("Choosing new PMS complete")
|
# Enable the main loop to continue
|
||||||
|
app.APP.suspend = False
|
||||||
|
LOG.info("Entering PMS address complete")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _do_auth(self):
|
def _do_auth(self):
|
||||||
|
@ -323,6 +316,8 @@ class Service():
|
||||||
if not user:
|
if not user:
|
||||||
LOG.info('No user received')
|
LOG.info('No user received')
|
||||||
app.APP.suspend = True
|
app.APP.suspend = True
|
||||||
|
app.APP.suspend_threads()
|
||||||
|
LOG.debug('Threads suspended')
|
||||||
return False
|
return False
|
||||||
username = user.title
|
username = user.title
|
||||||
user_id = user.id
|
user_id = user.id
|
||||||
|
@ -355,7 +350,10 @@ class Service():
|
||||||
app.ACCOUNT.load()
|
app.ACCOUNT.load()
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
|
LOG.debug('Suspending threads')
|
||||||
app.APP.suspend = True
|
app.APP.suspend = True
|
||||||
|
app.APP.suspend_threads()
|
||||||
|
LOG.debug('Threads suspended')
|
||||||
return False
|
return False
|
||||||
elif res >= 400:
|
elif res >= 400:
|
||||||
LOG.error('Answer from PMS is not as expected')
|
LOG.error('Answer from PMS is not as expected')
|
||||||
|
@ -378,13 +376,6 @@ class Service():
|
||||||
app.init()
|
app.init()
|
||||||
app.APP.monitor = kodimonitor.KodiMonitor()
|
app.APP.monitor = kodimonitor.KodiMonitor()
|
||||||
app.APP.player = xbmc.Player()
|
app.APP.player = xbmc.Player()
|
||||||
artwork.IMAGE_CACHING_SUSPENDS = [
|
|
||||||
app.APP.suspend_threads,
|
|
||||||
app.SYNC.stop_sync,
|
|
||||||
app.SYNC.db_scan
|
|
||||||
]
|
|
||||||
if not utils.settings('imageSyncDuringPlayback') == 'true':
|
|
||||||
artwork.IMAGE_CACHING_SUSPENDS.append(app.SYNC.suspend_sync)
|
|
||||||
# Initialize the PKC playqueues
|
# Initialize the PKC playqueues
|
||||||
PQ.init_playqueues()
|
PQ.init_playqueues()
|
||||||
|
|
||||||
|
@ -505,7 +496,10 @@ class Service():
|
||||||
|
|
||||||
# EXITING PKC
|
# EXITING PKC
|
||||||
# Tell all threads to terminate (e.g. several lib sync threads)
|
# Tell all threads to terminate (e.g. several lib sync threads)
|
||||||
|
LOG.debug('Aborting all threads')
|
||||||
app.APP.stop_pkc = True
|
app.APP.stop_pkc = True
|
||||||
|
# Will block until threads have quit
|
||||||
|
app.APP.stop_threads()
|
||||||
utils.window('plex_service_started', clear=True)
|
utils.window('plex_service_started', clear=True)
|
||||||
LOG.info("======== STOP %s ========", v.ADDON_NAME)
|
LOG.info("======== STOP %s ========", v.ADDON_NAME)
|
||||||
|
|
||||||
|
|
|
@ -15,19 +15,6 @@ if library_sync.PLAYLIST_SYNC_ENABLED:
|
||||||
LOG = getLogger('PLEX.sync')
|
LOG = getLogger('PLEX.sync')
|
||||||
|
|
||||||
|
|
||||||
def set_library_scan_toggle(boolean=True):
|
|
||||||
"""
|
|
||||||
Make sure to hit this function before starting large scans
|
|
||||||
"""
|
|
||||||
if not boolean:
|
|
||||||
# Deactivate
|
|
||||||
app.SYNC.db_scan = False
|
|
||||||
utils.window('plex_dbScan', clear=True)
|
|
||||||
else:
|
|
||||||
app.SYNC.db_scan = True
|
|
||||||
utils.window('plex_dbScan', value="true")
|
|
||||||
|
|
||||||
|
|
||||||
class Sync(backgroundthread.KillableThread):
|
class Sync(backgroundthread.KillableThread):
|
||||||
"""
|
"""
|
||||||
The one and only library sync thread. Spawn only 1!
|
The one and only library sync thread. Spawn only 1!
|
||||||
|
@ -35,24 +22,18 @@ class Sync(backgroundthread.KillableThread):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.sync_successful = False
|
self.sync_successful = False
|
||||||
self.last_full_sync = 0
|
self.last_full_sync = 0
|
||||||
self.fanart = None
|
self.fanart_thread = None
|
||||||
# Show sync dialog even if user deactivated?
|
|
||||||
self.force_dialog = False
|
|
||||||
self.image_cache_thread = None
|
self.image_cache_thread = None
|
||||||
# Lock used to wait on a full sync, e.g. on initial sync
|
# Lock used to wait on a full sync, e.g. on initial sync
|
||||||
# self.lock = backgroundthread.threading.Lock()
|
# self.lock = backgroundthread.threading.Lock()
|
||||||
super(Sync, self).__init__()
|
super(Sync, self).__init__()
|
||||||
|
|
||||||
def isSuspended(self):
|
|
||||||
return self._suspended or app.APP.suspend_threads
|
|
||||||
|
|
||||||
def triage_lib_scans(self):
|
def triage_lib_scans(self):
|
||||||
"""
|
"""
|
||||||
Decides what to do if app.SYNC.run_lib_scan has been set. E.g. manually
|
Decides what to do if app.SYNC.run_lib_scan has been set. E.g. manually
|
||||||
triggered full or repair syncs
|
triggered full or repair syncs
|
||||||
"""
|
"""
|
||||||
if app.SYNC.run_lib_scan in ("full", "repair"):
|
if app.SYNC.run_lib_scan in ("full", "repair"):
|
||||||
set_library_scan_toggle()
|
|
||||||
LOG.info('Full library scan requested, starting')
|
LOG.info('Full library scan requested, starting')
|
||||||
self.start_library_sync(show_dialog=True,
|
self.start_library_sync(show_dialog=True,
|
||||||
repair=app.SYNC.run_lib_scan == 'repair',
|
repair=app.SYNC.run_lib_scan == 'repair',
|
||||||
|
@ -89,23 +70,16 @@ class Sync(backgroundthread.KillableThread):
|
||||||
"""
|
"""
|
||||||
self.sync_successful = successful
|
self.sync_successful = successful
|
||||||
self.last_full_sync = timing.unix_timestamp()
|
self.last_full_sync = timing.unix_timestamp()
|
||||||
set_library_scan_toggle(boolean=False)
|
|
||||||
if not successful:
|
if not successful:
|
||||||
LOG.warn('Could not finish scheduled full sync')
|
LOG.warn('Could not finish scheduled full sync')
|
||||||
# try:
|
app.APP.resume_fanart_thread()
|
||||||
# self.lock.release()
|
app.APP.resume_caching_thread()
|
||||||
# except backgroundthread.threading.ThreadError:
|
|
||||||
# pass
|
|
||||||
|
|
||||||
def start_library_sync(self, show_dialog=None, repair=False, block=False):
|
def start_library_sync(self, show_dialog=None, repair=False, block=False):
|
||||||
set_library_scan_toggle(boolean=True)
|
app.APP.suspend_fanart_thread(block=True)
|
||||||
|
app.APP.suspend_caching_thread(block=True)
|
||||||
show_dialog = show_dialog if show_dialog is not None else app.SYNC.sync_dialog
|
show_dialog = show_dialog if show_dialog is not None else app.SYNC.sync_dialog
|
||||||
library_sync.start(show_dialog, repair, self.on_library_scan_finished)
|
library_sync.start(show_dialog, repair, self.on_library_scan_finished)
|
||||||
# if block:
|
|
||||||
# self.lock.acquire()
|
|
||||||
# Will block until scan is finished
|
|
||||||
# self.lock.acquire()
|
|
||||||
# self.lock.release()
|
|
||||||
|
|
||||||
def start_fanart_download(self, refresh):
|
def start_fanart_download(self, refresh):
|
||||||
if not utils.settings('FanartTV') == 'true':
|
if not utils.settings('FanartTV') == 'true':
|
||||||
|
@ -114,11 +88,11 @@ class Sync(backgroundthread.KillableThread):
|
||||||
if not app.SYNC.artwork:
|
if not app.SYNC.artwork:
|
||||||
LOG.info('Not synching Plex PMS artwork, not getting artwork')
|
LOG.info('Not synching Plex PMS artwork, not getting artwork')
|
||||||
return False
|
return False
|
||||||
elif self.fanart is None or not self.fanart.is_alive():
|
elif self.fanart_thread is None or not self.fanart_thread.is_alive():
|
||||||
LOG.info('Start downloading additional fanart with refresh %s',
|
LOG.info('Start downloading additional fanart with refresh %s',
|
||||||
refresh)
|
refresh)
|
||||||
self.fanart = library_sync.FanartThread(self.on_fanart_download_finished, refresh)
|
self.fanart_thread = library_sync.FanartThread(self.on_fanart_download_finished, refresh)
|
||||||
self.fanart.start()
|
self.fanart_thread.start()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
LOG.info('Still downloading fanart')
|
LOG.info('Still downloading fanart')
|
||||||
|
@ -144,16 +118,18 @@ class Sync(backgroundthread.KillableThread):
|
||||||
self.image_cache_thread.start()
|
self.image_cache_thread.start()
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
LOG.info("---===### Starting Sync Thread ###===---")
|
||||||
|
app.APP.register_thread(self)
|
||||||
try:
|
try:
|
||||||
self._run_internal()
|
self._run_internal()
|
||||||
except Exception:
|
except Exception:
|
||||||
app.SYNC.db_scan = False
|
|
||||||
utils.window('plex_dbScan', clear=True)
|
|
||||||
utils.ERROR(txt='sync.py crashed', notify=True)
|
utils.ERROR(txt='sync.py crashed', notify=True)
|
||||||
raise
|
raise
|
||||||
|
finally:
|
||||||
|
app.APP.deregister_thread(self)
|
||||||
|
LOG.info("###===--- Sync Thread Stopped ---===###")
|
||||||
|
|
||||||
def _run_internal(self):
|
def _run_internal(self):
|
||||||
LOG.info("---===### Starting Sync Thread ###===---")
|
|
||||||
install_sync_done = utils.settings('SyncInstallRunDone') == 'true'
|
install_sync_done = utils.settings('SyncInstallRunDone') == 'true'
|
||||||
playlist_monitor = None
|
playlist_monitor = None
|
||||||
initial_sync_done = False
|
initial_sync_done = False
|
||||||
|
@ -186,17 +162,10 @@ class Sync(backgroundthread.KillableThread):
|
||||||
|
|
||||||
while not self.isCanceled():
|
while not self.isCanceled():
|
||||||
# In the event the server goes offline
|
# In the event the server goes offline
|
||||||
while self.isSuspended():
|
if self.wait_while_suspended():
|
||||||
if self.isCanceled():
|
|
||||||
# Abort was requested while waiting. We should exit
|
|
||||||
LOG.info("###===--- Sync Thread Stopped ---===###")
|
|
||||||
return
|
return
|
||||||
app.APP.monitor.waitForAbort(1)
|
|
||||||
|
|
||||||
if not install_sync_done:
|
if not install_sync_done:
|
||||||
# Very FIRST sync ever upon installation or reset of Kodi DB
|
# Very FIRST sync ever upon installation or reset of Kodi DB
|
||||||
set_library_scan_toggle()
|
|
||||||
self.force_dialog = True
|
|
||||||
# Initialize time offset Kodi - PMS
|
# Initialize time offset Kodi - PMS
|
||||||
library_sync.sync_pms_time()
|
library_sync.sync_pms_time()
|
||||||
last_time_sync = timing.unix_timestamp()
|
last_time_sync = timing.unix_timestamp()
|
||||||
|
@ -217,7 +186,6 @@ class Sync(backgroundthread.KillableThread):
|
||||||
else:
|
else:
|
||||||
LOG.error('Initial start-up full sync unsuccessful')
|
LOG.error('Initial start-up full sync unsuccessful')
|
||||||
app.APP.monitor.waitForAbort(1)
|
app.APP.monitor.waitForAbort(1)
|
||||||
self.force_dialog = False
|
|
||||||
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
xbmc.executebuiltin('InhibitIdleShutdown(false)')
|
||||||
|
|
||||||
elif not initial_sync_done:
|
elif not initial_sync_done:
|
||||||
|
@ -237,13 +205,10 @@ class Sync(backgroundthread.KillableThread):
|
||||||
app.APP.monitor.waitForAbort(1)
|
app.APP.monitor.waitForAbort(1)
|
||||||
|
|
||||||
# Currently no db scan, so we could start a new scan
|
# Currently no db scan, so we could start a new scan
|
||||||
elif app.SYNC.db_scan is False:
|
else:
|
||||||
# Full scan was requested from somewhere else
|
# Full scan was requested from somewhere else
|
||||||
if app.SYNC.run_lib_scan is not None:
|
if app.SYNC.run_lib_scan is not None:
|
||||||
# Force-show dialogs since they are user-initiated
|
|
||||||
self.force_dialog = True
|
|
||||||
self.triage_lib_scans()
|
self.triage_lib_scans()
|
||||||
self.force_dialog = False
|
|
||||||
# Reset the flag
|
# Reset the flag
|
||||||
app.SYNC.run_lib_scan = None
|
app.SYNC.run_lib_scan = None
|
||||||
continue
|
continue
|
||||||
|
@ -251,7 +216,7 @@ class Sync(backgroundthread.KillableThread):
|
||||||
# Standard syncs - don't force-show dialogs
|
# Standard syncs - don't force-show dialogs
|
||||||
now = timing.unix_timestamp()
|
now = timing.unix_timestamp()
|
||||||
if (now - self.last_full_sync > app.SYNC.full_sync_intervall and
|
if (now - self.last_full_sync > app.SYNC.full_sync_intervall and
|
||||||
not app.SYNC.suspend_sync):
|
not app.APP.is_playing_video):
|
||||||
LOG.info('Doing scheduled full library scan')
|
LOG.info('Doing scheduled full library scan')
|
||||||
self.start_library_sync()
|
self.start_library_sync()
|
||||||
elif now - last_time_sync > one_day_in_seconds:
|
elif now - last_time_sync > one_day_in_seconds:
|
||||||
|
@ -287,4 +252,3 @@ class Sync(backgroundthread.KillableThread):
|
||||||
DU().stopSession()
|
DU().stopSession()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
LOG.info("###===--- Sync Thread Stopped ---===###")
|
|
||||||
|
|
|
@ -223,7 +223,7 @@ def ERROR(txt='', hide_tb=False, notify=False, cancel_sync=False):
|
||||||
LOG.error('Error encountered: %s - %s', txt, short)
|
LOG.error('Error encountered: %s - %s', txt, short)
|
||||||
if cancel_sync:
|
if cancel_sync:
|
||||||
from . import app
|
from . import app
|
||||||
app.SYNC.stop_sync = True
|
app.APP.stop_threads(block=False)
|
||||||
if hide_tb:
|
if hide_tb:
|
||||||
return short
|
return short
|
||||||
|
|
||||||
|
@ -502,19 +502,7 @@ def reset(ask_user=True):
|
||||||
return
|
return
|
||||||
from . import app
|
from . import app
|
||||||
# first stop any db sync
|
# first stop any db sync
|
||||||
app.APP.suspend_threads = True
|
app.APP.suspend_threads()
|
||||||
count = 15
|
|
||||||
while app.SYNC.db_scan:
|
|
||||||
LOG.info("Sync is running, will retry: %s...", count)
|
|
||||||
count -= 1
|
|
||||||
if count == 0:
|
|
||||||
LOG.error('Could not stop PKC syncing process to reset the DB')
|
|
||||||
# Could not stop the database from running. Please try again later.
|
|
||||||
messageDialog(lang(29999), lang(39601))
|
|
||||||
app.APP.suspend_threads = False
|
|
||||||
return
|
|
||||||
xbmc.sleep(1000)
|
|
||||||
|
|
||||||
# Reset all PlexKodiConnect Addon settings? (this is usually NOT
|
# Reset all PlexKodiConnect Addon settings? (this is usually NOT
|
||||||
# recommended and unnecessary!)
|
# recommended and unnecessary!)
|
||||||
if ask_user and yesno_dialog(lang(29999), lang(39603)):
|
if ask_user and yesno_dialog(lang(29999), lang(39603)):
|
||||||
|
|
|
@ -47,20 +47,20 @@ class WebSocket(backgroundthread.KillableThread):
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
LOG.info("----===## Starting %s ##===----", self.__class__.__name__)
|
LOG.info("----===## Starting %s ##===----", self.__class__.__name__)
|
||||||
|
app.APP.register_thread(self)
|
||||||
counter = 0
|
counter = 0
|
||||||
while not self.isCanceled():
|
while not self.isCanceled():
|
||||||
# In the event the server goes offline
|
# In the event the server goes offline
|
||||||
while self.isSuspended():
|
if self.isSuspended():
|
||||||
# Set in service.py
|
# Set in service.py
|
||||||
if self.ws is not None:
|
if self.ws is not None:
|
||||||
self.ws.close()
|
self.ws.close()
|
||||||
self.ws = None
|
self.ws = None
|
||||||
if self.isCanceled():
|
if self.wait_while_suspended():
|
||||||
# Abort was requested while waiting. We should exit
|
# Abort was requested while waiting. We should exit
|
||||||
LOG.info("##===---- %s Stopped ----===##",
|
LOG.info("##===---- %s Stopped ----===##",
|
||||||
self.__class__.__name__)
|
self.__class__.__name__)
|
||||||
return
|
return
|
||||||
app.APP.monitor.waitForAbort(1)
|
|
||||||
try:
|
try:
|
||||||
self.process(*self.receive(self.ws))
|
self.process(*self.receive(self.ws))
|
||||||
except websocket.WebSocketTimeoutException:
|
except websocket.WebSocketTimeoutException:
|
||||||
|
@ -136,6 +136,7 @@ class WebSocket(backgroundthread.KillableThread):
|
||||||
# Close websocket connection on shutdown
|
# Close websocket connection on shutdown
|
||||||
if self.ws is not None:
|
if self.ws is not None:
|
||||||
self.ws.close()
|
self.ws.close()
|
||||||
|
app.APP.deregister_thread(self)
|
||||||
LOG.info("##===---- %s Stopped ----===##", self.__class__.__name__)
|
LOG.info("##===---- %s Stopped ----===##", self.__class__.__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -147,9 +148,7 @@ class PMS_Websocket(WebSocket):
|
||||||
"""
|
"""
|
||||||
Returns True if the thread is suspended
|
Returns True if the thread is suspended
|
||||||
"""
|
"""
|
||||||
return (self._suspended or
|
return self._suspended or app.SYNC.background_sync_disabled
|
||||||
app.APP.suspend_threads or
|
|
||||||
app.SYNC.background_sync_disabled)
|
|
||||||
|
|
||||||
def getUri(self):
|
def getUri(self):
|
||||||
if self.redirect_uri:
|
if self.redirect_uri:
|
||||||
|
@ -201,11 +200,6 @@ class PMS_Websocket(WebSocket):
|
||||||
# Drop everything we're not interested in
|
# Drop everything we're not interested in
|
||||||
if typus not in ('playing', 'timeline', 'activity'):
|
if typus not in ('playing', 'timeline', 'activity'):
|
||||||
return
|
return
|
||||||
elif typus == 'activity' and app.SYNC.db_scan is True:
|
|
||||||
# Only add to processing if PKC is NOT doing a lib scan (and thus
|
|
||||||
# possibly causing these reprocessing messages en mass)
|
|
||||||
LOG.debug('%s: Dropping message as PKC is currently synching',
|
|
||||||
self.__class__.__name__)
|
|
||||||
else:
|
else:
|
||||||
# Put PMS message on queue and let libsync take care of it
|
# Put PMS message on queue and let libsync take care of it
|
||||||
app.APP.websocket_queue.put(message)
|
app.APP.websocket_queue.put(message)
|
||||||
|
|
Loading…
Reference in a new issue