backend: Move backend API to mopidy.backend

Keep imports from old locations working until extensions have been updated to
use the new location.
This commit is contained in:
Stein Magnus Jodal 2014-01-11 16:51:25 +01:00
parent ff4c965aff
commit b6b542a60f
5 changed files with 343 additions and 297 deletions

View File

@ -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:

View File

@ -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

294
mopidy/backend.py Normal file
View File

@ -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

View File

@ -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',
]

View File

@ -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']