1
1
# -*- coding: utf-8 -*-
2
2
import re
3
+ from typing import TYPE_CHECKING , Generic , Iterable , List , Optional , TypeVar , Union
3
4
import weakref
4
5
from functools import cached_property
5
6
from urllib .parse import urlencode
6
7
from xml .etree import ElementTree
8
+ from xml .etree .ElementTree import Element
7
9
8
10
from plexapi import CONFIG , X_PLEX_CONTAINER_SIZE , log , utils
9
11
from plexapi .exceptions import BadRequest , NotFound , UnknownType , Unsupported
10
12
13
+ if TYPE_CHECKING :
14
+ from plexapi .server import PlexServer
15
+
16
+ PlexObjectT = TypeVar ("PlexObjectT" , bound = 'PlexObject' )
17
+ MediaContainerT = TypeVar ("MediaContainerT" , bound = "MediaContainer" )
18
+
11
19
USER_DONT_RELOAD_FOR_KEYS = set ()
12
20
_DONT_RELOAD_FOR_KEYS = {'key' }
13
21
OPERATORS = {
@@ -253,8 +261,7 @@ def fetchItems(self, ekey, cls=None, container_start=None, container_size=None,
253
261
if maxresults is not None :
254
262
container_size = min (container_size , maxresults )
255
263
256
- results = []
257
- subresults = []
264
+ results = MediaContainer [cls ](self ._server , Element ('MediaContainer' ), initpath = ekey )
258
265
headers = {}
259
266
260
267
while True :
@@ -332,7 +339,7 @@ def findItems(self, data, cls=None, initpath=None, rtag=None, **kwargs):
332
339
if rtag :
333
340
data = next (utils .iterXMLBFS (data , rtag ), [])
334
341
# loop through all data elements to find matches
335
- items = []
342
+ items = MediaContainer [ cls ]( self . _server , data , initpath = initpath ) if data . tag == 'MediaContainer' else []
336
343
for elem in data :
337
344
if self ._checkAttrs (elem , ** kwargs ):
338
345
item = self ._buildItemOrNone (elem , cls , initpath )
@@ -1011,7 +1018,11 @@ def delete(self):
1011
1018
return self ._server .query (self .historyKey , method = self ._server ._session .delete )
1012
1019
1013
1020
1014
- class MediaContainer (PlexObject ):
1021
+ class MediaContainer (
1022
+ Generic [PlexObjectT ],
1023
+ List [PlexObjectT ],
1024
+ PlexObject ,
1025
+ ):
1015
1026
""" Represents a single MediaContainer.
1016
1027
1017
1028
Attributes:
@@ -1024,11 +1035,71 @@ class MediaContainer(PlexObject):
1024
1035
librarySectionUUID (str): :class:`~plexapi.library.LibrarySection` UUID.
1025
1036
mediaTagPrefix (str): "/system/bundle/media/flags/"
1026
1037
mediaTagVersion (int): Unknown
1038
+ offset (int): The offset of current results.
1027
1039
size (int): The number of items in the hub.
1040
+ totalSize (int): The total number of items for the query.
1028
1041
1029
1042
"""
1030
1043
TAG = 'MediaContainer'
1031
1044
1045
+ def __init__ (
1046
+ self ,
1047
+ server : "PlexServer" ,
1048
+ data : Element ,
1049
+ * args : PlexObjectT ,
1050
+ initpath : Optional [str ] = None ,
1051
+ parent : Optional [PlexObject ] = None ,
1052
+ ) -> None :
1053
+ # super calls Generic.__init__ which calls list.__init__ eventually
1054
+ super ().__init__ (* args )
1055
+ PlexObject .__init__ (self , server , data , initpath , parent )
1056
+
1057
+ def extend (
1058
+ self : MediaContainerT ,
1059
+ __iterable : Union [Iterable [PlexObjectT ], MediaContainerT ],
1060
+ ) -> None :
1061
+ curr_size = self .size if self .size is not None else len (self )
1062
+ super ().extend (__iterable )
1063
+ # update size, totalSize, and offset
1064
+ if not isinstance (__iterable , MediaContainer ):
1065
+ return
1066
+
1067
+ # prefer the totalSize of the new iterable even if it is smaller
1068
+ self .totalSize = (
1069
+ __iterable .totalSize
1070
+ if __iterable .totalSize is not None
1071
+ else self .totalSize
1072
+ ) # ideally both should be equal
1073
+
1074
+ # the size of the new iterable is added to the current size
1075
+ self .size = curr_size + (
1076
+ __iterable .size if __iterable .size is not None else len (__iterable )
1077
+ )
1078
+
1079
+ # the offset is the minimum of the two, prefering older values
1080
+ if self .offset is not None and __iterable .offset is not None :
1081
+ self .offset = min (self .offset , __iterable .offset )
1082
+ else :
1083
+ self .offset = (
1084
+ self .offset if self .offset is not None else __iterable .offset
1085
+ )
1086
+
1087
+ # for all other attributes, overwrite with the new iterable's values if previously None
1088
+ for key in (
1089
+ "allowSync" ,
1090
+ "augmentationKey" ,
1091
+ "identifier" ,
1092
+ "librarySectionID" ,
1093
+ "librarySectionTitle" ,
1094
+ "librarySectionUUID" ,
1095
+ "mediaTagPrefix" ,
1096
+ "mediaTagVersion" ,
1097
+ ):
1098
+ if (not hasattr (self , key )) or (getattr (self , key ) is None ):
1099
+ if not hasattr (__iterable , key ):
1100
+ continue
1101
+ setattr (self , key , getattr (__iterable , key ))
1102
+
1032
1103
def _loadData (self , data ):
1033
1104
self ._data = data
1034
1105
self .allowSync = utils .cast (int , data .attrib .get ('allowSync' ))
@@ -1039,4 +1110,6 @@ def _loadData(self, data):
1039
1110
self .librarySectionUUID = data .attrib .get ('librarySectionUUID' )
1040
1111
self .mediaTagPrefix = data .attrib .get ('mediaTagPrefix' )
1041
1112
self .mediaTagVersion = data .attrib .get ('mediaTagVersion' )
1113
+ self .offset = utils .cast (int , data .attrib .get ("offset" ))
1042
1114
self .size = utils .cast (int , data .attrib .get ('size' ))
1115
+ self .totalSize = utils .cast (int , data .attrib .get ("totalSize" ))
0 commit comments