From f29a0247fb17c745471dbe141bd75bb196ea30d6 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 17 Jun 2011 11:20:23 +0200 Subject: [PATCH 1/7] The tracks should have bitrate=96, not bitrate=2 --- mopidy/backends/spotify/translator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/backends/spotify/translator.py b/mopidy/backends/spotify/translator.py index 1bf7e5aa..95287d77 100644 --- a/mopidy/backends/spotify/translator.py +++ b/mopidy/backends/spotify/translator.py @@ -4,7 +4,7 @@ import logging from spotify import Link, SpotifyError from mopidy import settings -from mopidy.backends.spotify import ENCODING, BITRATES +from mopidy.backends.spotify import ENCODING from mopidy.models import Artist, Album, Track, Playlist logger = logging.getLogger('mopidy.backends.spotify.translator') @@ -44,7 +44,7 @@ class SpotifyTranslator(object): track_no=spotify_track.index(), date=date, length=spotify_track.duration(), - bitrate=BITRATES[settings.SPOTIFY_BITRATE], + bitrate=settings.SPOTIFY_BITRATE, ) @classmethod From 622a99ad3b6fd0d9aaed6c9769ff0c12abce0611 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 20 Jun 2011 19:28:58 +0300 Subject: [PATCH 2/7] Change uri_handlers to uri_schemes on backends --- docs/changes.rst | 4 +++- mopidy/backends/base/__init__.py | 4 ++-- mopidy/backends/dummy/__init__.py | 2 +- mopidy/backends/local/__init__.py | 2 +- mopidy/backends/spotify/__init__.py | 2 +- mopidy/frontends/mpd/protocol/current_playlist.py | 4 ++-- mopidy/frontends/mpd/protocol/reflection.py | 3 ++- tests/backends/local/playback_test.py | 4 ++-- tests/frontends/mpd/reflection_test.py | 2 +- 9 files changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 4125b788..2e5b6659 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -10,7 +10,9 @@ v0.6.0 (in development) **Changes** -- None yet +- Replace :attr:`mopidy.backends.base.Backend.uri_handlers` with + :attr:`mopidy.backends.base.Backend.uri_schemes`, which just takes the part + up to the colon of an URI, and not any prefix. v0.5.0 (2011-06-15) diff --git a/mopidy/backends/base/__init__.py b/mopidy/backends/base/__init__.py index 038e2d7b..76c7f078 100644 --- a/mopidy/backends/base/__init__.py +++ b/mopidy/backends/base/__init__.py @@ -25,5 +25,5 @@ class Backend(object): #: :class:`mopidy.backends.base.StoredPlaylistsController`. stored_playlists = None - #: List of URI prefixes this backend can handle. - uri_handlers = [] + #: List of URI schemes this backend can handle. + uri_schemes = [] diff --git a/mopidy/backends/dummy/__init__.py b/mopidy/backends/dummy/__init__.py index 90c87dac..70efb028 100644 --- a/mopidy/backends/dummy/__init__.py +++ b/mopidy/backends/dummy/__init__.py @@ -32,7 +32,7 @@ class DummyBackend(ThreadingActor, Backend): self.stored_playlists = StoredPlaylistsController(backend=self, provider=stored_playlists_provider) - self.uri_handlers = [u'dummy:'] + self.uri_schemes = [u'dummy'] class DummyLibraryProvider(BaseLibraryProvider): diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 93cf3534..af80a8eb 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -52,7 +52,7 @@ class LocalBackend(ThreadingActor, Backend): self.stored_playlists = StoredPlaylistsController(backend=self, provider=stored_playlists_provider) - self.uri_handlers = [u'file://'] + self.uri_schemes = [u'file'] self.gstreamer = None diff --git a/mopidy/backends/spotify/__init__.py b/mopidy/backends/spotify/__init__.py index 66bcffd4..02ccd802 100644 --- a/mopidy/backends/spotify/__init__.py +++ b/mopidy/backends/spotify/__init__.py @@ -67,7 +67,7 @@ class SpotifyBackend(ThreadingActor, Backend): self.stored_playlists = StoredPlaylistsController(backend=self, provider=stored_playlists_provider) - self.uri_handlers = [u'spotify:', u'http://open.spotify.com/'] + self.uri_schemes = [u'spotify'] self.gstreamer = None self.spotify = None diff --git a/mopidy/frontends/mpd/protocol/current_playlist.py b/mopidy/frontends/mpd/protocol/current_playlist.py index 8e26013d..c7136804 100644 --- a/mopidy/frontends/mpd/protocol/current_playlist.py +++ b/mopidy/frontends/mpd/protocol/current_playlist.py @@ -19,8 +19,8 @@ def add(context, uri): """ if not uri: return - for handler_prefix in context.backend.uri_handlers.get(): - if uri.startswith(handler_prefix): + for uri_scheme in context.backend.uri_schemes.get(): + if uri.startswith(uri_scheme): track = context.backend.library.lookup(uri).get() if track is not None: context.backend.current_playlist.add(track) diff --git a/mopidy/frontends/mpd/protocol/reflection.py b/mopidy/frontends/mpd/protocol/reflection.py index 920f48a5..3618f5e1 100644 --- a/mopidy/frontends/mpd/protocol/reflection.py +++ b/mopidy/frontends/mpd/protocol/reflection.py @@ -95,4 +95,5 @@ def urlhandlers(context): Gets a list of available URL handlers. """ - return [(u'handler', uri) for uri in context.backend.uri_handlers.get()] + return [(u'handler', uri_scheme) + for uri_scheme in context.backend.uri_schemes.get()] diff --git a/tests/backends/local/playback_test.py b/tests/backends/local/playback_test.py index 2cdeadb9..5f80e691 100644 --- a/tests/backends/local/playback_test.py +++ b/tests/backends/local/playback_test.py @@ -36,8 +36,8 @@ class LocalPlaybackControllerTest(PlaybackControllerTest, unittest.TestCase): track = Track(uri=uri, length=4464) self.backend.current_playlist.add(track) - def test_uri_handler(self): - self.assert_('file://' in self.backend.uri_handlers) + def test_uri_scheme(self): + self.assertIn('file', self.backend.uri_schemes) def test_play_mp3(self): self.add_track('blank.mp3') diff --git a/tests/frontends/mpd/reflection_test.py b/tests/frontends/mpd/reflection_test.py index 2abf5acc..c4fd632a 100644 --- a/tests/frontends/mpd/reflection_test.py +++ b/tests/frontends/mpd/reflection_test.py @@ -76,4 +76,4 @@ class ReflectionHandlerTest(unittest.TestCase): def test_urlhandlers(self): result = self.dispatcher.handle_request(u'urlhandlers') self.assert_(u'OK' in result) - self.assert_(u'handler: dummy:' in result) + self.assert_(u'handler: dummy' in result) From 9244087bfd255014359c86b12b0116ffed1397e0 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 27 Jun 2011 19:33:55 +0300 Subject: [PATCH 3/7] Do not include undoced members in the docs --- docs/api/backends/controllers.rst | 5 ----- docs/api/backends/providers.rst | 3 --- docs/api/mixers.rst | 1 - docs/api/models.rst | 1 - docs/modules/frontends/mpd.rst | 6 +----- docs/settings.rst | 1 - 6 files changed, 1 insertion(+), 16 deletions(-) diff --git a/docs/api/backends/controllers.rst b/docs/api/backends/controllers.rst index 28112cf7..20dc2d61 100644 --- a/docs/api/backends/controllers.rst +++ b/docs/api/backends/controllers.rst @@ -15,7 +15,6 @@ The backend .. autoclass:: mopidy.backends.base.Backend :members: - :undoc-members: Playback controller @@ -26,7 +25,6 @@ seek. .. autoclass:: mopidy.backends.base.PlaybackController :members: - :undoc-members: Mixer controller @@ -42,7 +40,6 @@ Manages everything related to the currently loaded playlist. .. autoclass:: mopidy.backends.base.CurrentPlaylistController :members: - :undoc-members: Stored playlists controller @@ -52,7 +49,6 @@ Manages stored playlist. .. autoclass:: mopidy.backends.base.StoredPlaylistsController :members: - :undoc-members: Library controller @@ -62,4 +58,3 @@ 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 index 903e220b..61e5f68a 100644 --- a/docs/api/backends/providers.rst +++ b/docs/api/backends/providers.rst @@ -14,7 +14,6 @@ Playback provider .. autoclass:: mopidy.backends.base.BasePlaybackProvider :members: - :undoc-members: Stored playlists provider @@ -22,7 +21,6 @@ Stored playlists provider .. autoclass:: mopidy.backends.base.BaseStoredPlaylistsProvider :members: - :undoc-members: Library provider @@ -30,7 +28,6 @@ Library provider .. autoclass:: mopidy.backends.base.BaseLibraryProvider :members: - :undoc-members: Backend provider implementations diff --git a/docs/api/mixers.rst b/docs/api/mixers.rst index 6daa7a4e..2459db8c 100644 --- a/docs/api/mixers.rst +++ b/docs/api/mixers.rst @@ -30,7 +30,6 @@ methods as described below. .. automodule:: mopidy.mixers.base :synopsis: Mixer API :members: - :undoc-members: Mixer implementations diff --git a/docs/api/models.rst b/docs/api/models.rst index ef11547e..5833e58c 100644 --- a/docs/api/models.rst +++ b/docs/api/models.rst @@ -25,4 +25,3 @@ Data model API .. automodule:: mopidy.models :synopsis: Data model API :members: - :undoc-members: diff --git a/docs/modules/frontends/mpd.rst b/docs/modules/frontends/mpd.rst index 6120c2a6..6f69b2a9 100644 --- a/docs/modules/frontends/mpd.rst +++ b/docs/modules/frontends/mpd.rst @@ -5,9 +5,8 @@ .. inheritance-diagram:: mopidy.frontends.mpd .. automodule:: mopidy.frontends.mpd - :synopsis: MPD frontend + :synopsis: MPD server frontend :members: - :undoc-members: MPD server @@ -18,7 +17,6 @@ MPD server .. automodule:: mopidy.frontends.mpd.server :synopsis: MPD server :members: - :undoc-members: MPD session @@ -29,7 +27,6 @@ MPD session .. automodule:: mopidy.frontends.mpd.session :synopsis: MPD client session :members: - :undoc-members: MPD dispatcher @@ -40,7 +37,6 @@ MPD dispatcher .. automodule:: mopidy.frontends.mpd.dispatcher :synopsis: MPD request dispatcher :members: - :undoc-members: MPD protocol diff --git a/docs/settings.rst b/docs/settings.rst index f0888670..68adfd55 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -151,4 +151,3 @@ Available settings .. automodule:: mopidy.settings :synopsis: Available settings and their default values :members: - :undoc-members: From 0794a8792a1eb66f287d63be346c2328fda1846d Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 27 Jun 2011 18:43:19 +0300 Subject: [PATCH 4/7] Add BackendListener interface to be impl by any who wants events from the backend --- docs/api/listeners.rst | 7 +++++++ docs/changes.rst | 3 +++ mopidy/listeners.py | 34 ++++++++++++++++++++++++++++++++++ tests/listeners_test.py | 14 ++++++++++++++ 4 files changed, 58 insertions(+) create mode 100644 docs/api/listeners.rst create mode 100644 mopidy/listeners.py create mode 100644 tests/listeners_test.py diff --git a/docs/api/listeners.rst b/docs/api/listeners.rst new file mode 100644 index 00000000..609dc3c7 --- /dev/null +++ b/docs/api/listeners.rst @@ -0,0 +1,7 @@ +************ +Listener API +************ + +.. automodule:: mopidy.listeners + :synopsis: Listener API + :members: diff --git a/docs/changes.rst b/docs/changes.rst index 2e5b6659..2347ddb0 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -13,6 +13,9 @@ v0.6.0 (in development) - Replace :attr:`mopidy.backends.base.Backend.uri_handlers` with :attr:`mopidy.backends.base.Backend.uri_schemes`, which just takes the part up to the colon of an URI, and not any prefix. +- Add Listener API, :mod:`mopidy.listeners`, to be implemented by actors + wanting to receive events from the backend. This is a formalization of the + ad hoc events the Last.fm scrobbler has already been using for some time. v0.5.0 (2011-06-15) diff --git a/mopidy/listeners.py b/mopidy/listeners.py new file mode 100644 index 00000000..f6d1c67e --- /dev/null +++ b/mopidy/listeners.py @@ -0,0 +1,34 @@ +class BackendListener(object): + """ + Marker interface for recipients of events sent by the backend. + + Any Pykka actor that mixes in this class will receive calls to the methods + defined here when the corresponding events happen in the backend. This + interface is used both for looking up what actors to notify of the events, + and for providing default implementations for those listeners that are not + interested in all events. + """ + + def started_playing(self, track): + """ + Called whenever a new track starts playing. + + *MAY* be implemented by actor. + + :param track: the track that just started playing + :type track: :class:`mopidy.models.Track` + """ + pass + + def stopped_playing(self, track, stop_position): + """ + Called whenever playback is stopped. + + *MAY* be implemented by actor. + + :param track: the track that was played before playback stopped + :type track: :class:`mopidy.models.Track` + :param stop_position: the time position when stopped in milliseconds + :type stop_position: int + """ + pass diff --git a/tests/listeners_test.py b/tests/listeners_test.py new file mode 100644 index 00000000..761aff4f --- /dev/null +++ b/tests/listeners_test.py @@ -0,0 +1,14 @@ +import unittest + +from mopidy.listeners import BackendListener +from mopidy.models import Track + +class BackendListenerTest(unittest.TestCase): + def setUp(self): + self.listener = BackendListener() + + def test_listener_has_default_impl_for_the_started_playing_event(self): + self.listener.started_playing(Track()) + + def test_listener_has_default_impl_for_the_stopped_playing_event(self): + self.listener.stopped_playing(Track(), 0) From 4fd83d3641ea6e1193ae03ca53330c6d376d132e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 27 Jun 2011 18:58:32 +0300 Subject: [PATCH 5/7] Update PlaybackController to use new BackendListener --- mopidy/backends/base/playback.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 530c4840..07e286fa 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -4,7 +4,7 @@ import time from pykka.registry import ActorRegistry -from mopidy.frontends.base import BaseFrontend +from mopidy.listeners import BackendListener logger = logging.getLogger('mopidy.backends.base') @@ -462,23 +462,21 @@ class PlaybackController(object): def _trigger_started_playing_event(self): """ - Notifies frontends that a track has started playing. + Notifies implementors of :class:`mopidy.listeners.BackendListener` that + a track has started playing. For internal use only. Should be called by the backend directly after a track has started playing. """ if self.current_track is None: return - frontend_refs = ActorRegistry.get_by_class(BaseFrontend) - for frontend_ref in frontend_refs: - frontend_ref.send_one_way({ - 'command': 'started_playing', - 'track': self.current_track, - }) + for listener_ref in ActorRegistry.get_by_class(BackendListener): + listener_ref.proxy().started_playing(track=self.current_track) def _trigger_stopped_playing_event(self): """ - Notifies frontends that a track has stopped playing. + Notifies implementors of :class:`mopidy.listeners.BackendListener` that + a track has stopped playing. For internal use only. Should be called by the backend before a track is stopped playing, e.g. at the next, previous, and stop actions and at @@ -486,13 +484,9 @@ class PlaybackController(object): """ if self.current_track is None: return - frontend_refs = ActorRegistry.get_by_class(BaseFrontend) - for frontend_ref in frontend_refs: - frontend_ref.send_one_way({ - 'command': 'stopped_playing', - 'track': self.current_track, - 'stop_position': self.time_position, - }) + for listener_ref in ActorRegistry.get_by_class(BackendListener): + listener_ref.proxy().stopped_playing( + track=self.current_track, stop_position=self.time_position) class BasePlaybackProvider(object): From 32bf7af73ff7c5eaca94507f5153a1087ee4e04b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 27 Jun 2011 18:58:42 +0300 Subject: [PATCH 6/7] Update LastfmFrontend to use new BackendListener --- mopidy/frontends/lastfm.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/mopidy/frontends/lastfm.py b/mopidy/frontends/lastfm.py index 04716c61..79ce286d 100644 --- a/mopidy/frontends/lastfm.py +++ b/mopidy/frontends/lastfm.py @@ -11,13 +11,14 @@ from pykka.actor import ThreadingActor from mopidy import settings, SettingsError from mopidy.frontends.base import BaseFrontend +from mopidy.listeners import BackendListener logger = logging.getLogger('mopidy.frontends.lastfm') API_KEY = '2236babefa8ebb3d93ea467560d00d04' API_SECRET = '94d9a09c0cd5be955c4afaeaffcaefcd' -class LastfmFrontend(ThreadingActor, BaseFrontend): +class LastfmFrontend(ThreadingActor, BaseFrontend, BackendListener): """ Frontend which scrobbles the music you play to your `Last.fm `_ profile. @@ -57,14 +58,6 @@ class LastfmFrontend(ThreadingActor, BaseFrontend): logger.error(u'Error during Last.fm setup: %s', e) self.stop() - def on_receive(self, message): - if message.get('command') == 'started_playing': - self.started_playing(message['track']) - elif message.get('command') == 'stopped_playing': - self.stopped_playing(message['track'], message['stop_position']) - else: - pass # Ignore any other messages - def started_playing(self, track): artists = ', '.join([a.name for a in track.artists]) duration = track.length and track.length // 1000 or 0 From 83d2601f680331041ef54bb4960b6c8cf72587e9 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 27 Jun 2011 19:30:57 +0300 Subject: [PATCH 7/7] Replace redundant BaseFrontend class with list of requirements for frontend implementations --- docs/api/frontends.rst | 34 ++++++++++++++++++-------------- mopidy/frontends/base.py | 5 ----- mopidy/frontends/lastfm.py | 3 +-- mopidy/frontends/mpd/__init__.py | 3 +-- 4 files changed, 21 insertions(+), 24 deletions(-) delete mode 100644 mopidy/frontends/base.py diff --git a/docs/api/frontends.rst b/docs/api/frontends.rst index 0c1e32a3..792e4bc9 100644 --- a/docs/api/frontends.rst +++ b/docs/api/frontends.rst @@ -2,22 +2,26 @@ Frontend API ************ -A frontend may do whatever it wants to, including creating threads, opening TCP -ports and exposing Mopidy for a type of clients. - -Frontends got one main limitation: they are restricted to passing messages -through the ``core_queue`` for all communication with the rest of Mopidy. Thus, -the frontend API is very small and reveals little of what a frontend may do. - -.. warning:: - - A stable frontend API is not available yet, as we've only implemented a - couple of frontend modules. - -.. automodule:: mopidy.frontends.base - :synopsis: Base class for frontends - :members: +The following requirements applies to any frontend implementation: +- A frontend MAY do mostly whatever it wants to, including creating threads, + opening TCP ports and exposing Mopidy for a group of clients. +- A frontend MUST implement at least one `Pykka + `_ actor, called the "main actor" from here + on. +- It MAY use additional actors to implement whatever it does, and using actors + in frontend implementations is encouraged. +- The frontend is activated by including its main actor in the + :attr:`mopidy.settings.FRONTENDS` setting. +- The main actor MUST be able to start and stop the frontend when the main + actor is started and stopped. +- The frontend MAY require additional settings to be set for it to + work. +- Such settings MUST be documented. +- The main actor MUST stop itself if the defined settings are not adequate for + the frontend to work properly. +- Any actor which is part of the frontend MAY implement any listener interface + from :mod:`mopidy.listeners` to receive notification of the specified events. Frontend implementations ======================== diff --git a/mopidy/frontends/base.py b/mopidy/frontends/base.py deleted file mode 100644 index 811644b1..00000000 --- a/mopidy/frontends/base.py +++ /dev/null @@ -1,5 +0,0 @@ -class BaseFrontend(object): - """ - Base class for frontends. - """ - pass diff --git a/mopidy/frontends/lastfm.py b/mopidy/frontends/lastfm.py index 79ce286d..33e724c0 100644 --- a/mopidy/frontends/lastfm.py +++ b/mopidy/frontends/lastfm.py @@ -10,7 +10,6 @@ except ImportError as import_error: from pykka.actor import ThreadingActor from mopidy import settings, SettingsError -from mopidy.frontends.base import BaseFrontend from mopidy.listeners import BackendListener logger = logging.getLogger('mopidy.frontends.lastfm') @@ -18,7 +17,7 @@ logger = logging.getLogger('mopidy.frontends.lastfm') API_KEY = '2236babefa8ebb3d93ea467560d00d04' API_SECRET = '94d9a09c0cd5be955c4afaeaffcaefcd' -class LastfmFrontend(ThreadingActor, BaseFrontend, BackendListener): +class LastfmFrontend(ThreadingActor, BackendListener): """ Frontend which scrobbles the music you play to your `Last.fm `_ profile. diff --git a/mopidy/frontends/mpd/__init__.py b/mopidy/frontends/mpd/__init__.py index 175aa0ee..f37b2deb 100644 --- a/mopidy/frontends/mpd/__init__.py +++ b/mopidy/frontends/mpd/__init__.py @@ -3,13 +3,12 @@ import logging from pykka.actor import ThreadingActor -from mopidy.frontends.base import BaseFrontend from mopidy.frontends.mpd.server import MpdServer from mopidy.utils.process import BaseThread logger = logging.getLogger('mopidy.frontends.mpd') -class MpdFrontend(ThreadingActor, BaseFrontend): +class MpdFrontend(ThreadingActor): """ The MPD frontend.