Esempio n. 1
0
    def as_list(self):
        """
        Get a list of the currently available playlists.

        Returns a list of :class:`~mopidy.models.Ref` objects referring to the
        playlists. In other words, no information about the playlists' content
        is given.

        :rtype: list of :class:`mopidy.models.Ref`

        .. versionadded:: 1.0
        """
        futures = {
            backend: backend.playlists.as_list()
            for backend in set(self.backends.with_playlists.values())
        }

        results = []
        for b, future in futures.items():
            try:
                with _backend_error_handling(b, reraise=NotImplementedError):
                    playlists = future.get()
                    if playlists is not None:
                        validation.check_instances(playlists, Ref)
                        results.extend(playlists)
            except NotImplementedError:
                backend_name = b.actor_ref.actor_class.__name__
                logger.warning(
                    "%s does not implement playlists.as_list(). "
                    "Please upgrade it.",
                    backend_name,
                )

        return results
Esempio n. 2
0
    def as_list(self):
        """
        Get a list of the currently available playlists.

        Returns a list of :class:`~mopidy.models.Ref` objects referring to the
        playlists. In other words, no information about the playlists' content
        is given.

        :rtype: list of :class:`mopidy.models.Ref`

        .. versionadded:: 1.0
        """
        futures = {
            backend: backend.playlists.as_list()
            for backend in set(self.backends.with_playlists.values())}

        results = []
        for b, future in futures.items():
            try:
                with _backend_error_handling(b, reraise=NotImplementedError):
                    playlists = future.get()
                    if playlists is not None:
                        validation.check_instances(playlists, Ref)
                        results.extend(playlists)
            except NotImplementedError:
                backend_name = b.actor_ref.actor_class.__name__
                logger.warning(
                    '%s does not implement playlists.as_list(). '
                    'Please upgrade it.', backend_name)

        return results
Esempio n. 3
0
    def get_distinct(self, field, query=None):
        """
        List distinct values for a given field from the library.

        This has mainly been added to support the list commands the MPD
        protocol supports in a more sane fashion. Other frontends are not
        recommended to use this method.

        :param string field: One of ``track``, ``artist``, ``albumartist``,
            ``album``, ``composer``, ``performer``, ``date`` or ``genre``.
        :param dict query: Query to use for limiting results, see
            :meth:`search` for details about the query format.
        :rtype: set of values corresponding to the requested field type.

        .. versionadded:: 1.0
        """
        validation.check_choice(field, validation.DISTINCT_FIELDS)
        query is None or validation.check_query(query)  # TODO: normalize?

        result = set()
        futures = {b: b.library.get_distinct(field, query) for b in self.backends.with_library.values()}
        for backend, future in futures.items():
            with _backend_error_handling(backend):
                values = future.get()
                if values is not None:
                    validation.check_instances(values, compat.text_type)
                    result.update(values)
        return result
Esempio n. 4
0
    def get_distinct(self, field, query=None):
        """
        List distinct values for a given field from the library.

        This has mainly been added to support the list commands the MPD
        protocol supports in a more sane fashion. Other frontends are not
        recommended to use this method.

        :param string field: One of ``track``, ``artist``, ``albumartist``,
            ``album``, ``composer``, ``performer``, ``date`` or ``genre``.
        :param dict query: Query to use for limiting results, see
            :meth:`search` for details about the query format.
        :rtype: set of values corresponding to the requested field type.

        .. versionadded:: 1.0
        """
        validation.check_choice(field, validation.DISTINCT_FIELDS)
        query is None or validation.check_query(query)  # TODO: normalize?

        result = set()
        futures = {
            b: b.library.get_distinct(field, query)
            for b in self.backends.with_library.values()
        }
        for backend, future in futures.items():
            with _backend_error_handling(backend):
                values = future.get()
                if values is not None:
                    validation.check_instances(values, str)
                    result.update(values)
        return result
