def connect(self, timeout=None): """ Alias of reload as any subsequent requests to this client will be made directly to the device even if the object attributes were initially populated from a PlexServer. """ if not self.key: raise Unsupported('Cannot reload an object not built from a URL.') self._initpath = self.key data = self.query(self.key, timeout=timeout) if not data: raise NotFound("Client not found at %s" % self._baseurl) if self._clientIdentifier: client = next( (x for x in data if x.attrib.get("machineIdentifier") == self._clientIdentifier ), None, ) if client is None: raise NotFound("Client with identifier %s not found at %s" % (self._clientIdentifier, self._baseurl)) else: client = data[0] self._loadData(client) return self
def fixMatch(self, searchResult=None, auto=False, agent=None): """ Use match result to update show metadata. Parameters: auto (bool): True uses first match from matches False allows user to provide the match searchResult (:class:`~plexapi.media.SearchResult`): Search result from ~plexapi.base.matches() agent (str): Agent name to be used (imdb, thetvdb, themoviedb, etc.) """ key = '/library/metadata/%s/match' % self.ratingKey if auto: autoMatch = self.matches(agent=agent) if autoMatch: searchResult = autoMatch[0] else: raise NotFound('No matches found using this agent: (%s:%s)' % (agent, autoMatch)) elif not searchResult: raise NotFound('fixMatch() requires either auto=True or ' 'searchResult=:class:`~plexapi.media.SearchResult`.') params = {'guid': searchResult.guid, 'name': searchResult.name} data = key + '?' + urlencode(params) self._server.query(data, method=self._server._session.put)
def _connect(self): """Used for fetching the attributes for __init__.""" try: return self.query('/') except Exception as err: log.error('%s: %s', self.baseurl, err) raise NotFound('No server found at: %s' % self.baseurl)
def _connect(self): try: return self.query('/') except Exception as err: log.error('%s:%s: %s', self.address, self.port, err) raise NotFound('No server found at: %s:%s' % (self.address, self.port))
def fetchItem(self, ekey, cls=None, **kwargs): """ Load the specified key to find and build the first item with the specified tag and attrs. If no tag or attrs are specified then the first item in the result set is returned. Parameters: ekey (str or int): Path in Plex to fetch items from. If an int is passed in, the key will be translated to /library/metadata/<key>. This allows fetching an item only knowing its key-id. cls (:class:`~plexapi.base.PlexObject`): If you know the class of the items to be fetched, passing this in will help the parser ensure it only returns those items. By default we convert the xml elements with the best guess PlexObjects based on tag and type attrs. etag (str): Only fetch items with the specified tag. **kwargs (dict): Optionally add XML attribute to filter the items. See :func:`~plexapi.base.PlexObject.fetchItems` for more details on how this is used. """ if ekey is None: raise BadRequest('ekey was not provided') if isinstance(ekey, int): ekey = '/library/metadata/%s' % ekey data = self._server.query(ekey) librarySectionID = utils.cast(int, data.attrib.get('librarySectionID')) for elem in data: if self._checkAttrs(elem, **kwargs): item = self._buildItem(elem, cls, ekey) if librarySectionID: item.librarySectionID = librarySectionID return item clsname = cls.__name__ if cls else 'None' raise NotFound('Unable to find elem: cls=%s, attrs=%s' % (clsname, kwargs))
def connect(self, ssl=None): # Only check non-local connections unless we own the resource connections = sorted(self.connections, key=lambda c: c.local, reverse=True) if not self.owned: connections = [c for c in connections if c.local is False] # Try connecting to all known resource connections in parellel, but # only return the first server (in order) that provides a response. threads, results = [], [] for testssl, attr in self.SSLTESTS: if ssl in [None, testssl]: for i in range(len(connections)): uri = getattr(connections[i], attr) args = (uri, results, len(results)) results.append(None) threads.append(Thread(target=self._connect, args=args)) threads[-1].start() for thread in threads: thread.join() # At this point we have a list of result tuples containing (uri, PlexServer) # or (uri, None) in the case a connection could not be established. for uri, result in results: log.info('Testing connection: %s %s', uri, 'OK' if result else 'ERR') results = list(filter(None, [r[1] for r in results if r])) if not results: raise NotFound('Unable to connect to resource: %s' % self.name) log.info('Connecting to server: %s', results[0]) return results[0]
def query(self, key, method=None, headers=None, timeout=None, **kwargs): """ Main method used to handle HTTPS requests to the Plex server. This method helps by encoding the response to utf-8 and parsing the returned XML into and ElementTree object. Returns None if no data exists in the response. """ url = self.url(key) method = method or self._session.get timeout = timeout or TIMEOUT log.debug('%s %s', method.__name__.upper(), url) headers = self._headers(**headers or {}) response = method(url, headers=headers, timeout=timeout, **kwargs) if response.status_code not in (200, 201, 204): codename = codes.get(response.status_code)[0] errtext = response.text.replace('\n', ' ') message = '(%s) %s; %s %s' % (response.status_code, codename, response.url, errtext) if response.status_code == 401: raise Unauthorized(message) elif response.status_code == 404: raise NotFound(message) else: raise BadRequest(message) data = response.text.encode('utf8') return ElementTree.fromstring(data) if data.strip() else None
def connect(self, ssl=None): """Connect to the first server. Args: ssl (None, optional): Use SSL? Returns: TYPE: Plexserver Raises: NotFound: Unable to connect to resource: name """ # Try connecting to all known resource connections in parellel, but # only return the first server (in order) that provides a response. listargs = [[c] for c in self.connections] results = utils.threaded(self._connect, listargs) # At this point we have a list of result tuples containing (url, token, PlexServer) # or (url, token, None) in the case a connection could not be # established. for url, token, result in results: okerr = 'OK' if result else 'ERR' log.info('Testing device connection: %s?X-Plex-Token=%s %s', url, token, okerr) results = [r[2] for r in results if r and r[2] is not None] if not results: raise NotFound('Unable to connect to resource: %s' % self.name) log.info('Connecting to server: %s?X-Plex-Token=%s', results[0].baseurl, results[0].token) return results[0]
def _getPlaylistItemID(self, item): """ Match an item to a playlist item and return the item playlistItemID. """ for _item in self.items(): if _item.ratingKey == item.ratingKey: return _item.playlistItemID raise NotFound('Item with title "%s" not found in the playlist' % item.title)
def episode(self, title=None, season=None, episode=None): """ Find a episode using a title or season and episode. Parameters: title (str): Title of the episode to return season (int): Season number (default:None; required if title not specified). episode (int): Episode number (default:None; required if title not specified). Raises: :class:`plexapi.exceptions.BadRequest`: If season and episode is missing. :class:`plexapi.exceptions.NotFound`: If the episode is missing. """ if title: key = '/library/metadata/%s/allLeaves' % self.ratingKey return self.fetchItem(key, title__iexact=title) elif season is not None and episode: results = [ i for i in self.episodes() if i.seasonNumber == season and i.index == episode ] if results: return results[0] raise NotFound('Couldnt find %s S%s E%s' % (self.title, season, episode)) raise BadRequest( 'Missing argument: title or season and episode are required')
def device(self): """ Returns the :class:`~plexapi.server.SystemDevice` associated with the bandwidth data. """ devices = self._server.systemDevices() try: return next(device for device in devices if device.id == self.deviceID) except StopIteration: raise NotFound('Unknown device for this bandwidth data: deviceID=%s' % self.deviceID)
def client(self, name): for elem in self.query('/clients'): if elem.attrib.get('name').lower() == name.lower(): baseurl = 'http://%s:%s' % (elem.attrib['address'], elem.attrib['port']) return PlexClient(baseurl, server=self, data=elem) raise NotFound('Unknown client name: %s' % name)
def connect(self): """ Returns a new :class:`~plexapi.client.PlexClient` object. Sometimes there is more than one address specified for a server or client. After trying to connect to all available addresses for this resource and assuming at least one connection was successful, the PlexClient object is built and returned. Raises: :class:`~plexapi.exceptions.NotFound`: When unable to connect to any addresses for this device. """ # Try connecting to all known resource connections in parellel, but # only return the first server (in order) that provides a response. listargs = [[c] for c in self.connections] results = utils.threaded(self._connect, listargs) # At this point we have a list of result tuples containing (url, token, PlexServer) # or (url, token, None) in the case a connection could not be # established. for url, token, result in results: okerr = 'OK' if result else 'ERR' log.info('Testing device connection: %s?X-Plex-Token=%s %s', url, token, okerr) results = [r[2] for r in results if r and r[2] is not None] if not results: raise NotFound('Unable to connect to resource: %s' % self.name) log.info('Connecting to server: %s?X-Plex-Token=%s', results[0].baseurl, results[0].token) return results[0]
def account(self): """ Returns the :class:`~plexapi.server.SystemAccount` associated with the bandwidth data. """ accounts = self._server.systemAccounts() try: return next(account for account in accounts if account.id == self.accountID) except StopIteration: raise NotFound('Unknown account for this bandwidth data: accountID=%s' % self.accountID)
def searchType(libtype): libtype = str(libtype) if libtype in [str(v) for v in SEARCHTYPES.values()]: return libtype if SEARCHTYPES.get(libtype) is not None: return SEARCHTYPES[libtype] raise NotFound('Unknown libtype: %s' % libtype)
def connect(self): try: data = self.query('/resources')[0] self._loadData(data) except Exception as err: log.error('%s: %s', self.baseurl, err) raise NotFound('No client found at: %s' % self.baseurl)
def _findItem(items, value, attrs=None): attrs = attrs or ['name'] for item in items: for attr in attrs: if value.lower() == getattr(item, attr).lower(): return item raise NotFound('Unable to find item %s' % value)
def getServer(self, nameOrSourceTitle): search = nameOrSourceTitle.lower() for server in self.servers(): if server.name and search == server.name.lower(): return server if server.sourceTitle and search == server.sourceTitle.lower(): return server raise NotFound('Unable to find server: %s' % nameOrSourceTitle)
def connect(self): # Create a list of addresses to try connecting to. # TODO: setup local addresses before external devices = MyPlexDevice.fetchDevices(self.accessToken) devices = filter( lambda x: x.clientIdentifier == self.machineIdentifier, devices) addresses = [] if len(devices) == 1: addresses += devices[0].connections else: addresses.append(Connection(self.address, self.port)) if self.owned: for local in self.localAddresses: addresses.append(Connection(local, self.port)) # Attempt to connect to all known addresses in parellel to save time, but # only return the first server (in order) that provides a response. threads = [None] * len(addresses) results = [None] * len(addresses) for i in range(len(addresses)): args = (addresses[i], results, i) threads[i] = Thread(target=self._connect, args=args) threads[i].start() for thread in threads: thread.join() results = filter(None, results) if results: return results[0] raise NotFound('Unable to connect to server: %s' % (self.name))
def getDevice(self, nameOrClientIdentifier): search = nameOrClientIdentifier.lower() for device in self.devices(): device_name = device.name.lower() device_cid = device.clientIdentifier.lower() if search in (device_name, device_cid): return device raise NotFound('Unable to find device: %s' % nameOrClientIdentifier)
def server(self): server = list( filter(lambda x: x.machineIdentifier == self.machineIdentifier, self._servers)) if 0 == len(server): raise NotFound('Unable to find server with uuid %s' % self.machineIdentifier) return server[0]
def find_key(server, key): path = '/library/metadata/{0}'.format(key) try: # Video seems to be the first sub element elem = server.query(path)[0] return build_item(server, elem, path) except: raise NotFound('Unable to find key: %s' % key)
def reload(self): """ Reload attribute values from Plex XML response. """ try: data = self.query('/') self._loadData(data) except Exception as err: log.error('%s: %s', self.baseurl, err) raise NotFound('No server found at: %s' % self.baseurl)
def getServer(self, nameOrSourceTitle): for server in self.servers(): if nameOrSourceTitle.lower() in [ server.name.lower(), server.sourceTitle.lower() ]: return server raise NotFound('Unable to find server: %s' % nameOrSourceTitle)
def fetchItem(self, ekey, cls=None, **kwargs): """ Load the specified key to find and build the first item with the specified tag and attrs. If no tag or attrs are specified then the first item in the result set is returned. Parameters: ekey (str or int): Path in Plex to fetch items from. If an int is passed in, the key will be translated to /library/metadata/<key>. This allows fetching an item only knowing its key-id. cls (:class:`~plexapi.base.PlexObject`): If you know the class of the items to be fetched, passing this in will help the parser ensure it only returns those items. By default we convert the xml elements with the best guess PlexObjects based on tag and type attrs. etag (str): Only fetch items with the specified tag. **kwargs (dict): Optionally add attribute filters on the items to fetch. For example, passing in viewCount=0 will only return matching items. Filtering is done before the Python objects are built to help keep things speedy. Note: Because some attribute names are already used as arguments to this function, such as 'tag', you may still reference the attr tag byappending an underscore. For example, passing in _tag='foobar' will return all items where tag='foobar'. Also Note: Case very much matters when specifying kwargs -- Optionally, operators can be specified by append it to the end of the attribute name for more complex lookups. For example, passing in viewCount__gte=0 will return all items where viewCount >= 0. Available operations include: * __contains: Value contains specified arg. * __endswith: Value ends with specified arg. * __exact: Value matches specified arg. * __exists (bool): Value is or is not present in the attrs. * __gt: Value is greater than specified arg. * __gte: Value is greater than or equal to specified arg. * __icontains: Case insensative value contains specified arg. * __iendswith: Case insensative value ends with specified arg. * __iexact: Case insensative value matches specified arg. * __in: Value is in a specified list or tuple. * __iregex: Case insensative value matches the specified regular expression. * __istartswith: Case insensative value starts with specified arg. * __lt: Value is less than specified arg. * __lte: Value is less than or equal to specified arg. * __regex: Value matches the specified regular expression. * __startswith: Value starts with specified arg. """ if ekey is None: raise BadRequest('ekey was not provided') if isinstance(ekey, int): ekey = '/library/metadata/%s' % ekey resp = self._server.query(ekey) for elem in resp: if self._checkAttrs(elem, **kwargs): videoItem = self._buildItem(elem, cls, ekey) videoItem.librarySectionID = resp.attrib['librarySectionID'] return videoItem clsname = cls.__name__ if cls else 'None' raise NotFound('Unable to find elem: cls=%s, attrs=%s' % (clsname, kwargs))
def editAdvanced(self, **kwargs): """ Edit a Plex object's advanced settings. """ data = {} key = '%s/prefs?' % self.key preferences = {pref.id: pref for pref in self.preferences() if pref.enumValues} for settingID, value in kwargs.items(): try: pref = preferences[settingID] except KeyError: raise NotFound('%s not found in %s' % (value, list(preferences.keys()))) enumValues = pref.enumValues if enumValues.get(value, enumValues.get(str(value))): data[settingID] = value else: raise NotFound('%s not found in %s' % (value, list(enumValues))) url = key + urlencode(data) self._server.query(url, method=self._server._session.put)
def _findItem(items, value, attrs=None): """ This will return the first item in the list of items where value is found in any of the specified attributes. """ attrs = attrs or ['name'] for item in items: for attr in attrs: if value.lower() == getattr(item, attr).lower(): return item raise NotFound('Unable to find item %s' % value)
def resource(self, name): """ Returns the :class:`~plexapi.myplex.MyPlexResource` that matches the name specified. Parameters: name (str): Name to match against. """ for resource in self.resources(): if resource.name.lower() == name.lower(): return resource raise NotFound('Unable to find resource %s' % name)
def getAgentIdentifier(section, agent): """ Return the full agent identifier from a short identifier, name, or confirm full identifier. """ agents = [] for ag in section.agents(): identifiers = [ag.identifier, ag.shortIdentifier, ag.name] if agent in identifiers: return ag.identifier agents += identifiers raise NotFound('Could not find "%s" in agents list (%s)' % (agent, ', '.join(agents)))
def section(self, title=None): """ Returns the :class:`~plexapi.library.LibrarySection` that matches the specified title. Parameters: title (str): Title of the section to return. """ for section in self.sections(): if section.title.lower() == title.lower(): return section raise NotFound('Invalid library section: %s' % title)