diff --git a/docs/api/backends.rst b/docs/api/backends.rst index 40d65f30..fa6d8410 100644 --- a/docs/api/backends.rst +++ b/docs/api/backends.rst @@ -4,46 +4,46 @@ Backend API *********** -.. module:: mopidy.backends.base +.. module:: mopidy.backend :synopsis: The API implemented by backends The backend API is the interface that must be implemented when you create a -backend. If you are working on a frontend and need to access the backend, see -the :ref:`core-api`. +backend. If you are working on a frontend and need to access the backends, see +the :ref:`core-api` instead. Backend class ============= -.. autoclass:: mopidy.backends.base.Backend +.. autoclass:: mopidy.backend.Backend :members: Playback provider ================= -.. autoclass:: mopidy.backends.base.BasePlaybackProvider +.. autoclass:: mopidy.backend.PlaybackProvider :members: Playlists provider ================== -.. autoclass:: mopidy.backends.base.BasePlaylistsProvider +.. autoclass:: mopidy.backend.PlaylistsProvider :members: Library provider ================ -.. autoclass:: mopidy.backends.base.BaseLibraryProvider +.. autoclass:: mopidy.backend.LibraryProvider :members: Backend listener ================ -.. autoclass:: mopidy.backends.listener.BackendListener +.. autoclass:: mopidy.backend.BackendListener :members: diff --git a/docs/changelog.rst b/docs/changelog.rst index 97be243b..fa20f791 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -20,6 +20,28 @@ v0.18.0 (UNRELEASED) virtual file system of tracks. Backends can implement support for this by implementing :meth:`mopidy.backends.base.BaseLibraryController.browse`. +**Backend API** + +- Move the backend API classes from :mod:`mopidy.backends.base` to + :mod:`mopidy.backend` and remove the ``Base`` prefix from the class names: + + - From :class:`mopidy.backends.base.Backend` + to :class:`mopidy.backend.Backend` + + - From :class:`mopidy.backends.base.BaseLibraryProvider` + to :class:`mopidy.backend.LibraryProvider` + + - From :class:`mopidy.backends.base.BasePlaybackProvider` + to :class:`mopidy.backend.PlaybackProvider` + + - From :class:`mopidy.backends.base.BasePlaylistsProvider` + to :class:`mopidy.backend.PlaylistsProvider` + + - From :class:`mopidy.backends.listener.BackendListener` + to :class:`mopidy.backend.BackendListener` + + Imports from the old locations still works, but are deprecated. + **Configuration** - The default for the :option:`mopidy --config` option has been updated to diff --git a/mopidy/backend.py b/mopidy/backend.py new file mode 100644 index 00000000..5db013e0 --- /dev/null +++ b/mopidy/backend.py @@ -0,0 +1,294 @@ +from __future__ import unicode_literals + +import copy + +from mopidy import listener + + +class Backend(object): + #: Actor proxy to an instance of :class:`mopidy.audio.Audio`. + #: + #: Should be passed to the backend constructor as the kwarg ``audio``, + #: which will then set this field. + audio = None + + #: The library provider. An instance of + #: :class:`~mopidy.backend.LibraryProvider`, or :class:`None` if + #: the backend doesn't provide a library. + library = None + + #: The playback provider. An instance of + #: :class:`~mopidy.backend.PlaybackProvider`, or :class:`None` if + #: the backend doesn't provide playback. + playback = None + + #: The playlists provider. An instance of + #: :class:`~mopidy.backend.PlaylistsProvider`, or class:`None` if + #: the backend doesn't provide playlists. + playlists = None + + #: List of URI schemes this backend can handle. + uri_schemes = [] + + # Because the providers is marked as pykka_traversible, we can't get() them + # from another actor, and need helper methods to check if the providers are + # set or None. + + def has_library(self): + return self.library is not None + + def has_playback(self): + return self.playback is not None + + def has_playlists(self): + return self.playlists is not None + + +class LibraryProvider(object): + """ + :param backend: backend the controller is a part of + :type backend: :class:`mopidy.backend.Backend` + """ + + pykka_traversable = True + + root_directory_name = None + """ + Name of the library's root directory in Mopidy's virtual file system. + + *MUST be set by any class that implements :meth:`browse`.* + """ + + def __init__(self, backend): + self.backend = backend + + def browse(self, path): + """ + See :meth:`mopidy.core.LibraryController.browse`. + + If you implement this method, make sure to also set + :attr:`root_directory_name`. + + *MAY be implemented by subclass.* + """ + return [] + + # TODO: replace with search(query, exact=True, ...) + def find_exact(self, query=None, uris=None): + """ + See :meth:`mopidy.core.LibraryController.find_exact`. + + *MAY be implemented by subclass.* + """ + pass + + def lookup(self, uri): + """ + See :meth:`mopidy.core.LibraryController.lookup`. + + *MUST be implemented by subclass.* + """ + raise NotImplementedError + + def refresh(self, uri=None): + """ + See :meth:`mopidy.core.LibraryController.refresh`. + + *MAY be implemented by subclass.* + """ + pass + + def search(self, query=None, uris=None): + """ + See :meth:`mopidy.core.LibraryController.search`. + + *MAY be implemented by subclass.* + """ + pass + + +class PlaybackProvider(object): + """ + :param audio: the audio actor + :type audio: actor proxy to an instance of :class:`mopidy.audio.Audio` + :param backend: the backend + :type backend: :class:`mopidy.backend.Backend` + """ + + pykka_traversable = True + + def __init__(self, audio, backend): + self.audio = audio + self.backend = backend + + def pause(self): + """ + Pause playback. + + *MAY be reimplemented by subclass.* + + :rtype: :class:`True` if successful, else :class:`False` + """ + return self.audio.pause_playback().get() + + def play(self, track): + """ + Play given track. + + *MAY be reimplemented by subclass.* + + :param track: the track to play + :type track: :class:`mopidy.models.Track` + :rtype: :class:`True` if successful, else :class:`False` + """ + self.audio.prepare_change() + self.change_track(track) + return self.audio.start_playback().get() + + def change_track(self, track): + """ + Swith to provided track. + + *MAY be reimplemented by subclass.* + + :param track: the track to play + :type track: :class:`mopidy.models.Track` + :rtype: :class:`True` if successful, else :class:`False` + """ + self.audio.set_uri(track.uri).get() + return True + + def resume(self): + """ + Resume playback at the same time position playback was paused. + + *MAY be reimplemented by subclass.* + + :rtype: :class:`True` if successful, else :class:`False` + """ + return self.audio.start_playback().get() + + def seek(self, time_position): + """ + Seek to a given time position. + + *MAY be reimplemented by subclass.* + + :param time_position: time position in milliseconds + :type time_position: int + :rtype: :class:`True` if successful, else :class:`False` + """ + return self.audio.set_position(time_position).get() + + def stop(self): + """ + Stop playback. + + *MAY be reimplemented by subclass.* + + :rtype: :class:`True` if successful, else :class:`False` + """ + return self.audio.stop_playback().get() + + def get_time_position(self): + """ + Get the current time position in milliseconds. + + *MAY be reimplemented by subclass.* + + :rtype: int + """ + return self.audio.get_position().get() + + +class PlaylistsProvider(object): + """ + :param backend: backend the controller is a part of + :type backend: :class:`mopidy.backend.Backend` instance + """ + + pykka_traversable = True + + def __init__(self, backend): + self.backend = backend + self._playlists = [] + + @property + def playlists(self): + """ + Currently available playlists. + + Read/write. List of :class:`mopidy.models.Playlist`. + """ + return copy.copy(self._playlists) + + @playlists.setter # noqa + def playlists(self, playlists): + self._playlists = playlists + + def create(self, name): + """ + See :meth:`mopidy.core.PlaylistsController.create`. + + *MUST be implemented by subclass.* + """ + raise NotImplementedError + + def delete(self, uri): + """ + See :meth:`mopidy.core.PlaylistsController.delete`. + + *MUST be implemented by subclass.* + """ + raise NotImplementedError + + def lookup(self, uri): + """ + See :meth:`mopidy.core.PlaylistsController.lookup`. + + *MUST be implemented by subclass.* + """ + raise NotImplementedError + + def refresh(self): + """ + See :meth:`mopidy.core.PlaylistsController.refresh`. + + *MUST be implemented by subclass.* + """ + raise NotImplementedError + + def save(self, playlist): + """ + See :meth:`mopidy.core.PlaylistsController.save`. + + *MUST be implemented by subclass.* + """ + raise NotImplementedError + + +class BackendListener(listener.Listener): + """ + Marker interface for recipients of events sent by the backend actors. + + Any Pykka actor that mixes in this class will receive calls to the methods + defined here when the corresponding events happen in the core actor. This + interface is used both for looking up what actors to notify of the events, + and for providing default implementations for those listeners that are not + interested in all events. + + Normally, only the Core actor should mix in this class. + """ + + @staticmethod + def send(event, **kwargs): + """Helper to allow calling of backend listener events""" + listener.send_async(BackendListener, event, **kwargs) + + def playlists_loaded(self): + """ + Called when playlists are loaded or refreshed. + + *MAY* be implemented by actor. + """ + pass diff --git a/mopidy/backends/base.py b/mopidy/backends/base.py index b9c67ad5..aed6ce3e 100644 --- a/mopidy/backends/base.py +++ b/mopidy/backends/base.py @@ -1,265 +1,17 @@ from __future__ import unicode_literals -import copy - - -class Backend(object): - #: Actor proxy to an instance of :class:`mopidy.audio.Audio`. - #: - #: Should be passed to the backend constructor as the kwarg ``audio``, - #: which will then set this field. - audio = None - - #: The library provider. An instance of - #: :class:`~mopidy.backends.base.BaseLibraryProvider`, or :class:`None` if - #: the backend doesn't provide a library. - library = None - - #: The playback provider. An instance of - #: :class:`~mopidy.backends.base.BasePlaybackProvider`, or :class:`None` if - #: the backend doesn't provide playback. - playback = None - - #: The playlists provider. An instance of - #: :class:`~mopidy.backends.base.BasePlaylistsProvider`, or class:`None` if - #: the backend doesn't provide playlists. - playlists = None - - #: List of URI schemes this backend can handle. - uri_schemes = [] - - # Because the providers is marked as pykka_traversible, we can't get() them - # from another actor, and need helper methods to check if the providers are - # set or None. - - def has_library(self): - return self.library is not None - - def has_playback(self): - return self.playback is not None - - def has_playlists(self): - return self.playlists is not None - - -class BaseLibraryProvider(object): - """ - :param backend: backend the controller is a part of - :type backend: :class:`mopidy.backends.base.Backend` - """ - - pykka_traversable = True - - root_directory_name = None - """ - Name of the library's root directory in Mopidy's virtual file system. - - *MUST be set by any class that implements :meth:`browse`.* - """ - - def __init__(self, backend): - self.backend = backend - - def browse(self, path): - """ - See :meth:`mopidy.core.LibraryController.browse`. - - If you implement this method, make sure to also set - :attr:`root_directory_name`. - - *MAY be implemented by subclass.* - """ - return [] - - # TODO: replace with search(query, exact=True, ...) - def find_exact(self, query=None, uris=None): - """ - See :meth:`mopidy.core.LibraryController.find_exact`. - - *MAY be implemented by subclass.* - """ - pass - - def lookup(self, uri): - """ - See :meth:`mopidy.core.LibraryController.lookup`. - - *MUST be implemented by subclass.* - """ - raise NotImplementedError - - def refresh(self, uri=None): - """ - See :meth:`mopidy.core.LibraryController.refresh`. - - *MAY be implemented by subclass.* - """ - pass - - def search(self, query=None, uris=None): - """ - See :meth:`mopidy.core.LibraryController.search`. - - *MAY be implemented by subclass.* - """ - pass - - -class BasePlaybackProvider(object): - """ - :param audio: the audio actor - :type audio: actor proxy to an instance of :class:`mopidy.audio.Audio` - :param backend: the backend - :type backend: :class:`mopidy.backends.base.Backend` - """ - - pykka_traversable = True - - def __init__(self, audio, backend): - self.audio = audio - self.backend = backend - - def pause(self): - """ - Pause playback. - - *MAY be reimplemented by subclass.* - - :rtype: :class:`True` if successful, else :class:`False` - """ - return self.audio.pause_playback().get() - - def play(self, track): - """ - Play given track. - - *MAY be reimplemented by subclass.* - - :param track: the track to play - :type track: :class:`mopidy.models.Track` - :rtype: :class:`True` if successful, else :class:`False` - """ - self.audio.prepare_change() - self.change_track(track) - return self.audio.start_playback().get() - - def change_track(self, track): - """ - Swith to provided track. - - *MAY be reimplemented by subclass.* - - :param track: the track to play - :type track: :class:`mopidy.models.Track` - :rtype: :class:`True` if successful, else :class:`False` - """ - self.audio.set_uri(track.uri).get() - return True - - def resume(self): - """ - Resume playback at the same time position playback was paused. - - *MAY be reimplemented by subclass.* - - :rtype: :class:`True` if successful, else :class:`False` - """ - return self.audio.start_playback().get() - - def seek(self, time_position): - """ - Seek to a given time position. - - *MAY be reimplemented by subclass.* - - :param time_position: time position in milliseconds - :type time_position: int - :rtype: :class:`True` if successful, else :class:`False` - """ - return self.audio.set_position(time_position).get() - - def stop(self): - """ - Stop playback. - - *MAY be reimplemented by subclass.* - - :rtype: :class:`True` if successful, else :class:`False` - """ - return self.audio.stop_playback().get() - - def get_time_position(self): - """ - Get the current time position in milliseconds. - - *MAY be reimplemented by subclass.* - - :rtype: int - """ - return self.audio.get_position().get() - - -class BasePlaylistsProvider(object): - """ - :param backend: backend the controller is a part of - :type backend: :class:`mopidy.backends.base.Backend` - """ - - pykka_traversable = True - - def __init__(self, backend): - self.backend = backend - self._playlists = [] - - @property - def playlists(self): - """ - Currently available playlists. - - Read/write. List of :class:`mopidy.models.Playlist`. - """ - return copy.copy(self._playlists) - - @playlists.setter # noqa - def playlists(self, playlists): - self._playlists = playlists - - def create(self, name): - """ - See :meth:`mopidy.core.PlaylistsController.create`. - - *MUST be implemented by subclass.* - """ - raise NotImplementedError - - def delete(self, uri): - """ - See :meth:`mopidy.core.PlaylistsController.delete`. - - *MUST be implemented by subclass.* - """ - raise NotImplementedError - - def lookup(self, uri): - """ - See :meth:`mopidy.core.PlaylistsController.lookup`. - - *MUST be implemented by subclass.* - """ - raise NotImplementedError - - def refresh(self): - """ - See :meth:`mopidy.core.PlaylistsController.refresh`. - - *MUST be implemented by subclass.* - """ - raise NotImplementedError - - def save(self, playlist): - """ - See :meth:`mopidy.core.PlaylistsController.save`. - - *MUST be implemented by subclass.* - """ - raise NotImplementedError +from mopidy.backend import ( + Backend, + LibraryProvider as BaseLibraryProvider, + PlaybackProvider as BasePlaybackProvider, + PlaylistsProvider as BasePlaylistsProvider) + + +# Make classes previously residing here available in the old location for +# backwards compatibility with extensions targeting Mopidy < 0.18. +__all__ = [ + 'Backend', + 'BaseLibraryProvider', + 'BasePlaybackProvider', + 'BasePlaylistsProvider', +] diff --git a/mopidy/backends/listener.py b/mopidy/backends/listener.py index ee4735e7..0b551f26 100644 --- a/mopidy/backends/listener.py +++ b/mopidy/backends/listener.py @@ -1,30 +1,8 @@ from __future__ import unicode_literals -from mopidy import listener +from mopidy.backend import BackendListener -class BackendListener(listener.Listener): - """ - Marker interface for recipients of events sent by the backend actors. - - Any Pykka actor that mixes in this class will receive calls to the methods - defined here when the corresponding events happen in the core actor. This - interface is used both for looking up what actors to notify of the events, - and for providing default implementations for those listeners that are not - interested in all events. - - Normally, only the Core actor should mix in this class. - """ - - @staticmethod - def send(event, **kwargs): - """Helper to allow calling of backend listener events""" - listener.send_async(BackendListener, event, **kwargs) - - def playlists_loaded(self): - """ - Called when playlists are loaded or refreshed. - - *MAY* be implemented by actor. - """ - pass +# Make classes previously residing here available in the old location for +# backwards compatibility with extensions targeting Mopidy < 0.18. +__all__ = ['BackendListener']