Esempio n. 5
0
    def lookup(self, uris):
        """
        Lookup the given URIs.

        If the URI expands to multiple tracks, the returned list will contain
        them all.

        :param uris: track URIs
        :type uris: list of string
        :rtype: {uri: list of :class:`mopidy.models.Track`}
        """
        validation.check_uris(uris)

        futures = {}
        results = {u: [] for u in uris}

        # TODO: lookup(uris) to backend APIs
        for backend, backend_uris in self._get_backends_to_uris(uris).items():
            if backend_uris:
                for u in backend_uris:
                    futures[(backend, u)] = backend.library.lookup(u)

        for (backend, u), future in futures.items():
            with _backend_error_handling(backend):
                result = future.get()
                if result is not None:
                    validation.check_instances(result, models.Track)
                    # TODO Consider making Track.uri field mandatory, and
                    # then remove this filtering of tracks without URIs.
                    results[u] = [r for r in result if r.uri]

        return results
Esempio n. 6
0
def test_check_instances_with_invalid_values():
    with raises(exceptions.ValidationError):
        validation.check_instances("abc", str)
    with raises(exceptions.ValidationError):
        validation.check_instances(["abc", 123], str)
    with raises(exceptions.ValidationError):
        validation.check_instances(None, str)
    with raises(exceptions.ValidationError):
        validation.check_instances([None], str)
    with raises(exceptions.ValidationError):
        validation.check_instances(iter(["abc"]), str)
Esempio n. 7
0
def test_check_instances_with_invalid_values():
    with raises(exceptions.ValidationError):
        validation.check_instances('abc', compat.string_types)
    with raises(exceptions.ValidationError):
        validation.check_instances(['abc', 123], compat.string_types)
    with raises(exceptions.ValidationError):
        validation.check_instances(None, compat.string_types)
    with raises(exceptions.ValidationError):
        validation.check_instances([None], compat.string_types)
    with raises(exceptions.ValidationError):
        validation.check_instances(iter(['abc']), compat.string_types)
Esempio n. 8
0
def test_check_instances_with_invalid_values():
    with raises(exceptions.ValidationError):
        validation.check_instances('abc', compat.string_types)
    with raises(exceptions.ValidationError):
        validation.check_instances(['abc', 123], compat.string_types)
    with raises(exceptions.ValidationError):
        validation.check_instances(None, compat.string_types)
    with raises(exceptions.ValidationError):
        validation.check_instances([None], compat.string_types)
    with raises(exceptions.ValidationError):
        validation.check_instances(iter(['abc']), compat.string_types)
Esempio n. 9
0
    def lookup(self, uri=None, uris=None):
        """
        Lookup the given URIs.

        If the URI expands to multiple tracks, the returned list will contain
        them all.

        :param uri: track URI
        :type uri: string or :class:`None`
        :param uris: track URIs
        :type uris: list of string or :class:`None`
        :rtype: list of :class:`mopidy.models.Track` if uri was set or
            {uri: list of :class:`mopidy.models.Track`} if uris was set.

        .. versionadded:: 1.0
            The ``uris`` argument.

        .. deprecated:: 1.0
            The ``uri`` argument. Use ``uris`` instead.
        """
        if sum(o is not None for o in [uri, uris]) != 1:
            raise ValueError('Exactly one of "uri" or "uris" must be set')

        uris is None or validation.check_uris(uris)
        uri is None or validation.check_uri(uri)

        if uri:
            deprecation.warn('core.library.lookup:uri_arg')

        if uri is not None:
            uris = [uri]

        futures = {}
        results = {u: [] for u in uris}

        # TODO: lookup(uris) to backend APIs
        for backend, backend_uris in self._get_backends_to_uris(uris).items():
            if backend_uris:
                for u in backend_uris:
                    futures[(backend, u)] = backend.library.lookup(u)

        for (backend, u), future in futures.items():
            with _backend_error_handling(backend):
                result = future.get()
                if result is not None:
                    validation.check_instances(result, models.Track)
                    # TODO Consider making Track.uri field mandatory, and
                    # then remove this filtering of tracks without URIs.
                    results[u] = [r for r in result if r.uri]

        if uri:
            return results[uri]
        return results
