From e0330c1a2856ec2a98b6cd5a0be71fda5472e380 Mon Sep 17 00:00:00 2001 From: tomkat83 Date: Sat, 23 Jan 2016 15:53:24 +0100 Subject: [PATCH] Fixed plex companion headers and resume point --- default.py | 2 +- resources/lib/PlexAPI.py | 57 ++++++++++----- resources/lib/PlexCompanion.py | 9 +-- resources/lib/entrypoint.py | 7 +- resources/lib/itemtypes.py | 4 +- resources/lib/librarysync.py | 83 +++++++++++----------- resources/lib/playbackutils.py | 7 +- resources/lib/plexbmchelper/subscribers.py | 13 ++-- 8 files changed, 102 insertions(+), 80 deletions(-) diff --git a/default.py b/default.py index a5f9e816..e308fdcd 100644 --- a/default.py +++ b/default.py @@ -90,7 +90,7 @@ class Main: folderid = params['folderid'][0] modes[mode](itemid, folderid) elif mode == "companion": - resume = params.get('resume', '') + resume = params.get('resume', '')[0] modes[mode](itemid, resume=resume) else: modes[mode]() diff --git a/resources/lib/PlexAPI.py b/resources/lib/PlexAPI.py index 928e9fc5..df77ae07 100644 --- a/resources/lib/PlexAPI.py +++ b/resources/lib/PlexAPI.py @@ -90,8 +90,6 @@ class PlexAPI(): self.userId = utils.window('emby_currUser') self.token = utils.window('emby_accessToken%s' % self.userId) self.server = utils.window('emby_server%s' % self.userId) - self.plexLogin = utils.settings('plexLogin') - self.plexToken = utils.settings('plexToken') self.doUtils = downloadutils.DownloadUtils() @@ -797,6 +795,8 @@ class PlexAPI(): """ # Get addon infos xargs = { + "Content-type": "application/x-www-form-urlencoded", + "Access-Control-Allow-Origin": "*", 'X-Plex-Language': 'en', 'X-Plex-Device': self.addonName, 'X-Plex-Client-Platform': self.platform, @@ -1052,8 +1052,8 @@ class PlexAPI(): Will return empty strings if failed. """ - plexLogin = self.plexLogin - plexToken = self.plexToken + 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 @@ -1412,7 +1412,7 @@ class PlexAPI(): def GetPlexSectionResults(self, viewId, headerOptions={}): """ - Returns a list (raw JSON API dump) of all Plex items in the Plex + Returns a list (raw JSON or XML API dump) of all Plex items in the Plex section with key = viewId. """ result = [] @@ -1421,21 +1421,29 @@ class PlexAPI(): try: result = jsondata['_children'] except TypeError: - # Received an XML - pass - except: + # Maybe we received an XML, check for that with tag attribute + try: + jsondata.tag + result = jsondata + # Nope, not an XML, abort + except AttributeError: + self.logMsg("Error retrieving all items for Plex section %s" + % viewId, -1) + return result + except KeyError: self.logMsg("Error retrieving all items for Plex section %s" % viewId, -1) - return [] return result def GetAllPlexLeaves(self, viewId, headerOptions={}): """ - Returns a list (raw JSON API dump) of all Plex subitems for the key. + Returns a list (raw JSON or XML API dump) of all Plex subitems for the + key. (e.g. /library/sections/2/allLeaves pointing to all TV shows) Input: viewId Id of Plex library, e.g. '2' + headerOptions to override the download headers """ result = [] url = "{server}/library/sections/%s/allLeaves" % viewId @@ -1443,12 +1451,18 @@ class PlexAPI(): try: result = jsondata['_children'] except TypeError: - # received an XMl - pass + # Maybe we received an XML, check for that with tag attribute + try: + jsondata.tag + result = jsondata + # Nope, not an XML, abort + except AttributeError: + self.logMsg("Error retrieving all leaves for Plex section %s" + % viewId, -1) + return result except KeyError: - self.logMsg("Error retrieving all children for Plex viewId %s" + self.logMsg("Error retrieving all leaves for Plex viewId %s" % viewId, -1) - pass return result def GetAllPlexChildren(self, key): @@ -1471,10 +1485,12 @@ class PlexAPI(): def GetPlexMetadata(self, key): """ - Returns raw API metadata for key. + Returns raw API metadata for key as an etree XML. Can be called with either Plex key '/library/metadata/xxxx'metadata OR with the digits 'xxxx' only. + + Returns an empty string '' if something went wrong """ xml = '' key = str(key) @@ -1495,9 +1511,13 @@ class PlexAPI(): url = url + '?' + urlencode(arguments) headerOptions = {'Accept': 'application/xml'} xml = self.doUtils.downloadUrl(url, headerOptions=headerOptions) - if not xml: + # Did we receive a valid XML? + try: + xml.tag + # Nope we did not receive a valid XML + except AttributeError: self.logMsg("Error retrieving metadata for %s" % url, -1) - xml = None + xml = '' return xml @@ -1534,7 +1554,6 @@ class API(): Which child in the XML response shall we look at and work with? """ self.child = int(number) - self.logMsg("Set child number to %s" % number, 1) def getChildNumber(self): """ @@ -1592,7 +1611,7 @@ class API(): def getChecksum(self): """ Can be used on both XML and JSON - Returns a string, not int! + Returns a string, not int """ item = self.item # XML diff --git a/resources/lib/PlexCompanion.py b/resources/lib/PlexCompanion.py index d0fd3a98..346de3bf 100644 --- a/resources/lib/PlexCompanion.py +++ b/resources/lib/PlexCompanion.py @@ -80,15 +80,12 @@ class PlexCompanion(threading.Thread): message_count += 1 if message_count > 30: - if self.stopped(): - break if self.client.check_client_registration(): 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.port, 1) + self.logMsg("Client is no longer registered", 1) + self.logMsg("PlexBMC Helper still running on port %s" + % self.port, 1) message_count = 0 if not is_running: diff --git a/resources/lib/entrypoint.py b/resources/lib/entrypoint.py index 70eb0424..fa7fd786 100644 --- a/resources/lib/entrypoint.py +++ b/resources/lib/entrypoint.py @@ -32,7 +32,7 @@ import embydb_functions ################################################################################################# -def plexCompanion(fullurl, resume=""): +def plexCompanion(fullurl, resume=None): regex = re.compile(r'''/(\d+)$''') itemid = regex.findall(fullurl) try: @@ -49,9 +49,12 @@ def plexCompanion(fullurl, resume=""): # Get dbid using itemid dbid = emby.getItem_byId(itemid)[0] embyconn.close() + # Fix resume timing + if resume: + resume = round(float(resume) / 1000.0, 6) # Start playing item = PlexAPI.PlexAPI().GetPlexMetadata(itemid) - pbutils.PlaybackUtils(item).play(itemid, dbid) + pbutils.PlaybackUtils(item).play(itemid, dbid, seektime=resume) def doPlayback(itemid, dbid): diff --git a/resources/lib/itemtypes.py b/resources/lib/itemtypes.py index 3a60dbd1..add7ca0a 100644 --- a/resources/lib/itemtypes.py +++ b/resources/lib/itemtypes.py @@ -270,7 +270,7 @@ class Movies(Items): Plex resume points for movies in progress. """ API = PlexAPI.API(itemList) - for itemNumber in range(0, len(itemList)): + for itemNumber in range(len(itemList)): API.setChildNumber(itemNumber) itemid = API.getKey() # Get key and db entry on the Kodi db side @@ -890,7 +890,7 @@ class TVShows(Items): Plex resume points for movies in progress. """ API = PlexAPI.API(itemList) - for itemNumber in range(0, len(itemList)): + for itemNumber in range(len(itemList)): API.setChildNumber(itemNumber) itemid = API.getKey() # Get key and db entry on the Kodi db side diff --git a/resources/lib/librarysync.py b/resources/lib/librarysync.py index 2ef9c269..18932b89 100644 --- a/resources/lib/librarysync.py +++ b/resources/lib/librarysync.py @@ -64,15 +64,12 @@ class ThreadedGetMetadata(threading.Thread): except: raise # check whether valid XML - try: - # .tag works for XML - plexXML.tag + if plexXML: updateItem['XML'] = plexXML # place item into out queue self.out_queue.put(updateItem) # If we don't have a valid XML, don't put that into the queue - except AttributeError: - pass + # but skip this item for now # Keep track of where we are at with self.lock: getMetadataCount += 1 @@ -128,11 +125,9 @@ class ThreadedProcessMetadata(threading.Thread): title = updateItem['title'] itemSubFkt = getattr(item, method) with self.lock: - itemSubFkt( - plexitem, - viewtag=viewName, - viewid=viewId - ) + itemSubFkt(plexitem, + viewtag=viewName, + viewid=viewId) # Keep track of where we are at processMetadataCount += 1 processingViewName = title @@ -170,11 +165,9 @@ class ThreadedShowSyncInfo(threading.Thread): total = self.total downloadLock = self.locks[0] processLock = self.locks[1] - self.dialog.create( - "%s: Sync %s: %s items" % (self.addonName, - self.itemType, - str(total)), - "Starting") + self.dialog.create("%s: Sync %s: %s items" + % (self.addonName, self.itemType, str(total)), + "Starting") global getMetadataCount global processMetadataCount global processingViewName @@ -191,11 +184,11 @@ class ThreadedShowSyncInfo(threading.Thread): percentage = int(float(totalProgress) / float(total)*100.0) except ZeroDivisionError: percentage = 0 - self.dialog.update( - percentage, - message="Downloaded: %s, Processed: %s: %s" % ( - getMetadataProgress, processMetadataProgress, viewName) - ) + self.dialog.update(percentage, + message="Downloaded: %s, Processed: %s: %s" + % (getMetadataProgress, + processMetadataProgress, viewName)) + # Sleep for x milliseconds xbmc.sleep(500) self.dialog.close() @@ -430,11 +423,11 @@ class LibrarySync(threading.Thread): utils.window('emby_dbScan', clear=True) utils.window('emby_initialScan', clear=True) xbmcgui.Dialog().notification( - heading=self.addonName, - message="%s completed in: %s" % - (message, str(elapsedtotal).split('.')[0]), - icon="special://home/addons/plugin.video.plexkodiconnect/icon.png", - sound=False) + heading=self.addonName, + message="%s completed in: %s" + % (message, str(elapsedtotal).split('.')[0]), + icon="special://home/addons/plugin.video.plexkodiconnect/icon.png", + sound=False) return True def maintainViews(self): @@ -567,22 +560,25 @@ class LibrarySync(threading.Thread): Adds items to self.updatelist as well as self.allPlexElementsId dict Input: - elementList: List of elements, e.g. a list of '_children' - movie elements as received via JSON from PMS + elementList: List of elements, e.g. list of '_children' + movie elements as received from PMS + itemType: 'Movies', 'TVShows', ... + method: Method name to be called with this itemtype + see itemtypes.py + viewName: Name of the Plex view (e.g. 'My TV shows') + viewId: Id/Key of Plex library (e.g. '1') Output: self.updatelist, self.allPlexElementsId - self.updatelist APPENDED(!!) list itemids (Plex Keys as - as received from API.getKey()) - = [ - { - 'itemId': xxx, - 'itemType': 'Movies'/'TVShows'/..., - 'method': 'add_update', 'add_updateSeason', ... - 'viewName': xxx, - 'viewId': xxx - }, ... - ] - self.allPlexElementsId APPENDED(!!) dic + self.updatelist APPENDED(!!) list itemids (Plex Keys as + as received from API.getKey()) + One item in this list is of the form: + 'itemId': xxx, + 'itemType': 'Movies','TVShows', ... + 'method': 'add_update', 'add_updateSeason', ... + 'viewName': xxx, + 'viewId': xxx + + self.allPlexElementsId APPENDED(!!) dict = {itemid: checksum} """ if self.compare: @@ -635,6 +631,7 @@ class LibrarySync(threading.Thread): by then calling itemtypes.() Input: + itemType: 'Movies', 'TVShows', ... self.updatelist """ # Some logging, just in case. @@ -672,7 +669,6 @@ class LibrarySync(threading.Thread): thread.start() threads.append(thread) self.logMsg("%s download threads spawned" % self.syncThreadNumber, 1) - self.logMsg("Queue populated", 1) # Spawn one more thread to process Metadata, once downloaded thread = ThreadedProcessMetadata(processMetadataQueue, itemType, @@ -783,17 +779,17 @@ class LibrarySync(threading.Thread): """ starttotal = datetime.now() plx = PlexAPI.PlexAPI() - # Download XML, not JSON + # Download XML, not JSON, because PMS JSON seems to be damaged headerOptions = {'Accept': 'application/xml'} plexItems = plx.GetAllPlexLeaves(viewId, headerOptions=headerOptions) itemMth = getattr(itemtypes, itemType) with itemMth() as method: - method.UpdateWatched(plexItems) + method.updateUserdata(plexItems) elapsedtotal = datetime.now() - starttotal self.logMsg("Syncing userdata for itemtype %s and viewid %s took " - "%s seconds" % (itemType, viewId, elapsedtotal), 0) + "%s seconds" % (itemType, viewId, elapsedtotal), 1) def musicvideos(self, embycursor, kodicursor, pdialog): # Get musicvideos from emby @@ -877,6 +873,7 @@ class LibrarySync(threading.Thread): self.allKodiElementsId.update(all_kodiepisodes) except ValueError: pass + # Close DB connections embyconn.close() ##### PROCESS TV Shows ##### diff --git a/resources/lib/playbackutils.py b/resources/lib/playbackutils.py index a7b231d0..0c8b3c30 100644 --- a/resources/lib/playbackutils.py +++ b/resources/lib/playbackutils.py @@ -47,7 +47,7 @@ class PlaybackUtils(): self.className = self.__class__.__name__ utils.logMsg("%s %s" % (self.addonName, self.className), msg, lvl) - def play(self, itemid, dbid=None): + def play(self, itemid, dbid=None, seektime=None): self.logMsg("Play called.", 1) @@ -89,8 +89,9 @@ class PlaybackUtils(): ############### RESUME POINT ################ - userdata = API.getUserData() - seektime = userdata['Resume'] + if seektime is None: + userdata = API.getUserData() + seektime = userdata['Resume'] # We need to ensure we add the intro and additional parts only once. # Otherwise we get a loop. diff --git a/resources/lib/plexbmchelper/subscribers.py b/resources/lib/plexbmchelper/subscribers.py index 56c7386d..56e55e9a 100644 --- a/resources/lib/plexbmchelper/subscribers.py +++ b/resources/lib/plexbmchelper/subscribers.py @@ -200,12 +200,17 @@ class Subscriber: printDebug("sending xml to subscriber %s: %s" % (self.tostr(), msg)) url = self.protocol + '://' + self.host + ':' + self.port \ + "/:/timeline" - response = self.download.downloadUrl(url, - postBody=msg, - type="POSTXML") + # Override some headers + headerOptions = { + 'Accept': '*/*' + } + response = self.download.downloadUrl( + url, + postBody=msg, + type="POSTXML", + headerOptions=headerOptions) # if not requests.post(self.host, self.port, "/:/timeline", msg, getPlexHeaders(), self.protocol): # subMgr.removeSubscriber(self.uuid) if response in [False, 401]: subMgr.removeSubscriber(self.uuid) - subMgr = SubscriptionManager()