krygal/samsung_multiroom

View on GitHub
samsung_multiroom/service/browser.py

Summary

Maintainability
A
0 mins
Test Coverage
A
90%
"""Abstract media browser and item."""
import abc


class Browser(metaclass=abc.ABCMeta):
    """
    Abstract media browser.

    Implementations should be immutable.
    """

    def __init__(self, path=None, items=None):
        """
        :param path: Path used to list items
        :param items: Items listed
        """
        self._path = path
        self._items = items.copy() if items else []

    @abc.abstractmethod
    def get_name(self):
        """
        :returns: Name of the browser
        """
        raise NotImplementedError()

    def get_path(self):
        """
        :returns: Path used to list items
        """
        return self._path

    @abc.abstractmethod
    def browse(self, path=None):
        """
        List items based on path.

        :param path: Forward slash separated string of folders e.g. /NAS/Music/By Folder/Air/Moon Safari/CD1
        :returns: Browser instance with listed items
        """
        raise NotImplementedError()

    def _merge_path(self, new_path):
        folders = path_to_folders(new_path)

        if folders[0] is not None:
            folders = path_to_folders(self.get_path()) + folders

        return folders_to_path(folders)

    def _get_items(self):
        return self._items

    def _get_initial_items(self, folders):
        """
        :returns: initial items to use, for root search return empty list
        """
        if folders[0] is None:
            return []

        return self._get_items()

    def _get_initial_depth(self, folders):
        """
        :returns: depth of the current browse, taking into account relative/absolute browsing
        """
        if folders[0] is None:
            return 0

        return len(path_to_folders(self._path))

    def __getitem__(self, i):
        return self._items[i]

    def __len__(self):
        return len(self._items)

    def __iter__(self):
        return iter(self._items)


class Item:
    """
    Immutable browser item.
    """

    def __init__(self, object_id, object_type, name, metadata=None):
        self._object_id = object_id
        self._object_type = object_type
        self._name = name
        self._metadata = metadata or {}

    @property
    def object_id(self):
        """
        :returns: Object id
        """
        return self._object_id

    @property
    def object_type(self):
        """
        :returns: Object type
        """
        return self._object_type

    @property
    def name(self):
        """
        :returns: Item name
        """
        return self._name

    @property
    def title(self):
        """
        :returns: Item title, equivallent of name
        """
        if 'title' in self._metadata:
            return self._metadata['title']

        return self.name

    @property
    def metadata(self):
        """
        :returns: Item metadata specific to item type
        """
        return self._metadata

    def __getattr__(self, name):
        """
        Look up name in metadata.

        :returns: Metadata value
        """
        if name in self._metadata:
            return self._metadata[name]

        return None


def path_to_folders(path):
    """
    Convert path to a standarised list of folder.

    If path is prepended with /, first element on the list will be special folder None denoting root.

    :param path: None or path string with folders '/' separated, e.g. /NAS/Music/By Folder/folder1/folder2
    :returns: List of folders e.g. [None, 'NAS', 'Music', 'By Folder', 'folder1', 'folder2']
    """
    if path in [None, '', '/']:
        path = '/'

    is_from_root = (path[0] == '/')

    path = path.strip(' /').split('/')
    path = [p for p in path if p != '']

    if is_from_root:
        path.insert(0, None)

    return path


def folders_to_path(folders):
    """
    Convert list of folders to a path.

    :param folders: List of folders, None denotes a root folder e.g. [None, 'NAS', 'Music', 'By Folder', 'folder1']
    :returns: path string e.g. /NAS/Music/By Folder/folder1
    """
    if not folders:
        return '/'

    if folders[0] is None:
        folders[0] = ''

    return '/'.join(folders) or '/'