Esempio n. 10
0
    def lookup(self, uri=None, uris=None):
        """
        Lookup the given URIs.

        If the URI expands to multiple tracks, the returned list will contain
        them all.

        :param uri: track URI
        :type uri: string or :class:`None`
        :param uris: track URIs
        :type uris: list of string or :class:`None`
        :rtype: list of :class:`mopidy.models.Track` if uri was set or
            {uri: list of :class:`mopidy.models.Track`} if uris was set.

        .. versionadded:: 1.0
            The ``uris`` argument.

        .. deprecated:: 1.0
            The ``uri`` argument. Use ``uris`` instead.
        """
        if sum(o is not None for o in [uri, uris]) != 1:
            raise ValueError('Exactly one of "uri" or "uris" must be set')

        uris is None or validation.check_uris(uris)
        uri is None or validation.check_uri(uri)

        if uri:
            deprecation.warn('core.library.lookup:uri_arg')

        if uri is not None:
            uris = [uri]

        futures = {}
        results = {u: [] for u in uris}

        # TODO: lookup(uris) to backend APIs
        for backend, backend_uris in self._get_backends_to_uris(uris).items():
            if backend_uris:
                for u in backend_uris:
                    futures[(backend, u)] = backend.library.lookup(u)

        for (backend, u), future in futures.items():
            with _backend_error_handling(backend):
                result = future.get()
                if result is not None:
                    validation.check_instances(result, models.Track)
                    # TODO Consider making Track.uri field mandatory, and
                    # then remove this filtering of tracks without URIs.
                    results[u] = [r for r in result if r.uri]

        if uri:
            return results[uri]
        return results
Esempio n. 11
0
    def _browse(self, uri):
        scheme = urllib.parse.urlparse(uri).scheme
        backend = self.backends.with_library_browse.get(scheme)

        if not backend:
            return []

        with _backend_error_handling(backend):
            result = backend.library.browse(uri).get()
            validation.check_instances(result, models.Ref)
            return result

        return []
Esempio n. 12
0
    def _browse(self, uri):
        scheme = urlparse.urlparse(uri).scheme
        backend = self.backends.with_library_browse.get(scheme)

        if not backend:
            return []

        with _backend_error_handling(backend):
            result = backend.library.browse(uri).get()
            validation.check_instances(result, models.Ref)
            return result

        return []
Esempio n. 13
0
    def get_items(self, uri):
        """
        Get the items in a playlist specified by ``uri``.

        Returns a list of :class:`~mopidy.models.Ref` objects referring to the
        playlist's items.

        If a playlist with the given ``uri`` doesn't exist, it returns
        :class:`None`.

        :rtype: list of :class:`mopidy.models.Ref`, or :class:`None`

        .. versionadded:: 1.0
        """
        validation.check_uri(uri)

        uri_scheme = urllib.parse.urlparse(uri).scheme
        backend = self.backends.with_playlists.get(uri_scheme, None)

        if not backend:
            return None

        with _backend_error_handling(backend):
            items = backend.playlists.get_items(uri).get()
            items is None or validation.check_instances(items, Ref)
            return items

        return None
Esempio n. 14
0
    def get_items(self, uri):
        """
        Get the items in a playlist specified by ``uri``.

        Returns a list of :class:`~mopidy.models.Ref` objects referring to the
        playlist's items.

        If a playlist with the given ``uri`` doesn't exist, it returns
        :class:`None`.

        :rtype: list of :class:`mopidy.models.Ref`, or :class:`None`

        .. versionadded:: 1.0
        """
        validation.check_uri(uri)

        uri_scheme = urllib.parse.urlparse(uri).scheme
        backend = self.backends.with_playlists.get(uri_scheme, None)

        if not backend:
            return None

        with _backend_error_handling(backend):
            items = backend.playlists.get_items(uri).get()
            items is None or validation.check_instances(items, Ref)
            return items

        return None
