Rewire image caching thread

This commit is contained in:
croneter 2018-11-05 15:23:51 +01:00
parent 30d85eebc0
commit e761567592
3 changed files with 119 additions and 106 deletions

View file

@ -9,8 +9,7 @@ import requests
import xbmc import xbmc
from . import path_ops from . import backgroundthread, path_ops, utils
from . import utils
from . import state from . import state
############################################################################### ###############################################################################
@ -19,9 +18,12 @@ LOG = getLogger('PLEX.artwork')
# Disable annoying requests warnings # Disable annoying requests warnings
requests.packages.urllib3.disable_warnings() requests.packages.urllib3.disable_warnings()
ARTWORK_QUEUE = Queue() ARTWORK_QUEUE = Queue()
IMAGE_CACHING_SUSPENDS = ['SUSPEND_LIBRARY_THREAD', 'DB_SCAN', 'STOP_SYNC'] IMAGE_CACHING_SUSPENDS = [
state.SUSPEND_LIBRARY_THREAD,
state.DB_SCAN
]
if not utils.settings('imageSyncDuringPlayback') == 'true': if not utils.settings('imageSyncDuringPlayback') == 'true':
IMAGE_CACHING_SUSPENDS.append('SUSPEND_SYNC') IMAGE_CACHING_SUSPENDS.append(state.SUSPEND_SYNC)
############################################################################### ###############################################################################
@ -34,9 +36,7 @@ def double_urldecode(text):
return unquote(unquote(text)) return unquote(unquote(text))
@utils.thread_methods(add_suspends=IMAGE_CACHING_SUSPENDS) class ImageCachingThread(backgroundthread.KillableThread):
class ImageCachingThread(Thread):
sleep_between = 50
# Potentially issues with limited number of threads # Potentially issues with limited number of threads
# Hence let Kodi wait till download is successful # Hence let Kodi wait till download is successful
timeout = (35.1, 35.1) timeout = (35.1, 35.1)
@ -45,36 +45,29 @@ class ImageCachingThread(Thread):
self.queue = ARTWORK_QUEUE self.queue = ARTWORK_QUEUE
Thread.__init__(self) Thread.__init__(self)
def run(self): def isCanceled(self):
LOG.info("---===### Starting ImageCachingThread ###===---") return state.STOP_PKC
stopped = self.stopped
suspended = self.suspended
queue = self.queue
sleep_between = self.sleep_between
while not stopped():
# In the event the server goes offline
while suspended():
# Set in service.py
if stopped():
# Abort was requested while waiting. We should exit
LOG.info("---===### Stopped ImageCachingThread ###===---")
return
xbmc.sleep(1000)
try: def isSuspended(self):
url = queue.get(block=False) return any(IMAGE_CACHING_SUSPENDS)
except Empty:
xbmc.sleep(1000) @staticmethod
continue def _art_url_generator():
if isinstance(url, ArtworkSyncMessage): from . import kodidb_functions as kodidb
if state.IMAGE_SYNC_NOTIFICATIONS: for kind in ('video', 'music'):
utils.dialog('notification', with kodidb.GetKodiDB(kind) as kodi_db:
heading=utils.lang(29999), for kodi_type in ('poster', 'fanart'):
message=url.message, for url in kodi_db.artwork_generator(kodi_type):
icon='{plex}', yield url
sound=False)
queue.task_done() def missing_art_cache_generator(self):
continue from . import kodidb_functions as kodidb
with kodidb.GetKodiDB('texture') as kodi_db:
for url in self._art_url_generator():
if kodi_db.url_not_yet_cached(url):
yield url
def _cache_url(self, url):
url = double_urlencode(utils.try_encode(url)) url = double_urlencode(utils.try_encode(url))
sleeptime = 0 sleeptime = 0
while True: while True:
@ -92,7 +85,7 @@ class ImageCachingThread(Thread):
# download. All is well # download. All is well
break break
except requests.ConnectionError: except requests.ConnectionError:
if stopped(): if self.isCanceled():
# Kodi terminated # Kodi terminated
break break
# Server thinks its a DOS attack, ('error 10053') # Server thinks its a DOS attack, ('error 10053')
@ -116,9 +109,50 @@ class ImageCachingThread(Thread):
break break
# We did not even get a timeout # We did not even get a timeout
break break
queue.task_done()
def run(self):
LOG.info("---===### Starting ImageCachingThread ###===---")
# Cache already synced artwork first
for url in self.missing_art_cache_generator():
if self.isCanceled():
return
while self.isSuspended():
# Set in service.py
if self.isCanceled():
# Abort was requested while waiting. We should exit
LOG.info("---===### Stopped ImageCachingThread ###===---")
return
xbmc.sleep(1000)
self._cache_url(url)
# Now wait for more stuff to cache - via the queue
while not self.isCanceled():
# In the event the server goes offline
while self.isSuspended():
# Set in service.py
if self.isCanceled():
# Abort was requested while waiting. We should exit
LOG.info("---===### Stopped ImageCachingThread ###===---")
return
xbmc.sleep(1000)
try:
url = self.queue.get(block=False)
except Empty:
xbmc.sleep(1000)
continue
if isinstance(url, ArtworkSyncMessage):
if state.IMAGE_SYNC_NOTIFICATIONS:
utils.dialog('notification',
heading=utils.lang(29999),
message=url.message,
icon='{plex}',
sound=False)
self.queue.task_done()
continue
self._cache_url(url)
self.queue.task_done()
# Sleep for a bit to reduce CPU strain # Sleep for a bit to reduce CPU strain
xbmc.sleep(sleep_between) xbmc.sleep(100)
LOG.info("---===### Stopped ImageCachingThread ###===---") LOG.info("---===### Stopped ImageCachingThread ###===---")
@ -127,47 +161,6 @@ class Artwork():
if enableTextureCache: if enableTextureCache:
queue = ARTWORK_QUEUE queue = ARTWORK_QUEUE
def cache_major_artwork(self):
"""
Takes the existing Kodi library and caches posters and fanart.
Necessary because otherwise PKC caches artwork e.g. from fanart.tv
which basically blocks Kodi from getting needed artwork fast (e.g.
while browsing the library)
"""
if not self.enableTextureCache:
return
artworks = list()
# Get all posters and fanart/background for video and music
for kind in ('video', 'music'):
connection = utils.kodi_sql(kind)
cursor = connection.cursor()
for typus in ('poster', 'fanart'):
cursor.execute('SELECT url FROM art WHERE type == ?',
(typus, ))
artworks.extend(cursor.fetchall())
connection.close()
artworks_to_cache = list()
connection = utils.kodi_sql('texture')
cursor = connection.cursor()
for url in artworks:
query = 'SELECT url FROM texture WHERE url == ? LIMIT 1'
cursor.execute(query, (url[0], ))
if not cursor.fetchone():
artworks_to_cache.append(url)
connection.close()
if not artworks_to_cache:
LOG.info('Caching of major images to Kodi texture cache done')
return
length = len(artworks_to_cache)
LOG.info('Caching has not been completed - caching %s major images',
length)
# Caching %s Plex images
self.queue.put(ArtworkSyncMessage(utils.lang(30006) % length))
for url in artworks_to_cache:
self.queue.put(url[0])
# Plex image caching done
self.queue.put(ArtworkSyncMessage(utils.lang(30007)))
def fullTextureCacheSync(self): def fullTextureCacheSync(self):
""" """
This method will sync all Kodi artwork to textures13.db This method will sync all Kodi artwork to textures13.db

