Limit max number of items requested from PMS
Otherwise, slow NAS PMS may lead to timeouts - and thus empty libraries
This commit is contained in:
parent
67f681cac3
commit
d8378584a2
5 changed files with 105 additions and 52 deletions
|
@ -277,7 +277,7 @@
|
|||
<string id="30512">Force artwork caching</string>
|
||||
<string id="30513">Limit artwork cache threads (recommended for rpi)</string>
|
||||
<string id="30514">Enable fast startup (requires server plugin)</string>
|
||||
<string id="30515">Maximum items to request from the server at once</string>
|
||||
<string id="30515">Maximum items to request from the server at once (restart!)</string>
|
||||
<string id="30516">Playback</string>
|
||||
<string id="30517">[COLOR yellow]Enter network credentials[/COLOR]</string>
|
||||
<string id="30518">Enable Plex Trailers (Plexpass is needed)</string>
|
||||
|
|
|
@ -252,6 +252,8 @@
|
|||
<string id="30249">Unterdrücke Server-Verbindungsmeldungen beim Starten</string>
|
||||
<string id="30250">Benutze lokale Pfade anstelle von Addon-Umleitungen beim Abspielen</string>
|
||||
|
||||
<string id="30515">Max. Anzahl gleichzeitig nachgefragter PMS Einträge (Neustart!)</string>
|
||||
|
||||
<string id="33010">Plex Media Server Authorisierung ist zu häufig fehlgeschlagen. In den Einstellungen können die Anzahl erfolgloser Versuche zurückgesetzt werden.</string>
|
||||
|
||||
<!-- Default views -->
|
||||
|
|
|
@ -3,6 +3,7 @@ from urllib import urlencode
|
|||
from ast import literal_eval
|
||||
from urlparse import urlparse, parse_qs
|
||||
import re
|
||||
from copy import deepcopy
|
||||
|
||||
from xbmcaddon import Addon
|
||||
|
||||
|
@ -184,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)
|
||||
|
@ -192,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.
|
||||
|
@ -213,26 +207,79 @@ 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
|
||||
logMsg(title, 'Downloading allLeaves of %s in chunks of %s'
|
||||
% (url, str(containerSize)), 1)
|
||||
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
|
||||
logMsg(title, 'Done downloading chunks', 1)
|
||||
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)
|
||||
|
@ -243,6 +290,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.
|
||||
|
||||
|
@ -259,19 +307,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):
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
<setting id="serverSync" type="bool" label="30514" default="true" visible="false"/><!-- Enable fast startup (requires server plugin) -->
|
||||
<setting id="dbSyncIndicator" label="30507" type="bool" default="true" />
|
||||
<setting type="sep" /><!-- show syncing progress -->
|
||||
<setting id="limitindex" type="number" label="30515" default="200" option="int" visible="false"/><!-- Maximum items to request from the server at once -->
|
||||
<setting id="limitindex" type="number" label="30515" default="200" option="int" /><!-- Maximum items to request from the server at once -->
|
||||
<setting id="enableTextureCache" label="30512" type="bool" default="true" /> <!-- Force Artwork Caching -->
|
||||
<setting id="imageCacheLimit" type="enum" label="30513" values="Disabled|5|10|15|20|25" default="5" visible="eq(-1,true)" subsetting="true" /> <!-- Limit artwork cache threads -->
|
||||
<setting id="syncThreadNumber" type="slider" label="39003" default="5" option="int" range="1,1,20"/>
|
||||
|
|
Loading…
Reference in a new issue