Esempio n. 15
0
    def filter(self, criteria=None, **kwargs):
        """
        Filter the tracklist by the given criterias.

        A criteria consists of a model field to check and a list of values to
        compare it against. If the model field matches one of the values, it
        may be returned.

        Only tracks that matches all the given criterias are returned.

        Examples::

            # Returns tracks with TLIDs 1, 2, 3, or 4 (tracklist ID)
            filter({'tlid': [1, 2, 3, 4]})

            # Returns track with URIs 'xyz' or 'abc'
            filter({'uri': ['xyz', 'abc']})

            # Returns track with a matching TLIDs (1, 3 or 6) and a
            # matching URI ('xyz' or 'abc')
            filter({'tlid': [1, 3, 6], 'uri': ['xyz', 'abc']})

        :param criteria: on or more criteria to match by
        :type criteria: dict, of (string, list) pairs
        :rtype: list of :class:`mopidy.models.TlTrack`

        .. deprecated:: 1.1
            Providing the criteria via ``kwargs``.
        """
        if kwargs:
            deprecation.warn('core.tracklist.filter:kwargs_criteria')

        criteria = criteria or kwargs
        tlids = criteria.pop('tlid', [])
        validation.check_query(criteria, validation.TRACKLIST_FIELDS)
        validation.check_instances(tlids, int)

        matches = self._tl_tracks
        for (key, values) in criteria.items():
            matches = [
                ct for ct in matches if getattr(ct.track, key) in values
            ]
        if tlids:
            matches = [ct for ct in matches if ct.tlid in tlids]
        return matches
Esempio n. 16
0
    def filter(self, criteria=None, **kwargs):
        """
        Filter the tracklist by the given criterias.

        A criteria consists of a model field to check and a list of values to
        compare it against. If the model field matches one of the values, it
        may be returned.

        Only tracks that matches all the given criterias are returned.

        Examples::

            # Returns tracks with TLIDs 1, 2, 3, or 4 (tracklist ID)
            filter({'tlid': [1, 2, 3, 4]})

            # Returns track with URIs 'xyz' or 'abc'
            filter({'uri': ['xyz', 'abc']})

            # Returns track with a matching TLIDs (1, 3 or 6) and a
            # matching URI ('xyz' or 'abc')
            filter({'tlid': [1, 3, 6], 'uri': ['xyz', 'abc']})

        :param criteria: on or more criteria to match by
        :type criteria: dict, of (string, list) pairs
        :rtype: list of :class:`mopidy.models.TlTrack`

        .. deprecated:: 1.1
            Providing the criteria via ``kwargs``.
        """
        if kwargs:
            deprecation.warn('core.tracklist.filter:kwargs_criteria')

        criteria = criteria or kwargs
        tlids = criteria.pop('tlid', [])
        validation.check_query(criteria, validation.TRACKLIST_FIELDS)
        validation.check_instances(tlids, int)

        matches = self._tl_tracks
        for (key, values) in criteria.items():
            matches = [
                ct for ct in matches if getattr(ct.track, key) in values]
        if tlids:
            matches = [ct for ct in matches if ct.tlid in tlids]
        return matches
Esempio n. 17
0
    def filter(self, criteria):
        """
        Filter the tracklist by the given criteria.

        Each rule in the criteria consists of a model field and a list of
        values to compare it against. If the model field matches any of the
        values, it may be returned.

        Only tracks that match all the given criteria are returned.

        Examples::

            # Returns tracks with TLIDs 1, 2, 3, or 4 (tracklist ID)
            filter({'tlid': [1, 2, 3, 4]})

            # Returns track with URIs 'xyz' or 'abc'
            filter({'uri': ['xyz', 'abc']})

            # Returns track with a matching TLIDs (1, 3 or 6) and a
            # matching URI ('xyz' or 'abc')
            filter({'tlid': [1, 3, 6], 'uri': ['xyz', 'abc']})

        :param criteria: one or more rules to match by
        :type criteria: dict, of (string, list) pairs
        :rtype: list of :class:`mopidy.models.TlTrack`
        """
        tlids = criteria.pop("tlid", [])
        validation.check_query(criteria, validation.TRACKLIST_FIELDS)
        validation.check_instances(tlids, int)

        matches = self._tl_tracks
        for (key, values) in criteria.items():
            matches = [
                ct for ct in matches if getattr(ct.track, key) in values
            ]
        if tlids:
            matches = [ct for ct in matches if ct.tlid in tlids]
        return matches
