diff --git a/addon.xml b/addon.xml index 1c919d4e..a4a6ba85 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ diff --git a/changelog.txt b/changelog.txt index 138db97a..c0c96be0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,8 @@ +version 1.0.5 +- Catch exceptions in itemtypes and log them +- Slightly increased download timeouts +- Overhaul userclient + version 1.0.4 - Sleep for a while in loops - drastically reduces CPU load - Connect to remote PMS! diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index 2f2ab15e..ebb0b7a0 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -298,6 +298,7 @@ Duration of the music library pop up (in seconds) Server messages Generate a new device Id + Users must log in every time when Kodi restarts Welcome diff --git a/resources/language/German/strings.xml b/resources/language/German/strings.xml index f3e70b34..932977f4 100644 --- a/resources/language/German/strings.xml +++ b/resources/language/German/strings.xml @@ -24,6 +24,8 @@ Bei Wiederaufnahme zurückspulen (in Sekunden) [COLOR yellow]Anzahl Login-Versuche zurücksetzen[/COLOR] + Benutzer müssen sich bei jedem Neustart von Kodi neu anmelden + Verbindung Netzwerk diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index ba72c46b..315a5a41 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -92,9 +92,11 @@ class PlexAPI(): 'plexToken': utils.settings('plexToken'), 'plexhome': utils.settings('plexhome'), 'plexid': utils.settings('plexid'), - 'myplexlogin': utils.settings('myplexlogin') + 'myplexlogin': utils.settings('myplexlogin'), + 'plexAvatar': utils.settings('plexAvatar'), + 'plexHomeSize': utils.settings('plexHomeSize') - plexLogin is unicode or empty unicode string u'' + Returns strings or unicode Returns empty strings '' for a setting if not found. @@ -102,11 +104,13 @@ class PlexAPI(): plexhome is 'true' if plex home is used (the default) """ return { - 'plexLogin': utils.settings('plexLogin').decode('utf-8'), + 'plexLogin': utils.settings('plexLogin'), 'plexToken': utils.settings('plexToken'), 'plexhome': utils.settings('plexhome'), 'plexid': utils.settings('plexid'), - 'myplexlogin': utils.settings('myplexlogin') + 'myplexlogin': utils.settings('myplexlogin'), + 'plexAvatar': utils.settings('plexAvatar'), + 'plexHomeSize': utils.settings('plexHomeSize') } def GetPlexLoginAndPassword(self): @@ -158,12 +162,14 @@ class PlexAPI(): """ Prompts user to sign in by visiting https://plex.tv/pin - Writes plexhome, username and token to Kodi settings file. Returns: + Writes to Kodi settings file. Also returns: { 'plexhome': 'true' if Plex Home, 'false' otherwise 'username': - 'avatar': URL to user avator + 'avatar': URL to user avator 'token': + 'plexid': Plex user ID + 'homesize': Number of Plex home users (defaults to '1') } Returns False if authentication did not work. """ @@ -202,19 +208,23 @@ class PlexAPI(): else: home = 'false' username = xml.get('username', '') - avatar = xml.get('thumb') + avatar = xml.get('thumb', '') token = xml.findtext('authentication-token') + homeSize = xml.get('homeSize', '1') result = { 'plexhome': home, 'username': username, 'avatar': avatar, 'token': token, - 'plexid': userid + 'plexid': userid, + 'homesize': homeSize } utils.settings('plexLogin', username) utils.settings('plexToken', token) utils.settings('plexhome', home) utils.settings('plexid', userid) + utils.settings('plexAvatar', avatar) + utils.settings('plexHomeSize', homeSize) # Let Kodi log into plex.tv on startup from now on utils.settings('myplexlogin', 'true') return result @@ -334,7 +344,7 @@ class PlexAPI(): # Add '/clients' to URL because then an authentication is necessary # If a plex.tv URL was passed, this does not work. header = self.getXArgsDeviceInfo() - if token is not None: + if token: header['X-Plex-Token'] = token sslverify = utils.settings('sslverify') if sslverify == "true": @@ -780,7 +790,7 @@ class PlexAPI(): XML = etree.parse(response) # Log received XML if debugging enabled. self.logMsg("====== received PMS-XML ======", 1) - self.logMsg(XML.getroot(), 1) + self.logMsg(XML, 1) self.logMsg("====== PMS-XML finished ======", 1) return XML @@ -1050,42 +1060,41 @@ class PlexAPI(): self.logMsg("Avatar url for user %s is: %s" % (username, url), 1) return url - def ChoosePlexHomeUser(self): + def ChoosePlexHomeUser(self, plexToken): """ Let's user choose from a list of Plex home users. Will switch to that user accordingly. - Output: - username - userid - authtoken + Returns a dict: + { + 'username': Unicode + 'userid': '' Plex ID of the user + 'token': '' User's token + 'protected': True if PIN is needed, else False + } - Will return empty strings if failed. + Will return False if something went wrong (wrong PIN, no connection) """ string = self.__language__ dialog = xbmcgui.Dialog() - plexLogin = utils.settings('plexLogin') - plexToken = utils.settings('plexToken') - machineIdentifier = utils.settings('plex_machineIdentifier') - self.logMsg("Getting user list.", 1) # Get list of Plex home users users = self.MyPlexListHomeUsers(plexToken) - # Download users failed. Set username to Plex login if not users: - utils.settings('username', value=plexLogin) - self.logMsg("User download failed. Set username = plexlogin", 0) - return ('', '', '') + self.logMsg("User download failed.", -1) + return False userlist = [] userlistCoded = [] for user in users: username = user['title'] userlist.append(username) + # To take care of non-ASCII usernames userlistCoded.append(username.encode('utf-8')) usernumber = len(userlist) + + username = '' usertoken = '' - # Plex home not in use: only 1 user returned trials = 0 while trials < 3: if usernumber > 1: @@ -1094,58 +1103,62 @@ class PlexAPI(): self.addonName + string(39306), userlistCoded) if user_select == -1: - self.logMsg("No user selected.", 1) + self.logMsg("No user selected.", 0) + utils.settings('username', value='') xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) - return ('', '', '') - # No Plex home in use - only 1 user + return False + # Only 1 user received, choose that one else: user_select = 0 selected_user = userlist[user_select] - self.logMsg("Selected user: %s" % selected_user, 1) - utils.settings('username', value=selected_user) + self.logMsg("Selected user: %s" % selected_user, 0) user = users[user_select] # Ask for PIN, if protected: + pin = None if user['protected'] == '1': - # Please enter pin for user self.logMsg('Asking for users PIN', 1) pin = dialog.input( string(39307) + selected_user, type=xbmcgui.INPUT_NUMERIC, option=xbmcgui.ALPHANUM_HIDE_INPUT) # User chose to cancel - if pin is None: - break - else: - pin = None + # Plex bug: don't call url for protected user with empty PIN + if not pin: + trials += 1 + continue # Switch to this Plex Home user, if applicable - # Plex bug: don't call url for protected user with empty PIN - if user['protected'] == '1' and not pin: - break username, usertoken = self.PlexSwitchHomeUser( user['id'], pin, plexToken, - machineIdentifier - ) + utils.settings('plex_machineIdentifier')) # Couldn't get user auth if not username: + trials += 1 # Could not login user, please try again if not dialog.yesno(self.addonName, string(39308) + selected_user, string(39309)): # User chose to cancel break - # Successfully retrieved: break out of while loop else: + # Successfully retrieved username: break out of while loop break - trials += trials - if not username: - xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) - return ('', '', '') - return (username, user['id'], usertoken) - def PlexSwitchHomeUser(self, userId, pin, token, machineId): + if not username: + self.logMsg('Failed signing in a user to plex.tv', -1) + xbmc.executebuiltin('Addon.OpenSettings(%s)' % self.addonId) + return False + + return { + 'username': username, + 'userid': user['id'], + 'protected': True if user['protected'] == '1' else False, + 'token': usertoken + } + + def PlexSwitchHomeUser(self, userId, pin, token, machineIdentifier): """ Retrieves Plex home token for a Plex home user. @@ -1153,7 +1166,6 @@ class PlexAPI(): userId id of the Plex home user pin PIN of the Plex home user, if protected token token for plex.tv - machineId Plex PMS machineIdentifier Output: (username, token) @@ -1165,7 +1177,9 @@ class PlexAPI(): url += '?pin=' + pin self.logMsg('Switching to user %s' % userId, 0) answer = self.TalkToPlexServer(url, talkType="POST", token=token) - if not answer: + try: + answer.attrib + except: self.logMsg('Error: plex.tv switch HomeUser change failed', -1) return ('', '') @@ -1173,28 +1187,25 @@ class PlexAPI(): token = answer.attrib.get('authenticationToken', '') # Get final token - url = 'https://plex.tv/pms/servers.xml' - answer = self.TalkToPlexServer(url, talkType="GET", token=token) - if not answer: - self.logMsg('Error: plex.tv switch HomeUser change failed', -1) + url = 'https://plex.tv/api/resources?includeHttps=1' + xml = self.TalkToPlexServer(url, talkType="GET", token=token) + try: + xml.attrib + except: + self.logMsg('switch HomeUser failed - plex.tv answer wrong', -1) return ('', '') found = 0 - for child in answer: - if child.attrib['machineIdentifier'] == machineId: - token = child.attrib['accessToken'] - self.logMsg('Found a plex home user token', 1) + for device in xml: + if device.attrib.get('clientIdentifier') == machineIdentifier: found += 1 + token = device.attrib.get('accessToken') if found == 0: - self.logMsg('Error: plex.tv switch HomeUser change failed', -1) + self.logMsg('No tokens found for your server!', -1) return ('', '') + self.logMsg('Plex.tv switch HomeUser change successfull', 0) - self.logMsg("username: %s, token: xxxx. " - "Saving to window and file settings" % username, 0) - utils.window('emby_currUser', value=userId) - utils.settings('userId', value=userId) - utils.settings('username', value=username) - utils.window('emby_accessToken%s' % userId, value=token) + self.logMsg("username: %s, token: xxxx. " % username, 0) return (username, token) def MyPlexListHomeUsers(self, authtoken): @@ -1218,9 +1229,11 @@ class PlexAPI(): If any value is missing, None is returned instead (or "" from plex.tv) If an error is encountered, False is returned """ - XML = self.getXMLFromPMS('https://plex.tv', '/api/home/users/', {}, authtoken) + XML = self.getXMLFromPMS( + 'https://plex.tv', '/api/home/users/', {}, authtoken) if not XML: - # Download failed; quitting with False + self.logMsg('Download of Plex home users failed.', -1) + self.logMsg('plex.tv xml received was: %s' % XML, -1) return False # analyse response root = XML.getroot() diff --git a/resources/lib/downloadutils.py b/resources/lib/downloadutils.py index 1a1a9984..c366d464 100644 --- a/resources/lib/downloadutils.py +++ b/resources/lib/downloadutils.py @@ -37,7 +37,7 @@ class DownloadUtils(): # Requests session s = None - timeout = 3 + timeout = 10 def __init__(self): @@ -224,7 +224,8 @@ class DownloadUtils(): # Get user information self.userId = utils.window('emby_currUser') self.server = utils.window('emby_server%s' % self.userId) - self.token = utils.window('emby_accessToken%s' % self.userId) + self.token = utils.window( + 'emby_accessToken%s' % self.userId) header = self.getHeader(options=headerOptions) verifyssl = False cert = None diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 901b8def..978aa85f 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -79,41 +79,61 @@ def reConnect(): string = xbmcaddon.Addon().getLocalizedString utils.logMsg("entrypoint reConnect", "Connection resets requested", 0) - # Pause library sync thread - user needs to be auth in order to sync - utils.window('suspend_LibraryThread', value='true') - # Suspend the user client during procedure - utils.window('suspend_Userclient', value='true') dialog = xbmcgui.Dialog() dialog.notification( heading=addonName, message=string(39207), icon="special://home/addons/plugin.video.plexkodiconnect/icon.png", sound=False) + # Pause library sync thread - user needs to be auth in order to sync + utils.window('suspend_LibraryThread', value='true') - # Wait max for 20 seconds for all lib scans to finish + # Delete plex credentials in settings + utils.settings('myplexlogin', value="true") + utils.settings('plexLogin', value="") + utils.settings('plexToken', value=""), + utils.settings('plexid', value="") + utils.settings('plexHomeSize', value="") + utils.settings('plexAvatar', value="") + + # Wait max for 5 seconds for all lib scans to finish counter = 0 while utils.window('emby_dbScan') == 'true': - xbmc.sleep(1000) - counter += 1 - if counter > 20: + if counter > 100: dialog.ok( heading=addonName, message=string(39208), ) # Resuming threads, just in case utils.window('suspend_LibraryThread', clear=True) - utils.window('suspend_Userclient', clear=True) # Abort reConnection return + counter += 1 + xbmc.sleep(50) + # Log out currently signed in user: + utils.window('emby_serverStatus', value="401") + + # Above method needs to have run its course! Hence wait + counter = 0 + while utils.window('emby_serverStatus') == "401": + if counter > 100: + dialog.ok( + heading=addonName, + message=string(39208), + ) + # Abort reConnection + return + counter += 1 + xbmc.sleep(50) + # Suspend the user client during procedure + utils.window('suspend_Userclient', value='true') import initialsetup initialsetup.InitialSetup().setup(forcePlexTV=True) - # Log out currently signed in user: - utils.window('emby_serverStatus', value="401") - # Restart user client - utils.window('suspend_Userclient', clear=True) # Request lib sync to get user view data (e.g. watched/unwatched) utils.window('plex_runLibScan', value='full') + # Restart user client + utils.window('suspend_Userclient', clear=True) def PassPlaylist(xml, resume=None): @@ -222,7 +242,7 @@ def doMainListing(): addDirectoryItem(label, path) # Plex user switch, if Plex home is in use - if utils.settings('plexhome') == 'true': + if int(utils.settings('plexHomeSize')) > 1: addDirectoryItem(string(39200), "plugin://plugin.video.plexkodiconnect/" "?mode=switchuser") @@ -401,8 +421,17 @@ def switchPlexUser(): # Pause library sync thread - user needs to be auth in order to sync utils.window('suspend_LibraryThread', value='true') # Wait to ensure that any sync already going on has finished + counter = 0 while utils.window('emby_dbScan') == 'true': - xbmc.sleep(1000) + if counter > 100: + # Something went wrong, aborting + # Resuming threads, just in case + utils.window('suspend_LibraryThread', clear=True) + # Abort reConnection + return + counter += 1 + xbmc.sleep(50) + # Log out currently signed in user: utils.window('emby_serverStatus', value="401") # Request lib sync to get user view data (e.g. watched/unwatched) diff --git a/resources/lib/image_cache_thread.py b/resources/lib/image_cache_thread.py index a9a76962..2f6aacbf 100644 --- a/resources/lib/image_cache_thread.py +++ b/resources/lib/image_cache_thread.py @@ -40,7 +40,7 @@ class image_cache_thread(threading.Thread): "http://%s:%s/image/image://%s" % (self.xbmc_host, self.xbmc_port, self.urlToProcess)), auth=(self.xbmc_username, self.xbmc_password), - timeout=(0.1, 0.1)) + timeout=(2, 2)) # We don't need the result except: pass diff --git a/resources/lib/initialsetup.py b/resources/lib/initialsetup.py index 8fa55c92..8c4f137c 100644 --- a/resources/lib/initialsetup.py +++ b/resources/lib/initialsetup.py @@ -68,6 +68,24 @@ class InitialSetup(): # Problems connecting to plex.tv. Network or internet issue? dialog.ok(self.addonName, string(39010)) + else: + # Successful connected to plex.tv + # Refresh the info from Plex.tv + url = 'https://plex.tv/' + path = 'users/account' + xml = self.plx.getXMLFromPMS(url, path, authtoken=plexToken) + if xml: + xml = xml.getroot() + plexLogin = xml.attrib.get('title') + utils.settings('plexLogin', value=plexLogin) + home = 'true' if xml.attrib.get('home') == '1' else 'false' + utils.settings('plexhome', value=home) + utils.settings('plexAvatar', value=xml.attrib.get('thumb')) + utils.settings( + 'plexHomeSize', value=xml.attrib.get('homeSize', '1')) + self.logMsg('Updated Plex info from plex.tv', 0) + else: + self.logMsg('Failed to update Plex info from plex.tv', -1) # If a Plex server IP has already been set, return. if server and forcePlexTV is False: self.logMsg("Server is already set.", 0) @@ -175,6 +193,7 @@ class InitialSetup(): return # Write to Kodi settings file utils.settings('plex_machineIdentifier', activeServer) + utils.settings('plex_servername', server['name']) if server['local'] == '1': scheme = server['scheme'] utils.settings('ipaddress', server['ip']) diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 0baa9d75..d3a05f63 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -284,6 +284,17 @@ class Movies(Items): self.add_updateBoxset(boxset) def add_update(self, item, viewtag=None, viewid=None): + try: + self.run_add_update(item, viewtag, viewid) + except Exception as e: + utils.window('emby_dbScan', clear=True) + self.logMsg('itemtypes.py for movies has crashed for item %s. ' + 'Error:' % item.attrib.get('ratingKey', None), -1) + self.logMsg(e, -1) + # skip this item for now + return + + def run_add_update(self, item, viewtag=None, viewid=None): # Process single movie kodicursor = self.kodicursor emby_db = self.emby_db @@ -296,6 +307,7 @@ class Movies(Items): # If the item doesn't exist, we'll add it to the database update_item = True itemid = API.getRatingKey() + self.logMsg("Processing item %s" % itemid, 1) # Cannot parse XML, abort if not itemid: self.logMsg("Cannot parse XML data for movie", -1) @@ -360,6 +372,7 @@ class Movies(Items): studio = studios[0] except IndexError: studio = None + self.logMsg('Read all attributes', 1) # Find one trailer trailer = None @@ -408,6 +421,7 @@ class Movies(Items): 'mode': "play" } filename = "%s?%s" % (path, urllib.urlencode(params)) + self.logMsg('Path set for item', 1) ##### UPDATE THE MOVIE ##### if update_item: self.logMsg("UPDATE movie itemid: %s - Title: %s" % (itemid, title), 1) @@ -454,6 +468,7 @@ class Movies(Items): # Create the reference in emby table emby_db.addReference(itemid, movieid, "Movie", "movie", fileid, pathid, None, checksum, viewid) + self.logMsg('Done add or update for item', 1) # Update the path query = ' '.join(( @@ -902,6 +917,17 @@ class TVShows(Items): self.contentPop(title, self.newvideo_time) def add_update(self, item, viewtag=None, viewid=None): + try: + self.run_add_update(item, viewtag, viewid) + except Exception as e: + utils.window('emby_dbScan', clear=True) + self.logMsg('itemtypes.py for tv show has crashed for item %s. ' + 'Error:' % item.attrib.get('ratingKey', None), -1) + self.logMsg(e, -1) + # skip this item for now + return + + def run_add_update(self, item, viewtag=None, viewid=None): # Process single tvshow kodicursor = self.kodicursor emby_db = self.emby_db @@ -911,6 +937,8 @@ class TVShows(Items): update_item = True itemid = API.getRatingKey() + self.logMsg("Processing item %s" % itemid, 1) + if not itemid: self.logMsg("Cannot parse XML data for TV show", -1) return @@ -964,6 +992,8 @@ class TVShows(Items): except IndexError: studio = None + self.logMsg('Read all attributes', 1) + # GET THE FILE AND PATH ##### playurl = API.getKey() @@ -997,7 +1027,7 @@ class TVShows(Items): # Set plugin path toplevelpath = "plugin://plugin.video.plexkodiconnect.tvshows/" path = "%s%s/" % (toplevelpath, itemid) - + self.logMsg('Path set for item', 1) # UPDATE THE TVSHOW ##### if update_item: self.logMsg("UPDATE tvshow itemid: %s - Title: %s" % (itemid, title), 1) @@ -1052,7 +1082,7 @@ class TVShows(Items): # Create the reference in emby table emby_db.addReference(itemid, showid, "Series", "tvshow", pathid=pathid, checksum=checksum, mediafolderid=viewid) - + self.logMsg('Done add or update for item', 1) # Update the path query = ' '.join(( @@ -1083,13 +1113,25 @@ class TVShows(Items): all_episodes = embyserver.getEpisodesbyShow(itemid) self.added_episode(all_episodes['Items'], None) - def add_updateSeason(self, item, viewid=None, viewtag=None): + def add_updateSeason(self, item, viewtag=None, viewid=None): + try: + self.run_add_updateSeason(item, viewtag, viewid) + except Exception as e: + utils.window('emby_dbScan', clear=True) + self.logMsg('itemtypes.py for tv seasons has crashed for item %s. ' + 'Error:' % item.attrib.get('ratingKey', None), -1) + self.logMsg(e, -1) + # skip this item for now + return + + def run_add_updateSeason(self, item, viewid=None, viewtag=None): API = PlexAPI.API(item) showid = viewid itemid = API.getRatingKey() if not itemid: self.logMsg('Error getting itemid for season, skipping', -1) return + self.logMsg("Processing item %s" % itemid, 1) kodicursor = self.kodicursor emby_db = self.emby_db kodi_db = self.kodi_db @@ -1117,6 +1159,17 @@ class TVShows(Items): emby_db.addReference(itemid, seasonid, "Season", "season", parentid=showid, checksum=checksum) def add_updateEpisode(self, item, viewtag=None, viewid=None): + try: + self.run_add_updateEpisode(item, viewtag, viewid) + except Exception as e: + utils.window('emby_dbScan', clear=True) + self.logMsg('itemtypes.py for tv episode has crashed for item %s. ' + 'Error:' % item.attrib.get('ratingKey', None), -1) + self.logMsg(e, -1) + # skip this item for now + return + + def run_add_updateEpisode(self, item, viewtag=None, viewid=None): """ viewtag and viewid are irrelevant! """ @@ -1136,6 +1189,7 @@ class TVShows(Items): if not itemid: self.logMsg('Error getting itemid for episode, skipping', -1) return + self.logMsg("Processing item %s" % itemid, 1) emby_dbitem = emby_db.getItem_byId(itemid) try: episodeid = emby_dbitem[0] @@ -1584,7 +1638,19 @@ class Music(Items): if not pdialog and self.contentmsg: self.contentPop(title, self.newmusic_time) - def add_updateArtist(self, item, viewtag=None, viewid=None, + def add_updateArtist(self, item, viewtag=None, viewid=None, artisttype="MusicArtist"): + try: + self.run_add_updateArtist(item, viewtag, viewid, artisttype) + except Exception as e: + utils.window('emby_dbScan', clear=True) + self.logMsg('itemtypes.py for music artist has crashed for ' + 'item %s. Error:' + % item.attrib.get('ratingKey', None), -1) + self.logMsg(e, -1) + # skip this item for now + return + + def run_add_updateArtist(self, item, viewtag=None, viewid=None, artisttype="MusicArtist"): kodicursor = self.kodicursor emby_db = self.emby_db @@ -1594,6 +1660,7 @@ class Music(Items): update_item = True itemid = API.getRatingKey() + self.logMsg("Processing item %s" % itemid, 1) emby_dbitem = emby_db.getItem_byId(itemid) try: artistid = emby_dbitem[0] @@ -1668,6 +1735,18 @@ class Music(Items): artwork.addArtwork(artworks, artistid, "artist", kodicursor) def add_updateAlbum(self, item, viewtag=None, viewid=None): + try: + self.run_add_updateAlbum(item, viewtag, viewid) + except Exception as e: + utils.window('emby_dbScan', clear=True) + self.logMsg('itemtypes.py for music album has crashed for ' + 'item %s. Error:' + % item.attrib.get('ratingKey', None), -1) + self.logMsg(e, -1) + # skip this item for now + return + + def run_add_updateAlbum(self, item, viewtag=None, viewid=None): kodiversion = self.kodiversion kodicursor = self.kodicursor emby_db = self.emby_db @@ -1680,6 +1759,7 @@ class Music(Items): if not itemid: self.logMsg('Error processing Album, skipping', -1) return + self.logMsg("Processing item %s" % itemid, 1) emby_dbitem = emby_db.getItem_byId(itemid) try: albumid = emby_dbitem[0] @@ -1853,6 +1933,18 @@ class Music(Items): artwork.addArtwork(artworks, albumid, "album", kodicursor) def add_updateSong(self, item, viewtag=None, viewid=None): + try: + self.run_add_updateSong(item, viewtag, viewid) + except Exception as e: + utils.window('emby_dbScan', clear=True) + self.logMsg('itemtypes.py for music song has crashed for ' + 'item %s. Error:' + % item.attrib.get('ratingKey', None), -1) + self.logMsg(e, -1) + # skip this item for now + return + + def run_add_updateSong(self, item, viewtag=None, viewid=None): # Process single song kodiversion = self.kodiversion kodicursor = self.kodicursor @@ -1866,6 +1958,7 @@ class Music(Items): if not itemid: self.logMsg('Error processing Song; skipping', -1) return + self.logMsg("Processing item %s" % itemid, 1) emby_dbitem = emby_db.getItem_byId(itemid) try: songid = emby_dbitem[0] diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index 3d67ba7a..ccac74fb 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -111,6 +111,8 @@ class UserClient(threading.Thread): settings = utils.settings # Original host + self.machineIdentifier = utils.settings('plex_machineIdentifier') + self.servername = utils.settings('plex_servername') HTTPS = settings('https') == "true" host = settings('ipaddress') port = settings('port') @@ -192,29 +194,17 @@ class UserClient(threading.Thread): def setUserPref(self): self.logMsg('Setting user preferences', 0) - url = PlexAPI.PlexAPI().GetUserArtworkURL(self.currUser) - if url: - utils.window('EmbyUserImage', value=url) + # Only try to get user avatar if there is a token + if self.currToken: + url = PlexAPI.PlexAPI().GetUserArtworkURL(self.currUser) + if url: + utils.window('EmbyUserImage', value=url) # Set resume point max # url = "{server}/emby/System/Configuration?format=json" # result = doUtils.downloadUrl(url) # utils.settings('markPlayed', value=str(result['MaxResumePct'])) - def getPublicUsers(self): - - server = self.getServer() - - # Get public Users - url = "%s/emby/Users/Public?format=json" % server - result = self.doUtils.downloadUrl(url, authenticate=False) - - if result != "": - return result - else: - # Server connection failed - return False - def hasAccess(self): # Plex: always return True for now return True @@ -241,18 +231,15 @@ class UserClient(threading.Thread): xbmcgui.Dialog().notification(self.addonName, utils.language(33007)) - def loadCurrUser(self, authenticated=False): + def loadCurrUser(self, username, userId, usertoken, authenticated=False): self.logMsg('Loading current user', 0) window = utils.window - + settings = utils.settings doUtils = self.doUtils - username = self.getUsername() - userId = self.getUserId() self.currUserId = userId + self.currToken = usertoken self.currServer = self.getServer() - self.currToken = self.getToken() - self.machineIdentifier = utils.settings('plex_machineIdentifier') self.ssl = self.getSSLverify() self.sslcert = self.getSSL() @@ -268,7 +255,6 @@ class UserClient(threading.Thread): return False elif res == 401: self.logMsg('Token is no longer valid', -1) - self.resetClient() return False elif res >= 400: self.logMsg('Answer from PMS is not as expected. Retrying', -1) @@ -280,9 +266,7 @@ class UserClient(threading.Thread): window('emby_accessToken%s' % userId, value=self.currToken) window('emby_server%s' % userId, value=self.currServer) window('plex_machineIdentifier', value=self.machineIdentifier) - - window('emby_serverStatus', clear=True) - window('suspend_LibraryThread', clear=True) + window('plex_servername', value=self.servername) # Set DownloadUtils values doUtils.setUsername(username) @@ -290,8 +274,6 @@ class UserClient(threading.Thread): doUtils.setServer(self.currServer) doUtils.setToken(self.currToken) doUtils.setSSL(self.ssl, self.sslcert) - # parental control - let's verify if access is restricted - # self.hasAccess() # Start DownloadUtils session doUtils.startSession() @@ -299,6 +281,23 @@ class UserClient(threading.Thread): # Set user preferences in settings self.currUser = username self.setUserPref() + + # Writing values to settings file + settings('username', value=username) + settings('userid', value=userId) + settings('accessToken', value=usertoken) + + dialog = xbmcgui.Dialog() + if username: + dialog.notification( + heading=self.addonName, + message="Welcome " + username, + icon="special://home/addons/plugin.video.plexkodiconnect/icon.png") + else: + dialog.notification( + heading=self.addonName, + message="Welcome", + icon="special://home/addons/plugin.video.plexkodiconnect/icon.png") return True def authenticate(self): @@ -309,6 +308,15 @@ class UserClient(threading.Thread): settings = utils.settings dialog = xbmcgui.Dialog() + # Give attempts at entering password / selecting user + if self.retry >= 2: + log("Too many retries to login.", -1) + window('emby_serverStatus', value="Stop") + dialog.ok(lang(33001), + lang(39023)) + xbmc.executebuiltin( + 'Addon.OpenSettings(plugin.video.plexkodiconnect)') + # Get /profile/addon_data addondir = xbmc.translatePath( self.addon.getAddonInfo('profile')).decode('utf-8') @@ -318,98 +326,94 @@ class UserClient(threading.Thread): if not hasSettings: log("Error, no settings.xml found.", -1) self.auth = False - return + return False server = self.getServer() - # If no user information + # If there is no server we can connect to if not server: log("Missing server information.", 0) self.auth = False - return + return False - username = self.getUsername() - userId = self.getUserId(username) - # If there's a token, load the user - if self.getToken(username=username, userId=userId): - if self.loadCurrUser() is False: - pass + # If there is a username in the settings, try authenticating + username = settings('username') + userId = settings('userid') + usertoken = settings('accessToken') + enforceLogin = settings('enforceUserLogin') + # Found a user in the settings, try to authenticate + if username and enforceLogin == 'false': + log('Trying to authenticate with old settings', 0) + if self.loadCurrUser(username, + userId, + usertoken, + authenticated=False): + # SUCCESS: loaded a user from the settings + return True else: - # We successfully loaded a user - log("Current user: %s" % self.currUser, 1) - log("Current userId: %s" % self.currUserId, 1) - log("Current accessToken: xxxx", 1) - return + # Failed to use the settings - delete them! + log("Failed to use the settings credentials. Deleting them", 1) + settings('username', value='') + settings('userid', value='') + settings('accessToken', value='') - # AUTHENTICATE USER ##### plx = PlexAPI.PlexAPI() - # Choose Plex user login - plexdict = plx.GetPlexLoginFromSettings() - myplexlogin = plexdict['myplexlogin'] - plexhome = plexdict['plexhome'] - if myplexlogin == "true" and plexhome == 'true': - username, userId, accessToken = plx.ChoosePlexHomeUser() - else: - log("Trying to connect to PMS without a token", 0) - accessToken = '' - # Check connection - if plx.CheckConnection(server, accessToken) == 200: - self.currUser = username - settings('accessToken', value=accessToken) - settings('userId', value=userId) - log("User authenticated with an access token", 1) - if self.loadCurrUser(authenticated=True) is False: - # Something went really wrong, return and try again + # Could not use settings - try to get Plex user list from plex.tv + plextoken = settings('plexToken') + if plextoken: + log("Trying to connect to plex.tv to get a user list", 0) + userInfo = plx.ChoosePlexHomeUser(plextoken) + if userInfo is False: + # FAILURE: Something went wrong, try again self.auth = True - self.currUser = None - return - # Success! - if username: - dialog.notification( - heading=self.addonName, - message="Welcome " + username, - icon="special://home/addons/plugin.video.plexkodiconnect/icon.png") - else: - dialog.notification( - heading=self.addonName, - message="Welcome", - icon="special://home/addons/plugin.video.plexkodiconnect/icon.png") - self.retry = 0 - # Make sure that lib sync thread is not paused + self.retry += 1 + return False + username = userInfo['username'] + userId = userInfo['userid'] + usertoken = userInfo['token'] else: - self.logMsg("Error: user authentication failed.", -1) - settings('accessToken', value="") - settings('userId', value="") - - # Give attempts at entering password / selecting user - if self.retry >= 2: - log("Too many retries to login.", -1) - window('emby_serverStatus', value="Stop") - dialog.ok(lang(33001), - lang(39023)) - xbmc.executebuiltin( - 'Addon.OpenSettings(plugin.video.plexkodiconnect)') + log("Trying to authenticate without a token", 0) + username = '' + userId = '' + usertoken = '' + if self.loadCurrUser(username, userId, usertoken, authenticated=False): + # SUCCESS: loaded a user from the settings + return True + else: + # FAILUR: Something went wrong, try again + self.auth = True self.retry += 1 - self.auth = False + return False def resetClient(self): self.logMsg("Reset UserClient authentication.", 1) + settings = utils.settings + window = utils.window + + window('emby_accessToken%s' % self.currUserId, clear=True) + window('emby_server%s' % self.currUserId, clear=True) + window('emby_currUser', clear=True) + window('plex_username', clear=True) + + settings('username', value='') + settings('userid', value='') + settings('accessToken', value='') + + # Reset token in downloads + self.doUtils.setToken('') + self.doUtils.setUserId('') + self.doUtils.setUsername('') - utils.settings('accessToken', value="") - utils.window('emby_accessToken%s' % self.currUserId, clear=True) self.currToken = None - self.logMsg("User token has been removed. Pausing Lib sync thread", 1) - utils.window('suspend_LibraryThread', value="true") - self.auth = True self.currUser = None self.currUserId = None + self.retry = 0 + def run(self): log = self.logMsg window = utils.window - # Start library sync thread in a suspended mode, until signed in - utils.window('suspend_LibraryThread', value="true") log("----===## Starting UserClient ##===----", 0) while not self.threadStopped(): @@ -429,6 +433,7 @@ class UserClient(threading.Thread): # Unauthorized access, revoke token window('emby_serverStatus', value="Auth") self.resetClient() + xbmc.sleep(2000) if self.auth and (self.currUser is None): # Try to authenticate user @@ -436,7 +441,14 @@ class UserClient(threading.Thread): # Set auth flag because we no longer need # to authenticate the user self.auth = False - self.authenticate() + if self.authenticate(): + # Successfully authenticated and loaded a user + log("Current user: %s" % self.currUser, 1) + log("Current userId: %s" % self.currUserId, 1) + log("Current accessToken: xxxx", 1) + self.retry = 0 + window('suspend_LibraryThread', clear=True) + window('emby_serverStatus', clear=True) if not self.auth and (self.currUser is None): # Loop if no server found diff --git a/resources/settings.xml b/resources/settings.xml index a521b967..28ac97b1 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -16,7 +16,9 @@ - + + + @@ -32,14 +34,16 @@ + - - - + + + + diff --git a/service.py b/service.py index 2d47e1fa..0eda6df4 100644 --- a/service.py +++ b/service.py @@ -253,7 +253,7 @@ class Service(): self.server_online = True log("Server is online and ready.", 1) window('emby_online', value="true") - + # Start the userclient thread if not self.userclient_running: self.userclient_running = True