View file

@ -1199,6 +1199,21 @@ class KodiDBMethods(object):
''' '''
self.cursor.execute(query, (kodi_id, kodi_type)) self.cursor.execute(query, (kodi_id, kodi_type))
def artwork_generator(self, kodi_type):
"""
"""
self.cursor.execute('SELECT url FROM art WHERE type == ?',
(kodi_type, ))
return (x[0] for x in self.cursor)
def url_not_yet_cached(self, url):
"""
Returns True if url has not yet been cached to the Kodi texture cache
"""
self.cursor.execute('SELECT url FROM texture WHERE url == ? LIMIT 1',
(url, ))
return self.cursor.fetchone() is None
def kodiid_from_filename(path, kodi_type=None, db_type=None): def kodiid_from_filename(path, kodi_type=None, db_type=None):
""" """

View file

@ -35,8 +35,7 @@ class Sync(backgroundthread.KillableThread):
self.fanart = None self.fanart = None
# Show sync dialog even if user deactivated? # Show sync dialog even if user deactivated?
self.force_dialog = False self.force_dialog = False
if utils.settings('enableTextureCache') == "true": self.image_cache_thread = None
self.image_cache_thread = artwork.ImageCachingThread()
# 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__()
@ -162,6 +161,13 @@ class Sync(backgroundthread.KillableThread):
icon='{plex}', icon='{plex}',
sound=False) sound=False)
def start_image_cache_thread(self):
if utils.settings('enableTextureCache') == "true":
self.image_cache_thread = artwork.ImageCachingThread()
self.image_cache_thread.start()
else:
LOG.info('Image caching has been deactivated')
def run(self): def run(self):
try: try:
self._run_internal() self._run_internal()
@ -250,7 +256,7 @@ class Sync(backgroundthread.KillableThread):
from . import playlists from . import playlists
playlist_monitor = playlists.kodi_playlist_monitor() playlist_monitor = playlists.kodi_playlist_monitor()
self.start_fanart_download(refresh=False) self.start_fanart_download(refresh=False)
self.image_cache_thread.start() self.start_image_cache_thread()
else: else:
LOG.error('Initial start-up full sync unsuccessful') LOG.error('Initial start-up full sync unsuccessful')
self.force_dialog = False self.force_dialog = False
@ -271,9 +277,8 @@ class Sync(backgroundthread.KillableThread):
if library_sync.PLAYLIST_SYNC_ENABLED: if library_sync.PLAYLIST_SYNC_ENABLED:
from . import playlists from . import playlists
playlist_monitor = playlists.kodi_playlist_monitor() playlist_monitor = playlists.kodi_playlist_monitor()
artwork.Artwork().cache_major_artwork()
self.start_fanart_download(refresh=False) self.start_fanart_download(refresh=False)
self.image_cache_thread.start() self.start_image_cache_thread()
else: else:
LOG.info('Startup sync has not yet been successful') LOG.info('Startup sync has not yet been successful')