diff --git a/plexapi/library.py b/plexapi/library.py index f61f077dd..b19159422 100644 --- a/plexapi/library.py +++ b/plexapi/library.py @@ -1,6 +1,9 @@ # -*- coding: utf-8 -*- + +import logging from plexapi import X_PLEX_CONTAINER_SIZE, log, utils from plexapi.compat import unquote +from plexapi.media import MediaTag, Genre, Role, Director from plexapi.exceptions import BadRequest, NotFound from plexapi.media import MediaTag @@ -152,6 +155,9 @@ def refresh(self): """ self.server.query('/library/sections/all/refresh') + def __len__(self): + return len(self.sections()) + class LibrarySection(object): """ Base class for a single library section. @@ -471,6 +477,42 @@ def searchPhotos(self, title, **kwargs): return [i for i in photos if i.title.lower() == title.lower()] +@utils.register_libtype +class Hub(object): + TYPE = 'Hub' + + def __init__(self, server, data, initpath): + self.server = server + self.initpath = initpath + self.type = data.attrib.get('type') + self.hubIdentifier = data.attrib.get('hubIdentifier') + self.title = data.attrib.get('title') + self._items = [] + self.size = utils.cast(int, data.attrib.get('title'), 0) + + if self.type == 'genre': + self._items = [Genre(self.server, elem) for elem in data] + elif self.type == 'director': + self._items = [Director(self.server, elem) for elem in data] + elif self.type == 'actor': + self._items = [Role(self.server, elem) for elem in data] + else: + for elem in data: + try: + self._items.append(utils.buildItem(self.server, elem, '/hubs')) + except Exception as e: + logging.exception('Failed %s to build %s' % (self.type, self.title)) + + def __repr__(self): + return '' % self.title.encode('utf8') + + def __len__(self): + return self.size + + def all(self): + return self._items + + @utils.register_libtype class FilterChoice(object): """ Represents a single filter choice. These objects are gathered when using filters diff --git a/plexapi/media.py b/plexapi/media.py index 79deb5c15..63218beb1 100644 --- a/plexapi/media.py +++ b/plexapi/media.py @@ -170,7 +170,7 @@ def __init__(self, server, data): self.tag = data.attrib.get('tag') def __repr__(self): - tag = self.tag.replace(' ','.')[0:20] + tag = self.tag.replace(' ', '.')[0:20].encode('utf-8') return '<%s:%s:%s>' % (self.__class__.__name__, self.id, tag) diff --git a/plexapi/server.py b/plexapi/server.py index 43ea27b0a..ee8cd3501 100644 --- a/plexapi/server.py +++ b/plexapi/server.py @@ -17,7 +17,7 @@ from plexapi import log, logfilter, utils from plexapi import audio, video, photo, playlist # noqa; required # why is this needed? from plexapi.client import PlexClient -from plexapi.compat import quote +from plexapi.compat import quote, urlencode from plexapi.exceptions import BadRequest, NotFound from plexapi.library import Library from plexapi.playlist import Playlist @@ -211,20 +211,55 @@ def query(self, path, method=None, headers=None, **kwargs): data = response.text.encode('utf8') return ElementTree.fromstring(data) if data else None - def search(self, query, mediatype=None): + def search(self, query, mediatype=None, limit=None, **kwargs): """Searching within a library section is much more powerful. Args: query (str): Search str mediatype (str, optional): Limit your search to a media type. + kwargs (dict): #TODO Returns: List """ - items = utils.listItems(self, '/search?query=%s' % quote(query)) + if query and not kwargs: + items = [] + for item in self.hubs(query, mediatype=mediatype, limit=limit): + items.extend(item.all()) + return items + + else: + items = utils.listItems(self, '/search?query=%s' % quote(query)) + if mediatype: + return [item for item in items if item.type == mediatype] + else: + return items + + def hubs(self, query, mediatype=None, limit=None): + """Searching within a library section is much more powerful. + + Args: + query (str): Search str + mediatype (str, optional): Limit your search to a media type. + limit (int) Default to None, limit your results to x results + + Returns: + List: of Hub + """ + p = {} + + if query: + p['query'] = query if mediatype: - return [item for item in items if item.type == mediatype] - return items + p['section'] = utils.SEARCHTYPES[mediatype] + if limit: + p['limit'] = limit + + u = '/hubs/search?%s' % urlencode(p) + + hubs = utils.listItems(self, u, bytag=True) + + return [i for i in hubs if i] def sessions(self): """List all active sessions.""" @@ -258,8 +293,6 @@ def transcodeImage(self, media, height, width, opacity=100, saturation=100): return self.url(transcode_url) - - class Account(object): """This is the locally cached MyPlex account information. The properties provided don't matchthe myplex.MyPlexAccount object very well. diff --git a/plexapi/utils.py b/plexapi/utils.py index ca1c735ce..7909e7b55 100644 --- a/plexapi/utils.py +++ b/plexapi/utils.py @@ -255,16 +255,19 @@ def cast(func, value): func (func): Calback function to used cast to type (int, bool, float, etc). value (any): value to be cast and returned. """ - if value not in [None, NA]: - if func == bool: - return bool(int(value)) - elif func in [int, float]: - try: - return func(value) - except ValueError: - return float('nan') - return func(value) - return value + if not value: + return + + if func in (int, float): + try: + return func(value) + except ValueError: + return float('nan') + + elif func == bool: + return bool(int(value)) + else: + raise TypeError('Cast only allows int, float and bool') def findKey(server, key):