Merge pull request #637 from jodal/feature/move-backend-api
Move backend API to mopidy.backend
This commit is contained in:
commit
719c67341b
@ -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:
|
||||
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -344,10 +344,10 @@ passed a reference to the core API when it's created. See the
|
||||
|
||||
import pykka
|
||||
|
||||
from mopidy.core import CoreListener
|
||||
from mopidy import core
|
||||
|
||||
|
||||
class SoundspotFrontend(pykka.ThreadingActor, CoreListener):
|
||||
class SoundspotFrontend(pykka.ThreadingActor, core.CoreListener):
|
||||
def __init__(self, core):
|
||||
super(SoundspotFrontend, self).__init__()
|
||||
self.core = core
|
||||
@ -370,10 +370,10 @@ details.
|
||||
|
||||
import pykka
|
||||
|
||||
from mopidy.backends import base
|
||||
from mopidy import backend
|
||||
|
||||
|
||||
class SoundspotBackend(pykka.ThreadingActor, base.BaseBackend):
|
||||
class SoundspotBackend(pykka.ThreadingActor, backend.Backend):
|
||||
def __init__(self, audio):
|
||||
super(SoundspotBackend, self).__init__()
|
||||
self.audio = audio
|
||||
|
||||
294
mopidy/backend.py
Normal file
294
mopidy/backend.py
Normal 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
|
||||
@ -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',
|
||||
]
|
||||
|
||||
@ -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']
|
||||
|
||||
@ -5,8 +5,8 @@ import itertools
|
||||
|
||||
import pykka
|
||||
|
||||
from mopidy.audio import AudioListener, PlaybackState
|
||||
from mopidy.backends.listener import BackendListener
|
||||
from mopidy import audio, backend
|
||||
from mopidy.audio import PlaybackState
|
||||
from mopidy.utils import versioning
|
||||
|
||||
from .library import LibraryController
|
||||
@ -16,7 +16,7 @@ from .playlists import PlaylistsController
|
||||
from .tracklist import TracklistController
|
||||
|
||||
|
||||
class Core(pykka.ThreadingActor, AudioListener, BackendListener):
|
||||
class Core(pykka.ThreadingActor, audio.AudioListener, backend.BackendListener):
|
||||
library = None
|
||||
"""The library controller. An instance of
|
||||
:class:`mopidy.core.LibraryController`."""
|
||||
|
||||
@ -123,7 +123,7 @@ class Extension(object):
|
||||
.. deprecated:: 0.18
|
||||
Use :meth:`setup` instead.
|
||||
|
||||
:returns: list of :class:`~mopidy.backends.base.Backend` subclasses
|
||||
:returns: list of :class:`~mopidy.backend.Backend` subclasses
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ import os
|
||||
|
||||
import pykka
|
||||
|
||||
from mopidy.backends import base
|
||||
from mopidy import backend
|
||||
from mopidy.utils import encoding, path
|
||||
|
||||
from .library import LocalLibraryProvider
|
||||
@ -15,7 +15,7 @@ from .playlists import LocalPlaylistsProvider
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LocalBackend(pykka.ThreadingActor, base.Backend):
|
||||
class LocalBackend(pykka.ThreadingActor, backend.Backend):
|
||||
uri_schemes = ['local']
|
||||
libraries = []
|
||||
|
||||
|
||||
@ -2,12 +2,12 @@ from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from mopidy.backends import base
|
||||
from mopidy import backend
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LocalLibraryProvider(base.BaseLibraryProvider):
|
||||
class LocalLibraryProvider(backend.LibraryProvider):
|
||||
"""Proxy library that delegates work to our active local library."""
|
||||
|
||||
root_directory_name = 'local'
|
||||
|
||||
@ -2,14 +2,14 @@ from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from mopidy.backends import base
|
||||
from mopidy import backend
|
||||
|
||||
from . import translator
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LocalPlaybackProvider(base.BasePlaybackProvider):
|
||||
class LocalPlaybackProvider(backend.PlaybackProvider):
|
||||
def change_track(self, track):
|
||||
track = track.copy(uri=translator.local_track_uri_to_file_uri(
|
||||
track.uri, self.backend.config['local']['media_dir']))
|
||||
|
||||
@ -5,7 +5,7 @@ import logging
|
||||
import os
|
||||
import shutil
|
||||
|
||||
from mopidy.backends import base, listener
|
||||
from mopidy import backend
|
||||
from mopidy.models import Playlist
|
||||
from mopidy.utils import formatting, path
|
||||
|
||||
@ -15,7 +15,7 @@ from .translator import parse_m3u
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LocalPlaylistsProvider(base.BasePlaylistsProvider):
|
||||
class LocalPlaylistsProvider(backend.PlaylistsProvider):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(LocalPlaylistsProvider, self).__init__(*args, **kwargs)
|
||||
self._media_dir = self.backend.config['local']['media_dir']
|
||||
@ -58,7 +58,7 @@ class LocalPlaylistsProvider(base.BasePlaylistsProvider):
|
||||
|
||||
self.playlists = playlists
|
||||
# TODO: send what scheme we loaded them for?
|
||||
listener.BackendListener.send('playlists_loaded')
|
||||
backend.BackendListener.send('playlists_loaded')
|
||||
|
||||
logger.info(
|
||||
'Loaded %d local playlists from %s',
|
||||
|
||||
@ -5,28 +5,27 @@ import urlparse
|
||||
|
||||
import pykka
|
||||
|
||||
from mopidy import audio as audio_lib, exceptions
|
||||
from mopidy import audio as audio_lib, backend, exceptions
|
||||
from mopidy.audio import scan
|
||||
from mopidy.backends import base
|
||||
from mopidy.models import Track
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StreamBackend(pykka.ThreadingActor, base.Backend):
|
||||
class StreamBackend(pykka.ThreadingActor, backend.Backend):
|
||||
def __init__(self, config, audio):
|
||||
super(StreamBackend, self).__init__()
|
||||
|
||||
self.library = StreamLibraryProvider(
|
||||
backend=self, timeout=config['stream']['timeout'])
|
||||
self.playback = base.BasePlaybackProvider(audio=audio, backend=self)
|
||||
self.playback = backend.PlaybackProvider(audio=audio, backend=self)
|
||||
self.playlists = None
|
||||
|
||||
self.uri_schemes = audio_lib.supported_uri_schemes(
|
||||
config['stream']['protocols'])
|
||||
|
||||
|
||||
class StreamLibraryProvider(base.BaseLibraryProvider):
|
||||
class StreamLibraryProvider(backend.LibraryProvider):
|
||||
def __init__(self, backend, timeout):
|
||||
super(StreamLibraryProvider, self).__init__(backend)
|
||||
self._scanner = scan.Scanner(min_duration=None, timeout=timeout)
|
||||
|
||||
@ -3,12 +3,12 @@ from __future__ import unicode_literals
|
||||
import mock
|
||||
import unittest
|
||||
|
||||
from mopidy.backends.listener import BackendListener
|
||||
from mopidy import backend
|
||||
|
||||
|
||||
class BackendListenerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.listener = BackendListener()
|
||||
self.listener = backend.BackendListener()
|
||||
|
||||
def test_on_event_forwards_to_specific_handler(self):
|
||||
self.listener.playlists_loaded = mock.Mock()
|
||||
@ -3,8 +3,7 @@ from __future__ import unicode_literals
|
||||
import mock
|
||||
import unittest
|
||||
|
||||
from mopidy.backends import base
|
||||
from mopidy.core import Core
|
||||
from mopidy import backend, core
|
||||
from mopidy.models import Ref, SearchResult, Track
|
||||
|
||||
|
||||
@ -12,13 +11,13 @@ class CoreLibraryTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.backend1 = mock.Mock()
|
||||
self.backend1.uri_schemes.get.return_value = ['dummy1']
|
||||
self.library1 = mock.Mock(spec=base.BaseLibraryProvider)
|
||||
self.library1 = mock.Mock(spec=backend.LibraryProvider)
|
||||
self.library1.root_directory_name.get.return_value = 'dummy1'
|
||||
self.backend1.library = self.library1
|
||||
|
||||
self.backend2 = mock.Mock()
|
||||
self.backend2.uri_schemes.get.return_value = ['dummy2']
|
||||
self.library2 = mock.Mock(spec=base.BaseLibraryProvider)
|
||||
self.library2 = mock.Mock(spec=backend.LibraryProvider)
|
||||
self.library2.root_directory_name.get.return_value = 'dummy2'
|
||||
self.backend2.library = self.library2
|
||||
|
||||
@ -27,7 +26,7 @@ class CoreLibraryTest(unittest.TestCase):
|
||||
self.backend3.uri_schemes.get.return_value = ['dummy3']
|
||||
self.backend3.has_library().get.return_value = False
|
||||
|
||||
self.core = Core(audio=None, backends=[
|
||||
self.core = core.Core(audio=None, backends=[
|
||||
self.backend1, self.backend2, self.backend3])
|
||||
|
||||
def test_browse_root_returns_dir_ref_for_each_lib_with_root_dir_name(self):
|
||||
|
||||
@ -3,8 +3,7 @@ from __future__ import unicode_literals
|
||||
import mock
|
||||
import unittest
|
||||
|
||||
from mopidy.backends import base
|
||||
from mopidy.core import Core, PlaybackState
|
||||
from mopidy import backend, core
|
||||
from mopidy.models import Track
|
||||
|
||||
|
||||
@ -12,12 +11,12 @@ class CorePlaybackTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.backend1 = mock.Mock()
|
||||
self.backend1.uri_schemes.get.return_value = ['dummy1']
|
||||
self.playback1 = mock.Mock(spec=base.BasePlaybackProvider)
|
||||
self.playback1 = mock.Mock(spec=backend.PlaybackProvider)
|
||||
self.backend1.playback = self.playback1
|
||||
|
||||
self.backend2 = mock.Mock()
|
||||
self.backend2.uri_schemes.get.return_value = ['dummy2']
|
||||
self.playback2 = mock.Mock(spec=base.BasePlaybackProvider)
|
||||
self.playback2 = mock.Mock(spec=backend.PlaybackProvider)
|
||||
self.backend2.playback = self.playback2
|
||||
|
||||
# A backend without the optional playback provider
|
||||
@ -32,7 +31,7 @@ class CorePlaybackTest(unittest.TestCase):
|
||||
Track(uri='dummy1:b', length=40000),
|
||||
]
|
||||
|
||||
self.core = Core(audio=None, backends=[
|
||||
self.core = core.Core(audio=None, backends=[
|
||||
self.backend1, self.backend2, self.backend3])
|
||||
self.core.tracklist.add(self.tracks)
|
||||
|
||||
@ -78,7 +77,7 @@ class CorePlaybackTest(unittest.TestCase):
|
||||
self.core.playback.current_tl_track = self.unplayable_tl_track
|
||||
self.core.playback.pause()
|
||||
|
||||
self.assertEqual(self.core.playback.state, PlaybackState.PAUSED)
|
||||
self.assertEqual(self.core.playback.state, core.PlaybackState.PAUSED)
|
||||
self.assertFalse(self.playback1.pause.called)
|
||||
self.assertFalse(self.playback2.pause.called)
|
||||
|
||||
@ -100,10 +99,10 @@ class CorePlaybackTest(unittest.TestCase):
|
||||
|
||||
def test_resume_does_nothing_if_track_is_unplayable(self):
|
||||
self.core.playback.current_tl_track = self.unplayable_tl_track
|
||||
self.core.playback.state = PlaybackState.PAUSED
|
||||
self.core.playback.state = core.PlaybackState.PAUSED
|
||||
self.core.playback.resume()
|
||||
|
||||
self.assertEqual(self.core.playback.state, PlaybackState.PAUSED)
|
||||
self.assertEqual(self.core.playback.state, core.PlaybackState.PAUSED)
|
||||
self.assertFalse(self.playback1.resume.called)
|
||||
self.assertFalse(self.playback2.resume.called)
|
||||
|
||||
@ -123,10 +122,10 @@ class CorePlaybackTest(unittest.TestCase):
|
||||
|
||||
def test_stop_changes_state_even_if_track_is_unplayable(self):
|
||||
self.core.playback.current_tl_track = self.unplayable_tl_track
|
||||
self.core.playback.state = PlaybackState.PAUSED
|
||||
self.core.playback.state = core.PlaybackState.PAUSED
|
||||
self.core.playback.stop()
|
||||
|
||||
self.assertEqual(self.core.playback.state, PlaybackState.STOPPED)
|
||||
self.assertEqual(self.core.playback.state, core.PlaybackState.STOPPED)
|
||||
self.assertFalse(self.playback1.stop.called)
|
||||
self.assertFalse(self.playback2.stop.called)
|
||||
|
||||
@ -146,7 +145,7 @@ class CorePlaybackTest(unittest.TestCase):
|
||||
|
||||
def test_seek_fails_for_unplayable_track(self):
|
||||
self.core.playback.current_tl_track = self.unplayable_tl_track
|
||||
self.core.playback.state = PlaybackState.PLAYING
|
||||
self.core.playback.state = core.PlaybackState.PLAYING
|
||||
success = self.core.playback.seek(1000)
|
||||
|
||||
self.assertFalse(success)
|
||||
|
||||
@ -3,8 +3,7 @@ from __future__ import unicode_literals
|
||||
import mock
|
||||
import unittest
|
||||
|
||||
from mopidy.backends import base
|
||||
from mopidy.core import Core
|
||||
from mopidy import backend, core
|
||||
from mopidy.models import Playlist, Track
|
||||
|
||||
|
||||
@ -12,12 +11,12 @@ class PlaylistsTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.backend1 = mock.Mock()
|
||||
self.backend1.uri_schemes.get.return_value = ['dummy1']
|
||||
self.sp1 = mock.Mock(spec=base.BasePlaylistsProvider)
|
||||
self.sp1 = mock.Mock(spec=backend.PlaylistsProvider)
|
||||
self.backend1.playlists = self.sp1
|
||||
|
||||
self.backend2 = mock.Mock()
|
||||
self.backend2.uri_schemes.get.return_value = ['dummy2']
|
||||
self.sp2 = mock.Mock(spec=base.BasePlaylistsProvider)
|
||||
self.sp2 = mock.Mock(spec=backend.PlaylistsProvider)
|
||||
self.backend2.playlists = self.sp2
|
||||
|
||||
# A backend without the optional playlists provider
|
||||
@ -34,7 +33,7 @@ class PlaylistsTest(unittest.TestCase):
|
||||
self.pl2b = Playlist(name='B', tracks=[Track(uri='dummy2:b')])
|
||||
self.sp2.playlists.get.return_value = [self.pl2a, self.pl2b]
|
||||
|
||||
self.core = Core(audio=None, backends=[
|
||||
self.core = core.Core(audio=None, backends=[
|
||||
self.backend3, self.backend1, self.backend2])
|
||||
|
||||
def test_get_playlists_combines_result_from_backends(self):
|
||||
|
||||
@ -3,8 +3,7 @@ from __future__ import unicode_literals
|
||||
import mock
|
||||
import unittest
|
||||
|
||||
from mopidy.backends import base
|
||||
from mopidy.core import Core
|
||||
from mopidy import backend, core
|
||||
from mopidy.models import Track
|
||||
|
||||
|
||||
@ -18,10 +17,10 @@ class TracklistTest(unittest.TestCase):
|
||||
|
||||
self.backend = mock.Mock()
|
||||
self.backend.uri_schemes.get.return_value = ['dummy1']
|
||||
self.library = mock.Mock(spec=base.BaseLibraryProvider)
|
||||
self.library = mock.Mock(spec=backend.LibraryProvider)
|
||||
self.backend.library = self.library
|
||||
|
||||
self.core = Core(audio=None, backends=[self.backend])
|
||||
self.core = core.Core(audio=None, backends=[self.backend])
|
||||
self.tl_tracks = self.core.tracklist.add(self.tracks)
|
||||
|
||||
def test_add_by_uri_looks_up_uri_in_library(self):
|
||||
@ -72,4 +71,4 @@ class TracklistTest(unittest.TestCase):
|
||||
def test_filter_fails_if_values_is_a_string(self):
|
||||
self.assertRaises(ValueError, self.core.tracklist.filter, uri='a')
|
||||
|
||||
# TODO Extract tracklist tests from the base backend tests
|
||||
# TODO Extract tracklist tests from the local backend tests
|
||||
|
||||
@ -8,7 +8,7 @@ from __future__ import unicode_literals
|
||||
|
||||
import pykka
|
||||
|
||||
from mopidy.backends import base
|
||||
from mopidy import backend
|
||||
from mopidy.models import Playlist, SearchResult
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ def create_dummy_backend_proxy(config=None, audio=None):
|
||||
return DummyBackend.start(config=config, audio=audio).proxy()
|
||||
|
||||
|
||||
class DummyBackend(pykka.ThreadingActor, base.Backend):
|
||||
class DummyBackend(pykka.ThreadingActor, backend.Backend):
|
||||
def __init__(self, config, audio):
|
||||
super(DummyBackend, self).__init__()
|
||||
|
||||
@ -27,7 +27,7 @@ class DummyBackend(pykka.ThreadingActor, base.Backend):
|
||||
self.uri_schemes = ['dummy']
|
||||
|
||||
|
||||
class DummyLibraryProvider(base.BaseLibraryProvider):
|
||||
class DummyLibraryProvider(backend.LibraryProvider):
|
||||
root_directory_name = 'dummy'
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -53,7 +53,7 @@ class DummyLibraryProvider(base.BaseLibraryProvider):
|
||||
return self.dummy_search_result
|
||||
|
||||
|
||||
class DummyPlaybackProvider(base.BasePlaybackProvider):
|
||||
class DummyPlaybackProvider(backend.PlaybackProvider):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DummyPlaybackProvider, self).__init__(*args, **kwargs)
|
||||
self._time_position = 0
|
||||
@ -80,7 +80,7 @@ class DummyPlaybackProvider(base.BasePlaybackProvider):
|
||||
return self._time_position
|
||||
|
||||
|
||||
class DummyPlaylistsProvider(base.BasePlaylistsProvider):
|
||||
class DummyPlaylistsProvider(backend.PlaylistsProvider):
|
||||
def create(self, name):
|
||||
playlist = Playlist(name=name, uri='dummy:%s' % name)
|
||||
self._playlists.append(playlist)
|
||||
|
||||
@ -5,14 +5,13 @@ import unittest
|
||||
import mock
|
||||
import pykka
|
||||
|
||||
from mopidy import audio, core
|
||||
from mopidy.backends import listener
|
||||
from mopidy import audio, backend, core
|
||||
from mopidy.local import actor
|
||||
|
||||
from tests import path_to_data_dir
|
||||
|
||||
|
||||
@mock.patch.object(listener.BackendListener, 'send')
|
||||
@mock.patch.object(backend.BackendListener, 'send')
|
||||
class LocalBackendEventsTest(unittest.TestCase):
|
||||
config = {
|
||||
'local': {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user