From 32a880cef017e3a2d6a0d976f43ea97ad59f34ec Mon Sep 17 00:00:00 2001 From: Croneter Date: Wed, 18 Apr 2018 08:39:41 +0200 Subject: [PATCH 01/21] Clean up code for userclient --- resources/lib/initialsetup.py | 2 +- resources/lib/userclient.py | 172 +++++++++++++++++----------------- service.py | 8 +- 3 files changed, 93 insertions(+), 89 deletions(-) diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index da05555e..24010482 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -141,7 +141,7 @@ class InitialSetup(object): """ def __init__(self): LOG.debug('Entering initialsetup class') - self.server = UserClient().getServer() + self.server = UserClient().get_server() self.serverid = settings('plex_machineIdentifier') # Get Plex credentials from settings file, if they exist plexdict = PF.GetPlexLoginFromSettings() diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index 8aaec05f..c9458e85 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -22,7 +22,9 @@ LOG = getLogger("PLEX." + __name__) @thread_methods(add_suspends=['SUSPEND_USER_CLIENT']) class UserClient(Thread): - + """ + Manage Plex users + """ # Borg - multiple instances, shared state __shared_state = {} @@ -32,100 +34,98 @@ class UserClient(Thread): self.auth = True self.retry = 0 - self.currUser = None - self.currServer = None - self.currToken = None - self.HasAccess = True - self.AdditionalUser = [] + self.user = None + self.has_access = True - self.userSettings = None + self.server = None + self.server_name = None + self.machine_identifier = None + self.token = None + self.ssl = None + self.sslcert = None self.addon = xbmcaddon.Addon() - self.doUtils = DU() + self.do_utils = None Thread.__init__(self) - def getUsername(self): + def get_server(self): """ - Returns username as unicode + Get the current PMS' URL """ - username = settings('username') - if not username: - LOG.debug("No username saved, trying to get Plex username") - username = settings('plexLogin') - if not username: - LOG.debug("Also no Plex username found") - return "" - return username - - def getServer(self, prefix=True): # Original host - self.servername = settings('plex_servername') - HTTPS = settings('https') == "true" + self.server_name = settings('plex_servername') + https = settings('https') == "true" host = settings('ipaddress') port = settings('port') - self.machineIdentifier = settings('plex_machineIdentifier') - - server = host + ":" + port - + self.machine_identifier = settings('plex_machineIdentifier') if not host: LOG.debug("No server information saved.") return False - + server = host + ":" + port # If https is true - if prefix and HTTPS: + if https: server = "https://%s" % server # If https is false - elif prefix and not HTTPS: + else: server = "http://%s" % server # User entered IP; we need to get the machineIdentifier - if self.machineIdentifier == '' and prefix is True: - self.machineIdentifier = PF.GetMachineIdentifier(server) - if self.machineIdentifier is None: - self.machineIdentifier = '' - settings('plex_machineIdentifier', value=self.machineIdentifier) + if not self.machine_identifier: + self.machine_identifier = PF.GetMachineIdentifier(server) + if not self.machine_identifier: + self.machine_identifier = '' + settings('plex_machineIdentifier', value=self.machine_identifier) LOG.debug('Returning active server: %s', server) return server - def getSSLverify(self): - # Verify host certificate + @staticmethod + def get_ssl_verify(): + """ + Do we need to verify the SSL certificate? Return None if that is the + case, else False + """ return None if settings('sslverify') == 'true' else False - def getSSL(self): - # Client side certificate + @staticmethod + def get_ssl_certificate(): + """ + Client side certificate + """ return None if settings('sslcert') == 'None' \ else settings('sslcert') - def setUserPref(self): + def set_user_prefs(self): + """ + Load a user's profile picture + """ LOG.debug('Setting user preferences') # Only try to get user avatar if there is a token - if self.currToken: - url = PF.GetUserArtworkURL(self.currUser) + if self.token: + url = PF.GetUserArtworkURL(self.user) if url: window('PlexUserImage', value=url) - # Set resume point max - # url = "{server}/emby/System/Configuration?format=json" - # result = doUtils.downloadUrl(url) - def hasAccess(self): + @staticmethod + def check_access(): # Plex: always return True for now return True - def loadCurrUser(self, username, userId, usertoken, authenticated=False): + def load_user(self, username, user_id, usertoken, authenticated=False): + """ + Load the current user's details for PKC + """ LOG.debug('Loading current user') - doUtils = self.doUtils - - self.currToken = usertoken - self.currServer = self.getServer() - self.ssl = self.getSSLverify() - self.sslcert = self.getSSL() + self.token = usertoken + self.server = self.get_server() + self.ssl = self.get_ssl_verify() + self.sslcert = self.get_ssl_certificate() if authenticated is False: - if self.currServer is None: + if self.server is None: return False LOG.debug('Testing validity of current token') - res = PF.check_connection(self.currServer, - token=self.currToken, + res = PF.check_connection(self.server, + token=self.token, verifySSL=self.ssl) if res is False: # PMS probably offline @@ -138,7 +138,7 @@ class UserClient(Thread): return False # Set to windows property - state.PLEX_USER_ID = userId or None + state.PLEX_USER_ID = user_id or None state.PLEX_USERNAME = username # This is the token for the current PMS (might also be '') window('pms_token', value=usertoken) @@ -150,9 +150,9 @@ class UserClient(Thread): window('plex_restricteduser', value=settings('plex_restricteduser')) state.RESTRICTED_USER = True \ if settings('plex_restricteduser') == 'true' else False - window('pms_server', value=self.currServer) - window('plex_machineIdentifier', value=self.machineIdentifier) - window('plex_servername', value=self.servername) + window('pms_server', value=self.server) + window('plex_machineIdentifier', value=self.machine_identifier) + window('plex_servername', value=self.server_name) window('plex_authenticated', value='true') state.AUTHENTICATED = True @@ -166,19 +166,22 @@ class UserClient(Thread): if settings('force_transcode_pix') == "1" else 'false') # Start DownloadUtils session - doUtils.startSession(reset=True) - # self.getAdditionalUsers() + self.do_utils = DU() + self.do_utils.startSession(reset=True) # Set user preferences in settings - self.currUser = username - self.setUserPref() + self.user = username + self.set_user_prefs() # Writing values to settings file settings('username', value=username) - settings('userid', value=userId) + settings('userid', value=user_id) settings('accessToken', value=usertoken) return True def authenticate(self): + """ + Authenticate the current user + """ LOG.debug('Authenticating user') # Give attempts at entering password / selecting user @@ -198,7 +201,7 @@ class UserClient(Thread): LOG.error("Error, no settings.xml found.") self.auth = False return False - server = self.getServer() + server = self.get_server() # If there is no server we can connect to if not server: LOG.info("Missing server information.") @@ -213,10 +216,10 @@ class UserClient(Thread): # Found a user in the settings, try to authenticate if username and enforceLogin == 'false': LOG.debug('Trying to authenticate with old settings') - answ = self.loadCurrUser(username, - userId, - usertoken, - authenticated=False) + answ = self.load_user(username, + userId, + usertoken, + authenticated=False) if answ is True: # SUCCESS: loaded a user from the settings return True @@ -248,7 +251,7 @@ class UserClient(Thread): userId = '' usertoken = '' - if self.loadCurrUser(username, userId, usertoken, authenticated=False): + if self.load_user(username, userId, usertoken, authenticated=False): # SUCCESS: loaded a user from the settings return True # Something went wrong, try again @@ -256,9 +259,12 @@ class UserClient(Thread): self.retry += 1 return False - def resetClient(self): + def reset_client(self): + """ + Reset all user settings + """ LOG.debug("Reset UserClient authentication.") - self.doUtils.stopSession() + self.do_utils.stopSession() window('plex_authenticated', clear=True) state.AUTHENTICATED = False @@ -279,13 +285,16 @@ class UserClient(Thread): settings('userid', value='') settings('accessToken', value='') - self.currToken = None + self.token = None self.auth = True - self.currUser = None + self.user = None self.retry = 0 def run(self): + """ + Do the work + """ LOG.info("----===## Starting UserClient ##===----") stopped = self.stopped suspended = self.suspended @@ -299,19 +308,14 @@ class UserClient(Thread): sleep(500) continue - # Verify the connection status to server - elif state.PMS_STATUS == "restricted": - # Parental control is restricting access - self.HasAccess = False - elif state.PMS_STATUS == "401": # Unauthorized access, revoke token state.PMS_STATUS = 'Auth' window('plex_serverStatus', value='Auth') - self.resetClient() + self.reset_client() sleep(3000) - if self.auth and (self.currUser is None): + if self.auth and (self.user is None): # Try to authenticate user if not state.PMS_STATUS or state.PMS_STATUS == "Auth": # Set auth flag because we no longer need @@ -320,16 +324,16 @@ class UserClient(Thread): if self.authenticate(): # Successfully authenticated and loaded a user LOG.info("Successfully authenticated!") - LOG.info("Current user: %s", self.currUser) + LOG.info("Current user: %s", self.user) LOG.info("Current userId: %s", state.PLEX_USER_ID) self.retry = 0 state.SUSPEND_LIBRARY_THREAD = False window('plex_serverStatus', clear=True) state.PMS_STATUS = False - if not self.auth and (self.currUser is None): + if not self.auth and (self.user is None): # Loop if no server found - server = self.getServer() + server = self.get_server() # The status Stop is for when user cancelled password dialog. # Or retried too many times diff --git a/service.py b/service.py index 258f055d..237ff1fe 100644 --- a/service.py +++ b/service.py @@ -135,7 +135,7 @@ class Service(): if window('plex_online') == "true": # Plex server is online # Verify if user is set and has access to the server - if (self.user.currUser is not None) and self.user.HasAccess: + if (self.user.currUser is not None) and self.user.has_access: if not self.kodimonitor_running: # Start up events self.warn_auth = True @@ -187,9 +187,9 @@ class Service(): # User access is restricted. # Keep verifying until access is granted # unless server goes offline or Kodi is shut down. - while self.user.HasAccess is False: + while self.user.has_access is False: # Verify access with an API call - self.user.hasAccess() + self.user.check_access() if window('plex_online') != "true": # Server went offline @@ -202,7 +202,7 @@ class Service(): # Wait until Plex server is online # or Kodi is shut down. while not self.__stop_PKC(): - server = self.user.getServer() + server = self.user.get_server() if server is False: # No server info set in add-on settings pass From 165b85c52d9e1c3fc9ca6ce1606a02728b15a670 Mon Sep 17 00:00:00 2001 From: Croneter Date: Thu, 19 Apr 2018 08:06:51 +0200 Subject: [PATCH 02/21] Fix TV On Deck direct paths asking to choose between different media --- resources/lib/entrypoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 7fd7dfc9..3a89d8c3 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -553,7 +553,7 @@ def getOnDeck(viewid, mediatype, tagname, limit): append_show_title=append_show_title, append_sxxexx=append_sxxexx) if directpaths: - url = api.file_path() + url = api.file_path(force_first_media=True) else: url = ('plugin://%s.tvshows/?plex_id=%s&plex_type=%s&mode=play&filename=%s' % (v.ADDON_ID, From 4285e8f28a26cdbdd2e764e23160314a37645983 Mon Sep 17 00:00:00 2001 From: Croneter Date: Thu, 19 Apr 2018 08:11:25 +0200 Subject: [PATCH 03/21] Fix AttributeError --- service.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/service.py b/service.py index 237ff1fe..7de827b7 100644 --- a/service.py +++ b/service.py @@ -135,7 +135,7 @@ class Service(): if window('plex_online') == "true": # Plex server is online # Verify if user is set and has access to the server - if (self.user.currUser is not None) and self.user.has_access: + if (self.user.user is not None) and self.user.has_access: if not self.kodimonitor_running: # Start up events self.warn_auth = True @@ -145,7 +145,7 @@ class Service(): dialog('notification', lang(29999), "%s %s" % (lang(33000), - self.user.currUser), + self.user.user), icon='{plex}', time=2000, sound=False) @@ -178,7 +178,7 @@ class Service(): self.image_cache_thread_running = True self.image_cache_thread.start() else: - if (self.user.currUser is None) and self.warn_auth: + if (self.user.user is None) and self.warn_auth: # Alert user is not authenticated and suppress future # warning self.warn_auth = False From d4e15d6dfb153755f988f0ed063d3b6fa0af46d4 Mon Sep 17 00:00:00 2001 From: Croneter Date: Thu, 19 Apr 2018 11:52:36 +0200 Subject: [PATCH 04/21] Prettify --- resources/lib/entrypoint.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 3a89d8c3..1bd5318e 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -561,8 +561,7 @@ def getOnDeck(viewid, mediatype, tagname, limit): api.plex_type(), api.file_name(force_first_media=True))) if api.resume_point(): - listitem.setProperty('resumetime', - str(api.resume_point())) + listitem.setProperty('resumetime', str(api.resume_point())) xbmcplugin.addDirectoryItem( handle=HANDLE, url=url, From 00613e7ef5eab9e8fd3923fdce9005dfc7e6aec7 Mon Sep 17 00:00:00 2001 From: Croneter Date: Fri, 20 Apr 2018 07:41:59 +0200 Subject: [PATCH 05/21] Fix episode artwork sometimes not being complete - Partially fixes #453 --- resources/lib/PlexAPI.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 19cfe07a..19b95844 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -772,9 +772,10 @@ class API(object): # Artwork lookup for episodes is broken for addon paths # Episodes is a bit special, only get the thumb, because all # the other artwork will be saved under season and show - art = self._one_artwork('thumb') - if art: - artworks['thumb'] = art + for kodi_artwork, plex_artwork in v.KODI_TO_PLEX_ARTWORK.iteritems(): + art = self._one_artwork(plex_artwork) + if art: + artworks[kodi_artwork] = art if full_artwork: with plexdb.Get_Plex_DB() as plex_db: db_item = plex_db.getItem_byId(self.plex_id()) From 3bba2199e89b89b96d6fc43d2e6d66bd25e0b3ef Mon Sep 17 00:00:00 2001 From: Croneter Date: Fri, 20 Apr 2018 15:58:35 +0200 Subject: [PATCH 06/21] Fix missing episode poster in certain views - Fixes #453 --- resources/lib/PlexAPI.py | 39 +++++++++++++++++++------------------- resources/lib/variables.py | 7 +++++++ 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 19b95844..5d7b6b4c 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -772,34 +772,35 @@ class API(object): # Artwork lookup for episodes is broken for addon paths # Episodes is a bit special, only get the thumb, because all # the other artwork will be saved under season and show - for kodi_artwork, plex_artwork in v.KODI_TO_PLEX_ARTWORK.iteritems(): + for kodi_artwork, plex_artwork in \ + v.KODI_TO_PLEX_ARTWORK_EPISODE.iteritems(): art = self._one_artwork(plex_artwork) if art: artworks[kodi_artwork] = art - if full_artwork: - with plexdb.Get_Plex_DB() as plex_db: - db_item = plex_db.getItem_byId(self.plex_id()) + if not full_artwork: + return artworks + with plexdb.Get_Plex_DB() as plex_db: try: - season_id = db_item[3] + season_id = plex_db.getItem_byId(self.plex_id())[3] except TypeError: return artworks - # Grab artwork from the season - with kodidb.GetKodiDB('video') as kodi_db: - season_art = kodi_db.get_art(season_id, v.KODI_TYPE_SEASON) - for kodi_art in season_art: - artworks['season.%s' % kodi_art] = season_art[kodi_art] - # Get the show id - with plexdb.Get_Plex_DB() as plex_db: - db_item = plex_db.getItem_byId(self.grandparent_id()) + # Grab artwork from the season + with kodidb.GetKodiDB('video') as kodi_db: + season_art = kodi_db.get_art(season_id, v.KODI_TYPE_SEASON) + for kodi_art in season_art: + artworks['season.%s' % kodi_art] = season_art[kodi_art] + # Get the show id + with plexdb.Get_Plex_DB() as plex_db: try: - show_id = db_item[0] + show_id = plex_db.getItem_byKodiId(season_id, + v.KODI_TYPE_SEASON)[1] except TypeError: return artworks - # Grab more artwork from the show - with kodidb.GetKodiDB('video') as kodi_db: - show_art = kodi_db.get_art(show_id, v.KODI_TYPE_SHOW) - for kodi_art in show_art: - artworks['tvshow.%s' % kodi_art] = show_art[kodi_art] + # Grab more artwork from the show + with kodidb.GetKodiDB('video') as kodi_db: + show_art = kodi_db.get_art(show_id, v.KODI_TYPE_SHOW) + for kodi_art in show_art: + artworks['tvshow.%s' % kodi_art] = show_art[kodi_art] return artworks if kodi_id: diff --git a/resources/lib/variables.py b/resources/lib/variables.py index 5ba38cc3..d0ac4aa0 100644 --- a/resources/lib/variables.py +++ b/resources/lib/variables.py @@ -325,6 +325,13 @@ KODI_TO_PLEX_ARTWORK = { 'fanart': 'art' } +KODI_TO_PLEX_ARTWORK_EPISODE = { + 'thumb': 'thumb', + 'poster': 'grandparentThumb', + 'banner': 'banner', + 'fanart': 'art' +} + # Might be implemented in the future: 'icon', 'landscape' (16:9) ALL_KODI_ARTWORK = ( 'thumb', From a376dbe2a589b21354c02fbc328154904c85e94e Mon Sep 17 00:00:00 2001 From: Croneter Date: Fri, 20 Apr 2018 16:52:25 +0200 Subject: [PATCH 07/21] Less logging --- resources/lib/PlexAPI.py | 1 - 1 file changed, 1 deletion(-) diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 5d7b6b4c..38cb4c3e 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -846,7 +846,6 @@ class API(object): external_id = self.retrieve_external_item_id() if external_id is not None: artworks = self.lookup_fanart_tv(external_id[0], artworks) - LOG.debug('fanart artworks: %s', artworks) return artworks def retrieve_external_item_id(self, collection=False): From 7bc5f3ad16c5cebe542f7a561e8867149e949eaf Mon Sep 17 00:00:00 2001 From: Croneter Date: Mon, 23 Apr 2018 07:39:36 +0200 Subject: [PATCH 08/21] Fix IndexError for certain Plex channels - Fixes #454 --- resources/lib/PlexAPI.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 38cb4c3e..8db0757a 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -124,8 +124,12 @@ class API(object): # Local path filename = ans.rsplit("\\", 1)[1] else: - # Network share - filename = ans.rsplit("/", 1)[1] + try: + # Network share + filename = ans.rsplit("/", 1)[1] + except IndexError: + # E.g. certain Plex channels + filename = None return filename def file_path(self, force_first_media=False): From 8a08d85cce43f55e71b544472a366be4cb8b76e3 Mon Sep 17 00:00:00 2001 From: Croneter Date: Mon, 23 Apr 2018 19:50:16 +0200 Subject: [PATCH 09/21] Kodi Leia: Fix playback failing - Hopefully fixes #455 --- addon.xml | 4 ++-- resources/lib/pickler.py | 21 ++++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/addon.xml b/addon.xml index cd065450..6ac2243f 100644 --- a/addon.xml +++ b/addon.xml @@ -3,8 +3,8 @@ - - + + video audio image diff --git a/resources/lib/pickler.py b/resources/lib/pickler.py index 490da9d4..3e277104 100644 --- a/resources/lib/pickler.py +++ b/resources/lib/pickler.py @@ -4,12 +4,31 @@ from cPickle import dumps, loads from xbmcgui import Window from xbmc import log, LOGDEBUG + ############################################################################### WINDOW = Window(10000) PREFIX = 'PLEX.%s: ' % __name__ ############################################################################### +def try_encode(input_str, encoding='utf-8'): + """ + Will try to encode input_str (in unicode) to encoding. This possibly + fails with e.g. Android TV's Python, which does not accept arguments for + string.encode() + + COPY to avoid importing utils on calling default.py + """ + if isinstance(input_str, str): + # already encoded + return input_str + try: + input_str = input_str.encode(encoding, "ignore") + except TypeError: + input_str = input_str.encode() + return input_str + + def pickl_window(property, value=None, clear=False): """ Get or set window property - thread safe! For use with Pickle @@ -20,7 +39,7 @@ def pickl_window(property, value=None, clear=False): elif value is not None: WINDOW.setProperty(property, value) else: - return WINDOW.getProperty(property) + return try_encode(WINDOW.getProperty(property)) def pickle_me(obj, window_var='plex_result'): From 7863329c66e5d8b238e00d682c6082b543f3b6e9 Mon Sep 17 00:00:00 2001 From: Croneter Date: Wed, 25 Apr 2018 16:31:30 +0200 Subject: [PATCH 10/21] Version bump --- addon.xml | 11 +++++++++-- changelog.txt | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/addon.xml b/addon.xml index 6ac2243f..d1b93556 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + @@ -67,7 +67,14 @@ Нативная интеграция сервера Plex в Kodi Подключите Kodi к своему серверу Plex. Плагин предполагает что вы управляете своими видео с помощью Plex (а не в Kodi). Вы можете потерять текущие базы данных музыки и видео в Kodi (так как плагин напрямую их изменяет). Используйте на свой страх и риск Используйте на свой страх и риск - version 2.0.19 (beta only): + version 2.0.20 (beta only): +- Fix missing episode poster in certain views. You will have to manually reset your Kodi database to benefit +- Fix episode artwork sometimes not being complete +- Fix IndexError for certain Plex channels +- Kodi Leia: Fix playback failing +- Fix TV On Deck direct paths asking to choose between different media + +version 2.0.19 (beta only): - Fix PKC playback startup getting caught in infinity loop - Rewire library sync, suspend sync during playback - Fix playback failing in certain cases diff --git a/changelog.txt b/changelog.txt index 6a19f3f5..3e401c76 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,10 @@ +version 2.0.20 (beta only): +- Fix missing episode poster in certain views. You will have to manually reset your Kodi database to benefit +- Fix episode artwork sometimes not being complete +- Fix IndexError for certain Plex channels +- Kodi Leia: Fix playback failing +- Fix TV On Deck direct paths asking to choose between different media + version 2.0.19 (beta only): - Fix PKC playback startup getting caught in infinity loop - Rewire library sync, suspend sync during playback From 4e7829969bcfd0e73b68bf74fab62502b277fa11 Mon Sep 17 00:00:00 2001 From: Croneter Date: Wed, 25 Apr 2018 17:58:14 +0200 Subject: [PATCH 11/21] Update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ab106c62..efd5e0bb 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![stable version](https://img.shields.io/badge/stable_version-1.8.18-blue.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/stable/repository.plexkodiconnect/repository.plexkodiconnect-1.0.2.zip) -[![beta version](https://img.shields.io/badge/beta_version-2.0.19-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip) +[![beta version](https://img.shields.io/badge/beta_version-2.0.20-red.svg?maxAge=60&style=flat) ](https://github.com/croneter/binary_repo/raw/master/beta/repository.plexkodiconnectbeta/repository.plexkodiconnectbeta-1.0.2.zip) [![Installation](https://img.shields.io/badge/wiki-installation-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/Installation) [![FAQ](https://img.shields.io/badge/wiki-FAQ-brightgreen.svg?maxAge=60&style=flat)](https://github.com/croneter/PlexKodiConnect/wiki/faq) From 54c132d3e3c765c34456fc91f93d72f2dd6c3965 Mon Sep 17 00:00:00 2001 From: Croneter Date: Thu, 26 Apr 2018 08:01:27 +0200 Subject: [PATCH 12/21] Fix music database if new music is added in the background --- resources/lib/itemtypes.py | 94 ++++++++++++++------------------------ 1 file changed, 34 insertions(+), 60 deletions(-) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 301913f3..d753af5c 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -1217,6 +1217,7 @@ class Music(Items): """ Adds a single music album children: list of child xml's, so in this case songs + scan_children: set to False if you don't want to add children """ kodicursor = self.kodicursor plex_db = self.plex_db @@ -1224,20 +1225,18 @@ class Music(Items): api = API(item) update_item = True - itemid = api.plex_id() - if not itemid: + plex_id = api.plex_id() + if not plex_id: LOG.error('Error processing Album, skipping') return - plex_dbitem = plex_db.getItem_byId(itemid) + plex_dbitem = plex_db.getItem_byId(plex_id) try: - albumid = plex_dbitem[0] + album_id = plex_dbitem[0] except TypeError: - # Albumid not found update_item = False # The album details ##### lastScraped = datetime.now().strftime('%Y-%m-%d %H:%M:%S') - dateadded = api.date_created() userdata = api.userdata() checksum = api.checksum() @@ -1269,22 +1268,22 @@ class Music(Items): # UPDATE THE ALBUM ##### if update_item: - LOG.info("UPDATE album itemid: %s - Name: %s", itemid, name) + LOG.info("UPDATE album plex_id: %s - Name: %s", plex_id, name) # Update the checksum in plex table - plex_db.updateReference(itemid, checksum) + plex_db.updateReference(plex_id, checksum) # OR ADD THE ALBUM ##### else: - LOG.info("ADD album itemid: %s - Name: %s", itemid, name) + LOG.info("ADD album plex_id: %s - Name: %s", plex_id, name) # safety checks: It looks like plex supports the same artist # multiple times. # Kodi doesn't allow that. In case that happens we just merge the # artist entries. - albumid = self.kodi_db.addAlbum(name, musicBrainzId) + album_id = self.kodi_db.addAlbum(name, musicBrainzId) # Create the reference in plex table - plex_db.addReference(itemid, + plex_db.addReference(plex_id, v.PLEX_TYPE_ALBUM, - albumid, + album_id, v.KODI_TYPE_ALBUM, view_id=viewid, checksum=checksum) @@ -1302,7 +1301,7 @@ class Music(Items): kodicursor.execute(query, (artistname, year, self.genre, bio, thumb, rating, lastScraped, v.KODI_TYPE_ALBUM, studio, - self.compilation, albumid)) + self.compilation, album_id)) elif v.KODIVERSION == 17: # Kodi Krypton query = ''' @@ -1315,7 +1314,7 @@ class Music(Items): kodicursor.execute(query, (artistname, year, self.genre, bio, thumb, rating, lastScraped, v.KODI_TYPE_ALBUM, studio, - self.compilation, albumid)) + self.compilation, album_id)) elif v.KODIVERSION == 16: # Kodi Jarvis query = ''' @@ -1328,71 +1327,46 @@ class Music(Items): kodicursor.execute(query, (artistname, year, self.genre, bio, thumb, rating, lastScraped, v.KODI_TYPE_ALBUM, studio, - self.compilation, albumid)) + self.compilation, album_id)) # Associate the parentid for plex reference parent_id = api.parent_plex_id() + artist_id = None if parent_id is not None: - plex_dbartist = plex_db.getItem_byId(parent_id) try: - artistid = plex_dbartist[0] + artist_id = plex_db.getItem_byId(parent_id)[0] except TypeError: - LOG.info('Artist %s does not exist in plex database', - parent_id) + LOG.info('Artist %s does not yet exist in Plex DB', parent_id) artist = GetPlexMetadata(parent_id) - # Item may not be an artist, verification necessary. - if artist is not None and artist != 401: - if artist[0].attrib.get('type') == v.PLEX_TYPE_ARTIST: - # Update with the parent_id, for remove reference - plex_db.addReference(parent_id, - v.PLEX_TYPE_ARTIST, - parent_id, - v.KODI_TYPE_ARTIST, - view_id=viewid) - plex_db.updateParentId(itemid, parent_id) - else: - # Update plex reference with the artistid - plex_db.updateParentId(itemid, artistid) - - # Assign main artists to album - # Plex unfortunately only supports 1 artist :-( - artist_id = parent_id - plex_dbartist = plex_db.getItem_byId(artist_id) - try: - artistid = plex_dbartist[0] - except TypeError: - # Artist does not exist in plex database, create the reference - LOG.info('Artist %s does not exist in Plex database', artist_id) - artist = GetPlexMetadata(artist_id) - if artist is not None and artist != 401: - self.add_updateArtist(artist[0]) - plex_dbartist = plex_db.getItem_byId(artist_id) - artistid = plex_dbartist[0] - else: - # Best take this name over anything else. - query = "UPDATE artist SET strArtist = ? WHERE idArtist = ?" - kodicursor.execute(query, (artistname, artistid,)) - LOG.info("UPDATE artist: strArtist: %s, idArtist: %s", - artistname, artistid) - + try: + artist[0].attrib + except (TypeError, IndexError, AttributeError): + LOG.error('Could not get artist xml for %s', parent_id) + else: + self.add_updateArtist(artist[0]) + plex_dbartist = plex_db.getItem_byId(parent_id) + try: + artist_id = plex_dbartist[0] + except TypeError: + LOG.error('Adding artist failed for %s', parent_id) + # Update plex reference with the artist_id + plex_db.updateParentId(plex_id, artist_id) # Add artist to album query = ''' INSERT OR REPLACE INTO album_artist(idArtist, idAlbum, strArtist) VALUES (?, ?, ?) ''' - kodicursor.execute(query, (artistid, albumid, artistname)) + kodicursor.execute(query, (artist_id, album_id, artistname)) # Update discography query = ''' INSERT OR REPLACE INTO discography(idArtist, strAlbum, strYear) VALUES (?, ?, ?) ''' - kodicursor.execute(query, (artistid, name, year)) - # Update plex reference with parentid - plex_db.updateParentId(artist_id, albumid) + kodicursor.execute(query, (artist_id, name, year)) if v.KODIVERSION < 18: - self.kodi_db.addMusicGenres(albumid, self.genres, v.KODI_TYPE_ALBUM) + self.kodi_db.addMusicGenres(album_id, self.genres, v.KODI_TYPE_ALBUM) # Update artwork - artwork.modify_artwork(artworks, albumid, v.KODI_TYPE_ALBUM, kodicursor) + artwork.modify_artwork(artworks, album_id, v.KODI_TYPE_ALBUM, kodicursor) # Add all children - all tracks if scan_children: for child in children: From 7fb0f32bcf0eeed64ac89dd237a57f9d01f0312f Mon Sep 17 00:00:00 2001 From: Croneter Date: Thu, 26 Apr 2018 08:18:51 +0200 Subject: [PATCH 13/21] Fix TV show artwork Kodi native library (reset Kodi DB!) --- resources/lib/PlexAPI.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 8db0757a..03b9767e 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -776,6 +776,12 @@ class API(object): # Artwork lookup for episodes is broken for addon paths # Episodes is a bit special, only get the thumb, because all # the other artwork will be saved under season and show + # EXCEPT if you're constructing a listitem + if not full_artwork: + art = self._one_artwork('thumb') + if art: + artworks['thumb'] = art + return artworks for kodi_artwork, plex_artwork in \ v.KODI_TO_PLEX_ARTWORK_EPISODE.iteritems(): art = self._one_artwork(plex_artwork) From 5cea57e935c98607ec1894f4fa669c8a7b67e38c Mon Sep 17 00:00:00 2001 From: Croneter Date: Sun, 29 Apr 2018 12:39:29 +0200 Subject: [PATCH 14/21] Use list instead of set --- resources/lib/entrypoint.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 1bd5318e..7ec66d72 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -369,7 +369,7 @@ def getRecentEpisodes(viewid, mediatype, tagname, limit): append_show_title = settings('RecentTvAppendShow') == 'true' append_sxxexx = settings('RecentTvAppendSeason') == 'true' # First we get a list of all the TV shows - filtered by tag - allshowsIds = set() + allshowsIds = list() params = { 'sort': {'order': "descending", 'method': "dateadded"}, 'filter': {'operator': "is", 'field': "tag", 'value': "%s" % tagname}, From 51f47452f29bf57a797506bc1507cb2f9589270a Mon Sep 17 00:00:00 2001 From: Croneter Date: Sun, 29 Apr 2018 14:11:03 +0200 Subject: [PATCH 15/21] Add toggle to deactivate image caching during playback - Partially fixes #451 - you can now disable image caching during playback --- resources/language/resource.language.en_gb/strings.po | 5 +++++ resources/lib/artwork.py | 9 ++++++--- resources/settings.xml | 1 + 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index db179cee..292beee7 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -41,6 +41,11 @@ msgctxt "#30005" msgid "Username: " msgstr "" +# PKC settings artwork: Enable image caching during Kodi playback +msgctxt "#30009" +msgid "Enable image caching during Kodi playback (restart Kodi!)" +msgstr "" + # Button text msgctxt "#30012" msgid "OK" diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 1c39b806..d4e1e80d 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -22,6 +22,10 @@ LOG = getLogger("PLEX." + __name__) # Disable annoying requests warnings requests.packages.urllib3.disable_warnings() ARTWORK_QUEUE = Queue() +IMAGE_CACHING_SUSPENDS = ['SUSPEND_LIBRARY_THREAD', 'DB_SCAN', 'STOP_SYNC'] +if not settings('imageSyncDuringPlayback') == 'true': + IMAGE_CACHING_SUSPENDS.append('SUSPEND_SYNC') + ############################################################################### @@ -33,9 +37,7 @@ def double_urldecode(text): return unquote(unquote(text)) -@thread_methods(add_suspends=['SUSPEND_LIBRARY_THREAD', - 'DB_SCAN', - 'STOP_SYNC']) +@thread_methods(add_suspends=IMAGE_CACHING_SUSPENDS) class Image_Cache_Thread(Thread): sleep_between = 50 # Potentially issues with limited number of threads @@ -47,6 +49,7 @@ class Image_Cache_Thread(Thread): Thread.__init__(self) def run(self): + LOG.info("---===### Starting Image_Cache_Thread ###===---") stopped = self.stopped suspended = self.suspended queue = self.queue diff --git a/resources/settings.xml b/resources/settings.xml index fea2ad2d..928c125f 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -125,6 +125,7 @@ + + From 62d6c8fe458fc3dfe5b7804d3c897035ecdccc35 Mon Sep 17 00:00:00 2001 From: Croneter Date: Sun, 29 Apr 2018 14:14:31 +0200 Subject: [PATCH 17/21] Increase timeout between syncing images - Partially fixes #457 --- resources/lib/artwork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 2b4d609b..8b0ce224 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -39,7 +39,7 @@ def double_urldecode(text): @thread_methods(add_suspends=IMAGE_CACHING_SUSPENDS) class Image_Cache_Thread(Thread): - sleep_between = 50 + sleep_between = 100 # Potentially issues with limited number of threads # Hence let Kodi wait till download is successful timeout = (35.1, 35.1) From 1ca2bdba79015d873a76e2e9c08b36d823bc0e72 Mon Sep 17 00:00:00 2001 From: Croneter Date: Sun, 29 Apr 2018 14:16:56 +0200 Subject: [PATCH 18/21] Less logging --- resources/lib/artwork.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 8b0ce224..fb62a00a 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -148,8 +148,6 @@ class Artwork(): (typus, )) artworks.extend(cursor.fetchall()) connection.close() - LOG.debug('artworks: %s', artworks) - LOG.debug('artworks: %s', len(artworks)) artworks_to_cache = list() connection = kodi_sql('texture') cursor = connection.cursor() From 559bd5408fd14a431ef0664e2101d4c22b7ba7a6 Mon Sep 17 00:00:00 2001 From: Croneter Date: Sun, 29 Apr 2018 14:26:53 +0200 Subject: [PATCH 19/21] Optimize startup of image caching --- resources/lib/librarysync.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index e94d35c0..c15aac44 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -1586,12 +1586,12 @@ class LibrarySync(Thread): if settings('FanartTV') == 'true': self.sync_fanart() LOG.info('Done initial sync on Kodi startup') + artwork.Artwork().cache_major_artwork() + self.fanartthread.start() else: LOG.info('Startup sync has not yet been successful') window('plex_dbScan', clear=True) state.DB_SCAN = False - artwork.Artwork().cache_major_artwork() - self.fanartthread.start() # Currently no db scan, so we can start a new scan elif state.DB_SCAN is False: From f87a63167421245b289739d28996573768f4d170 Mon Sep 17 00:00:00 2001 From: Croneter Date: Sun, 29 Apr 2018 14:39:08 +0200 Subject: [PATCH 20/21] Revert "Increase timeout between syncing images" This reverts commit 62d6c8fe458fc3dfe5b7804d3c897035ecdccc35. --- resources/lib/artwork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index fb62a00a..a6592016 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -39,7 +39,7 @@ def double_urldecode(text): @thread_methods(add_suspends=IMAGE_CACHING_SUSPENDS) class Image_Cache_Thread(Thread): - sleep_between = 100 + sleep_between = 50 # Potentially issues with limited number of threads # Hence let Kodi wait till download is successful timeout = (35.1, 35.1) From 0807ce53149f6ee221c2cc4df5e86cd2bceff6b3 Mon Sep 17 00:00:00 2001 From: Croneter Date: Sun, 29 Apr 2018 14:39:36 +0200 Subject: [PATCH 21/21] Increase timeout between syncing images --- resources/lib/artwork.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index a6592016..84d7b612 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -39,7 +39,7 @@ def double_urldecode(text): @thread_methods(add_suspends=IMAGE_CACHING_SUSPENDS) class Image_Cache_Thread(Thread): - sleep_between = 50 + sleep_between = 200 # Potentially issues with limited number of threads # Hence let Kodi wait till download is successful timeout = (35.1, 35.1)