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)