Esempio n. 18
0
    def get_images(self, uris):
        """Lookup the images for the given URIs

        Backends can use this to return image URIs for any URI they know about
        be it tracks, albums, playlists. The lookup result is a dictionary
        mapping the provided URIs to lists of images.

        Unknown URIs or URIs the corresponding backend couldn't find anything
        for will simply return an empty list for that URI.

        :param uris: list of URIs to find images for
        :type uris: list of string
        :rtype: {uri: tuple of :class:`mopidy.models.Image`}

        .. versionadded:: 1.0
        """
        validation.check_uris(uris)

        futures = {
            backend: backend.library.get_images(backend_uris)
            for (backend,
                 backend_uris) in self._get_backends_to_uris(uris).items()
            if backend_uris
        }

        results = {uri: tuple() for uri in uris}
        for backend, future in futures.items():
            with _backend_error_handling(backend):
                if future.get() is None:
                    continue
                validation.check_instance(future.get(), Mapping)
                for uri, images in future.get().items():
                    if uri not in uris:
                        raise exceptions.ValidationError(
                            f"Got unknown image URI: {uri}")
                    validation.check_instances(images, models.Image)
                    results[uri] += tuple(images)
        return results
Esempio n. 19
0
    def get_images(self, uris):
        """Lookup the images for the given URIs

        Backends can use this to return image URIs for any URI they know about
        be it tracks, albums, playlists... The lookup result is a dictionary
        mapping the provided URIs to lists of images.

        Unknown URIs or URIs the corresponding backend couldn't find anything
        for will simply return an empty list for that URI.

        :param uris: list of URIs to find images for
        :type uris: list of string
        :rtype: {uri: tuple of :class:`mopidy.models.Image`}

        .. versionadded:: 1.0
        """
        validation.check_uris(uris)

        futures = {
            backend: backend.library.get_images(backend_uris)
            for (backend, backend_uris) in self._get_backends_to_uris(uris).items()
            if backend_uris
        }

        results = {uri: tuple() for uri in uris}
        for backend, future in futures.items():
            with _backend_error_handling(backend):
                if future.get() is None:
                    continue
                validation.check_instance(future.get(), collections.Mapping)
                for uri, images in future.get().items():
                    if uri not in uris:
                        raise exceptions.ValidationError("Got unknown image URI: %s" % uri)
                    validation.check_instances(images, models.Image)
                    results[uri] += tuple(images)
        return results
Esempio n. 20
0
def test_check_instances_error_message():
    with raises(exceptions.ValidationError) as excinfo:
        validation.check_instances([1], compat.string_types)
    assert 'Expected a list of basestring, not [1]' == str(excinfo.value)
Esempio n. 21
0
    def add(self, tracks=None, at_position=None, uris=None):
        """
        Add tracks to the tracklist.

        If ``uris`` is given instead of ``tracks``, the URIs are
        looked up in the library and the resulting tracks are added to the
        tracklist.

        If ``at_position`` is given, the tracks are inserted at the given
        position in the tracklist. If ``at_position`` is not given, the tracks
        are appended to the end of the tracklist.

        Triggers the :meth:`mopidy.core.CoreListener.tracklist_changed` event.

        :param tracks: tracks to add
        :type tracks: list of :class:`mopidy.models.Track` or :class:`None`
        :param at_position: position in tracklist to add tracks
        :type at_position: int or :class:`None`
        :param uris: list of URIs for tracks to add
        :type uris: list of string or :class:`None`
        :rtype: list of :class:`mopidy.models.TlTrack`

        .. versionadded:: 1.0
            The ``uris`` argument.

        .. deprecated:: 1.0
            The ``tracks`` argument. Use ``uris``.
        """
        if sum(o is not None for o in [tracks, uris]) != 1:
            raise ValueError('Exactly one of "tracks" or "uris" must be set')

        tracks is None or validation.check_instances(tracks, Track)
        uris is None or validation.check_uris(uris)
        validation.check_integer(at_position or 0)

        if tracks:
            deprecation.warn("core.tracklist.add:tracks_arg")

        if tracks is None:
            tracks = []
            track_map = self.core.library.lookup(uris=uris)
            for uri in uris:
                tracks.extend(track_map[uri])

        tl_tracks = []
        max_length = self.core._config["core"]["max_tracklist_length"]

        for track in tracks:
            if self.get_length() >= max_length:
                raise exceptions.TracklistFull(
                    f"Tracklist may contain at most {max_length:d} tracks."
                )

            tl_track = TlTrack(self._next_tlid, track)
            self._next_tlid += 1
            if at_position is not None:
                self._tl_tracks.insert(at_position, tl_track)
                at_position += 1
            else:
                self._tl_tracks.append(tl_track)
            tl_tracks.append(tl_track)

        if tl_tracks:
            self._increase_version()

        return tl_tracks
