diff --git a/resources/language/English/strings.xml b/resources/language/English/strings.xml index f7a65d32..02c28b05 100644 --- a/resources/language/English/strings.xml +++ b/resources/language/English/strings.xml @@ -145,14 +145,14 @@ Recently Added TV Shows - On Deck TV Shows + In Progress TV Shows All Music Channels Recently Added Movies Recently Added Episodes Recently Added Albums - On Deck Movies - On Deck Episodes + In Progress Movies + In Progress Episodes Next Episodes Favorite Movies Favorite Shows @@ -277,7 +277,7 @@ Force artwork caching Limit artwork cache threads (recommended for rpi) Enable fast startup (requires server plugin) - Maximum items to request from the server at once + Maximum items to request from the server at once (restart!) Playback [COLOR yellow]Enter network credentials[/COLOR] Enable Plex Trailers (Plexpass is needed) @@ -379,6 +379,7 @@ Reseting PMS connections, please wait Failed to reset PMS and plex.tv connects. Try to restart Kodi. [COLOR yellow]Log-in to plex.tv[/COLOR] + Not yet connected to Plex Server @@ -408,6 +409,6 @@ Plex playlists/nodes refreshed Plex playlists/nodes refresh failed Full library sync finished - Sync had to skip some items because they could not be processed. Please post your Kodi logs to the Plex forum. + Sync had to skip some items because they could not be processed. Kodi may be instable now!! Please post your Kodi logs to the Plex forum. diff --git a/resources/language/German/strings.xml b/resources/language/German/strings.xml index 5025cf0c..65446c10 100644 --- a/resources/language/German/strings.xml +++ b/resources/language/German/strings.xml @@ -173,14 +173,14 @@ Addresse : Zuletzt hinzugefügte Serien - Aktuell Serien + Begonnene Serien Alles an Musik Kanäle Zuletzt hinzugefügte Filme Zuletzt hinzugefügte Episoden Zuletzt hinzugefügte Alben - Aktuelle Filme - Aktuelle Episoden + Begonnene Filme + Begonnene Episoden Nächste Episoden Favorisierte Filme Favorisierte Serien @@ -252,6 +252,8 @@ Unterdrücke Server-Verbindungsmeldungen beim Starten Benutze lokale Pfade anstelle von Addon-Umleitungen beim Abspielen + Max. Anzahl gleichzeitig nachgefragter PMS Einträge (Neustart!) + Plex Media Server Authorisierung ist zu häufig fehlgeschlagen. In den Einstellungen können die Anzahl erfolgloser Versuche zurückgesetzt werden. @@ -310,8 +312,7 @@ PMS Verbindungen werden zurückgesetzt PMS und plex.tv Verbindungen konnten nicht zurückgesetzt werden. Bitte versuchen Sie, Kodi neu zu starten, um das Problem zu beheben. [COLOR yellow]Bei plex.tv einloggen[/COLOR] - - + Noch nicht mit Plex Server verbunden Alle Plex Bilder in Kodi zwischenzuspeichern kann sehr lange dauern. Möchten Sie wirklich fortfahren? @@ -340,6 +341,6 @@ Plex Playlisten/Nodes aktualisiert Plex Playlisten/Nodes Aktualisierung fehlgeschlagen Plex Bibliotheken aktualisiert - Einige Plex Einträge mussten übersprungen werden, da sie nicht verarbeitet werden konnten. Bitte teilen Sie Ihr Kodi log im Plex Forum. + Einige Plex Einträge mussten übersprungen werden, da sie nicht verarbeitet werden konnten. Kodi ist nun möglicherweise instabil!! Bitte teilen Sie Ihr Kodi log im Plex Forum. diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 71a5bc67..11734fb2 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -1056,8 +1056,6 @@ class PlexAPI(): self.logMsg("No URL for user avatar.", 1) return False for user in users: - self.logMsg('type user: %s, type username: %s' - % (type(user['title']), type(username))) if username in user['title']: url = user['thumb'] self.logMsg("Avatar url for user %s is: %s" % (username, url), 1) @@ -2092,7 +2090,7 @@ class API(): allartworks['Backdrop'].append(background) if not allartworks['Primary']: - primary = item['parentThumb'] + primary = item.get('parentThumb') if primary: primary = "%s%s" % (self.server, primary) primary = self.addPlexCredentialsToUrl(primary) diff --git a/resources/lib/PlexCompanion.py b/resources/lib/PlexCompanion.py index bf46a9a1..93275547 100644 --- a/resources/lib/PlexCompanion.py +++ b/resources/lib/PlexCompanion.py @@ -84,12 +84,12 @@ class PlexCompanion(threading.Thread): self.logMsg("Client is still registered", 1) else: self.logMsg("Client is no longer registered", 1) - self.logMsg("PlexBMC Helper still running on port %s" + self.logMsg("Plex Companion still running on port %s" % self.port, 1) message_count = 0 if not is_running: - self.logMsg("PleXBMC Helper has started", 0) + self.logMsg("Plex Companion has started", 0) is_running = True subscribers.subMgr.notify() @@ -106,4 +106,4 @@ class PlexCompanion(threading.Thread): finally: httpd.socket.close() requests.dumpConnections() - self.logMsg("----===## STOP PlexBMC Helper ##===----", 0) + self.logMsg("----===## STOP Plex Companion ##===----", 0) diff --git a/resources/lib/PlexFunctions.py b/resources/lib/PlexFunctions.py index e8c4ace3..fb96485c 100644 --- a/resources/lib/PlexFunctions.py +++ b/resources/lib/PlexFunctions.py @@ -3,7 +3,7 @@ from urllib import urlencode from ast import literal_eval from urlparse import urlparse, parse_qs import re -import time +from copy import deepcopy from xbmcaddon import Addon @@ -185,7 +185,7 @@ def GetPlexMetadata(key): return xml -def GetAllPlexChildren(key): +def GetAllPlexChildren(key, containerSize=None): """ Returns a list (raw xml API dump) of all Plex children for the key. (e.g. /library/metadata/194853/children pointing to a season) @@ -193,18 +193,11 @@ def GetAllPlexChildren(key): Input: key Key to a Plex item, e.g. 12345 """ - xml = downloadutils.DownloadUtils().downloadUrl( - "{server}/library/metadata/%s/children" % key) - try: - xml.attrib - except AttributeError: - logMsg( - title, "Error retrieving all children for Plex item %s" % key, -1) - xml = None - return xml + url = "{server}/library/metadata/%s/children?" % key + return DownloadChunks(url, containerSize) -def GetPlexSectionResults(viewId, args=None): +def GetPlexSectionResults(viewId, args=None, containerSize=None): """ Returns a list (XML API dump) of all Plex items in the Plex section with key = viewId. @@ -214,26 +207,76 @@ def GetPlexSectionResults(viewId, args=None): Returns None if something went wrong """ - result = [] - url = "{server}/library/sections/%s/all" % viewId + url = "{server}/library/sections/%s/all?" % viewId if args: - url += "?" + urlencode(args) - - result = downloadutils.DownloadUtils().downloadUrl(url) - - try: - result.tag - # Nope, not an XML, abort - except AttributeError: - logMsg(title, - "Error retrieving all items for Plex section %s" - % viewId, -1) - result = None - - return result + url += urlencode(args) + '&' + return DownloadChunks(url, containerSize) -def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None): +def DownloadChunks(url, containerSize): + """ + Downloads PMS url in chunks of containerSize (int). + If containerSize is None: ONE xml is fetched directly + + url MUST end with '?' (if no other url encoded args are present) or '&' + + Returns a stitched-together xml or None. + """ + if containerSize is None: + # Get rid of '?' or '&' at the end of url + xml = downloadutils.DownloadUtils().downloadUrl(url[:-1]) + try: + xml.attrib + except AttributeError: + # Nope, not an XML, abort + logMsg(title, "Error getting url %s" % url[:-1], -1) + return None + else: + return xml + + xml = None + pos = 0 + errorCounter = 0 + while errorCounter < 10: + args = { + 'X-Plex-Container-Size': containerSize, + 'X-Plex-Container-Start': pos + } + xmlpart = downloadutils.DownloadUtils().downloadUrl( + url + urlencode(args)) + # If something went wrong - skip in the hope that it works next time + try: + xmlpart.attrib + except AttributeError: + logMsg(title, 'Error while downloading chunks: %s' + % (url + urlencode(args)), -1) + pos += containerSize + errorCounter += 1 + continue + + # Very first run: starting xml (to retain data in xml's root!) + if xml is None: + xml = deepcopy(xmlpart) + if len(xmlpart) < containerSize: + break + else: + pos += containerSize + continue + # Build answer xml - containing the entire library + for child in xmlpart: + xml.append(child) + # Done as soon as we don't receive a full complement of items + if len(xmlpart) < containerSize: + break + pos += containerSize + if errorCounter == 10: + logMsg(title, 'Fatal error while downloading chunks for %s' % url, -1) + return None + return xml + + +def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None, + containerSize=None): """ Returns a list (raw XML API dump) of all Plex subitems for the key. (e.g. /library/sections/2/allLeaves pointing to all TV shows) @@ -244,6 +287,7 @@ def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None): since that point of time until now. updatedAt Unix timestamp; only retrieves PMS items updated by the PMS since that point of time until now. + containerSize Number of items simultaneously fetched from PMS If lastViewedAt and updatedAt=None, ALL PMS items are returned. @@ -260,19 +304,11 @@ def GetAllPlexLeaves(viewId, lastViewedAt=None, updatedAt=None): if updatedAt: args.append('updatedAt>=%s' % updatedAt) if args: - url += '?' + '&'.join(args) + url += '?' + '&'.join(args) + '&' + else: + url += '?' - xml = downloadutils.DownloadUtils().downloadUrl(url) - - try: - xml.attrib - # Nope, not an XML, abort - except AttributeError: - logMsg(title, - "Error retrieving all leaves for Plex section %s" - % viewId, -1) - xml = None - return xml + return DownloadChunks(url, containerSize) def GetPlexCollections(mediatype): diff --git a/resources/lib/artwork.py b/resources/lib/artwork.py index 75988e63..68adf648 100644 --- a/resources/lib/artwork.py +++ b/resources/lib/artwork.py @@ -421,7 +421,7 @@ class Artwork(): cursor.execute(query, (imageUrl, kodiId, mediaType, imageType)) # Cache fanart and poster in Kodi texture cache - if cacheimage and imageType in ("fanart", "poster"): + if cacheimage and imageType in ("fanart", "poster", "thumb"): self.CacheTexture(imageUrl) def deleteArtwork(self, kodiid, mediatype, cursor): diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index bf44f303..c927dd79 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -159,6 +159,18 @@ def doPlayback(itemid, dbid): """ Called only for a SINGLE element, not playQueues """ + if utils.window('plex_authenticated') != "true": + utils.logMsg('doPlayback', 'Not yet authenticated for a PMS, abort ' + 'starting playback', -1) + string = xbmcaddon.Addon().getLocalizedString + # Not yet connected to a PMS server + xbmcgui.Dialog().notification( + heading=addonName, + message=string(39210), + icon=xbmcgui.NOTIFICATION_ERROR, + time=7000, + sound=True) + return False item = PlexFunctions.GetPlexMetadata(itemid) if item is None: diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 77ce3015..e75913b8 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -43,6 +43,8 @@ class Items(object): self.artwork = artwork.Artwork() self.emby = embyserver.Read_EmbyServer() + self.userid = utils.window('currUserId') + self.server = utils.window('pms_server') def __enter__(self): """ @@ -1445,8 +1447,21 @@ class TVShows(Items): people = API.getPeopleList() kodi_db.addPeople(episodeid, people, "episode") # Process artwork - allartworks = API.getAllArtwork() - artwork.addArtwork(allartworks, episodeid, "episode", kodicursor) + # Wide "screenshot" of particular episode + poster = item.attrib.get('thumb') + if poster: + poster = API.addPlexCredentialsToUrl( + "%s%s" % (self.server, poster)) + artwork.addOrUpdateArt( + poster, episodeid, "episode", "thumb", kodicursor) + # poster of TV show itself + # poster = item.attrib.get('grandparentThumb') + # if poster: + # poster = API.addPlexCredentialsToUrl( + # "%s%s" % (self.server, poster)) + # artwork.addOrUpdateArt( + # poster, episodeid, "episode", "poster", kodicursor) + # Process stream details streams = API.getMediaStreams() kodi_db.addStreams(fileid, streams, runtime) @@ -1609,8 +1624,6 @@ class Music(Items): self.enableimportsongrating = utils.settings('enableImportSongRating') == "true" self.enableexportsongrating = utils.settings('enableExportSongRating') == "true" self.enableupdatesongrating = utils.settings('enableUpdateSongRating') == "true" - self.userid = utils.window('currUserId') - self.server = utils.window('pms_server') def __enter__(self): """ diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 5e030133..e1c3b1e1 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -237,6 +237,7 @@ class LibrarySync(Thread): else False self.enableBackgroundSync = True if utils.settings( 'enableBackgroundSync') == "true" else False + self.limitindex = int(utils.settings('limitindex')) # Time offset between Kodi and PMS in seconds (=Koditime - PMStime) self.timeoffset = 0 @@ -340,7 +341,8 @@ class LibrarySync(Thread): xbmc.sleep(2000) # Get all PMS items to find the item we changed items = PlexFunctions.GetAllPlexLeaves(libraryId, - lastViewedAt=timestamp) + lastViewedAt=timestamp, + containerSize=self.limitindex) # Toggle watched state back PlexFunctions.scrobble(plexId, 'unwatched') # Get server timestamp for this change @@ -482,7 +484,9 @@ class LibrarySync(Thread): self.updatelist = [] # Get items per view items = PlexFunctions.GetAllPlexLeaves( - view['id'], updatedAt=self.getPMSfromKodiTime(lastSync)) + view['id'], + updatedAt=self.getPMSfromKodiTime(lastSync), + containerSize=self.limitindex) # Just skip if something went wrong if not items: continue @@ -516,7 +520,9 @@ class LibrarySync(Thread): songupdate = False for view in self.views: items = PlexFunctions.GetAllPlexLeaves( - view['id'], lastViewedAt=self.getPMSfromKodiTime(lastSync)) + view['id'], + lastViewedAt=self.getPMSfromKodiTime(lastSync), + containerSize=self.limitindex) for item in items: itemId = item.attrib.get('ratingKey') # Skipping items 'title=All episodes' without a 'ratingKey' @@ -1070,7 +1076,8 @@ class LibrarySync(Thread): # Get items per view viewId = view['id'] viewName = view['name'] - all_plexmovies = PlexFunctions.GetPlexSectionResults(viewId) + all_plexmovies = PlexFunctions.GetPlexSectionResults( + viewId, args=None, containerSize=self.limitindex) if not all_plexmovies: self.logMsg("Couldnt get section items, aborting for view.", 1) continue @@ -1107,7 +1114,8 @@ class LibrarySync(Thread): """ xml = PlexFunctions.GetAllPlexLeaves(viewId, lastViewedAt=lastViewedAt, - updatedAt=updatedAt) + updatedAt=updatedAt, + containerSize=self.limitindex) # Return if there are no items in PMS reply - it's faster try: xml[0].attrib @@ -1202,7 +1210,8 @@ class LibrarySync(Thread): # Get items per view viewId = view['id'] viewName = view['name'] - allPlexTvShows = PlexFunctions.GetPlexSectionResults(viewId) + allPlexTvShows = PlexFunctions.GetPlexSectionResults( + viewId, containerSize=self.limitindex) if not allPlexTvShows: self.logMsg( "Error downloading show view xml for view %s" % viewId, -1) @@ -1228,7 +1237,8 @@ class LibrarySync(Thread): if self.threadStopped(): return False # Grab all seasons to tvshow from PMS - seasons = PlexFunctions.GetAllPlexChildren(tvShowId) + seasons = PlexFunctions.GetAllPlexChildren( + tvShowId, containerSize=self.limitindex) if not seasons: self.logMsg( "Error downloading season xml for show %s" % tvShowId, -1) @@ -1252,7 +1262,8 @@ class LibrarySync(Thread): if self.threadStopped(): return False # Grab all episodes to tvshow from PMS - episodes = PlexFunctions.GetAllPlexLeaves(view['id']) + episodes = PlexFunctions.GetAllPlexLeaves( + view['id'], containerSize=self.limitindex) if not episodes: self.logMsg( "Error downloading episod xml for view %s" @@ -1265,7 +1276,7 @@ class LibrarySync(Thread): None, None) self.logMsg("Analyzed all episodes of TV show with Plex Id %s" - % tvShowId, 1) + % view['id'], 1) # Process self.updatelist self.GetAndProcessXMLs(itemType) @@ -1350,7 +1361,7 @@ class LibrarySync(Thread): viewId = view['id'] viewName = view['name'] itemsXML = PlexFunctions.GetPlexSectionResults( - viewId, args=urlArgs) + viewId, args=urlArgs, containerSize=self.limitindex) if not itemsXML: self.logMsg("Error downloading xml for view %s" % viewId, -1) diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py index c5e63ab2..b11d762c 100644 --- a/resources/lib/plexbmchelper/subscribers.py +++ b/resources/lib/plexbmchelper/subscribers.py @@ -155,7 +155,7 @@ class SubscriptionManager: serv = getServerByHost(self.server) url = serv.get('protocol', 'http') + '://' \ + serv.get('server', 'localhost') + ':' \ - + serv.get('port', 32400) + "/:/timeline" + + serv.get('port', '32400') + "/:/timeline" self.download.downloadUrl(url, type="GET", parameters=params) # requests.getwithparams(serv.get('server', 'localhost'), serv.get('port', 32400), "/:/timeline", params, getPlexHeaders(), serv.get('protocol', 'http')) printDebug("params: %s" % params) diff --git a/resources/lib/userclient.py b/resources/lib/userclient.py index 72084b36..1f2579b6 100644 --- a/resources/lib/userclient.py +++ b/resources/lib/userclient.py @@ -179,9 +179,6 @@ class UserClient(threading.Thread): if authenticated is False: self.logMsg('Testing validity of current token', 0) - window('currUserId', value=userId) - window('plex_username', value=username) - window('pms_token', value=self.currToken) res = PlexAPI.PlexAPI().CheckConnection( self.currServer, self.currToken) if res is False: @@ -205,6 +202,7 @@ class UserClient(threading.Thread): window('pms_server', value=self.currServer) window('plex_machineIdentifier', value=self.machineIdentifier) window('plex_servername', value=self.servername) + window('plex_authenticated', value='true') # Set DownloadUtils values doUtils.setUsername(username) @@ -331,6 +329,7 @@ class UserClient(threading.Thread): settings = utils.settings window = utils.window + window('plex_authenticated', clear=True) window('pms_token', clear=True) window('plex_token', clear=True) window('pms_server', clear=True) @@ -410,7 +409,7 @@ class UserClient(threading.Thread): self.auth = True # Minimize CPU load - xbmc.sleep(500) + xbmc.sleep(100) self.doUtils.stopSession() log("##===---- UserClient Stopped ----===##", 0) diff --git a/resources/settings.xml b/resources/settings.xml index 28ac97b1..5c2ac6f8 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -49,7 +49,7 @@ - + diff --git a/service.py b/service.py index 70518944..1f007bd2 100644 --- a/service.py +++ b/service.py @@ -78,7 +78,8 @@ class Service(): "emby_shouldStop", "currUserId", "emby_dbScan", "emby_sessionId", "emby_initialScan", "emby_customplaylist", "emby_playbackProps", "plex_runLibScan", "plex_username", "pms_token", "plex_token", - "pms_server", "plex_machineIdentifier", "plex_servername" + "pms_server", "plex_machineIdentifier", "plex_servername", + "plex_authenticated" ] for prop in properties: window(prop, clear=True)