diff --git a/docs/api/backends.rst b/docs/api/backends.rst
deleted file mode 100644
index c8a72b4d..00000000
--- a/docs/api/backends.rst
+++ /dev/null
@@ -1,90 +0,0 @@
-**********************
-:mod:`mopidy.backends`
-**********************
-
-.. automodule:: mopidy.backends
- :synopsis: Backend API
-
-
-The backend and its controllers
-===============================
-
-.. graph:: backend_relations
-
- backend -- current_playlist
- backend -- library
- backend -- playback
- backend -- stored_playlists
-
-
-Backend API
-===========
-
-.. note::
-
- Currently this only documents the API that is available for use by
- frontends like :mod:`mopidy.frontends.mpd`, and not what is required to
- implement your own backend. :class:`mopidy.backends.base.BaseBackend` and
- its controllers implements many of these methods in a matter that should be
- independent of most concrete backend implementations, so you should
- generally just implement or override a few of these methods yourself to
- create a new backend with a complete feature set.
-
-.. autoclass:: mopidy.backends.base.BaseBackend
- :members:
- :undoc-members:
-
-
-Playback controller
--------------------
-
-Manages playback, with actions like play, pause, stop, next, previous, and
-seek.
-
-.. autoclass:: mopidy.backends.base.BasePlaybackController
- :members:
- :undoc-members:
-
-
-Mixer controller
-----------------
-
-Manages volume. See :class:`mopidy.mixers.BaseMixer`.
-
-
-Current playlist controller
----------------------------
-
-Manages everything related to the currently loaded playlist.
-
-.. autoclass:: mopidy.backends.base.BaseCurrentPlaylistController
- :members:
- :undoc-members:
-
-
-Stored playlists controller
----------------------------
-
-Manages stored playlist.
-
-.. autoclass:: mopidy.backends.base.BaseStoredPlaylistsController
- :members:
- :undoc-members:
-
-
-Library controller
-------------------
-
-Manages the music library, e.g. searching for tracks to be added to a playlist.
-
-.. autoclass:: mopidy.backends.base.BaseLibraryController
- :members:
- :undoc-members:
-
-
-Backend implementations
-=======================
-
-* :mod:`mopidy.backends.dummy`
-* :mod:`mopidy.backends.libspotify`
-* :mod:`mopidy.backends.local`
diff --git a/docs/api/backends/concepts.rst b/docs/api/backends/concepts.rst
new file mode 100644
index 00000000..fd7b4d13
--- /dev/null
+++ b/docs/api/backends/concepts.rst
@@ -0,0 +1,28 @@
+**********************************************
+The backend, controller, and provider concepts
+**********************************************
+
+Backend:
+ The backend is mostly for convenience. It is a container that holds
+ references to all the controllers.
+Controllers:
+ Each controller has responsibility for a given part of the backend
+ functionality. Most, but not all, controllers delegates some work to one or
+ more providers. The controllers are responsible for choosing the right
+ provider for any given task based upon i.e. the track's URI. See
+ :ref:`backend-controller-api` for more details.
+Providers:
+ Anything specific to i.e. Spotify integration or local storage is contained
+ in the providers. To integrate with new music sources, you just add new
+ providers. See :ref:`backend-provider-api` for more details.
+
+.. digraph:: backend_relations
+
+ Backend -> "Current\nplaylist\ncontroller"
+ Backend -> "Library\ncontroller"
+ "Library\ncontroller" -> "Library\nproviders"
+ Backend -> "Playback\ncontroller"
+ "Playback\ncontroller" -> "Playback\nproviders"
+ Backend -> "Stored\nplaylists\ncontroller"
+ "Stored\nplaylists\ncontroller" -> "Stored\nplaylist\nproviders"
+ Backend -> Mixer
diff --git a/docs/api/backends/controllers.rst b/docs/api/backends/controllers.rst
new file mode 100644
index 00000000..88bb48ff
--- /dev/null
+++ b/docs/api/backends/controllers.rst
@@ -0,0 +1,65 @@
+.. _backend-controller-api:
+
+**********************
+Backend controller API
+**********************
+
+
+The backend controller API is the interface that is used by frontends like
+:mod:`mopidy.frontends.mpd`. If you want to implement your own backend, see the
+:ref:`backend-provider-api`.
+
+
+The backend
+===========
+
+.. autoclass:: mopidy.backends.base.Backend
+ :members:
+ :undoc-members:
+
+
+Playback controller
+===================
+
+Manages playback, with actions like play, pause, stop, next, previous, and
+seek.
+
+.. autoclass:: mopidy.backends.base.PlaybackController
+ :members:
+ :undoc-members:
+
+
+Mixer controller
+================
+
+Manages volume. See :class:`mopidy.mixers.BaseMixer`.
+
+
+Current playlist controller
+===========================
+
+Manages everything related to the currently loaded playlist.
+
+.. autoclass:: mopidy.backends.base.CurrentPlaylistController
+ :members:
+ :undoc-members:
+
+
+Stored playlists controller
+===========================
+
+Manages stored playlist.
+
+.. autoclass:: mopidy.backends.base.StoredPlaylistsController
+ :members:
+ :undoc-members:
+
+
+Library controller
+==================
+
+Manages the music library, e.g. searching for tracks to be added to a playlist.
+
+.. autoclass:: mopidy.backends.base.LibraryController
+ :members:
+ :undoc-members:
diff --git a/docs/api/backends/providers.rst b/docs/api/backends/providers.rst
new file mode 100644
index 00000000..9289dd06
--- /dev/null
+++ b/docs/api/backends/providers.rst
@@ -0,0 +1,41 @@
+.. _backend-provider-api:
+
+********************
+Backend provider API
+********************
+
+The backend provider 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:`backend-controller-api`.
+
+
+Playback provider
+=================
+
+.. autoclass:: mopidy.backends.base.BasePlaybackProvider
+ :members:
+ :undoc-members:
+
+
+Stored playlists provider
+=========================
+
+.. autoclass:: mopidy.backends.base.BaseStoredPlaylistsProvider
+ :members:
+ :undoc-members:
+
+
+Library provider
+================
+
+.. autoclass:: mopidy.backends.base.BaseLibraryProvider
+ :members:
+ :undoc-members:
+
+
+Backend provider implementations
+================================
+
+* :mod:`mopidy.backends.dummy`
+* :mod:`mopidy.backends.libspotify`
+* :mod:`mopidy.backends.local`
diff --git a/docs/api/index.rst b/docs/api/index.rst
index 87ec9bb3..1f37e9ff 100644
--- a/docs/api/index.rst
+++ b/docs/api/index.rst
@@ -5,4 +5,7 @@ API reference
.. toctree::
:glob:
- **
+ backends/concepts
+ backends/controllers
+ backends/providers
+ *
diff --git a/docs/conf.py b/docs/conf.py
index d0d8f3af..16a85975 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -16,8 +16,8 @@ import sys, os
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
-sys.path.append(os.path.abspath(os.path.dirname(__file__)))
-sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/../'))
+sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
+sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/../'))
import mopidy
diff --git a/mopidy/backends/base/__init__.py b/mopidy/backends/base/__init__.py
index 491c5b73..096a433f 100644
--- a/mopidy/backends/base/__init__.py
+++ b/mopidy/backends/base/__init__.py
@@ -4,21 +4,19 @@ import random
import time
from mopidy import settings
-from mopidy.backends.base.current_playlist import BaseCurrentPlaylistController
-from mopidy.backends.base.library import BaseLibraryController
-from mopidy.backends.base.playback import BasePlaybackController
-from mopidy.backends.base.stored_playlists import BaseStoredPlaylistsController
from mopidy.frontends.mpd import translator
from mopidy.models import Playlist
from mopidy.utils import get_class
+from .current_playlist import CurrentPlaylistController
+from .library import LibraryController, BaseLibraryProvider
+from .playback import PlaybackController, BasePlaybackProvider
+from .stored_playlists import (StoredPlaylistsController,
+ BaseStoredPlaylistsProvider)
+
logger = logging.getLogger('mopidy.backends.base')
-__all__ = ['BaseBackend', 'BasePlaybackController',
- 'BaseCurrentPlaylistController', 'BaseStoredPlaylistsController',
- 'BaseLibraryController']
-
-class BaseBackend(object):
+class Backend(object):
"""
:param core_queue: a queue for sending messages to
:class:`mopidy.process.CoreProcess`
@@ -44,22 +42,22 @@ class BaseBackend(object):
core_queue = None
#: The current playlist controller. An instance of
- #: :class:`mopidy.backends.base.BaseCurrentPlaylistController`.
+ #: :class:`mopidy.backends.base.CurrentPlaylistController`.
current_playlist = None
#: The library controller. An instance of
- # :class:`mopidy.backends.base.BaseLibraryController`.
+ # :class:`mopidy.backends.base.LibraryController`.
library = None
#: The sound mixer. An instance of :class:`mopidy.mixers.BaseMixer`.
mixer = None
#: The playback controller. An instance of
- #: :class:`mopidy.backends.base.BasePlaybackController`.
+ #: :class:`mopidy.backends.base.PlaybackController`.
playback = None
#: The stored playlists controller. An instance of
- #: :class:`mopidy.backends.base.BaseStoredPlaylistsController`.
+ #: :class:`mopidy.backends.base.StoredPlaylistsController`.
stored_playlists = None
#: List of URI prefixes this backend can handle.
diff --git a/mopidy/backends/base/current_playlist.py b/mopidy/backends/base/current_playlist.py
index 34a16369..fe7d1de9 100644
--- a/mopidy/backends/base/current_playlist.py
+++ b/mopidy/backends/base/current_playlist.py
@@ -6,10 +6,10 @@ from mopidy.frontends.mpd import translator
logger = logging.getLogger('mopidy.backends.base')
-class BaseCurrentPlaylistController(object):
+class CurrentPlaylistController(object):
"""
:param backend: backend the controller is a part of
- :type backend: :class:`BaseBackend`
+ :type backend: :class:`mopidy.backends.base.Backend`
"""
def __init__(self, backend):
diff --git a/mopidy/backends/base/library.py b/mopidy/backends/base/library.py
index 94f40863..fd018b5f 100644
--- a/mopidy/backends/base/library.py
+++ b/mopidy/backends/base/library.py
@@ -2,18 +2,21 @@ import logging
logger = logging.getLogger('mopidy.backends.base')
-class BaseLibraryController(object):
+class LibraryController(object):
"""
:param backend: backend the controller is a part of
- :type backend: :class:`BaseBackend`
+ :type backend: :class:`mopidy.backends.base.Backend`
+ :param provider: provider the controller should use
+ :type provider: instance of :class:`BaseLibraryProvider`
"""
- def __init__(self, backend):
+ def __init__(self, backend, provider):
self.backend = backend
+ self.provider = provider
def destroy(self):
"""Cleanup after component."""
- pass
+ self.provider.destroy()
def find_exact(self, **query):
"""
@@ -32,7 +35,7 @@ class BaseLibraryController(object):
:type query: dict
:rtype: :class:`mopidy.models.Playlist`
"""
- raise NotImplementedError
+ return self.provider.find_exact(**query)
def lookup(self, uri):
"""
@@ -42,7 +45,7 @@ class BaseLibraryController(object):
:type uri: string
:rtype: :class:`mopidy.models.Track` or :class:`None`
"""
- raise NotImplementedError
+ return self.provider.lookup(uri)
def refresh(self, uri=None):
"""
@@ -51,7 +54,7 @@ class BaseLibraryController(object):
:param uri: directory or track URI
:type uri: string
"""
- raise NotImplementedError
+ return self.provider.refresh(uri)
def search(self, **query):
"""
@@ -70,4 +73,54 @@ class BaseLibraryController(object):
:type query: dict
:rtype: :class:`mopidy.models.Playlist`
"""
+ return self.provider.search(**query)
+
+
+class BaseLibraryProvider(object):
+ """
+ :param backend: backend the controller is a part of
+ :type backend: :class:`mopidy.backends.base.Backend`
+ """
+
+ def __init__(self, backend):
+ self.backend = backend
+
+ def destroy(self):
+ """
+ Cleanup after component.
+
+ *MAY be implemented by subclasses.*
+ """
+ pass
+
+ def find_exact(self, **query):
+ """
+ See :meth:`mopidy.backends.base.LibraryController.find_exact`.
+
+ *MUST be implemented by subclass.*
+ """
+ raise NotImplementedError
+
+ def lookup(self, uri):
+ """
+ See :meth:`mopidy.backends.base.LibraryController.lookup`.
+
+ *MUST be implemented by subclass.*
+ """
+ raise NotImplementedError
+
+ def refresh(self, uri=None):
+ """
+ See :meth:`mopidy.backends.base.LibraryController.refresh`.
+
+ *MUST be implemented by subclass.*
+ """
+ raise NotImplementedError
+
+ def search(self, **query):
+ """
+ See :meth:`mopidy.backends.base.LibraryController.search`.
+
+ *MUST be implemented by subclass.*
+ """
raise NotImplementedError
diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py
index b7ceeee2..8a3eeee5 100644
--- a/mopidy/backends/base/playback.py
+++ b/mopidy/backends/base/playback.py
@@ -4,10 +4,12 @@ import time
logger = logging.getLogger('mopidy.backends.base')
-class BasePlaybackController(object):
+class PlaybackController(object):
"""
- :param backend: backend the controller is a part of
- :type backend: :class:`BaseBackend`
+ :param backend: the backend
+ :type backend: :class:`mopidy.backends.base.Backend`
+ :param provider: provider the controller should use
+ :type provider: instance of :class:`BasePlaybackProvider`
"""
# pylint: disable = R0902
@@ -54,8 +56,9 @@ class BasePlaybackController(object):
#: Playback continues after current song.
single = False
- def __init__(self, backend):
+ def __init__(self, backend, provider):
self.backend = backend
+ self.provider = provider
self._state = self.STOPPED
self._shuffled = []
self._first_shuffle = True
@@ -65,10 +68,8 @@ class BasePlaybackController(object):
def destroy(self):
"""
Cleanup after component.
-
- May be overridden by subclasses.
"""
- pass
+ self.provider.destroy()
def _get_cpid(self, cp_track):
if cp_track is None:
@@ -330,7 +331,7 @@ class BasePlaybackController(object):
"""
Tell the playback controller that the current playlist has changed.
- Used by :class:`mopidy.backends.base.BaseCurrentPlaylistController`.
+ Used by :class:`mopidy.backends.base.CurrentPlaylistController`.
"""
self._first_shuffle = True
self._shuffled = []
@@ -353,18 +354,9 @@ class BasePlaybackController(object):
def pause(self):
"""Pause playback."""
- if self.state == self.PLAYING and self._pause():
+ if self.state == self.PLAYING and self.provider.pause():
self.state = self.PAUSED
- def _pause(self):
- """
- To be overridden by subclass. Implement your backend's pause
- functionality here.
-
- :rtype: :class:`True` if successful, else :class:`False`
- """
- raise NotImplementedError
-
def play(self, cp_track=None, on_error_step=1):
"""
Play the given track, or if the given track is :class:`None`, play the
@@ -391,7 +383,7 @@ class BasePlaybackController(object):
self.state = self.STOPPED
self.current_cp_track = cp_track
self.state = self.PLAYING
- if not self._play(cp_track[1]):
+ if not self.provider.play(cp_track[1]):
# Track is not playable
if self.random and self._shuffled:
self._shuffled.remove(cp_track)
@@ -405,18 +397,6 @@ class BasePlaybackController(object):
self._trigger_started_playing_event()
- def _play(self, track):
- """
- To be overridden by subclass. Implement your backend's play
- functionality here.
-
- :param track: the track to play
- :type track: :class:`mopidy.models.Track`
- :rtype: :class:`True` if successful, else :class:`False`
- """
-
- raise NotImplementedError
-
def previous(self):
"""Play the previous track."""
if self.cp_track_at_previous is None:
@@ -428,18 +408,9 @@ class BasePlaybackController(object):
def resume(self):
"""If paused, resume playing the current track."""
- if self.state == self.PAUSED and self._resume():
+ if self.state == self.PAUSED and self.provider.resume():
self.state = self.PLAYING
- def _resume(self):
- """
- To be overridden by subclass. Implement your backend's resume
- functionality here.
-
- :rtype: :class:`True` if successful, else :class:`False`
- """
- raise NotImplementedError
-
def seek(self, time_position):
"""
Seeks to time position given in milliseconds.
@@ -465,18 +436,7 @@ class BasePlaybackController(object):
self._play_time_started = self._current_wall_time
self._play_time_accumulated = time_position
- return self._seek(time_position)
-
- def _seek(self, time_position):
- """
- To be overridden by subclass. Implement your backend's seek
- functionality here.
-
- :param time_position: time position in milliseconds
- :type time_position: int
- :rtype: :class:`True` if successful, else :class:`False`
- """
- raise NotImplementedError
+ return self.provider.seek(time_position)
def stop(self, clear_current_track=False):
"""
@@ -489,20 +449,11 @@ class BasePlaybackController(object):
if self.state == self.STOPPED:
return
self._trigger_stopped_playing_event()
- if self._stop():
+ if self.provider.stop():
self.state = self.STOPPED
if clear_current_track:
self.current_cp_track = None
- def _stop(self):
- """
- To be overridden by subclass. Implement your backend's stop
- functionality here.
-
- :rtype: :class:`True` if successful, else :class:`False`
- """
- raise NotImplementedError
-
def _trigger_started_playing_event(self):
"""
Notifies frontends that a track has started playing.
@@ -532,3 +483,75 @@ class BasePlaybackController(object):
'track': self.current_track,
'stop_position': self.time_position,
})
+
+
+class BasePlaybackProvider(object):
+ """
+ :param backend: the backend
+ :type backend: :class:`mopidy.backends.base.Backend`
+ """
+
+ def __init__(self, backend):
+ self.backend = backend
+
+ def destroy(self):
+ """
+ Cleanup after component.
+
+ *MAY be implemented by subclasses.*
+ """
+ pass
+
+ def pause(self):
+ """
+ Pause playback.
+
+ *MUST be implemented by subclass.*
+
+ :rtype: :class:`True` if successful, else :class:`False`
+ """
+ raise NotImplementedError
+
+ def play(self, track):
+ """
+ Play given track.
+
+ *MUST be implemented by subclass.*
+
+ :param track: the track to play
+ :type track: :class:`mopidy.models.Track`
+ :rtype: :class:`True` if successful, else :class:`False`
+ """
+ raise NotImplementedError
+
+ def resume(self):
+ """
+ Resume playback at the same time position playback was paused.
+
+ *MUST be implemented by subclass.*
+
+ :rtype: :class:`True` if successful, else :class:`False`
+ """
+ raise NotImplementedError
+
+ def seek(self, time_position):
+ """
+ Seek to a given time position.
+
+ *MUST be implemented by subclass.*
+
+ :param time_position: time position in milliseconds
+ :type time_position: int
+ :rtype: :class:`True` if successful, else :class:`False`
+ """
+ raise NotImplementedError
+
+ def stop(self):
+ """
+ Stop playback.
+
+ *MUST be implemented by subclass.*
+
+ :rtype: :class:`True` if successful, else :class:`False`
+ """
+ raise NotImplementedError
diff --git a/mopidy/backends/base/stored_playlists.py b/mopidy/backends/base/stored_playlists.py
index 61722c81..6578c046 100644
--- a/mopidy/backends/base/stored_playlists.py
+++ b/mopidy/backends/base/stored_playlists.py
@@ -3,28 +3,34 @@ import logging
logger = logging.getLogger('mopidy.backends.base')
-class BaseStoredPlaylistsController(object):
+class StoredPlaylistsController(object):
"""
:param backend: backend the controller is a part of
- :type backend: :class:`BaseBackend`
+ :type backend: :class:`mopidy.backends.base.Backend`
+ :param provider: provider the controller should use
+ :type provider: instance of :class:`BaseStoredPlaylistsProvider`
"""
- def __init__(self, backend):
+ def __init__(self, backend, provider):
self.backend = backend
- self._playlists = []
+ self.provider = provider
def destroy(self):
"""Cleanup after component."""
- pass
+ self.provider.destroy()
@property
def playlists(self):
- """List of :class:`mopidy.models.Playlist`."""
- return copy(self._playlists)
+ """
+ Currently stored playlists.
+
+ Read/write. List of :class:`mopidy.models.Playlist`.
+ """
+ return self.provider.playlists
@playlists.setter
def playlists(self, playlists):
- self._playlists = playlists
+ self.provider.playlists = playlists
def create(self, name):
"""
@@ -34,7 +40,7 @@ class BaseStoredPlaylistsController(object):
:type name: string
:rtype: :class:`mopidy.models.Playlist`
"""
- raise NotImplementedError
+ return self.provider.create(name)
def delete(self, playlist):
"""
@@ -43,7 +49,7 @@ class BaseStoredPlaylistsController(object):
:param playlist: the playlist to delete
:type playlist: :class:`mopidy.models.Playlist`
"""
- raise NotImplementedError
+ return self.provider.delete(playlist)
def get(self, **criteria):
"""
@@ -55,13 +61,14 @@ class BaseStoredPlaylistsController(object):
get(name='a') # Returns track with name 'a'
get(uri='xyz') # Returns track with URI 'xyz'
- get(name='a', uri='xyz') # Returns track with name 'a' and URI 'xyz'
+ get(name='a', uri='xyz') # Returns track with name 'a' and URI
+ # 'xyz'
:param criteria: one or more criteria to match by
:type criteria: dict
:rtype: :class:`mopidy.models.Playlist`
"""
- matches = self._playlists
+ matches = self.playlists
for (key, value) in criteria.iteritems():
matches = filter(lambda p: getattr(p, key) == value, matches)
if len(matches) == 1:
@@ -82,11 +89,14 @@ class BaseStoredPlaylistsController(object):
:type uri: string
:rtype: :class:`mopidy.models.Playlist`
"""
- raise NotImplementedError
+ return self.provider.lookup(uri)
def refresh(self):
- """Refresh stored playlists."""
- raise NotImplementedError
+ """
+ Refresh the stored playlists in
+ :attr:`mopidy.backends.base.StoredPlaylistsController.playlists`.
+ """
+ return self.provider.refresh()
def rename(self, playlist, new_name):
"""
@@ -97,7 +107,7 @@ class BaseStoredPlaylistsController(object):
:param new_name: the new name
:type new_name: string
"""
- raise NotImplementedError
+ return self.provider.rename(playlist, new_name)
def save(self, playlist):
"""
@@ -106,4 +116,85 @@ class BaseStoredPlaylistsController(object):
:param playlist: the playlist
:type playlist: :class:`mopidy.models.Playlist`
"""
+ return self.provider.save(playlist)
+
+
+class BaseStoredPlaylistsProvider(object):
+ """
+ :param backend: backend the controller is a part of
+ :type backend: :class:`mopidy.backends.base.Backend`
+ """
+
+ def __init__(self, backend):
+ self.backend = backend
+ self._playlists = []
+
+ def destroy(self):
+ """
+ Cleanup after component.
+
+ *MAY be implemented by subclass.*
+ """
+ pass
+
+ @property
+ def playlists(self):
+ """
+ Currently stored playlists.
+
+ Read/write. List of :class:`mopidy.models.Playlist`.
+ """
+ return copy(self._playlists)
+
+ @playlists.setter
+ def playlists(self, playlists):
+ self._playlists = playlists
+
+ def create(self, name):
+ """
+ See :meth:`mopidy.backends.base.StoredPlaylistsController.create`.
+
+ *MUST be implemented by subclass.*
+ """
raise NotImplementedError
+
+ def delete(self, playlist):
+ """
+ See :meth:`mopidy.backends.base.StoredPlaylistsController.delete`.
+
+ *MUST be implemented by subclass.*
+ """
+ raise NotImplementedError
+
+ def lookup(self, uri):
+ """
+ See :meth:`mopidy.backends.base.StoredPlaylistsController.lookup`.
+
+ *MUST be implemented by subclass.*
+ """
+ raise NotImplementedError
+
+ def refresh(self):
+ """
+ See :meth:`mopidy.backends.base.StoredPlaylistsController.refresh`.
+
+ *MUST be implemented by subclass.*
+ """
+ raise NotImplementedError
+
+ def rename(self, playlist, new_name):
+ """
+ See :meth:`mopidy.backends.base.StoredPlaylistsController.rename`.
+
+ *MUST be implemented by subclass.*
+ """
+ raise NotImplementedError
+
+ def save(self, playlist):
+ """
+ See :meth:`mopidy.backends.base.StoredPlaylistsController.save`.
+
+ *MUST be implemented by subclass.*
+ """
+ raise NotImplementedError
+
diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py
index 42acdbf2..9c6885bc 100644
--- a/mopidy/backends/dummy/__init__.py
+++ b/mopidy/backends/dummy/__init__.py
@@ -1,9 +1,19 @@
-from mopidy.backends.base import (BaseBackend, BaseCurrentPlaylistController,
- BasePlaybackController, BaseLibraryController,
- BaseStoredPlaylistsController)
+from mopidy.backends.base import (Backend, CurrentPlaylistController,
+ PlaybackController, BasePlaybackProvider, LibraryController,
+ BaseLibraryProvider, StoredPlaylistsController,
+ BaseStoredPlaylistsProvider)
from mopidy.models import Playlist
-class DummyBackend(BaseBackend):
+
+class DummyQueue(object):
+ def __init__(self):
+ self.received_messages = []
+
+ def put(self, message):
+ self.received_messages.append(message)
+
+
+class DummyBackend(Backend):
"""
A backend which implements the backend API in the simplest way possible.
Used in tests of the frontends.
@@ -13,19 +23,30 @@ class DummyBackend(BaseBackend):
def __init__(self, *args, **kwargs):
super(DummyBackend, self).__init__(*args, **kwargs)
- self.current_playlist = DummyCurrentPlaylistController(backend=self)
- self.library = DummyLibraryController(backend=self)
- self.playback = DummyPlaybackController(backend=self)
- self.stored_playlists = DummyStoredPlaylistsController(backend=self)
+
+ self.core_queue = DummyQueue()
+
+ self.current_playlist = CurrentPlaylistController(backend=self)
+
+ library_provider = DummyLibraryProvider(backend=self)
+ self.library = LibraryController(backend=self,
+ provider=library_provider)
+
+ playback_provider = DummyPlaybackProvider(backend=self)
+ self.playback = PlaybackController(backend=self,
+ provider=playback_provider)
+
+ stored_playlists_provider = DummyStoredPlaylistsProvider(backend=self)
+ self.stored_playlists = StoredPlaylistsController(backend=self,
+ provider=stored_playlists_provider)
+
self.uri_handlers = [u'dummy:']
-class DummyCurrentPlaylistController(BaseCurrentPlaylistController):
- pass
-
-
-class DummyLibraryController(BaseLibraryController):
- _library = []
+class DummyLibraryProvider(BaseLibraryProvider):
+ def __init__(self, *args, **kwargs):
+ super(DummyLibraryProvider, self).__init__(*args, **kwargs)
+ self._library = []
def find_exact(self, **query):
return Playlist()
@@ -42,41 +63,25 @@ class DummyLibraryController(BaseLibraryController):
return Playlist()
-class DummyPlaybackController(BasePlaybackController):
- def _next(self, track):
+class DummyPlaybackProvider(BasePlaybackProvider):
+ def pause(self):
+ return True
+
+ def play(self, track):
"""Pass None as track to force failure"""
return track is not None
- def _pause(self):
+ def resume(self):
return True
- def _play(self, track):
- """Pass None as track to force failure"""
- return track is not None
-
- def _previous(self, track):
- """Pass None as track to force failure"""
- return track is not None
-
- def _resume(self):
+ def seek(self, time_position):
return True
- def _seek(self, time_position):
+ def stop(self):
return True
- def _stop(self):
- return True
-
- def _trigger_started_playing_event(self):
- pass # noop
-
- def _trigger_stopped_playing_event(self):
- pass # noop
-
-
-class DummyStoredPlaylistsController(BaseStoredPlaylistsController):
- _playlists = []
+class DummyStoredPlaylistsProvider(BaseStoredPlaylistsProvider):
def create(self, name):
playlist = Playlist(name=name)
self._playlists.append(playlist)
diff --git a/mopidy/backends/libspotify/__init__.py b/mopidy/backends/libspotify/__init__.py
index 75739d66..4d8b67d5 100644
--- a/mopidy/backends/libspotify/__init__.py
+++ b/mopidy/backends/libspotify/__init__.py
@@ -1,13 +1,14 @@
import logging
from mopidy import settings
-from mopidy.backends.base import BaseBackend, BaseCurrentPlaylistController
+from mopidy.backends.base import (Backend, CurrentPlaylistController,
+ LibraryController, PlaybackController, StoredPlaylistsController)
logger = logging.getLogger('mopidy.backends.libspotify')
ENCODING = 'utf-8'
-class LibspotifyBackend(BaseBackend):
+class LibspotifyBackend(Backend):
"""
A `Spotify `_ backend which uses the official
`libspotify `_
@@ -33,18 +34,29 @@ class LibspotifyBackend(BaseBackend):
# missing spotify dependencies.
def __init__(self, *args, **kwargs):
- from .library import LibspotifyLibraryController
- from .playback import LibspotifyPlaybackController
- from .stored_playlists import LibspotifyStoredPlaylistsController
+ from .library import LibspotifyLibraryProvider
+ from .playback import LibspotifyPlaybackProvider
+ from .stored_playlists import LibspotifyStoredPlaylistsProvider
super(LibspotifyBackend, self).__init__(*args, **kwargs)
- self.current_playlist = BaseCurrentPlaylistController(backend=self)
- self.library = LibspotifyLibraryController(backend=self)
- self.playback = LibspotifyPlaybackController(backend=self)
- self.stored_playlists = LibspotifyStoredPlaylistsController(
+ self.current_playlist = CurrentPlaylistController(backend=self)
+
+ library_provider = LibspotifyLibraryProvider(backend=self)
+ self.library = LibraryController(backend=self,
+ provider=library_provider)
+
+ playback_provider = LibspotifyPlaybackProvider(backend=self)
+ self.playback = PlaybackController(backend=self,
+ provider=playback_provider)
+
+ stored_playlists_provider = LibspotifyStoredPlaylistsProvider(
backend=self)
+ self.stored_playlists = StoredPlaylistsController(backend=self,
+ provider=stored_playlists_provider)
+
self.uri_handlers = [u'spotify:', u'http://open.spotify.com/']
+
self.spotify = self._connect()
def _connect(self):
diff --git a/mopidy/backends/libspotify/library.py b/mopidy/backends/libspotify/library.py
index 972eaf03..948c69b2 100644
--- a/mopidy/backends/libspotify/library.py
+++ b/mopidy/backends/libspotify/library.py
@@ -3,14 +3,14 @@ import multiprocessing
from spotify import Link, SpotifyError
-from mopidy.backends.base import BaseLibraryController
+from mopidy.backends.base import BaseLibraryProvider
from mopidy.backends.libspotify import ENCODING
from mopidy.backends.libspotify.translator import LibspotifyTranslator
from mopidy.models import Playlist
logger = logging.getLogger('mopidy.backends.libspotify.library')
-class LibspotifyLibraryController(BaseLibraryController):
+class LibspotifyLibraryProvider(BaseLibraryProvider):
def find_exact(self, **query):
return self.search(**query)
diff --git a/mopidy/backends/libspotify/playback.py b/mopidy/backends/libspotify/playback.py
index 39c56bf6..29409ff4 100644
--- a/mopidy/backends/libspotify/playback.py
+++ b/mopidy/backends/libspotify/playback.py
@@ -2,17 +2,17 @@ import logging
from spotify import Link, SpotifyError
-from mopidy.backends.base import BasePlaybackController
+from mopidy.backends.base import BasePlaybackProvider
logger = logging.getLogger('mopidy.backends.libspotify.playback')
-class LibspotifyPlaybackController(BasePlaybackController):
- def _pause(self):
+class LibspotifyPlaybackProvider(BasePlaybackProvider):
+ def pause(self):
return self.backend.output.set_state('PAUSED')
- def _play(self, track):
+ def play(self, track):
self.backend.output.set_state('READY')
- if self.state == self.PLAYING:
+ if self.backend.playback.state == self.backend.playback.PLAYING:
self.backend.spotify.session.play(0)
if track.uri is None:
return False
@@ -26,16 +26,16 @@ class LibspotifyPlaybackController(BasePlaybackController):
logger.warning('Play %s failed: %s', track.uri, e)
return False
- def _resume(self):
- return self._seek(self.time_position)
+ def resume(self):
+ return self.seek(self.backend.playback.time_position)
- def _seek(self, time_position):
+ def seek(self, time_position):
self.backend.output.set_state('READY')
self.backend.spotify.session.seek(time_position)
self.backend.output.set_state('PLAYING')
return True
- def _stop(self):
+ def stop(self):
result = self.backend.output.set_state('READY')
self.backend.spotify.session.play(0)
return result
diff --git a/mopidy/backends/libspotify/stored_playlists.py b/mopidy/backends/libspotify/stored_playlists.py
index 3339578c..6f2a7aad 100644
--- a/mopidy/backends/libspotify/stored_playlists.py
+++ b/mopidy/backends/libspotify/stored_playlists.py
@@ -1,6 +1,6 @@
-from mopidy.backends.base import BaseStoredPlaylistsController
+from mopidy.backends.base import BaseStoredPlaylistsProvider
-class LibspotifyStoredPlaylistsController(BaseStoredPlaylistsController):
+class LibspotifyStoredPlaylistsProvider(BaseStoredPlaylistsProvider):
def create(self, name):
pass # TODO
diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py
index c8331a48..532c3976 100644
--- a/mopidy/backends/local/__init__.py
+++ b/mopidy/backends/local/__init__.py
@@ -5,9 +5,10 @@ import os
import shutil
from mopidy import settings
-from mopidy.backends.base import (BaseBackend, BaseLibraryController,
- BaseStoredPlaylistsController, BaseCurrentPlaylistController,
- BasePlaybackController)
+from mopidy.backends.base import (Backend, CurrentPlaylistController,
+ LibraryController, BaseLibraryProvider, PlaybackController,
+ BasePlaybackProvider, StoredPlaylistsController,
+ BaseStoredPlaylistsProvider)
from mopidy.models import Playlist, Track, Album
from mopidy.utils.process import pickle_connection
@@ -15,7 +16,7 @@ from .translator import parse_m3u, parse_mpd_tag_cache
logger = logging.getLogger(u'mopidy.backends.local')
-class LocalBackend(BaseBackend):
+class LocalBackend(Backend):
"""
A backend for playing music from a local music archive.
@@ -31,41 +32,55 @@ class LocalBackend(BaseBackend):
def __init__(self, *args, **kwargs):
super(LocalBackend, self).__init__(*args, **kwargs)
- self.library = LocalLibraryController(self)
- self.stored_playlists = LocalStoredPlaylistsController(self)
- self.current_playlist = BaseCurrentPlaylistController(self)
- self.playback = LocalPlaybackController(self)
+ self.current_playlist = CurrentPlaylistController(backend=self)
+
+ library_provider = LocalLibraryProvider(backend=self)
+ self.library = LibraryController(backend=self,
+ provider=library_provider)
+
+ playback_provider = LocalPlaybackProvider(backend=self)
+ self.playback = LocalPlaybackController(backend=self,
+ provider=playback_provider)
+
+ stored_playlists_provider = LocalStoredPlaylistsProvider(backend=self)
+ self.stored_playlists = StoredPlaylistsController(backend=self,
+ provider=stored_playlists_provider)
+
self.uri_handlers = [u'file://']
-class LocalPlaybackController(BasePlaybackController):
- def __init__(self, backend):
- super(LocalPlaybackController, self).__init__(backend)
+class LocalPlaybackController(PlaybackController):
+ def __init__(self, *args, **kwargs):
+ super(LocalPlaybackController, self).__init__(*args, **kwargs)
+
+ # XXX Why do we call stop()? Is it to set GStreamer state to 'READY'?
self.stop()
- def _play(self, track):
- return self.backend.output.play_uri(track.uri)
-
- def _stop(self):
- return self.backend.output.set_state('READY')
-
- def _pause(self):
- return self.backend.output.set_state('PAUSED')
-
- def _resume(self):
- return self.backend.output.set_state('PLAYING')
-
- def _seek(self, time_position):
- return self.backend.output.set_position(time_position)
-
@property
def time_position(self):
return self.backend.output.get_position()
-class LocalStoredPlaylistsController(BaseStoredPlaylistsController):
- def __init__(self, *args):
- super(LocalStoredPlaylistsController, self).__init__(*args)
+class LocalPlaybackProvider(BasePlaybackProvider):
+ def pause(self):
+ return self.backend.output.set_state('PAUSED')
+
+ def play(self, track):
+ return self.backend.output.play_uri(track.uri)
+
+ def resume(self):
+ return self.backend.output.set_state('PLAYING')
+
+ def seek(self, time_position):
+ return self.backend.output.set_position(time_position)
+
+ def stop(self):
+ return self.backend.output.set_state('READY')
+
+
+class LocalStoredPlaylistsProvider(BaseStoredPlaylistsProvider):
+ def __init__(self, *args, **kwargs):
+ super(LocalStoredPlaylistsProvider, self).__init__(*args, **kwargs)
self._folder = settings.LOCAL_PLAYLIST_PATH
self.refresh()
@@ -136,9 +151,9 @@ class LocalStoredPlaylistsController(BaseStoredPlaylistsController):
self._playlists.append(playlist)
-class LocalLibraryController(BaseLibraryController):
- def __init__(self, backend):
- super(LocalLibraryController, self).__init__(backend)
+class LocalLibraryProvider(BaseLibraryProvider):
+ def __init__(self, *args, **kwargs):
+ super(LocalLibraryProvider, self).__init__(*args, **kwargs)
self._uri_mapping = {}
self.refresh()
diff --git a/mopidy/frontends/base.py b/mopidy/frontends/base.py
index 92545b73..bf1c9bda 100644
--- a/mopidy/frontends/base.py
+++ b/mopidy/frontends/base.py
@@ -5,7 +5,7 @@ class BaseFrontend(object):
:param core_queue: queue for messaging the core
:type core_queue: :class:`multiprocessing.Queue`
:param backend: the backend
- :type backend: :class:`mopidy.backends.base.BaseBackend`
+ :type backend: :class:`mopidy.backends.base.Backend`
"""
def __init__(self, core_queue, backend):
@@ -13,17 +13,27 @@ class BaseFrontend(object):
self.backend = backend
def start(self):
- """Start the frontend."""
+ """
+ Start the frontend.
+
+ *MAY be implemented by subclass.*
+ """
pass
def destroy(self):
- """Destroy the frontend."""
+ """
+ Destroy the frontend.
+
+ *MAY be implemented by subclass.*
+ """
pass
def process_message(self, message):
"""
Process messages for the frontend.
+ *MUST be implemented by subclass.*
+
:param message: the message
:type message: dict
"""
diff --git a/mopidy/mixers/__init__.py b/mopidy/mixers/__init__.py
index 332718a6..e69de29b 100644
--- a/mopidy/mixers/__init__.py
+++ b/mopidy/mixers/__init__.py
@@ -1,55 +0,0 @@
-from mopidy import settings
-
-class BaseMixer(object):
- """
- :param backend: a backend instance
- :type mixer: :class:`mopidy.backends.base.BaseBackend`
-
- **Settings:**
-
- - :attr:`mopidy.settings.MIXER_MAX_VOLUME`
- """
-
- def __init__(self, backend, *args, **kwargs):
- self.backend = backend
- self.amplification_factor = settings.MIXER_MAX_VOLUME / 100.0
-
- @property
- def volume(self):
- """
- The audio volume
-
- Integer in range [0, 100]. :class:`None` if unknown. Values below 0 is
- equal to 0. Values above 100 is equal to 100.
- """
- if self._get_volume() is None:
- return None
- return int(self._get_volume() / self.amplification_factor)
-
- @volume.setter
- def volume(self, volume):
- volume = int(int(volume) * self.amplification_factor)
- if volume < 0:
- volume = 0
- elif volume > 100:
- volume = 100
- self._set_volume(volume)
-
- def destroy(self):
- pass
-
- def _get_volume(self):
- """
- Return volume as integer in range [0, 100]. :class:`None` if unknown.
-
- *Must be implemented by subclass.*
- """
- raise NotImplementedError
-
- def _set_volume(self, volume):
- """
- Set volume as integer in range [0, 100].
-
- *Must be implemented by subclass.*
- """
- raise NotImplementedError
diff --git a/mopidy/mixers/alsa.py b/mopidy/mixers/alsa.py
index 6eef6da4..f90060ce 100644
--- a/mopidy/mixers/alsa.py
+++ b/mopidy/mixers/alsa.py
@@ -2,7 +2,7 @@ import alsaaudio
import logging
from mopidy import settings
-from mopidy.mixers import BaseMixer
+from mopidy.mixers.base import BaseMixer
logger = logging.getLogger('mopidy.mixers.alsa')
diff --git a/mopidy/mixers/base.py b/mopidy/mixers/base.py
new file mode 100644
index 00000000..f7f9525c
--- /dev/null
+++ b/mopidy/mixers/base.py
@@ -0,0 +1,55 @@
+from mopidy import settings
+
+class BaseMixer(object):
+ """
+ :param backend: a backend instance
+ :type backend: :class:`mopidy.backends.base.Backend`
+
+ **Settings:**
+
+ - :attr:`mopidy.settings.MIXER_MAX_VOLUME`
+ """
+
+ def __init__(self, backend, *args, **kwargs):
+ self.backend = backend
+ self.amplification_factor = settings.MIXER_MAX_VOLUME / 100.0
+
+ @property
+ def volume(self):
+ """
+ The audio volume
+
+ Integer in range [0, 100]. :class:`None` if unknown. Values below 0 is
+ equal to 0. Values above 100 is equal to 100.
+ """
+ if self._get_volume() is None:
+ return None
+ return int(self._get_volume() / self.amplification_factor)
+
+ @volume.setter
+ def volume(self, volume):
+ volume = int(int(volume) * self.amplification_factor)
+ if volume < 0:
+ volume = 0
+ elif volume > 100:
+ volume = 100
+ self._set_volume(volume)
+
+ def destroy(self):
+ pass
+
+ def _get_volume(self):
+ """
+ Return volume as integer in range [0, 100]. :class:`None` if unknown.
+
+ *MUST be implemented by subclass.*
+ """
+ raise NotImplementedError
+
+ def _set_volume(self, volume):
+ """
+ Set volume as integer in range [0, 100].
+
+ *MUST be implemented by subclass.*
+ """
+ raise NotImplementedError
diff --git a/mopidy/mixers/denon.py b/mopidy/mixers/denon.py
index 32750f60..e6d752b6 100644
--- a/mopidy/mixers/denon.py
+++ b/mopidy/mixers/denon.py
@@ -4,7 +4,7 @@ from threading import Lock
from serial import Serial
from mopidy import settings
-from mopidy.mixers import BaseMixer
+from mopidy.mixers.base import BaseMixer
logger = logging.getLogger(u'mopidy.mixers.denon')
diff --git a/mopidy/mixers/dummy.py b/mopidy/mixers/dummy.py
index b0ea0e47..12a8137e 100644
--- a/mopidy/mixers/dummy.py
+++ b/mopidy/mixers/dummy.py
@@ -1,4 +1,4 @@
-from mopidy.mixers import BaseMixer
+from mopidy.mixers.base import BaseMixer
class DummyMixer(BaseMixer):
"""Mixer which just stores and reports the chosen volume."""
diff --git a/mopidy/mixers/gstreamer_software.py b/mopidy/mixers/gstreamer_software.py
index 333690ea..9dca3690 100644
--- a/mopidy/mixers/gstreamer_software.py
+++ b/mopidy/mixers/gstreamer_software.py
@@ -1,4 +1,4 @@
-from mopidy.mixers import BaseMixer
+from mopidy.mixers.base import BaseMixer
class GStreamerSoftwareMixer(BaseMixer):
"""Mixer which uses GStreamer to control volume in software."""
diff --git a/mopidy/mixers/nad.py b/mopidy/mixers/nad.py
index 8caa9700..3215a761 100644
--- a/mopidy/mixers/nad.py
+++ b/mopidy/mixers/nad.py
@@ -3,7 +3,7 @@ from serial import Serial
from multiprocessing import Pipe
from mopidy import settings
-from mopidy.mixers import BaseMixer
+from mopidy.mixers.base import BaseMixer
from mopidy.utils.process import BaseThread
logger = logging.getLogger('mopidy.mixers.nad')
diff --git a/mopidy/mixers/osa.py b/mopidy/mixers/osa.py
index 3aeaed5c..8d69eb47 100644
--- a/mopidy/mixers/osa.py
+++ b/mopidy/mixers/osa.py
@@ -1,7 +1,7 @@
from subprocess import Popen, PIPE
import time
-from mopidy.mixers import BaseMixer
+from mopidy.mixers.base import BaseMixer
class OsaMixer(BaseMixer):
"""Mixer which uses ``osascript`` on OS X to control volume."""
diff --git a/mopidy/outputs/base.py b/mopidy/outputs/base.py
index bb312323..372d7d70 100644
--- a/mopidy/outputs/base.py
+++ b/mopidy/outputs/base.py
@@ -7,21 +7,35 @@ class BaseOutput(object):
self.core_queue = core_queue
def start(self):
- """Start the output."""
+ """
+ Start the output.
+
+ *MAY be implemented by subclasses.*
+ """
pass
def destroy(self):
- """Destroy the output."""
+ """
+ Destroy the output.
+
+ *MAY be implemented by subclasses.*
+ """
pass
def process_message(self, message):
- """Process messages with the output as destination."""
+ """
+ Process messages with the output as destination.
+
+ *MUST be implemented by subclass.*
+ """
raise NotImplementedError
def play_uri(self, uri):
"""
Play URI.
+ *MUST be implemented by subclass.*
+
:param uri: the URI to play
:type uri: string
:rtype: :class:`True` if successful, else :class:`False`
@@ -32,19 +46,27 @@ class BaseOutput(object):
"""
Deliver audio data to be played.
+ *MUST be implemented by subclass.*
+
:param capabilities: a GStreamer capabilities string
:type capabilities: string
"""
raise NotImplementedError
def end_of_data_stream(self):
- """Signal that the last audio data has been delivered."""
+ """
+ Signal that the last audio data has been delivered.
+
+ *MUST be implemented by subclass.*
+ """
raise NotImplementedError
def get_position(self):
"""
Get position in milliseconds.
+ *MUST be implemented by subclass.*
+
:rtype: int
"""
raise NotImplementedError
@@ -53,6 +75,8 @@ class BaseOutput(object):
"""
Set position in milliseconds.
+ *MUST be implemented by subclass.*
+
:param position: the position in milliseconds
:type volume: int
:rtype: :class:`True` if successful, else :class:`False`
@@ -63,6 +87,8 @@ class BaseOutput(object):
"""
Set playback state.
+ *MUST be implemented by subclass.*
+
:param state: the state
:type state: string
:rtype: :class:`True` if successful, else :class:`False`
@@ -73,6 +99,8 @@ class BaseOutput(object):
"""
Get volume level for software mixer.
+ *MUST be implemented by subclass.*
+
:rtype: int in range [0..100]
"""
raise NotImplementedError
@@ -81,6 +109,8 @@ class BaseOutput(object):
"""
Set volume level for software mixer.
+ *MUST be implemented by subclass.*
+
:param volume: the volume in the range [0..100]
:type volume: int
:rtype: :class:`True` if successful, else :class:`False`
diff --git a/tests/backends/base/current_playlist.py b/tests/backends/base/current_playlist.py
index 05f08e18..2b6cb84e 100644
--- a/tests/backends/base/current_playlist.py
+++ b/tests/backends/base/current_playlist.py
@@ -9,7 +9,7 @@ from mopidy.utils import get_class
from tests.backends.base import populate_playlist
-class BaseCurrentPlaylistControllerTest(object):
+class CurrentPlaylistControllerTest(object):
tracks = []
def setUp(self):
diff --git a/tests/backends/base/library.py b/tests/backends/base/library.py
index 1239bd08..71f62147 100644
--- a/tests/backends/base/library.py
+++ b/tests/backends/base/library.py
@@ -3,7 +3,7 @@ from mopidy.models import Playlist, Track, Album, Artist
from tests import SkipTest, data_folder
-class BaseLibraryControllerTest(object):
+class LibraryControllerTest(object):
artists = [Artist(name='artist1'), Artist(name='artist2'), Artist()]
albums = [Album(name='album1', artists=artists[:1]),
Album(name='album2', artists=artists[1:2]),
diff --git a/tests/backends/base/playback.py b/tests/backends/base/playback.py
index 4caaf44b..26662f96 100644
--- a/tests/backends/base/playback.py
+++ b/tests/backends/base/playback.py
@@ -13,7 +13,7 @@ from tests.backends.base import populate_playlist
# TODO Test 'playlist repeat', e.g. repeat=1,single=0
-class BasePlaybackControllerTest(object):
+class PlaybackControllerTest(object):
tracks = []
def setUp(self):
@@ -104,8 +104,8 @@ class BasePlaybackControllerTest(object):
@populate_playlist
def test_play_skips_to_next_track_on_failure(self):
- # If _play() returns False, it is a failure.
- self.playback._play = lambda track: track != self.tracks[0]
+ # If provider.play() returns False, it is a failure.
+ self.playback.provider.play = lambda track: track != self.tracks[0]
self.playback.play()
self.assertNotEqual(self.playback.current_track, self.tracks[0])
self.assertEqual(self.playback.current_track, self.tracks[1])
@@ -164,8 +164,8 @@ class BasePlaybackControllerTest(object):
@populate_playlist
def test_previous_skips_to_previous_track_on_failure(self):
- # If _play() returns False, it is a failure.
- self.playback._play = lambda track: track != self.tracks[1]
+ # If provider.play() returns False, it is a failure.
+ self.playback.provider.play = lambda track: track != self.tracks[1]
self.playback.play(self.current_playlist.cp_tracks[2])
self.assertEqual(self.playback.current_track, self.tracks[2])
self.playback.previous()
@@ -228,8 +228,8 @@ class BasePlaybackControllerTest(object):
@populate_playlist
def test_next_skips_to_next_track_on_failure(self):
- # If _play() returns False, it is a failure.
- self.playback._play = lambda track: track != self.tracks[1]
+ # If provider.play() returns False, it is a failure.
+ self.playback.provider.play = lambda track: track != self.tracks[1]
self.playback.play()
self.assertEqual(self.playback.current_track, self.tracks[0])
self.playback.next()
@@ -364,8 +364,8 @@ class BasePlaybackControllerTest(object):
@populate_playlist
def test_end_of_track_skips_to_next_track_on_failure(self):
- # If _play() returns False, it is a failure.
- self.playback._play = lambda track: track != self.tracks[1]
+ # If provider.play() returns False, it is a failure.
+ self.playback.provider.play = lambda track: track != self.tracks[1]
self.playback.play()
self.assertEqual(self.playback.current_track, self.tracks[0])
self.playback.on_end_of_track()
diff --git a/tests/backends/base/stored_playlists.py b/tests/backends/base/stored_playlists.py
index 5bcd322c..0ac0b167 100644
--- a/tests/backends/base/stored_playlists.py
+++ b/tests/backends/base/stored_playlists.py
@@ -8,7 +8,7 @@ from mopidy.models import Playlist
from tests import SkipTest, data_folder
-class BaseStoredPlaylistsControllerTest(object):
+class StoredPlaylistsControllerTest(object):
def setUp(self):
settings.LOCAL_PLAYLIST_PATH = tempfile.mkdtemp()
settings.LOCAL_TAG_CACHE_FILE = data_folder('library_tag_cache')
diff --git a/tests/backends/libspotify/__init__.py b/tests/backends/libspotify/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/tests/backends/libspotify/backend_integrationtest.py b/tests/backends/libspotify/backend_integrationtest.py
deleted file mode 100644
index 8d1f0b0e..00000000
--- a/tests/backends/libspotify/backend_integrationtest.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# TODO This integration test is work in progress.
-
-import unittest
-
-from mopidy.backends.libspotify import LibspotifyBackend
-from mopidy.models import Track
-
-from tests.backends.base.current_playlist import \
- BaseCurrentPlaylistControllerTest
-from tests.backends.base.library import BaseLibraryControllerTest
-from tests.backends.base.playback import BasePlaybackControllerTest
-from tests.backends.base.stored_playlists import \
- BaseStoredPlaylistsControllerTest
-
-uris = [
- 'spotify:track:6vqcpVcbI3Zu6sH3ieLDNt',
- 'spotify:track:111sulhaZqgsnypz3MkiaW',
- 'spotify:track:7t8oznvbeiAPMDRuK0R5ZT',
-]
-
-class LibspotifyCurrentPlaylistControllerTest(
- BaseCurrentPlaylistControllerTest, unittest.TestCase):
-
- backend_class = LibspotifyBackend
- tracks = [Track(uri=uri, length=4464) for i, uri in enumerate(uris)]
-
-
-class LibspotifyPlaybackControllerTest(
- BasePlaybackControllerTest, unittest.TestCase):
-
- backend_class = LibspotifyBackend
- tracks = [Track(uri=uri, length=4464) for i, uri in enumerate(uris)]
-
-
-class LibspotifyStoredPlaylistsControllerTest(
- BaseStoredPlaylistsControllerTest, unittest.TestCase):
-
- backend_class = LibspotifyBackend
-
-
-class LibspotifyLibraryControllerTest(
- BaseLibraryControllerTest, unittest.TestCase):
-
- backend_class = LibspotifyBackend
diff --git a/tests/backends/local/current_playlist_test.py b/tests/backends/local/current_playlist_test.py
index 3895497a..6f72d7d5 100644
--- a/tests/backends/local/current_playlist_test.py
+++ b/tests/backends/local/current_playlist_test.py
@@ -10,11 +10,10 @@ from mopidy import settings
from mopidy.backends.local import LocalBackend
from mopidy.models import Track
-from tests.backends.base.current_playlist import \
- BaseCurrentPlaylistControllerTest
+from tests.backends.base.current_playlist import CurrentPlaylistControllerTest
from tests.backends.local import generate_song
-class LocalCurrentPlaylistControllerTest(BaseCurrentPlaylistControllerTest,
+class LocalCurrentPlaylistControllerTest(CurrentPlaylistControllerTest,
unittest.TestCase):
backend_class = LocalBackend
diff --git a/tests/backends/local/library_test.py b/tests/backends/local/library_test.py
index 34465d09..0c44924a 100644
--- a/tests/backends/local/library_test.py
+++ b/tests/backends/local/library_test.py
@@ -10,9 +10,9 @@ from mopidy import settings
from mopidy.backends.local import LocalBackend
from tests import data_folder
-from tests.backends.base.library import BaseLibraryControllerTest
+from tests.backends.base.library import LibraryControllerTest
-class LocalLibraryControllerTest(BaseLibraryControllerTest, unittest.TestCase):
+class LocalLibraryControllerTest(LibraryControllerTest, unittest.TestCase):
backend_class = LocalBackend
diff --git a/tests/backends/local/playback_test.py b/tests/backends/local/playback_test.py
index a84dfcde..2007cff8 100644
--- a/tests/backends/local/playback_test.py
+++ b/tests/backends/local/playback_test.py
@@ -12,12 +12,10 @@ from mopidy.models import Track
from mopidy.utils.path import path_to_uri
from tests import data_folder
-from tests.backends.base.playback import BasePlaybackControllerTest
+from tests.backends.base.playback import PlaybackControllerTest
from tests.backends.local import generate_song
-class LocalPlaybackControllerTest(BasePlaybackControllerTest,
- unittest.TestCase):
-
+class LocalPlaybackControllerTest(PlaybackControllerTest, unittest.TestCase):
backend_class = LocalBackend
tracks = [Track(uri=generate_song(i), length=4464)
for i in range(1, 4)]
diff --git a/tests/backends/local/stored_playlists_test.py b/tests/backends/local/stored_playlists_test.py
index 4db9e1e2..a7d9043f 100644
--- a/tests/backends/local/stored_playlists_test.py
+++ b/tests/backends/local/stored_playlists_test.py
@@ -16,10 +16,10 @@ from mopidy.utils.path import path_to_uri
from tests import data_folder
from tests.backends.base.stored_playlists import \
- BaseStoredPlaylistsControllerTest
+ StoredPlaylistsControllerTest
from tests.backends.local import generate_song
-class LocalStoredPlaylistsControllerTest(BaseStoredPlaylistsControllerTest,
+class LocalStoredPlaylistsControllerTest(StoredPlaylistsControllerTest,
unittest.TestCase):
backend_class = LocalBackend
diff --git a/tests/frontends/mpd/current_playlist_test.py b/tests/frontends/mpd/current_playlist_test.py
index 8a4b9ab5..a4179637 100644
--- a/tests/frontends/mpd/current_playlist_test.py
+++ b/tests/frontends/mpd/current_playlist_test.py
@@ -12,7 +12,7 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
def test_add(self):
needle = Track(uri='dummy://foo')
- self.b.library._library = [Track(), Track(), needle, Track()]
+ self.b.library.provider._library = [Track(), Track(), needle, Track()]
self.b.current_playlist.append(
[Track(), Track(), Track(), Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks), 5)
@@ -40,7 +40,7 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
def test_addid_without_songpos(self):
needle = Track(uri='dummy://foo')
- self.b.library._library = [Track(), Track(), needle, Track()]
+ self.b.library.provider._library = [Track(), Track(), needle, Track()]
self.b.current_playlist.append(
[Track(), Track(), Track(), Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks), 5)
@@ -58,7 +58,7 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
def test_addid_with_songpos(self):
needle = Track(uri='dummy://foo')
- self.b.library._library = [Track(), Track(), needle, Track()]
+ self.b.library.provider._library = [Track(), Track(), needle, Track()]
self.b.current_playlist.append(
[Track(), Track(), Track(), Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks), 5)
@@ -71,7 +71,7 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
def test_addid_with_songpos_out_of_bounds_should_ack(self):
needle = Track(uri='dummy://foo')
- self.b.library._library = [Track(), Track(), needle, Track()]
+ self.b.library.provider._library = [Track(), Track(), needle, Track()]
self.b.current_playlist.append(
[Track(), Track(), Track(), Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks), 5)