Esempio n. 22
0
def test_check_instances_error_message():
    with raises(exceptions.ValidationError) as excinfo:
        validation.check_instances([1], compat.string_types)
    assert 'Expected a list of basestring, not [1]' == str(excinfo.value)
Esempio n. 23
0
def test_check_instances_error_message():
    with raises(exceptions.ValidationError) as excinfo:
        validation.check_instances([1], str)
    assert "Expected a list of str, not [1]" == str(excinfo.value)
Esempio n. 24
0
def test_check_instances_with_valid_values():
    validation.check_instances([], int)
    validation.check_instances([1, 2], int)
    validation.check_instances((1, 2), int)
Esempio n. 25
0
    def add(self, tracks=None, at_position=None, uri=None, uris=None):
        """
        Add tracks to the tracklist.

        If ``uri`` is given instead of ``tracks``, the URI is looked up in the
        library and the resulting tracks are added to the tracklist.

        If ``uris`` is given instead of ``uri`` or ``tracks``, the URIs are
        looked up in the library and the resulting tracks are added to the
        tracklist.

        If ``at_position`` is given, the tracks are inserted at the given
        position in the tracklist. If ``at_position`` is not given, the tracks
        are appended to the end of the tracklist.

        Triggers the :meth:`mopidy.core.CoreListener.tracklist_changed` event.

        :param tracks: tracks to add
        :type tracks: list of :class:`mopidy.models.Track` or :class:`None`
        :param at_position: position in tracklist to add tracks
        :type at_position: int or :class:`None`
        :param uri: URI for tracks to add
        :type uri: string or :class:`None`
        :param uris: list of URIs for tracks to add
        :type uris: list of string or :class:`None`
        :rtype: list of :class:`mopidy.models.TlTrack`

        .. versionadded:: 1.0
            The ``uris`` argument.

        .. deprecated:: 1.0
            The ``tracks`` and ``uri`` arguments. Use ``uris``.
        """
        if sum(o is not None for o in [tracks, uri, uris]) != 1:
            raise ValueError(
                'Exactly one of "tracks", "uri" or "uris" must be set')

        tracks is None or validation.check_instances(tracks, Track)
        uri is None or validation.check_uri(uri)
        uris is None or validation.check_uris(uris)
        validation.check_integer(at_position or 0)

        if tracks:
            deprecation.warn('core.tracklist.add:tracks_arg')

        if uri:
            deprecation.warn('core.tracklist.add:uri_arg')

        if tracks is None:
            if uri is not None:
                uris = [uri]

            tracks = []
            track_map = self.core.library.lookup(uris=uris)
            for uri in uris:
                tracks.extend(track_map[uri])

        tl_tracks = []
        max_length = self.core._config['core']['max_tracklist_length']

        for track in tracks:
            if self.get_length() >= max_length:
                raise exceptions.TracklistFull(
                    'Tracklist may contain at most %d tracks.' % max_length)

            tl_track = TlTrack(self._next_tlid, track)
            self._next_tlid += 1
            if at_position is not None:
                self._tl_tracks.insert(at_position, tl_track)
                at_position += 1
            else:
                self._tl_tracks.append(tl_track)
            tl_tracks.append(tl_track)

        if tl_tracks:
            self._increase_version()

        return tl_tracks
Esempio n. 26
0
def test_check_instances_with_valid_values():
    validation.check_instances([], int)
    validation.check_instances([1, 2], int)
    validation.check_instances((1, 2), int)