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) diff --git a/addon.xml b/addon.xml index cd065450..d1b93556 100644 --- a/addon.xml +++ b/addon.xml @@ -1,10 +1,10 @@ - + - - + + video audio image @@ -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 diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index db179cee..e62c76dc 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -41,6 +41,26 @@ msgctxt "#30005" msgid "Username: " msgstr "" +# Sync notification displayed if there is still artwork to be cached to Kodi +msgctxt "#30006" +msgid "Caching %s images" +msgstr "" + +# Sync notification displayed if syncing of major artwork is done +msgctxt "#30007" +msgid "Major image caching done" +msgstr "" + +# PKC settings artwork: Enable notifications for artwork image sync +msgctxt "#30008" +msgid "Enable notifications for image caching" +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/PlexAPI.py b/resources/lib/PlexAPI.py index 19cfe07a..03b9767e 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): @@ -772,33 +776,41 @@ 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 - if full_artwork: - with plexdb.Get_Plex_DB() as plex_db: - db_item = plex_db.getItem_byId(self.plex_id()) + # 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) + if art: + artworks[kodi_artwork] = art + 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: @@ -844,7 +856,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): diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 1c39b806..84d7b612 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,11 +37,9 @@ 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 + sleep_between = 200 # Potentially issues with limited number of threads # Hence let Kodi wait till download is successful timeout = (35.1, 35.1) @@ -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 @@ -65,6 +68,16 @@ class Image_Cache_Thread(Thread): except Empty: sleep(1000) continue + if isinstance(url, ArtworkSyncMessage): + if state.IMAGE_SYNC_NOTIFICATIONS: + dialog('notification', + heading=lang(29999), + message=url.message, + icon='{plex}', + sound=False) + queue.task_done() + continue + url = double_urlencode(try_encode(url)) sleeptime = 0 while True: try: @@ -116,6 +129,44 @@ class Artwork(): if enableTextureCache: 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 = 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 = 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 + LOG.info('Caching has not been completed - caching %s major images', + len(artworks_to_cache)) + self.queue.put(ArtworkSyncMessage(lang(30006) % len(artworks_to_cache))) + for url in artworks_to_cache: + self.queue.put(url[0]) + self.queue.put(ArtworkSyncMessage(lang(30007))) + def fullTextureCacheSync(self): """ This method will sync all Kodi artwork to textures13.db @@ -174,10 +225,10 @@ class Artwork(): def cache_texture(self, url): ''' - Cache a single image url to the texture cache + Cache a single image url to the texture cache. url: unicode ''' if url and self.enableTextureCache: - self.queue.put(double_urlencode(try_encode(url))) + self.queue.put(url) def modify_artwork(self, artworks, kodi_id, kodi_type, cursor): """ @@ -266,3 +317,11 @@ class Artwork(): for path in paths: makedirs(try_decode(translatePath("special://thumbnails/%s" % path))) + + +class ArtworkSyncMessage(object): + """ + Put in artwork queue to display the message as a Kodi notification + """ + def __init__(self, message): + self.message = message diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 7fd7dfc9..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}, @@ -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, @@ -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, 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/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: diff --git a/resources/lib/kodimonitor.py b/resources/lib/kodimonitor.py index d5070df3..00a63f85 100644 --- a/resources/lib/kodimonitor.py +++ b/resources/lib/kodimonitor.py @@ -50,7 +50,8 @@ STATE_SETTINGS = { 'remapSMBphotoNew': 'remapSMBphotoNew', 'enableMusic': 'ENABLE_MUSIC', 'forceReloadSkinOnPlaybackStop': 'FORCE_RELOAD_SKIN', - 'fetch_pms_item_number': 'FETCH_PMS_ITEM_NUMBER' + 'fetch_pms_item_number': 'FETCH_PMS_ITEM_NUMBER', + 'imageSyncNotifications': 'IMAGE_SYNC_NOTIFICATIONS' } ############################################################################### diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 4d63c609..c15aac44 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -16,6 +16,7 @@ from downloadutils import DownloadUtils as DU import itemtypes import plexdb_functions as plexdb import kodidb_functions as kodidb +import artwork import videonodes import variables as v @@ -1453,7 +1454,6 @@ class LibrarySync(Thread): elif state.RUN_LIB_SCAN == 'textures': state.DB_SCAN = True window('plex_dbScan', value="true") - import artwork artwork.Artwork().fullTextureCacheSync() window('plex_dbScan', clear=True) state.DB_SCAN = False @@ -1517,8 +1517,6 @@ class LibrarySync(Thread): last_time_sync = utils.unix_timestamp() window('plex_dbScan', clear=True) state.DB_SCAN = False - # Start the fanart download thread - self.fanartthread.start() while not self.stopped(): # In the event the server goes offline @@ -1544,6 +1542,7 @@ class LibrarySync(Thread): initial_sync_done = True kodi_db_version_checked = True last_sync = utils.unix_timestamp() + self.fanartthread.start() else: LOG.error('Initial start-up full sync unsuccessful') xbmc.executebuiltin('InhibitIdleShutdown(false)') @@ -1587,6 +1586,8 @@ 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) 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'): diff --git a/resources/lib/state.py b/resources/lib/state.py index 69532239..60b15ede 100644 --- a/resources/lib/state.py +++ b/resources/lib/state.py @@ -39,6 +39,8 @@ FORCE_RELOAD_SKIN = True # Stemming from the PKC settings.xml # Shall we show Kodi dialogs when synching? SYNC_DIALOG = True +# Shall Kodi show dialogs for syncing/caching images? (e.g. images left to sync) +IMAGE_SYNC_NOTIFICATIONS = True # Is synching of Plex music enabled? ENABLE_MUSIC = True # How often shall we sync? 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/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', diff --git a/resources/settings.xml b/resources/settings.xml index fea2ad2d..d8e46400 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -125,6 +125,8 @@ + +