From 6640e2da172b1fd43a6c64f3626679149cfe3f41 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 20 May 2011 22:02:14 +0200 Subject: [PATCH 01/48] Use tag event to set tags instead of taginject which is a debug feature (fixes #93) --- mopidy/gstreamer.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index c79b742f..45cd39dc 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -36,7 +36,6 @@ class GStreamer(ThreadingActor): def __init__(self): self._pipeline = None self._source = None - self._taginject = None self._tee = None self._uridecodebin = None self._volume = None @@ -56,13 +55,11 @@ class GStreamer(ThreadingActor): 'uridecodebin name=uri', 'audioconvert name=convert', 'volume name=volume', - 'taginject name=inject', 'tee name=tee']) logger.debug(u'Setting up base GStreamer pipeline: %s', description) self._pipeline = gst.parse_launch(description) - self._taginject = self._pipeline.get_by_name('inject') self._tee = self._pipeline.get_by_name('tee') self._volume = self._pipeline.get_by_name('volume') self._uridecodebin = self._pipeline.get_by_name('uri') @@ -277,13 +274,19 @@ class GStreamer(ThreadingActor): :param track: the current track :type track: :class:`mopidy.modes.Track` """ - # FIXME what if we want to unset taginject tags? - tags = u'artist="%(artist)s",title="%(title)s"' % { - 'artist': u', '.join([a.name for a in track.artists]), - 'title': track.name, - } - logger.debug('Setting tags to: %s', tags) - self._taginject.set_property('tags', tags) + taglist = gst.TagList() + artists = [a for a in (track.artists or []) if a.name] + + if artists: + taglist[gst.TAG_ARTIST] = u', '.join([a.name for a in artists]) + if track.name: + taglist[gst.TAG_TITLE] = track.name + if track.album and track.album.name: + taglist[gst.TAG_ALBUM] = track.album.name + + event = gst.event_new_tag(taglist) + self._pipeline.send_event(event) + logger.debug('Setting tags to: %s', taglist) def connect_output(self, output): """ From 3ce42b2f6b21ce51aa63d227abcd2d463c2b084c Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Fri, 20 May 2011 22:07:57 +0200 Subject: [PATCH 02/48] Remove logging of taglist --- mopidy/gstreamer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mopidy/gstreamer.py b/mopidy/gstreamer.py index 45cd39dc..f507cd28 100644 --- a/mopidy/gstreamer.py +++ b/mopidy/gstreamer.py @@ -286,7 +286,6 @@ class GStreamer(ThreadingActor): event = gst.event_new_tag(taglist) self._pipeline.send_event(event) - logger.debug('Setting tags to: %s', taglist) def connect_output(self, output): """ From 9c23949284bf2927e76649a6c981e1eeedbed0ee Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 23 May 2011 10:13:57 +0200 Subject: [PATCH 03/48] Homebrew no longer carries pip --- docs/installation/index.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/installation/index.rst b/docs/installation/index.rst index d1fbd0f6..1f497e3a 100644 --- a/docs/installation/index.rst +++ b/docs/installation/index.rst @@ -98,7 +98,7 @@ install Mopidy from PyPI using Pip. #. Then, you need to install Pip:: sudo aptitude install python-setuptools python-pip # On Ubuntu/Debian - sudo brew install pip # On OS X + sudo easy_install pip # On OS X #. To install the currently latest stable release of Mopidy:: @@ -132,7 +132,7 @@ Mopidy's ``develop`` branch. #. Then, you need to install Pip:: sudo aptitude install python-setuptools python-pip # On Ubuntu/Debian - sudo brew install pip # On OS X + sudo easy_install pip # On OS X #. To install the latest snapshot of Mopidy, run:: @@ -155,7 +155,7 @@ If you want to contribute to Mopidy, you should install Mopidy using Git. #. Then install Git, if haven't already:: sudo aptitude install git-core # On Ubuntu/Debian - sudo brew install git # On OS X + sudo brew install git # On OS X using Homebrew #. Clone the official Mopidy repository, or your own fork of it:: From b992277e7fa669bdf1f5cb8408959fd4b558e044 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 24 May 2011 21:15:12 +0200 Subject: [PATCH 04/48] docs: Move changes further down the front page --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 0af45835..769aed20 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -33,13 +33,13 @@ User documentation .. toctree:: :maxdepth: 3 - changes installation/index settings running clients/index authors licenses + changes Reference documentation ======================= From 86e90f14c6233467a5f73dbd530fb048591e7a99 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 25 May 2011 20:47:52 +0200 Subject: [PATCH 05/48] Add MpdSystemError exception --- mopidy/frontends/mpd/exceptions.py | 5 +++++ tests/frontends/mpd/exception_test.py | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/mopidy/frontends/mpd/exceptions.py b/mopidy/frontends/mpd/exceptions.py index faf4ce2f..df90aed7 100644 --- a/mopidy/frontends/mpd/exceptions.py +++ b/mopidy/frontends/mpd/exceptions.py @@ -54,6 +54,11 @@ class MpdNoExistError(MpdAckError): super(MpdNoExistError, self).__init__(*args, **kwargs) self.error_code = MpdAckError.ACK_ERROR_NO_EXIST +class MpdSystemError(MpdAckError): + def __init__(self, *args, **kwargs): + super(MpdSystemError, self).__init__(*args, **kwargs) + self.error_code = MpdAckError.ACK_ERROR_SYSTEM + class MpdNotImplemented(MpdAckError): def __init__(self, *args, **kwargs): super(MpdNotImplemented, self).__init__(*args, **kwargs) diff --git a/tests/frontends/mpd/exception_test.py b/tests/frontends/mpd/exception_test.py index ef222d46..9b1b47a2 100644 --- a/tests/frontends/mpd/exception_test.py +++ b/tests/frontends/mpd/exception_test.py @@ -1,7 +1,7 @@ import unittest from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdUnknownCommand, - MpdNotImplemented) + MpdSystemError, MpdNotImplemented) class MpdExceptionsTest(unittest.TestCase): def test_key_error_wrapped_in_mpd_ack_error(self): @@ -36,3 +36,10 @@ class MpdExceptionsTest(unittest.TestCase): except MpdAckError as e: self.assertEqual(e.get_mpd_ack(), u'ACK [5@0] {} unknown command "play"') + + def test_mpd_system_error(self): + try: + raise MpdSystemError('foo') + except MpdSystemError as e: + self.assertEqual(e.get_mpd_ack(), + u'ACK [52@0] {} foo') From 63918ac3f3864ec315b1b0be63d5eb54e8fb750c Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 25 May 2011 20:52:54 +0200 Subject: [PATCH 06/48] Log a warning if MPD tries to communicate with dead actors. --- mopidy/frontends/mpd/dispatcher.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index f5c30b23..a72789f1 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -1,10 +1,12 @@ +import logging import re +from pykka import ActorDeadError from pykka.registry import ActorRegistry from mopidy.backends.base import Backend from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdArgError, - MpdUnknownCommand) + MpdUnknownCommand, MpdSystemError) from mopidy.frontends.mpd.protocol import mpd_commands, request_handlers # Do not remove the following import. The protocol modules must be imported to # get them registered as request handlers. @@ -16,6 +18,8 @@ from mopidy.frontends.mpd.protocol import (audio_output, command_list, from mopidy.mixers.base import BaseMixer from mopidy.utils import flatten +logger = logging.getLogger('mopidy.frontends.mpd.dispatcher') + class MpdDispatcher(object): """ The MPD session feeds the MPD dispatcher with requests. The dispatcher @@ -49,6 +53,10 @@ class MpdDispatcher(object): if command_list_index is not None: e.index = command_list_index return self.handle_response(e.get_mpd_ack(), add_ok=False) + except ActorDeadError as e: + logger.warning(u'Tried to communicate with dead actor.') + mpd_error = MpdSystemError(e.message) + return self.handle_response(mpd_error.get_mpd_ack(), add_ok=False) if request in (u'command_list_begin', u'command_list_ok_begin'): return None if command_list_index is not None: From fc9875bf3d39da85026fcd564ee7acae257972ec Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 25 May 2011 21:08:34 +0200 Subject: [PATCH 07/48] Spotify albums may be None --- mopidy/backends/spotify/translator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mopidy/backends/spotify/translator.py b/mopidy/backends/spotify/translator.py index dca2b285..15aa59ac 100644 --- a/mopidy/backends/spotify/translator.py +++ b/mopidy/backends/spotify/translator.py @@ -21,7 +21,7 @@ class SpotifyTranslator(object): @classmethod def to_mopidy_album(cls, spotify_album): - if not spotify_album.is_loaded(): + if spotify_album is None or not spotify_album.is_loaded(): return Album(name=u'[loading...]') # TODO pyspotify got much more data on albums than this return Album(name=spotify_album.name().decode(ENCODING)) @@ -31,7 +31,8 @@ class SpotifyTranslator(object): uri = str(Link.from_track(spotify_track, 0)) if not spotify_track.is_loaded(): return Track(uri=uri, name=u'[loading...]') - if dt.MINYEAR <= int(spotify_track.album().year()) <= dt.MAXYEAR: + if (spotify_track.album() is not None and + dt.MINYEAR <= int(spotify_track.album().year()) <= dt.MAXYEAR): date = dt.date(spotify_track.album().year(), 1, 1) else: date = None From 55bc7b19fb1d4a1f7c311cf608176767b24031d5 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 25 May 2011 23:05:54 +0200 Subject: [PATCH 08/48] Remove dead code in BaseThread --- mopidy/utils/process.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/mopidy/utils/process.py b/mopidy/utils/process.py index dbc6cada..cf676519 100644 --- a/mopidy/utils/process.py +++ b/mopidy/utils/process.py @@ -21,26 +21,17 @@ class BaseThread(threading.Thread): self.run_inside_try() except KeyboardInterrupt: logger.info(u'Interrupted by user') - self.exit(0, u'Interrupted by user') except SettingsError as e: logger.error(e.message) - self.exit(1, u'Settings error') except ImportError as e: logger.error(e) - self.exit(2, u'Import error') except Exception as e: logger.exception(e) - self.exit(3, u'Unknown error') + logger.debug(u'%s: Exiting thread', self.name) def run_inside_try(self): raise NotImplementedError - def destroy(self): - pass - - def exit(self, status=0, reason=None): - self.destroy() - class GObjectEventThread(BaseThread): """ From 3b27ba47464aad9d79b8378d19c30e23bfca809d Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 25 May 2011 23:06:15 +0200 Subject: [PATCH 09/48] Catch and log ActorDeadError in BaseThread --- mopidy/utils/process.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mopidy/utils/process.py b/mopidy/utils/process.py index cf676519..7f6cf664 100644 --- a/mopidy/utils/process.py +++ b/mopidy/utils/process.py @@ -4,6 +4,8 @@ import threading import gobject gobject.threads_init() +from pykka import ActorDeadError + from mopidy import SettingsError logger = logging.getLogger('mopidy.utils.process') @@ -25,6 +27,8 @@ class BaseThread(threading.Thread): logger.error(e.message) except ImportError as e: logger.error(e) + except ActorDeadError as e: + logger.warning(e) except Exception as e: logger.exception(e) logger.debug(u'%s: Exiting thread', self.name) From 0e098e9b60a2665d0d29ee4a357c36f673b26ef2 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Thu, 2 Jun 2011 18:38:11 +0200 Subject: [PATCH 10/48] Replace not decodable characters returned from Spotify instead of crashing --- docs/changes.rst | 3 +++ mopidy/backends/spotify/translator.py | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 6d7117c6..b9df87df 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -26,6 +26,9 @@ No description yet. - Improve :option:`--list-settings` output. (Fixes: :issue:`91`) +- Replace not decodable characters returned from Spotify instead of throwing an + exception, as we won't try to figure out the encoding of non-UTF-8-data. + v0.4.1 (2011-05-06) =================== diff --git a/mopidy/backends/spotify/translator.py b/mopidy/backends/spotify/translator.py index 15aa59ac..21abdf78 100644 --- a/mopidy/backends/spotify/translator.py +++ b/mopidy/backends/spotify/translator.py @@ -16,7 +16,7 @@ class SpotifyTranslator(object): return Artist(name=u'[loading...]') return Artist( uri=str(Link.from_artist(spotify_artist)), - name=spotify_artist.name().decode(ENCODING), + name=spotify_artist.name().decode(ENCODING, 'replace'), ) @classmethod @@ -24,7 +24,7 @@ class SpotifyTranslator(object): if spotify_album is None or not spotify_album.is_loaded(): return Album(name=u'[loading...]') # TODO pyspotify got much more data on albums than this - return Album(name=spotify_album.name().decode(ENCODING)) + return Album(name=spotify_album.name().decode(ENCODING, 'replace')) @classmethod def to_mopidy_track(cls, spotify_track): @@ -38,7 +38,7 @@ class SpotifyTranslator(object): date = None return Track( uri=uri, - name=spotify_track.name().decode(ENCODING), + name=spotify_track.name().decode(ENCODING, 'replace'), artists=[cls.to_mopidy_artist(a) for a in spotify_track.artists()], album=cls.to_mopidy_album(spotify_track.album()), track_no=spotify_track.index(), @@ -57,7 +57,7 @@ class SpotifyTranslator(object): try: return Playlist( uri=str(Link.from_playlist(spotify_playlist)), - name=spotify_playlist.name().decode(ENCODING), + name=spotify_playlist.name().decode(ENCODING, 'replace'), # FIXME if check on link is a hackish workaround for is_local tracks=[cls.to_mopidy_track(t) for t in spotify_playlist if str(Link.from_track(t, 0))], From 76d0314eff7854db33e4bb4b1ff7b475e9d0e7a3 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 16:08:53 +0200 Subject: [PATCH 11/48] Replace 'frontend' with 'context' in MPD protocol impl --- mopidy/frontends/mpd/protocol/audio_output.py | 6 +- mopidy/frontends/mpd/protocol/command_list.py | 24 ++-- mopidy/frontends/mpd/protocol/connection.py | 8 +- .../mpd/protocol/current_playlist.py | 132 +++++++++--------- mopidy/frontends/mpd/protocol/empty.py | 2 +- mopidy/frontends/mpd/protocol/music_db.py | 48 +++---- mopidy/frontends/mpd/protocol/playback.py | 116 +++++++-------- mopidy/frontends/mpd/protocol/reflection.py | 12 +- mopidy/frontends/mpd/protocol/status.py | 110 +++++++-------- mopidy/frontends/mpd/protocol/stickers.py | 10 +- .../mpd/protocol/stored_playlists.py | 32 ++--- 11 files changed, 250 insertions(+), 250 deletions(-) diff --git a/mopidy/frontends/mpd/protocol/audio_output.py b/mopidy/frontends/mpd/protocol/audio_output.py index 98c1d645..6111332a 100644 --- a/mopidy/frontends/mpd/protocol/audio_output.py +++ b/mopidy/frontends/mpd/protocol/audio_output.py @@ -2,7 +2,7 @@ from mopidy.frontends.mpd.protocol import handle_pattern from mopidy.frontends.mpd.exceptions import MpdNotImplemented @handle_pattern(r'^disableoutput "(?P\d+)"$') -def disableoutput(frontend, outputid): +def disableoutput(context, outputid): """ *musicpd.org, audio output section:* @@ -13,7 +13,7 @@ def disableoutput(frontend, outputid): raise MpdNotImplemented # TODO @handle_pattern(r'^enableoutput "(?P\d+)"$') -def enableoutput(frontend, outputid): +def enableoutput(context, outputid): """ *musicpd.org, audio output section:* @@ -24,7 +24,7 @@ def enableoutput(frontend, outputid): raise MpdNotImplemented # TODO @handle_pattern(r'^outputs$') -def outputs(frontend): +def outputs(context): """ *musicpd.org, audio output section:* diff --git a/mopidy/frontends/mpd/protocol/command_list.py b/mopidy/frontends/mpd/protocol/command_list.py index b3df0be6..cecff9fd 100644 --- a/mopidy/frontends/mpd/protocol/command_list.py +++ b/mopidy/frontends/mpd/protocol/command_list.py @@ -2,7 +2,7 @@ from mopidy.frontends.mpd.protocol import handle_pattern from mopidy.frontends.mpd.exceptions import MpdUnknownCommand @handle_pattern(r'^command_list_begin$') -def command_list_begin(frontend): +def command_list_begin(context): """ *musicpd.org, command list section:* @@ -18,21 +18,21 @@ def command_list_begin(frontend): returned. If ``command_list_ok_begin`` is used, ``list_OK`` is returned for each successful command executed in the command list. """ - frontend.command_list = [] - frontend.command_list_ok = False + context.command_list = [] + context.command_list_ok = False @handle_pattern(r'^command_list_end$') -def command_list_end(frontend): +def command_list_end(context): """See :meth:`command_list_begin()`.""" - if frontend.command_list is False: + if context.command_list is False: # Test for False exactly, and not e.g. empty list raise MpdUnknownCommand(command='command_list_end') - (command_list, frontend.command_list) = (frontend.command_list, False) - (command_list_ok, frontend.command_list_ok) = ( - frontend.command_list_ok, False) + (command_list, context.command_list) = (context.command_list, False) + (command_list_ok, context.command_list_ok) = ( + context.command_list_ok, False) result = [] for i, command in enumerate(command_list): - response = frontend.handle_request(command, command_list_index=i) + response = context.handle_request(command, command_list_index=i) if response is not None: result.append(response) if response and response[-1].startswith(u'ACK'): @@ -42,7 +42,7 @@ def command_list_end(frontend): return result @handle_pattern(r'^command_list_ok_begin$') -def command_list_ok_begin(frontend): +def command_list_ok_begin(context): """See :meth:`command_list_begin()`.""" - frontend.command_list = [] - frontend.command_list_ok = True + context.command_list = [] + context.command_list_ok = True diff --git a/mopidy/frontends/mpd/protocol/connection.py b/mopidy/frontends/mpd/protocol/connection.py index 65811d09..48e5ebd4 100644 --- a/mopidy/frontends/mpd/protocol/connection.py +++ b/mopidy/frontends/mpd/protocol/connection.py @@ -3,7 +3,7 @@ from mopidy.frontends.mpd.protocol import handle_pattern from mopidy.frontends.mpd.exceptions import MpdPasswordError @handle_pattern(r'^close$') -def close(frontend): +def close(context): """ *musicpd.org, connection section:* @@ -14,7 +14,7 @@ def close(frontend): pass # TODO @handle_pattern(r'^kill$') -def kill(frontend): +def kill(context): """ *musicpd.org, connection section:* @@ -25,7 +25,7 @@ def kill(frontend): pass # TODO @handle_pattern(r'^password "(?P[^"]+)"$') -def password_(frontend, password): +def password_(context, password): """ *musicpd.org, connection section:* @@ -41,7 +41,7 @@ def password_(frontend, password): raise MpdPasswordError(u'incorrect password', command=u'password') @handle_pattern(r'^ping$') -def ping(frontend): +def ping(context): """ *musicpd.org, connection section:* diff --git a/mopidy/frontends/mpd/protocol/current_playlist.py b/mopidy/frontends/mpd/protocol/current_playlist.py index 8ef5e026..e73e0a9c 100644 --- a/mopidy/frontends/mpd/protocol/current_playlist.py +++ b/mopidy/frontends/mpd/protocol/current_playlist.py @@ -4,7 +4,7 @@ from mopidy.frontends.mpd.protocol import handle_pattern from mopidy.frontends.mpd.translator import tracks_to_mpd_format @handle_pattern(r'^add "(?P[^"]*)"$') -def add(frontend, uri): +def add(context, uri): """ *musicpd.org, current playlist section:* @@ -19,17 +19,17 @@ def add(frontend, uri): """ if not uri: return - for handler_prefix in frontend.backend.uri_handlers.get(): + for handler_prefix in context.backend.uri_handlers.get(): if uri.startswith(handler_prefix): - track = frontend.backend.library.lookup(uri).get() + track = context.backend.library.lookup(uri).get() if track is not None: - frontend.backend.current_playlist.add(track) + context.backend.current_playlist.add(track) return raise MpdNoExistError( u'directory or file not found', command=u'add') @handle_pattern(r'^addid "(?P[^"]*)"( "(?P\d+)")*$') -def addid(frontend, uri, songpos=None): +def addid(context, uri, songpos=None): """ *musicpd.org, current playlist section:* @@ -51,18 +51,18 @@ def addid(frontend, uri, songpos=None): raise MpdNoExistError(u'No such song', command=u'addid') if songpos is not None: songpos = int(songpos) - track = frontend.backend.library.lookup(uri).get() + track = context.backend.library.lookup(uri).get() if track is None: raise MpdNoExistError(u'No such song', command=u'addid') if songpos and songpos > len( - frontend.backend.current_playlist.tracks.get()): + context.backend.current_playlist.tracks.get()): raise MpdArgError(u'Bad song index', command=u'addid') - cp_track = frontend.backend.current_playlist.add(track, + cp_track = context.backend.current_playlist.add(track, at_position=songpos).get() return ('Id', cp_track[0]) @handle_pattern(r'^delete "(?P\d+):(?P\d+)*"$') -def delete_range(frontend, start, end=None): +def delete_range(context, start, end=None): """ *musicpd.org, current playlist section:* @@ -74,25 +74,25 @@ def delete_range(frontend, start, end=None): if end is not None: end = int(end) else: - end = len(frontend.backend.current_playlist.tracks.get()) - cp_tracks = frontend.backend.current_playlist.cp_tracks.get()[start:end] + end = len(context.backend.current_playlist.tracks.get()) + cp_tracks = context.backend.current_playlist.cp_tracks.get()[start:end] if not cp_tracks: raise MpdArgError(u'Bad song index', command=u'delete') for (cpid, _) in cp_tracks: - frontend.backend.current_playlist.remove(cpid=cpid) + context.backend.current_playlist.remove(cpid=cpid) @handle_pattern(r'^delete "(?P\d+)"$') -def delete_songpos(frontend, songpos): +def delete_songpos(context, songpos): """See :meth:`delete_range`""" try: songpos = int(songpos) - (cpid, _) = frontend.backend.current_playlist.cp_tracks.get()[songpos] - frontend.backend.current_playlist.remove(cpid=cpid) + (cpid, _) = context.backend.current_playlist.cp_tracks.get()[songpos] + context.backend.current_playlist.remove(cpid=cpid) except IndexError: raise MpdArgError(u'Bad song index', command=u'delete') @handle_pattern(r'^deleteid "(?P\d+)"$') -def deleteid(frontend, cpid): +def deleteid(context, cpid): """ *musicpd.org, current playlist section:* @@ -102,14 +102,14 @@ def deleteid(frontend, cpid): """ try: cpid = int(cpid) - if frontend.backend.playback.current_cpid.get() == cpid: - frontend.backend.playback.next() - return frontend.backend.current_playlist.remove(cpid=cpid).get() + if context.backend.playback.current_cpid.get() == cpid: + context.backend.playback.next() + return context.backend.current_playlist.remove(cpid=cpid).get() except LookupError: raise MpdNoExistError(u'No such song', command=u'deleteid') @handle_pattern(r'^clear$') -def clear(frontend): +def clear(context): """ *musicpd.org, current playlist section:* @@ -117,10 +117,10 @@ def clear(frontend): Clears the current playlist. """ - frontend.backend.current_playlist.clear() + context.backend.current_playlist.clear() @handle_pattern(r'^move "(?P\d+):(?P\d+)*" "(?P\d+)"$') -def move_range(frontend, start, to, end=None): +def move_range(context, start, to, end=None): """ *musicpd.org, current playlist section:* @@ -130,21 +130,21 @@ def move_range(frontend, start, to, end=None): ``TO`` in the playlist. """ if end is None: - end = len(frontend.backend.current_playlist.tracks.get()) + end = len(context.backend.current_playlist.tracks.get()) start = int(start) end = int(end) to = int(to) - frontend.backend.current_playlist.move(start, end, to) + context.backend.current_playlist.move(start, end, to) @handle_pattern(r'^move "(?P\d+)" "(?P\d+)"$') -def move_songpos(frontend, songpos, to): +def move_songpos(context, songpos, to): """See :meth:`move_range`.""" songpos = int(songpos) to = int(to) - frontend.backend.current_playlist.move(songpos, songpos + 1, to) + context.backend.current_playlist.move(songpos, songpos + 1, to) @handle_pattern(r'^moveid "(?P\d+)" "(?P\d+)"$') -def moveid(frontend, cpid, to): +def moveid(context, cpid, to): """ *musicpd.org, current playlist section:* @@ -156,13 +156,13 @@ def moveid(frontend, cpid, to): """ cpid = int(cpid) to = int(to) - cp_track = frontend.backend.current_playlist.get(cpid=cpid).get() - position = frontend.backend.current_playlist.cp_tracks.get().index( + cp_track = context.backend.current_playlist.get(cpid=cpid).get() + position = context.backend.current_playlist.cp_tracks.get().index( cp_track) - frontend.backend.current_playlist.move(position, position + 1, to) + context.backend.current_playlist.move(position, position + 1, to) @handle_pattern(r'^playlist$') -def playlist(frontend): +def playlist(context): """ *musicpd.org, current playlist section:* @@ -174,11 +174,11 @@ def playlist(frontend): Do not use this, instead use ``playlistinfo``. """ - return playlistinfo(frontend) + return playlistinfo(context) @handle_pattern(r'^playlistfind (?P[^"]+) "(?P[^"]+)"$') @handle_pattern(r'^playlistfind "(?P[^"]+)" "(?P[^"]+)"$') -def playlistfind(frontend, tag, needle): +def playlistfind(context, tag, needle): """ *musicpd.org, current playlist section:* @@ -192,9 +192,9 @@ def playlistfind(frontend, tag, needle): """ if tag == 'filename': try: - cp_track = frontend.backend.current_playlist.get(uri=needle).get() + cp_track = context.backend.current_playlist.get(uri=needle).get() (cpid, track) = cp_track - position = frontend.backend.current_playlist.cp_tracks.get().index( + position = context.backend.current_playlist.cp_tracks.get().index( cp_track) return track.mpd_format(cpid=cpid, position=position) except LookupError: @@ -202,7 +202,7 @@ def playlistfind(frontend, tag, needle): raise MpdNotImplemented # TODO @handle_pattern(r'^playlistid( "(?P\d+)")*$') -def playlistid(frontend, cpid=None): +def playlistid(context, cpid=None): """ *musicpd.org, current playlist section:* @@ -214,22 +214,22 @@ def playlistid(frontend, cpid=None): if cpid is not None: try: cpid = int(cpid) - cp_track = frontend.backend.current_playlist.get(cpid=cpid).get() - position = frontend.backend.current_playlist.cp_tracks.get().index( + cp_track = context.backend.current_playlist.get(cpid=cpid).get() + position = context.backend.current_playlist.cp_tracks.get().index( cp_track) return cp_track[1].mpd_format(position=position, cpid=cpid) except LookupError: raise MpdNoExistError(u'No such song', command=u'playlistid') else: cpids = [ct[0] for ct in - frontend.backend.current_playlist.cp_tracks.get()] + context.backend.current_playlist.cp_tracks.get()] return tracks_to_mpd_format( - frontend.backend.current_playlist.tracks.get(), cpids=cpids) + context.backend.current_playlist.tracks.get(), cpids=cpids) @handle_pattern(r'^playlistinfo$') @handle_pattern(r'^playlistinfo "(?P-?\d+)"$') @handle_pattern(r'^playlistinfo "(?P\d+):(?P\d+)*"$') -def playlistinfo(frontend, songpos=None, +def playlistinfo(context, songpos=None, start=None, end=None): """ *musicpd.org, current playlist section:* @@ -255,30 +255,30 @@ def playlistinfo(frontend, songpos=None, if start == -1: end = None cpids = [ct[0] for ct in - frontend.backend.current_playlist.cp_tracks.get()] + context.backend.current_playlist.cp_tracks.get()] return tracks_to_mpd_format( - frontend.backend.current_playlist.tracks.get(), + context.backend.current_playlist.tracks.get(), start, end, cpids=cpids) else: if start is None: start = 0 start = int(start) if not (0 <= start <= len( - frontend.backend.current_playlist.tracks.get())): + context.backend.current_playlist.tracks.get())): raise MpdArgError(u'Bad song index', command=u'playlistinfo') if end is not None: end = int(end) - if end > len(frontend.backend.current_playlist.tracks.get()): + if end > len(context.backend.current_playlist.tracks.get()): end = None cpids = [ct[0] for ct in - frontend.backend.current_playlist.cp_tracks.get()] + context.backend.current_playlist.cp_tracks.get()] return tracks_to_mpd_format( - frontend.backend.current_playlist.tracks.get(), + context.backend.current_playlist.tracks.get(), start, end, cpids=cpids) @handle_pattern(r'^playlistsearch "(?P[^"]+)" "(?P[^"]+)"$') @handle_pattern(r'^playlistsearch (?P\S+) "(?P[^"]+)"$') -def playlistsearch(frontend, tag, needle): +def playlistsearch(context, tag, needle): """ *musicpd.org, current playlist section:* @@ -296,7 +296,7 @@ def playlistsearch(frontend, tag, needle): @handle_pattern(r'^plchanges (?P-?\d+)$') @handle_pattern(r'^plchanges "(?P-?\d+)"$') -def plchanges(frontend, version): +def plchanges(context, version): """ *musicpd.org, current playlist section:* @@ -312,14 +312,14 @@ def plchanges(frontend, version): - Calls ``plchanges "-1"`` two times per second to get the entire playlist. """ # XXX Naive implementation that returns all tracks as changed - if int(version) < frontend.backend.current_playlist.version: + if int(version) < context.backend.current_playlist.version: cpids = [ct[0] for ct in - frontend.backend.current_playlist.cp_tracks.get()] + context.backend.current_playlist.cp_tracks.get()] return tracks_to_mpd_format( - frontend.backend.current_playlist.tracks.get(), cpids=cpids) + context.backend.current_playlist.tracks.get(), cpids=cpids) @handle_pattern(r'^plchangesposid "(?P\d+)"$') -def plchangesposid(frontend, version): +def plchangesposid(context, version): """ *musicpd.org, current playlist section:* @@ -333,17 +333,17 @@ def plchangesposid(frontend, version): ``playlistlength`` returned by status command. """ # XXX Naive implementation that returns all tracks as changed - if int(version) != frontend.backend.current_playlist.version.get(): + if int(version) != context.backend.current_playlist.version.get(): result = [] for (position, (cpid, _)) in enumerate( - frontend.backend.current_playlist.cp_tracks.get()): + context.backend.current_playlist.cp_tracks.get()): result.append((u'cpos', position)) result.append((u'Id', cpid)) return result @handle_pattern(r'^shuffle$') @handle_pattern(r'^shuffle "(?P\d+):(?P\d+)*"$') -def shuffle(frontend, start=None, end=None): +def shuffle(context, start=None, end=None): """ *musicpd.org, current playlist section:* @@ -356,10 +356,10 @@ def shuffle(frontend, start=None, end=None): start = int(start) if end is not None: end = int(end) - frontend.backend.current_playlist.shuffle(start, end) + context.backend.current_playlist.shuffle(start, end) @handle_pattern(r'^swap "(?P\d+)" "(?P\d+)"$') -def swap(frontend, songpos1, songpos2): +def swap(context, songpos1, songpos2): """ *musicpd.org, current playlist section:* @@ -369,18 +369,18 @@ def swap(frontend, songpos1, songpos2): """ songpos1 = int(songpos1) songpos2 = int(songpos2) - tracks = frontend.backend.current_playlist.tracks.get() + tracks = context.backend.current_playlist.tracks.get() song1 = tracks[songpos1] song2 = tracks[songpos2] del tracks[songpos1] tracks.insert(songpos1, song2) del tracks[songpos2] tracks.insert(songpos2, song1) - frontend.backend.current_playlist.clear() - frontend.backend.current_playlist.append(tracks) + context.backend.current_playlist.clear() + context.backend.current_playlist.append(tracks) @handle_pattern(r'^swapid "(?P\d+)" "(?P\d+)"$') -def swapid(frontend, cpid1, cpid2): +def swapid(context, cpid1, cpid2): """ *musicpd.org, current playlist section:* @@ -390,9 +390,9 @@ def swapid(frontend, cpid1, cpid2): """ cpid1 = int(cpid1) cpid2 = int(cpid2) - cp_track1 = frontend.backend.current_playlist.get(cpid=cpid1).get() - cp_track2 = frontend.backend.current_playlist.get(cpid=cpid2).get() - cp_tracks = frontend.backend.current_playlist.cp_tracks.get() + cp_track1 = context.backend.current_playlist.get(cpid=cpid1).get() + cp_track2 = context.backend.current_playlist.get(cpid=cpid2).get() + cp_tracks = context.backend.current_playlist.cp_tracks.get() position1 = cp_tracks.index(cp_track1) position2 = cp_tracks.index(cp_track2) - swap(frontend, position1, position2) + swap(context, position1, position2) diff --git a/mopidy/frontends/mpd/protocol/empty.py b/mopidy/frontends/mpd/protocol/empty.py index a39d79eb..b84f08f4 100644 --- a/mopidy/frontends/mpd/protocol/empty.py +++ b/mopidy/frontends/mpd/protocol/empty.py @@ -1,6 +1,6 @@ from mopidy.frontends.mpd.protocol import handle_pattern @handle_pattern(r'^$') -def empty(frontend): +def empty(context): """The original MPD server returns ``OK`` on an empty request.""" pass diff --git a/mopidy/frontends/mpd/protocol/music_db.py b/mopidy/frontends/mpd/protocol/music_db.py index a6836533..0183d471 100644 --- a/mopidy/frontends/mpd/protocol/music_db.py +++ b/mopidy/frontends/mpd/protocol/music_db.py @@ -29,7 +29,7 @@ def _build_query(mpd_query): return query @handle_pattern(r'^count "(?P[^"]+)" "(?P[^"]*)"$') -def count(frontend, tag, needle): +def count(context, tag, needle): """ *musicpd.org, music database section:* @@ -43,7 +43,7 @@ def count(frontend, tag, needle): @handle_pattern(r'^find ' r'(?P("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ilename|' r'[Tt]itle|[Aa]ny)"? "[^"]+"\s?)+)$') -def find(frontend, mpd_query): +def find(context, mpd_query): """ *musicpd.org, music database section:* @@ -68,12 +68,12 @@ def find(frontend, mpd_query): - also uses the search type "date". """ query = _build_query(mpd_query) - return frontend.backend.library.find_exact(**query).get().mpd_format() + return context.backend.library.find_exact(**query).get().mpd_format() @handle_pattern(r'^findadd ' r'(?P("?([Aa]lbum|[Aa]rtist|[Ff]ilename|[Tt]itle|[Aa]ny)"? ' '"[^"]+"\s?)+)$') -def findadd(frontend, query): +def findadd(context, query): """ *musicpd.org, music database section:* @@ -84,11 +84,11 @@ def findadd(frontend, query): ``WHAT`` is what to find. """ # TODO Add result to current playlist - #result = frontend.find(query) + #result = context.find(query) @handle_pattern(r'^list "?(?P([Aa]rtist|[Aa]lbum|[Dd]ate|[Gg]enre))"?' '( (?P.*))?$') -def list_(frontend, field, mpd_query=None): +def list_(context, field, mpd_query=None): """ *musicpd.org, music database section:* @@ -175,11 +175,11 @@ def list_(frontend, field, mpd_query=None): field = field.lower() query = _list_build_query(field, mpd_query) if field == u'artist': - return _list_artist(frontend, query) + return _list_artist(context, query) elif field == u'album': - return _list_album(frontend, query) + return _list_album(context, query) elif field == u'date': - return _list_date(frontend, query) + return _list_date(context, query) elif field == u'genre': pass # TODO We don't have genre in our internal data structures yet @@ -213,32 +213,32 @@ def _list_build_query(field, mpd_query): else: raise MpdArgError(u'not able to parse args', command=u'list') -def _list_artist(frontend, query): +def _list_artist(context, query): artists = set() - playlist = frontend.backend.library.find_exact(**query).get() + playlist = context.backend.library.find_exact(**query).get() for track in playlist.tracks: for artist in track.artists: artists.add((u'Artist', artist.name)) return artists -def _list_album(frontend, query): +def _list_album(context, query): albums = set() - playlist = frontend.backend.library.find_exact(**query).get() + playlist = context.backend.library.find_exact(**query).get() for track in playlist.tracks: if track.album is not None: albums.add((u'Album', track.album.name)) return albums -def _list_date(frontend, query): +def _list_date(context, query): dates = set() - playlist = frontend.backend.library.find_exact(**query).get() + playlist = context.backend.library.find_exact(**query).get() for track in playlist.tracks: if track.date is not None: dates.add((u'Date', track.date.strftime('%Y-%m-%d'))) return dates @handle_pattern(r'^listall "(?P[^"]+)"') -def listall(frontend, uri): +def listall(context, uri): """ *musicpd.org, music database section:* @@ -249,7 +249,7 @@ def listall(frontend, uri): raise MpdNotImplemented # TODO @handle_pattern(r'^listallinfo "(?P[^"]+)"') -def listallinfo(frontend, uri): +def listallinfo(context, uri): """ *musicpd.org, music database section:* @@ -262,7 +262,7 @@ def listallinfo(frontend, uri): @handle_pattern(r'^lsinfo$') @handle_pattern(r'^lsinfo "(?P[^"]*)"$') -def lsinfo(frontend, uri=None): +def lsinfo(context, uri=None): """ *musicpd.org, music database section:* @@ -279,11 +279,11 @@ def lsinfo(frontend, uri=None): ""``, and ``lsinfo "/"``. """ if uri is None or uri == u'/' or uri == u'': - return stored_playlists.listplaylists(frontend) + return stored_playlists.listplaylists(context) raise MpdNotImplemented # TODO @handle_pattern(r'^rescan( "(?P[^"]+)")*$') -def rescan(frontend, uri=None): +def rescan(context, uri=None): """ *musicpd.org, music database section:* @@ -291,12 +291,12 @@ def rescan(frontend, uri=None): Same as ``update``, but also rescans unmodified files. """ - return update(frontend, uri, rescan_unmodified_files=True) + return update(context, uri, rescan_unmodified_files=True) @handle_pattern(r'^search ' r'(?P("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ilename|' r'[Tt]itle|[Aa]ny)"? "[^"]+"\s?)+)$') -def search(frontend, mpd_query): +def search(context, mpd_query): """ *musicpd.org, music database section:* @@ -324,10 +324,10 @@ def search(frontend, mpd_query): - also uses the search type "date". """ query = _build_query(mpd_query) - return frontend.backend.library.search(**query).get().mpd_format() + return context.backend.library.search(**query).get().mpd_format() @handle_pattern(r'^update( "(?P[^"]+)")*$') -def update(frontend, uri=None, rescan_unmodified_files=False): +def update(context, uri=None, rescan_unmodified_files=False): """ *musicpd.org, music database section:* diff --git a/mopidy/frontends/mpd/protocol/playback.py b/mopidy/frontends/mpd/protocol/playback.py index 65282f42..b8646a8b 100644 --- a/mopidy/frontends/mpd/protocol/playback.py +++ b/mopidy/frontends/mpd/protocol/playback.py @@ -5,7 +5,7 @@ from mopidy.frontends.mpd.exceptions import (MpdArgError, MpdNoExistError, @handle_pattern(r'^consume (?P[01])$') @handle_pattern(r'^consume "(?P[01])"$') -def consume(frontend, state): +def consume(context, state): """ *musicpd.org, playback section:* @@ -16,12 +16,12 @@ def consume(frontend, state): playlist. """ if int(state): - frontend.backend.playback.consume = True + context.backend.playback.consume = True else: - frontend.backend.playback.consume = False + context.backend.playback.consume = False @handle_pattern(r'^crossfade "(?P\d+)"$') -def crossfade(frontend, seconds): +def crossfade(context, seconds): """ *musicpd.org, playback section:* @@ -33,7 +33,7 @@ def crossfade(frontend, seconds): raise MpdNotImplemented # TODO @handle_pattern(r'^next$') -def next_(frontend): +def next_(context): """ *musicpd.org, playback section:* @@ -87,11 +87,11 @@ def next_(frontend): order as the first time. """ - return frontend.backend.playback.next().get() + return context.backend.playback.next().get() @handle_pattern(r'^pause$') @handle_pattern(r'^pause "(?P[01])"$') -def pause(frontend, state=None): +def pause(context, state=None): """ *musicpd.org, playback section:* @@ -104,28 +104,28 @@ def pause(frontend, state=None): - Calls ``pause`` without any arguments to toogle pause. """ if state is None: - if (frontend.backend.playback.state.get() == + if (context.backend.playback.state.get() == PlaybackController.PLAYING): - frontend.backend.playback.pause() - elif (frontend.backend.playback.state.get() == + context.backend.playback.pause() + elif (context.backend.playback.state.get() == PlaybackController.PAUSED): - frontend.backend.playback.resume() + context.backend.playback.resume() elif int(state): - frontend.backend.playback.pause() + context.backend.playback.pause() else: - frontend.backend.playback.resume() + context.backend.playback.resume() @handle_pattern(r'^play$') -def play(frontend): +def play(context): """ The original MPD server resumes from the paused state on ``play`` without arguments. """ - return frontend.backend.playback.play().get() + return context.backend.playback.play().get() @handle_pattern(r'^playid "(?P\d+)"$') @handle_pattern(r'^playid "(?P-1)"$') -def playid(frontend, cpid): +def playid(context, cpid): """ *musicpd.org, playback section:* @@ -144,16 +144,16 @@ def playid(frontend, cpid): """ cpid = int(cpid) if cpid == -1: - return _play_minus_one(frontend) + return _play_minus_one(context) try: - cp_track = frontend.backend.current_playlist.get(cpid=cpid).get() - return frontend.backend.playback.play(cp_track).get() + cp_track = context.backend.current_playlist.get(cpid=cpid).get() + return context.backend.playback.play(cp_track).get() except LookupError: raise MpdNoExistError(u'No such song', command=u'playid') @handle_pattern(r'^play (?P-?\d+)$') @handle_pattern(r'^play "(?P-?\d+)"$') -def playpos(frontend, songpos): +def playpos(context, songpos): """ *musicpd.org, playback section:* @@ -176,29 +176,29 @@ def playpos(frontend, songpos): """ songpos = int(songpos) if songpos == -1: - return _play_minus_one(frontend) + return _play_minus_one(context) try: - cp_track = frontend.backend.current_playlist.cp_tracks.get()[songpos] - return frontend.backend.playback.play(cp_track).get() + cp_track = context.backend.current_playlist.cp_tracks.get()[songpos] + return context.backend.playback.play(cp_track).get() except IndexError: raise MpdArgError(u'Bad song index', command=u'play') -def _play_minus_one(frontend): - if (frontend.backend.playback.state.get() == PlaybackController.PLAYING): +def _play_minus_one(context): + if (context.backend.playback.state.get() == PlaybackController.PLAYING): return # Nothing to do - elif (frontend.backend.playback.state.get() == PlaybackController.PAUSED): - return frontend.backend.playback.resume().get() - elif frontend.backend.playback.current_cp_track.get() is not None: - cp_track = frontend.backend.playback.current_cp_track.get() - return frontend.backend.playback.play(cp_track).get() - elif frontend.backend.current_playlist.cp_tracks.get(): - cp_track = frontend.backend.current_playlist.cp_tracks.get()[0] - return frontend.backend.playback.play(cp_track).get() + elif (context.backend.playback.state.get() == PlaybackController.PAUSED): + return context.backend.playback.resume().get() + elif context.backend.playback.current_cp_track.get() is not None: + cp_track = context.backend.playback.current_cp_track.get() + return context.backend.playback.play(cp_track).get() + elif context.backend.current_playlist.cp_tracks.get(): + cp_track = context.backend.current_playlist.cp_tracks.get()[0] + return context.backend.playback.play(cp_track).get() else: return # Fail silently @handle_pattern(r'^previous$') -def previous(frontend): +def previous(context): """ *musicpd.org, playback section:* @@ -241,11 +241,11 @@ def previous(frontend): ``previous`` should do a seek to time position 0. """ - return frontend.backend.playback.previous().get() + return context.backend.playback.previous().get() @handle_pattern(r'^random (?P[01])$') @handle_pattern(r'^random "(?P[01])"$') -def random(frontend, state): +def random(context, state): """ *musicpd.org, playback section:* @@ -254,13 +254,13 @@ def random(frontend, state): Sets random state to ``STATE``, ``STATE`` should be 0 or 1. """ if int(state): - frontend.backend.playback.random = True + context.backend.playback.random = True else: - frontend.backend.playback.random = False + context.backend.playback.random = False @handle_pattern(r'^repeat (?P[01])$') @handle_pattern(r'^repeat "(?P[01])"$') -def repeat(frontend, state): +def repeat(context, state): """ *musicpd.org, playback section:* @@ -269,12 +269,12 @@ def repeat(frontend, state): Sets repeat state to ``STATE``, ``STATE`` should be 0 or 1. """ if int(state): - frontend.backend.playback.repeat = True + context.backend.playback.repeat = True else: - frontend.backend.playback.repeat = False + context.backend.playback.repeat = False @handle_pattern(r'^replay_gain_mode "(?P(off|track|album))"$') -def replay_gain_mode(frontend, mode): +def replay_gain_mode(context, mode): """ *musicpd.org, playback section:* @@ -290,7 +290,7 @@ def replay_gain_mode(frontend, mode): raise MpdNotImplemented # TODO @handle_pattern(r'^replay_gain_status$') -def replay_gain_status(frontend): +def replay_gain_status(context): """ *musicpd.org, playback section:* @@ -303,7 +303,7 @@ def replay_gain_status(frontend): @handle_pattern(r'^seek (?P\d+) (?P\d+)$') @handle_pattern(r'^seek "(?P\d+)" "(?P\d+)"$') -def seek(frontend, songpos, seconds): +def seek(context, songpos, seconds): """ *musicpd.org, playback section:* @@ -316,12 +316,12 @@ def seek(frontend, songpos, seconds): - issues ``seek 1 120`` without quotes around the arguments. """ - if frontend.backend.playback.current_playlist_position != songpos: - playpos(frontend, songpos) - frontend.backend.playback.seek(int(seconds) * 1000) + if context.backend.playback.current_playlist_position != songpos: + playpos(context, songpos) + context.backend.playback.seek(int(seconds) * 1000) @handle_pattern(r'^seekid "(?P\d+)" "(?P\d+)"$') -def seekid(frontend, cpid, seconds): +def seekid(context, cpid, seconds): """ *musicpd.org, playback section:* @@ -329,13 +329,13 @@ def seekid(frontend, cpid, seconds): Seeks to the position ``TIME`` (in seconds) of song ``SONGID``. """ - if frontend.backend.playback.current_cpid != cpid: - playid(frontend, cpid) - frontend.backend.playback.seek(int(seconds) * 1000) + if context.backend.playback.current_cpid != cpid: + playid(context, cpid) + context.backend.playback.seek(int(seconds) * 1000) @handle_pattern(r'^setvol (?P[-+]*\d+)$') @handle_pattern(r'^setvol "(?P[-+]*\d+)"$') -def setvol(frontend, volume): +def setvol(context, volume): """ *musicpd.org, playback section:* @@ -352,11 +352,11 @@ def setvol(frontend, volume): volume = 0 if volume > 100: volume = 100 - frontend.mixer.volume = volume + context.mixer.volume = volume @handle_pattern(r'^single (?P[01])$') @handle_pattern(r'^single "(?P[01])"$') -def single(frontend, state): +def single(context, state): """ *musicpd.org, playback section:* @@ -367,12 +367,12 @@ def single(frontend, state): song is repeated if the ``repeat`` mode is enabled. """ if int(state): - frontend.backend.playback.single = True + context.backend.playback.single = True else: - frontend.backend.playback.single = False + context.backend.playback.single = False @handle_pattern(r'^stop$') -def stop(frontend): +def stop(context): """ *musicpd.org, playback section:* @@ -380,4 +380,4 @@ def stop(frontend): Stops playing. """ - frontend.backend.playback.stop() + context.backend.playback.stop() diff --git a/mopidy/frontends/mpd/protocol/reflection.py b/mopidy/frontends/mpd/protocol/reflection.py index ab782440..181dce54 100644 --- a/mopidy/frontends/mpd/protocol/reflection.py +++ b/mopidy/frontends/mpd/protocol/reflection.py @@ -2,7 +2,7 @@ from mopidy.frontends.mpd.protocol import handle_pattern, mpd_commands from mopidy.frontends.mpd.exceptions import MpdNotImplemented @handle_pattern(r'^commands$') -def commands(frontend): +def commands(context): """ *musicpd.org, reflection section:* @@ -28,7 +28,7 @@ def commands(frontend): return [('command', c) for c in sorted_commands] @handle_pattern(r'^decoders$') -def decoders(frontend): +def decoders(context): """ *musicpd.org, reflection section:* @@ -47,7 +47,7 @@ def decoders(frontend): raise MpdNotImplemented # TODO @handle_pattern(r'^notcommands$') -def notcommands(frontend): +def notcommands(context): """ *musicpd.org, reflection section:* @@ -62,7 +62,7 @@ def notcommands(frontend): pass @handle_pattern(r'^tagtypes$') -def tagtypes(frontend): +def tagtypes(context): """ *musicpd.org, reflection section:* @@ -73,7 +73,7 @@ def tagtypes(frontend): pass # TODO @handle_pattern(r'^urlhandlers$') -def urlhandlers(frontend): +def urlhandlers(context): """ *musicpd.org, reflection section:* @@ -81,4 +81,4 @@ def urlhandlers(frontend): Gets a list of available URL handlers. """ - return [(u'handler', uri) for uri in frontend.backend.uri_handlers.get()] + return [(u'handler', uri) for uri in context.backend.uri_handlers.get()] diff --git a/mopidy/frontends/mpd/protocol/status.py b/mopidy/frontends/mpd/protocol/status.py index a78efc0a..4f9e00cd 100644 --- a/mopidy/frontends/mpd/protocol/status.py +++ b/mopidy/frontends/mpd/protocol/status.py @@ -3,7 +3,7 @@ from mopidy.frontends.mpd.protocol import handle_pattern from mopidy.frontends.mpd.exceptions import MpdNotImplemented @handle_pattern(r'^clearerror$') -def clearerror(frontend): +def clearerror(context): """ *musicpd.org, status section:* @@ -15,7 +15,7 @@ def clearerror(frontend): raise MpdNotImplemented # TODO @handle_pattern(r'^currentsong$') -def currentsong(frontend): +def currentsong(context): """ *musicpd.org, status section:* @@ -24,15 +24,15 @@ def currentsong(frontend): Displays the song info of the current song (same song that is identified in status). """ - current_cp_track = frontend.backend.playback.current_cp_track.get() + current_cp_track = context.backend.playback.current_cp_track.get() if current_cp_track is not None: return current_cp_track[1].mpd_format( - position=frontend.backend.playback.current_playlist_position.get(), + position=context.backend.playback.current_playlist_position.get(), cpid=current_cp_track[0]) @handle_pattern(r'^idle$') @handle_pattern(r'^idle (?P.+)$') -def idle(frontend, subsystems=None): +def idle(context, subsystems=None): """ *musicpd.org, status section:* @@ -68,12 +68,12 @@ def idle(frontend, subsystems=None): pass # TODO @handle_pattern(r'^noidle$') -def noidle(frontend): +def noidle(context): """See :meth:`_status_idle`.""" pass # TODO @handle_pattern(r'^stats$') -def stats(frontend): +def stats(context): """ *musicpd.org, status section:* @@ -99,7 +99,7 @@ def stats(frontend): } @handle_pattern(r'^status$') -def status(frontend): +def status(context): """ *musicpd.org, status section:* @@ -131,64 +131,64 @@ def status(frontend): - ``error``: if there is an error, returns message here """ result = [ - ('volume', _status_volume(frontend)), - ('repeat', _status_repeat(frontend)), - ('random', _status_random(frontend)), - ('single', _status_single(frontend)), - ('consume', _status_consume(frontend)), - ('playlist', _status_playlist_version(frontend)), - ('playlistlength', _status_playlist_length(frontend)), - ('xfade', _status_xfade(frontend)), - ('state', _status_state(frontend)), + ('volume', _status_volume(context)), + ('repeat', _status_repeat(context)), + ('random', _status_random(context)), + ('single', _status_single(context)), + ('consume', _status_consume(context)), + ('playlist', _status_playlist_version(context)), + ('playlistlength', _status_playlist_length(context)), + ('xfade', _status_xfade(context)), + ('state', _status_state(context)), ] - if frontend.backend.playback.current_track.get() is not None: - result.append(('song', _status_songpos(frontend))) - result.append(('songid', _status_songid(frontend))) - if frontend.backend.playback.state.get() in (PlaybackController.PLAYING, + if context.backend.playback.current_track.get() is not None: + result.append(('song', _status_songpos(context))) + result.append(('songid', _status_songid(context))) + if context.backend.playback.state.get() in (PlaybackController.PLAYING, PlaybackController.PAUSED): - result.append(('time', _status_time(frontend))) - result.append(('elapsed', _status_time_elapsed(frontend))) - result.append(('bitrate', _status_bitrate(frontend))) + result.append(('time', _status_time(context))) + result.append(('elapsed', _status_time_elapsed(context))) + result.append(('bitrate', _status_bitrate(context))) return result -def _status_bitrate(frontend): - current_track = frontend.backend.playback.current_track.get() +def _status_bitrate(context): + current_track = context.backend.playback.current_track.get() if current_track is not None: return current_track.bitrate -def _status_consume(frontend): - if frontend.backend.playback.consume.get(): +def _status_consume(context): + if context.backend.playback.consume.get(): return 1 else: return 0 -def _status_playlist_length(frontend): - return len(frontend.backend.current_playlist.tracks.get()) +def _status_playlist_length(context): + return len(context.backend.current_playlist.tracks.get()) -def _status_playlist_version(frontend): - return frontend.backend.current_playlist.version.get() +def _status_playlist_version(context): + return context.backend.current_playlist.version.get() -def _status_random(frontend): - return int(frontend.backend.playback.random.get()) +def _status_random(context): + return int(context.backend.playback.random.get()) -def _status_repeat(frontend): - return int(frontend.backend.playback.repeat.get()) +def _status_repeat(context): + return int(context.backend.playback.repeat.get()) -def _status_single(frontend): - return int(frontend.backend.playback.single.get()) +def _status_single(context): + return int(context.backend.playback.single.get()) -def _status_songid(frontend): - current_cpid = frontend.backend.playback.current_cpid.get() +def _status_songid(context): + current_cpid = context.backend.playback.current_cpid.get() if current_cpid is not None: return current_cpid else: - return _status_songpos(frontend) + return _status_songpos(context) -def _status_songpos(frontend): - return frontend.backend.playback.current_playlist_position.get() +def _status_songpos(context): + return context.backend.playback.current_playlist_position.get() -def _status_state(frontend): - state = frontend.backend.playback.state.get() +def _status_state(context): + state = context.backend.playback.state.get() if state == PlaybackController.PLAYING: return u'play' elif state == PlaybackController.STOPPED: @@ -196,15 +196,15 @@ def _status_state(frontend): elif state == PlaybackController.PAUSED: return u'pause' -def _status_time(frontend): - return u'%s:%s' % (_status_time_elapsed(frontend) // 1000, - _status_time_total(frontend) // 1000) +def _status_time(context): + return u'%s:%s' % (_status_time_elapsed(context) // 1000, + _status_time_total(context) // 1000) -def _status_time_elapsed(frontend): - return frontend.backend.playback.time_position.get() +def _status_time_elapsed(context): + return context.backend.playback.time_position.get() -def _status_time_total(frontend): - current_track = frontend.backend.playback.current_track.get() +def _status_time_total(context): + current_track = context.backend.playback.current_track.get() if current_track is None: return 0 elif current_track.length is None: @@ -212,12 +212,12 @@ def _status_time_total(frontend): else: return current_track.length -def _status_volume(frontend): - volume = frontend.mixer.volume.get() +def _status_volume(context): + volume = context.mixer.volume.get() if volume is not None: return volume else: return 0 -def _status_xfade(frontend): +def _status_xfade(context): return 0 # TODO diff --git a/mopidy/frontends/mpd/protocol/stickers.py b/mopidy/frontends/mpd/protocol/stickers.py index 145665eb..c1b7be16 100644 --- a/mopidy/frontends/mpd/protocol/stickers.py +++ b/mopidy/frontends/mpd/protocol/stickers.py @@ -3,7 +3,7 @@ from mopidy.frontends.mpd.exceptions import MpdNotImplemented @handle_pattern(r'^sticker delete "(?P[^"]+)" ' r'"(?P[^"]+)"( "(?P[^"]+)")*$') -def sticker_delete(frontend, field, uri, name=None): +def sticker_delete(context, field, uri, name=None): """ *musicpd.org, sticker section:* @@ -16,7 +16,7 @@ def sticker_delete(frontend, field, uri, name=None): @handle_pattern(r'^sticker find "(?P[^"]+)" "(?P[^"]+)" ' r'"(?P[^"]+)"$') -def sticker_find(frontend, field, uri, name): +def sticker_find(context, field, uri, name): """ *musicpd.org, sticker section:* @@ -30,7 +30,7 @@ def sticker_find(frontend, field, uri, name): @handle_pattern(r'^sticker get "(?P[^"]+)" "(?P[^"]+)" ' r'"(?P[^"]+)"$') -def sticker_get(frontend, field, uri, name): +def sticker_get(context, field, uri, name): """ *musicpd.org, sticker section:* @@ -41,7 +41,7 @@ def sticker_get(frontend, field, uri, name): raise MpdNotImplemented # TODO @handle_pattern(r'^sticker list "(?P[^"]+)" "(?P[^"]+)"$') -def sticker_list(frontend, field, uri): +def sticker_list(context, field, uri): """ *musicpd.org, sticker section:* @@ -53,7 +53,7 @@ def sticker_list(frontend, field, uri): @handle_pattern(r'^sticker set "(?P[^"]+)" "(?P[^"]+)" ' r'"(?P[^"]+)" "(?P[^"]+)"$') -def sticker_set(frontend, field, uri, name, value): +def sticker_set(context, field, uri, name, value): """ *musicpd.org, sticker section:* diff --git a/mopidy/frontends/mpd/protocol/stored_playlists.py b/mopidy/frontends/mpd/protocol/stored_playlists.py index 6eccffac..764a8e12 100644 --- a/mopidy/frontends/mpd/protocol/stored_playlists.py +++ b/mopidy/frontends/mpd/protocol/stored_playlists.py @@ -4,7 +4,7 @@ from mopidy.frontends.mpd.protocol import handle_pattern from mopidy.frontends.mpd.exceptions import MpdNoExistError, MpdNotImplemented @handle_pattern(r'^listplaylist "(?P[^"]+)"$') -def listplaylist(frontend, name): +def listplaylist(context, name): """ *musicpd.org, stored playlists section:* @@ -19,13 +19,13 @@ def listplaylist(frontend, name): file: relative/path/to/file3.mp3 """ try: - playlist = frontend.backend.stored_playlists.get(name=name).get() + playlist = context.backend.stored_playlists.get(name=name).get() return ['file: %s' % t.uri for t in playlist.tracks] except LookupError: raise MpdNoExistError(u'No such playlist', command=u'listplaylist') @handle_pattern(r'^listplaylistinfo "(?P[^"]+)"$') -def listplaylistinfo(frontend, name): +def listplaylistinfo(context, name): """ *musicpd.org, stored playlists section:* @@ -39,14 +39,14 @@ def listplaylistinfo(frontend, name): Album, Artist, Track """ try: - playlist = frontend.backend.stored_playlists.get(name=name).get() + playlist = context.backend.stored_playlists.get(name=name).get() return playlist.mpd_format() except LookupError: raise MpdNoExistError( u'No such playlist', command=u'listplaylistinfo') @handle_pattern(r'^listplaylists$') -def listplaylists(frontend): +def listplaylists(context): """ *musicpd.org, stored playlists section:* @@ -67,7 +67,7 @@ def listplaylists(frontend): Last-Modified: 2010-02-06T02:11:08Z """ result = [] - for playlist in frontend.backend.stored_playlists.playlists.get(): + for playlist in context.backend.stored_playlists.playlists.get(): result.append((u'playlist', playlist.name)) last_modified = (playlist.last_modified or dt.datetime.now()).isoformat() @@ -80,7 +80,7 @@ def listplaylists(frontend): return result @handle_pattern(r'^load "(?P[^"]+)"$') -def load(frontend, name): +def load(context, name): """ *musicpd.org, stored playlists section:* @@ -93,13 +93,13 @@ def load(frontend, name): - ``load`` appends the given playlist to the current playlist. """ try: - playlist = frontend.backend.stored_playlists.get(name=name).get() - frontend.backend.current_playlist.append(playlist.tracks) + playlist = context.backend.stored_playlists.get(name=name).get() + context.backend.current_playlist.append(playlist.tracks) except LookupError: raise MpdNoExistError(u'No such playlist', command=u'load') @handle_pattern(r'^playlistadd "(?P[^"]+)" "(?P[^"]+)"$') -def playlistadd(frontend, name, uri): +def playlistadd(context, name, uri): """ *musicpd.org, stored playlists section:* @@ -112,7 +112,7 @@ def playlistadd(frontend, name, uri): raise MpdNotImplemented # TODO @handle_pattern(r'^playlistclear "(?P[^"]+)"$') -def playlistclear(frontend, name): +def playlistclear(context, name): """ *musicpd.org, stored playlists section:* @@ -123,7 +123,7 @@ def playlistclear(frontend, name): raise MpdNotImplemented # TODO @handle_pattern(r'^playlistdelete "(?P[^"]+)" "(?P\d+)"$') -def playlistdelete(frontend, name, songpos): +def playlistdelete(context, name, songpos): """ *musicpd.org, stored playlists section:* @@ -135,7 +135,7 @@ def playlistdelete(frontend, name, songpos): @handle_pattern(r'^playlistmove "(?P[^"]+)" ' r'"(?P\d+)" "(?P\d+)"$') -def playlistmove(frontend, name, from_pos, to_pos): +def playlistmove(context, name, from_pos, to_pos): """ *musicpd.org, stored playlists section:* @@ -153,7 +153,7 @@ def playlistmove(frontend, name, from_pos, to_pos): raise MpdNotImplemented # TODO @handle_pattern(r'^rename "(?P[^"]+)" "(?P[^"]+)"$') -def rename(frontend, old_name, new_name): +def rename(context, old_name, new_name): """ *musicpd.org, stored playlists section:* @@ -164,7 +164,7 @@ def rename(frontend, old_name, new_name): raise MpdNotImplemented # TODO @handle_pattern(r'^rm "(?P[^"]+)"$') -def rm(frontend, name): +def rm(context, name): """ *musicpd.org, stored playlists section:* @@ -175,7 +175,7 @@ def rm(frontend, name): raise MpdNotImplemented # TODO @handle_pattern(r'^save "(?P[^"]+)"$') -def save(frontend, name): +def save(context, name): """ *musicpd.org, stored playlists section:* From d57727282e94026cc140e495d56f85f5e41c8334 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 16:34:47 +0200 Subject: [PATCH 12/48] Create new MpdContext object which is passed to command handlers --- mopidy/frontends/mpd/dispatcher.py | 42 +++++++++--- mopidy/frontends/mpd/protocol/command_list.py | 20 +++--- tests/frontends/mpd/command_list_test.py | 50 +++++++------- tests/frontends/mpd/status_test.py | 66 ++++++++++--------- 4 files changed, 103 insertions(+), 75 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index f5c30b23..87a7112c 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -26,16 +26,9 @@ class MpdDispatcher(object): # XXX Consider merging MpdDispatcher into MpdSession def __init__(self): - backend_refs = ActorRegistry.get_by_class(Backend) - assert len(backend_refs) == 1, 'Expected exactly one running backend.' - self.backend = backend_refs[0].proxy() - - mixer_refs = ActorRegistry.get_by_class(BaseMixer) - assert len(mixer_refs) == 1, 'Expected exactly one running mixer.' - self.mixer = mixer_refs[0].proxy() - self.command_list = False self.command_list_ok = False + self.context = MpdContext(self) def handle_request(self, request, command_list_index=None): """Dispatch incoming requests to the correct handler.""" @@ -44,7 +37,7 @@ class MpdDispatcher(object): return None try: (handler, kwargs) = self.find_handler(request) - result = handler(self, **kwargs) + result = handler(self.context, **kwargs) except MpdAckError as e: if command_list_index is not None: e.index = command_list_index @@ -87,3 +80,34 @@ class MpdDispatcher(object): if add_ok and (not response or not response[-1].startswith(u'ACK')): response.append(u'OK') return response + + +class MpdContext(object): + """ + This object is passed as the first argument to all MPD command handlers to + give the command handlers access to important parts of Mopidy. + """ + + #: The current :class:`MpdDispatcher`. + dispatcher = None + + #: The backend. An instance of :class:`mopidy.backends.base.Backend`. + backend = None + + #: The mixer. An instance of :class:`mopidy.mixers.base.BaseMixer`. + mixer = None + + def __init__(self, dispatcher): + self.dispatcher = dispatcher + self.backend = self._get_backend() + self.mixer = self._get_mixer() + + def _get_backend(self): + backend_refs = ActorRegistry.get_by_class(Backend) + assert len(backend_refs) == 1, 'Expected exactly one running backend.' + return backend_refs[0].proxy() + + def _get_mixer(self): + mixer_refs = ActorRegistry.get_by_class(BaseMixer) + assert len(mixer_refs) == 1, 'Expected exactly one running mixer.' + return mixer_refs[0].proxy() diff --git a/mopidy/frontends/mpd/protocol/command_list.py b/mopidy/frontends/mpd/protocol/command_list.py index cecff9fd..78fccec6 100644 --- a/mopidy/frontends/mpd/protocol/command_list.py +++ b/mopidy/frontends/mpd/protocol/command_list.py @@ -18,21 +18,23 @@ def command_list_begin(context): returned. If ``command_list_ok_begin`` is used, ``list_OK`` is returned for each successful command executed in the command list. """ - context.command_list = [] - context.command_list_ok = False + context.dispatcher.command_list = [] + context.dispatcher.command_list_ok = False @handle_pattern(r'^command_list_end$') def command_list_end(context): """See :meth:`command_list_begin()`.""" - if context.command_list is False: + if context.dispatcher.command_list is False: # Test for False exactly, and not e.g. empty list raise MpdUnknownCommand(command='command_list_end') - (command_list, context.command_list) = (context.command_list, False) - (command_list_ok, context.command_list_ok) = ( - context.command_list_ok, False) + (command_list, context.dispatcher.command_list) = ( + context.dispatcher.command_list, False) + (command_list_ok, context.dispatcher.command_list_ok) = ( + context.dispatcher.command_list_ok, False) result = [] for i, command in enumerate(command_list): - response = context.handle_request(command, command_list_index=i) + response = context.dispatcher.handle_request( + command, command_list_index=i) if response is not None: result.append(response) if response and response[-1].startswith(u'ACK'): @@ -44,5 +46,5 @@ def command_list_end(context): @handle_pattern(r'^command_list_ok_begin$') def command_list_ok_begin(context): """See :meth:`command_list_begin()`.""" - context.command_list = [] - context.command_list_ok = True + context.dispatcher.command_list = [] + context.dispatcher.command_list_ok = True diff --git a/tests/frontends/mpd/command_list_test.py b/tests/frontends/mpd/command_list_test.py index 7ff96bac..3537ee77 100644 --- a/tests/frontends/mpd/command_list_test.py +++ b/tests/frontends/mpd/command_list_test.py @@ -8,55 +8,55 @@ class CommandListsTest(unittest.TestCase): def setUp(self): self.b = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = dispatcher.MpdDispatcher() def tearDown(self): self.b.stop().get() self.mixer.stop().get() def test_command_list_begin(self): - result = self.h.handle_request(u'command_list_begin') + result = self.dispatcher.handle_request(u'command_list_begin') self.assert_(result is None) def test_command_list_end(self): - self.h.handle_request(u'command_list_begin') - result = self.h.handle_request(u'command_list_end') + self.dispatcher.handle_request(u'command_list_begin') + result = self.dispatcher.handle_request(u'command_list_end') self.assert_(u'OK' in result) def test_command_list_end_without_start_first_is_an_unknown_command(self): - result = self.h.handle_request(u'command_list_end') + result = self.dispatcher.handle_request(u'command_list_end') self.assertEquals(result[0], u'ACK [5@0] {} unknown command "command_list_end"') def test_command_list_with_ping(self): - self.h.handle_request(u'command_list_begin') - self.assertEqual([], self.h.command_list) - self.assertEqual(False, self.h.command_list_ok) - self.h.handle_request(u'ping') - self.assert_(u'ping' in self.h.command_list) - result = self.h.handle_request(u'command_list_end') + self.dispatcher.handle_request(u'command_list_begin') + self.assertEqual([], self.dispatcher.command_list) + self.assertEqual(False, self.dispatcher.command_list_ok) + self.dispatcher.handle_request(u'ping') + self.assert_(u'ping' in self.dispatcher.command_list) + result = self.dispatcher.handle_request(u'command_list_end') self.assert_(u'OK' in result) - self.assertEqual(False, self.h.command_list) + self.assertEqual(False, self.dispatcher.command_list) def test_command_list_with_error_returns_ack_with_correct_index(self): - self.h.handle_request(u'command_list_begin') - self.h.handle_request(u'play') # Known command - self.h.handle_request(u'paly') # Unknown command - result = self.h.handle_request(u'command_list_end') + self.dispatcher.handle_request(u'command_list_begin') + self.dispatcher.handle_request(u'play') # Known command + self.dispatcher.handle_request(u'paly') # Unknown command + result = self.dispatcher.handle_request(u'command_list_end') self.assertEqual(result[0], u'ACK [5@1] {} unknown command "paly"') def test_command_list_ok_begin(self): - result = self.h.handle_request(u'command_list_ok_begin') + result = self.dispatcher.handle_request(u'command_list_ok_begin') self.assert_(result is None) def test_command_list_ok_with_ping(self): - self.h.handle_request(u'command_list_ok_begin') - self.assertEqual([], self.h.command_list) - self.assertEqual(True, self.h.command_list_ok) - self.h.handle_request(u'ping') - self.assert_(u'ping' in self.h.command_list) - result = self.h.handle_request(u'command_list_end') + self.dispatcher.handle_request(u'command_list_ok_begin') + self.assertEqual([], self.dispatcher.command_list) + self.assertEqual(True, self.dispatcher.command_list_ok) + self.dispatcher.handle_request(u'ping') + self.assert_(u'ping' in self.dispatcher.command_list) + result = self.dispatcher.handle_request(u'command_list_end') self.assert_(u'list_OK' in result) self.assert_(u'OK' in result) - self.assertEqual(False, self.h.command_list) - self.assertEqual(False, self.h.command_list_ok) + self.assertEqual(False, self.dispatcher.command_list) + self.assertEqual(False, self.dispatcher.command_list_ok) diff --git a/tests/frontends/mpd/status_test.py b/tests/frontends/mpd/status_test.py index 791d734f..7fa9d8de 100644 --- a/tests/frontends/mpd/status_test.py +++ b/tests/frontends/mpd/status_test.py @@ -2,7 +2,8 @@ import unittest from mopidy.backends.base import PlaybackController from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import dispatcher +from mopidy.frontends.mpd.dispatcher import MpdDispatcher +from mopidy.frontends.mpd.protocol import status from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track @@ -14,21 +15,22 @@ class StatusHandlerTest(unittest.TestCase): def setUp(self): self.b = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = MpdDispatcher() + self.context = self.dispatcher.context def tearDown(self): self.b.stop().get() self.mixer.stop().get() def test_clearerror(self): - result = self.h.handle_request(u'clearerror') + result = self.dispatcher.handle_request(u'clearerror') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_currentsong(self): track = Track() self.b.current_playlist.append([track]) self.b.playback.play() - result = self.h.handle_request(u'currentsong') + result = self.dispatcher.handle_request(u'currentsong') self.assert_(u'file: ' in result) self.assert_(u'Time: 0' in result) self.assert_(u'Artist: ' in result) @@ -41,27 +43,27 @@ class StatusHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_currentsong_without_song(self): - result = self.h.handle_request(u'currentsong') + result = self.dispatcher.handle_request(u'currentsong') self.assert_(u'OK' in result) def test_idle_without_subsystems(self): - result = self.h.handle_request(u'idle') + result = self.dispatcher.handle_request(u'idle') self.assert_(u'OK' in result) def test_idle_with_subsystems(self): - result = self.h.handle_request(u'idle database playlist') + result = self.dispatcher.handle_request(u'idle database playlist') self.assert_(u'OK' in result) def test_noidle(self): - result = self.h.handle_request(u'noidle') + result = self.dispatcher.handle_request(u'noidle') self.assert_(u'OK' in result) def test_stats_command(self): - result = self.h.handle_request(u'stats') + result = self.dispatcher.handle_request(u'stats') self.assert_(u'OK' in result) def test_stats_method(self): - result = dispatcher.status.stats(self.h) + result = status.stats(self.context) self.assert_('artists' in result) self.assert_(int(result['artists']) >= 0) self.assert_('albums' in result) @@ -78,110 +80,110 @@ class StatusHandlerTest(unittest.TestCase): self.assert_(int(result['playtime']) >= 0) def test_status_command(self): - result = self.h.handle_request(u'status') + result = self.dispatcher.handle_request(u'status') self.assert_(u'OK' in result) def test_status_method_contains_volume_which_defaults_to_0(self): - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('volume' in result) self.assertEqual(int(result['volume']), 0) def test_status_method_contains_volume(self): self.mixer.volume = 17 - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('volume' in result) self.assertEqual(int(result['volume']), 17) def test_status_method_contains_repeat_is_0(self): - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('repeat' in result) self.assertEqual(int(result['repeat']), 0) def test_status_method_contains_repeat_is_1(self): self.b.playback.repeat = 1 - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('repeat' in result) self.assertEqual(int(result['repeat']), 1) def test_status_method_contains_random_is_0(self): - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('random' in result) self.assertEqual(int(result['random']), 0) def test_status_method_contains_random_is_1(self): self.b.playback.random = 1 - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('random' in result) self.assertEqual(int(result['random']), 1) def test_status_method_contains_single(self): - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('single' in result) self.assert_(int(result['single']) in (0, 1)) def test_status_method_contains_consume_is_0(self): - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('consume' in result) self.assertEqual(int(result['consume']), 0) def test_status_method_contains_consume_is_1(self): self.b.playback.consume = 1 - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('consume' in result) self.assertEqual(int(result['consume']), 1) def test_status_method_contains_playlist(self): - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('playlist' in result) self.assert_(int(result['playlist']) in xrange(0, 2**31 - 1)) def test_status_method_contains_playlistlength(self): - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('playlistlength' in result) self.assert_(int(result['playlistlength']) >= 0) def test_status_method_contains_xfade(self): - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('xfade' in result) self.assert_(int(result['xfade']) >= 0) def test_status_method_contains_state_is_play(self): self.b.playback.state = PLAYING - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('state' in result) self.assertEqual(result['state'], 'play') def test_status_method_contains_state_is_stop(self): self.b.playback.state = STOPPED - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('state' in result) self.assertEqual(result['state'], 'stop') def test_status_method_contains_state_is_pause(self): self.b.playback.state = PLAYING self.b.playback.state = PAUSED - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('state' in result) self.assertEqual(result['state'], 'pause') def test_status_method_when_playlist_loaded_contains_song(self): self.b.current_playlist.append([Track()]) self.b.playback.play() - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('song' in result) self.assert_(int(result['song']) >= 0) def test_status_method_when_playlist_loaded_contains_cpid_as_songid(self): self.b.current_playlist.append([Track()]) self.b.playback.play() - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('songid' in result) self.assertEqual(int(result['songid']), 0) def test_status_method_when_playing_contains_time_with_no_length(self): self.b.current_playlist.append([Track(length=None)]) self.b.playback.play() - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('time' in result) (position, total) = result['time'].split(':') position = int(position) @@ -191,7 +193,7 @@ class StatusHandlerTest(unittest.TestCase): def test_status_method_when_playing_contains_time_with_length(self): self.b.current_playlist.append([Track(length=10000)]) self.b.playback.play() - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('time' in result) (position, total) = result['time'].split(':') position = int(position) @@ -201,13 +203,13 @@ class StatusHandlerTest(unittest.TestCase): def test_status_method_when_playing_contains_elapsed(self): self.b.playback.state = PAUSED self.b.playback.play_time_accumulated = 59123 - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('elapsed' in result) self.assertEqual(int(result['elapsed']), 59123) def test_status_method_when_playing_contains_bitrate(self): self.b.current_playlist.append([Track(bitrate=320)]) self.b.playback.play() - result = dict(dispatcher.status.status(self.h)) + result = dict(status.status(self.context)) self.assert_('bitrate' in result) self.assertEqual(int(result['bitrate']), 320) From cbdc37a24da152c9d3d007a9fccd89e8131f4b04 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 16:52:52 +0200 Subject: [PATCH 13/48] Replace all single letter instance variables in MPD protocol tests --- tests/frontends/mpd/audio_output_test.py | 14 +- tests/frontends/mpd/connection_test.py | 22 +- tests/frontends/mpd/current_playlist_test.py | 273 +++++++------- tests/frontends/mpd/dispatcher_test.py | 16 +- tests/frontends/mpd/music_db_test.py | 210 ++++++----- tests/frontends/mpd/playback_test.py | 371 +++++++++---------- tests/frontends/mpd/reflection_test.py | 18 +- tests/frontends/mpd/stickers_test.py | 20 +- tests/frontends/mpd/stored_playlists_test.py | 52 +-- 9 files changed, 502 insertions(+), 494 deletions(-) diff --git a/tests/frontends/mpd/audio_output_test.py b/tests/frontends/mpd/audio_output_test.py index afa99d26..82d9e203 100644 --- a/tests/frontends/mpd/audio_output_test.py +++ b/tests/frontends/mpd/audio_output_test.py @@ -1,29 +1,29 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import dispatcher +from mopidy.frontends.mpd.dispatcher import MpdDispatcher from mopidy.mixers.dummy import DummyMixer class AudioOutputHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend.start().proxy() + self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = MpdDispatcher() def tearDown(self): - self.b.stop().get() + self.backend.stop().get() self.mixer.stop().get() def test_enableoutput(self): - result = self.h.handle_request(u'enableoutput "0"') + result = self.dispatcher.handle_request(u'enableoutput "0"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_disableoutput(self): - result = self.h.handle_request(u'disableoutput "0"') + result = self.dispatcher.handle_request(u'disableoutput "0"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_outputs(self): - result = self.h.handle_request(u'outputs') + result = self.dispatcher.handle_request(u'outputs') self.assert_(u'outputid: 0' in result) self.assert_(u'outputname: None' in result) self.assert_(u'outputenabled: 1' in result) diff --git a/tests/frontends/mpd/connection_test.py b/tests/frontends/mpd/connection_test.py index cf161a5a..e34265d4 100644 --- a/tests/frontends/mpd/connection_test.py +++ b/tests/frontends/mpd/connection_test.py @@ -2,47 +2,47 @@ import unittest from mopidy import settings from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import dispatcher +from mopidy.frontends.mpd.dispatcher import MpdDispatcher from mopidy.mixers.dummy import DummyMixer class ConnectionHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend.start().proxy() + self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = MpdDispatcher() def tearDown(self): - self.b.stop().get() + self.backend.stop().get() self.mixer.stop().get() settings.runtime.clear() def test_close(self): - result = self.h.handle_request(u'close') + result = self.dispatcher.handle_request(u'close') self.assert_(u'OK' in result) def test_empty_request(self): - result = self.h.handle_request(u'') + result = self.dispatcher.handle_request(u'') self.assert_(u'OK' in result) def test_kill(self): - result = self.h.handle_request(u'kill') + result = self.dispatcher.handle_request(u'kill') self.assert_(u'OK' in result) def test_valid_password_is_accepted(self): settings.MPD_SERVER_PASSWORD = u'topsecret' - result = self.h.handle_request(u'password "topsecret"') + result = self.dispatcher.handle_request(u'password "topsecret"') self.assert_(u'OK' in result) def test_invalid_password_is_not_accepted(self): settings.MPD_SERVER_PASSWORD = u'topsecret' - result = self.h.handle_request(u'password "secret"') + result = self.dispatcher.handle_request(u'password "secret"') self.assert_(u'ACK [3@0] {password} incorrect password' in result) def test_any_password_is_not_accepted_when_password_check_turned_off(self): settings.MPD_SERVER_PASSWORD = None - result = self.h.handle_request(u'password "secret"') + result = self.dispatcher.handle_request(u'password "secret"') self.assert_(u'ACK [3@0] {password} incorrect password' in result) def test_ping(self): - result = self.h.handle_request(u'ping') + result = self.dispatcher.handle_request(u'ping') self.assert_(u'OK' in result) diff --git a/tests/frontends/mpd/current_playlist_test.py b/tests/frontends/mpd/current_playlist_test.py index eb113ed7..c7f47429 100644 --- a/tests/frontends/mpd/current_playlist_test.py +++ b/tests/frontends/mpd/current_playlist_test.py @@ -1,160 +1,160 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import dispatcher +from mopidy.frontends.mpd.dispatcher import MpdDispatcher from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track class CurrentPlaylistHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend.start().proxy() + self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = MpdDispatcher() def tearDown(self): - self.b.stop().get() + self.backend.stop().get() self.mixer.stop().get() def test_add(self): needle = Track(uri='dummy://foo') - self.b.library.provider.dummy_library = [ + self.backend.library.provider.dummy_library = [ Track(), Track(), needle, Track()] - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 5) - result = self.h.handle_request(u'add "dummy://foo"') + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) + result = self.dispatcher.handle_request(u'add "dummy://foo"') self.assertEqual(len(result), 1) self.assertEqual(result[0], u'OK') - self.assertEqual(len(self.b.current_playlist.tracks.get()), 6) - self.assertEqual(self.b.current_playlist.tracks.get()[5], needle) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 6) + self.assertEqual(self.backend.current_playlist.tracks.get()[5], needle) def test_add_with_uri_not_found_in_library_should_ack(self): - result = self.h.handle_request(u'add "dummy://foo"') + result = self.dispatcher.handle_request(u'add "dummy://foo"') self.assertEqual(result[0], u'ACK [50@0] {add} directory or file not found') def test_add_with_empty_uri_should_add_all_known_tracks_and_ok(self): - result = self.h.handle_request(u'add ""') + result = self.dispatcher.handle_request(u'add ""') # TODO check that we add all tracks (we currently don't) self.assert_(u'OK' in result) def test_addid_without_songpos(self): needle = Track(uri='dummy://foo') - self.b.library.provider.dummy_library = [ + self.backend.library.provider.dummy_library = [ Track(), Track(), needle, Track()] - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 5) - result = self.h.handle_request(u'addid "dummy://foo"') - self.assertEqual(len(self.b.current_playlist.tracks.get()), 6) - self.assertEqual(self.b.current_playlist.tracks.get()[5], needle) - self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks.get()[5][0] - in result) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) + result = self.dispatcher.handle_request(u'addid "dummy://foo"') + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 6) + self.assertEqual(self.backend.current_playlist.tracks.get()[5], needle) + self.assert_(u'Id: %d' % + self.backend.current_playlist.cp_tracks.get()[5][0] in result) self.assert_(u'OK' in result) def test_addid_with_empty_uri_acks(self): - result = self.h.handle_request(u'addid ""') + result = self.dispatcher.handle_request(u'addid ""') self.assertEqual(result[0], u'ACK [50@0] {addid} No such song') def test_addid_with_songpos(self): needle = Track(uri='dummy://foo') - self.b.library.provider.dummy_library = [ + self.backend.library.provider.dummy_library = [ Track(), Track(), needle, Track()] - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 5) - result = self.h.handle_request(u'addid "dummy://foo" "3"') - self.assertEqual(len(self.b.current_playlist.tracks.get()), 6) - self.assertEqual(self.b.current_playlist.tracks.get()[3], needle) - self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks.get()[3][0] - in result) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) + result = self.dispatcher.handle_request(u'addid "dummy://foo" "3"') + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 6) + self.assertEqual(self.backend.current_playlist.tracks.get()[3], needle) + self.assert_(u'Id: %d' % + self.backend.current_playlist.cp_tracks.get()[3][0] in result) self.assert_(u'OK' in result) def test_addid_with_songpos_out_of_bounds_should_ack(self): needle = Track(uri='dummy://foo') - self.b.library.provider.dummy_library = [ + self.backend.library.provider.dummy_library = [ Track(), Track(), needle, Track()] - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 5) - result = self.h.handle_request(u'addid "dummy://foo" "6"') + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) + result = self.dispatcher.handle_request(u'addid "dummy://foo" "6"') self.assertEqual(result[0], u'ACK [2@0] {addid} Bad song index') def test_addid_with_uri_not_found_in_library_should_ack(self): - result = self.h.handle_request(u'addid "dummy://foo"') + result = self.dispatcher.handle_request(u'addid "dummy://foo"') self.assertEqual(result[0], u'ACK [50@0] {addid} No such song') def test_clear(self): - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 5) - result = self.h.handle_request(u'clear') - self.assertEqual(len(self.b.current_playlist.tracks.get()), 0) - self.assertEqual(self.b.playback.current_track.get(), None) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) + result = self.dispatcher.handle_request(u'clear') + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 0) + self.assertEqual(self.backend.playback.current_track.get(), None) self.assert_(u'OK' in result) def test_delete_songpos(self): - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 5) - result = self.h.handle_request(u'delete "%d"' % - self.b.current_playlist.cp_tracks.get()[2][0]) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 4) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) + result = self.dispatcher.handle_request(u'delete "%d"' % + self.backend.current_playlist.cp_tracks.get()[2][0]) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 4) self.assert_(u'OK' in result) def test_delete_songpos_out_of_bounds(self): - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 5) - result = self.h.handle_request(u'delete "5"') - self.assertEqual(len(self.b.current_playlist.tracks.get()), 5) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) + result = self.dispatcher.handle_request(u'delete "5"') + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) self.assertEqual(result[0], u'ACK [2@0] {delete} Bad song index') def test_delete_open_range(self): - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 5) - result = self.h.handle_request(u'delete "1:"') - self.assertEqual(len(self.b.current_playlist.tracks.get()), 1) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) + result = self.dispatcher.handle_request(u'delete "1:"') + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 1) self.assert_(u'OK' in result) def test_delete_closed_range(self): - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 5) - result = self.h.handle_request(u'delete "1:3"') - self.assertEqual(len(self.b.current_playlist.tracks.get()), 3) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) + result = self.dispatcher.handle_request(u'delete "1:3"') + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 3) self.assert_(u'OK' in result) def test_delete_range_out_of_bounds(self): - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(), Track(), Track(), Track(), Track()]) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 5) - result = self.h.handle_request(u'delete "5:7"') - self.assertEqual(len(self.b.current_playlist.tracks.get()), 5) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) + result = self.dispatcher.handle_request(u'delete "5:7"') + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5) self.assertEqual(result[0], u'ACK [2@0] {delete} Bad song index') def test_deleteid(self): - self.b.current_playlist.append([Track(), Track()]) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 2) - result = self.h.handle_request(u'deleteid "1"') - self.assertEqual(len(self.b.current_playlist.tracks.get()), 1) + self.backend.current_playlist.append([Track(), Track()]) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 2) + result = self.dispatcher.handle_request(u'deleteid "1"') + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 1) self.assert_(u'OK' in result) def test_deleteid_does_not_exist(self): - self.b.current_playlist.append([Track(), Track()]) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 2) - result = self.h.handle_request(u'deleteid "12345"') - self.assertEqual(len(self.b.current_playlist.tracks.get()), 2) + self.backend.current_playlist.append([Track(), Track()]) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 2) + result = self.dispatcher.handle_request(u'deleteid "12345"') + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 2) self.assertEqual(result[0], u'ACK [50@0] {deleteid} No such song') def test_move_songpos(self): - self.b.current_playlist.append([ + self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.h.handle_request(u'move "1" "0"') - tracks = self.b.current_playlist.tracks.get() + result = self.dispatcher.handle_request(u'move "1" "0"') + tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'b') self.assertEqual(tracks[1].name, 'a') self.assertEqual(tracks[2].name, 'c') @@ -164,12 +164,12 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_move_open_range(self): - self.b.current_playlist.append([ + self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.h.handle_request(u'move "2:" "0"') - tracks = self.b.current_playlist.tracks.get() + result = self.dispatcher.handle_request(u'move "2:" "0"') + tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'c') self.assertEqual(tracks[1].name, 'd') self.assertEqual(tracks[2].name, 'e') @@ -179,12 +179,12 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_move_closed_range(self): - self.b.current_playlist.append([ + self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.h.handle_request(u'move "1:3" "0"') - tracks = self.b.current_playlist.tracks.get() + result = self.dispatcher.handle_request(u'move "1:3" "0"') + tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'b') self.assertEqual(tracks[1].name, 'c') self.assertEqual(tracks[2].name, 'a') @@ -194,12 +194,12 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_moveid(self): - self.b.current_playlist.append([ + self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.h.handle_request(u'moveid "4" "2"') - tracks = self.b.current_playlist.tracks.get() + result = self.dispatcher.handle_request(u'moveid "4" "2"') + tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'a') self.assertEqual(tracks[1].name, 'b') self.assertEqual(tracks[2].name, 'e') @@ -209,30 +209,30 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_playlist_returns_same_as_playlistinfo(self): - playlist_result = self.h.handle_request(u'playlist') - playlistinfo_result = self.h.handle_request(u'playlistinfo') + playlist_result = self.dispatcher.handle_request(u'playlist') + playlistinfo_result = self.dispatcher.handle_request(u'playlistinfo') self.assertEqual(playlist_result, playlistinfo_result) def test_playlistfind(self): - result = self.h.handle_request(u'playlistfind "tag" "needle"') + result = self.dispatcher.handle_request(u'playlistfind "tag" "needle"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_playlistfind_by_filename_not_in_current_playlist(self): - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'playlistfind "filename" "file:///dev/null"') self.assertEqual(len(result), 1) self.assert_(u'OK' in result) def test_playlistfind_by_filename_without_quotes(self): - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'playlistfind filename "file:///dev/null"') self.assertEqual(len(result), 1) self.assert_(u'OK' in result) def test_playlistfind_by_filename_in_current_playlist(self): - self.b.current_playlist.append([ + self.backend.current_playlist.append([ Track(uri='file:///exists')]) - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'playlistfind filename "file:///exists"') self.assert_(u'file: file:///exists' in result) self.assert_(u'Id: 0' in result) @@ -240,15 +240,15 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_playlistid_without_songid(self): - self.b.current_playlist.append([Track(name='a'), Track(name='b')]) - result = self.h.handle_request(u'playlistid') + self.backend.current_playlist.append([Track(name='a'), Track(name='b')]) + result = self.dispatcher.handle_request(u'playlistid') self.assert_(u'Title: a' in result) self.assert_(u'Title: b' in result) self.assert_(u'OK' in result) def test_playlistid_with_songid(self): - self.b.current_playlist.append([Track(name='a'), Track(name='b')]) - result = self.h.handle_request(u'playlistid "1"') + self.backend.current_playlist.append([Track(name='a'), Track(name='b')]) + result = self.dispatcher.handle_request(u'playlistid "1"') self.assert_(u'Title: a' not in result) self.assert_(u'Id: 0' not in result) self.assert_(u'Title: b' in result) @@ -256,16 +256,16 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_playlistid_with_not_existing_songid_fails(self): - self.b.current_playlist.append([Track(name='a'), Track(name='b')]) - result = self.h.handle_request(u'playlistid "25"') + self.backend.current_playlist.append([Track(name='a'), Track(name='b')]) + result = self.dispatcher.handle_request(u'playlistid "25"') self.assertEqual(result[0], u'ACK [50@0] {playlistid} No such song') def test_playlistinfo_without_songpos_or_range(self): - self.b.current_playlist.append([ + self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.h.handle_request(u'playlistinfo') + result = self.dispatcher.handle_request(u'playlistinfo') self.assert_(u'Title: a' in result) self.assert_(u'Title: b' in result) self.assert_(u'Title: c' in result) @@ -275,11 +275,11 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_playlistinfo_with_songpos(self): - self.b.current_playlist.append([ + self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.h.handle_request(u'playlistinfo "4"') + result = self.dispatcher.handle_request(u'playlistinfo "4"') self.assert_(u'Title: a' not in result) self.assert_(u'Title: b' not in result) self.assert_(u'Title: c' not in result) @@ -289,16 +289,16 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_playlistinfo_with_negative_songpos_same_as_playlistinfo(self): - result1 = self.h.handle_request(u'playlistinfo "-1"') - result2 = self.h.handle_request(u'playlistinfo') + result1 = self.dispatcher.handle_request(u'playlistinfo "-1"') + result2 = self.dispatcher.handle_request(u'playlistinfo') self.assertEqual(result1, result2) def test_playlistinfo_with_open_range(self): - self.b.current_playlist.append([ + self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.h.handle_request(u'playlistinfo "2:"') + result = self.dispatcher.handle_request(u'playlistinfo "2:"') self.assert_(u'Title: a' not in result) self.assert_(u'Title: b' not in result) self.assert_(u'Title: c' in result) @@ -308,11 +308,11 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_playlistinfo_with_closed_range(self): - self.b.current_playlist.append([ + self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.h.handle_request(u'playlistinfo "2:4"') + result = self.dispatcher.handle_request(u'playlistinfo "2:4"') self.assert_(u'Title: a' not in result) self.assert_(u'Title: b' not in result) self.assert_(u'Title: c' in result) @@ -322,52 +322,53 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_playlistinfo_with_too_high_start_of_range_returns_arg_error(self): - result = self.h.handle_request(u'playlistinfo "10:20"') + result = self.dispatcher.handle_request(u'playlistinfo "10:20"') self.assert_(u'ACK [2@0] {playlistinfo} Bad song index' in result) def test_playlistinfo_with_too_high_end_of_range_returns_ok(self): - result = self.h.handle_request(u'playlistinfo "0:20"') + result = self.dispatcher.handle_request(u'playlistinfo "0:20"') self.assert_(u'OK' in result) def test_playlistsearch(self): - result = self.h.handle_request(u'playlistsearch "any" "needle"') + result = self.dispatcher.handle_request( + u'playlistsearch "any" "needle"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_playlistsearch_without_quotes(self): - result = self.h.handle_request(u'playlistsearch any "needle"') + result = self.dispatcher.handle_request(u'playlistsearch any "needle"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_plchanges(self): - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(name='a'), Track(name='b'), Track(name='c')]) - result = self.h.handle_request(u'plchanges "0"') + result = self.dispatcher.handle_request(u'plchanges "0"') self.assert_(u'Title: a' in result) self.assert_(u'Title: b' in result) self.assert_(u'Title: c' in result) self.assert_(u'OK' in result) def test_plchanges_with_minus_one_returns_entire_playlist(self): - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(name='a'), Track(name='b'), Track(name='c')]) - result = self.h.handle_request(u'plchanges "-1"') + result = self.dispatcher.handle_request(u'plchanges "-1"') self.assert_(u'Title: a' in result) self.assert_(u'Title: b' in result) self.assert_(u'Title: c' in result) self.assert_(u'OK' in result) def test_plchanges_without_quotes_works(self): - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(name='a'), Track(name='b'), Track(name='c')]) - result = self.h.handle_request(u'plchanges 0') + result = self.dispatcher.handle_request(u'plchanges 0') self.assert_(u'Title: a' in result) self.assert_(u'Title: b' in result) self.assert_(u'Title: c' in result) self.assert_(u'OK' in result) def test_plchangesposid(self): - self.b.current_playlist.append([Track(), Track(), Track()]) - result = self.h.handle_request(u'plchangesposid "0"') - cp_tracks = self.b.current_playlist.cp_tracks.get() + self.backend.current_playlist.append([Track(), Track(), Track()]) + result = self.dispatcher.handle_request(u'plchangesposid "0"') + cp_tracks = self.backend.current_playlist.cp_tracks.get() self.assert_(u'cpos: 0' in result) self.assert_(u'Id: %d' % cp_tracks[0][0] in result) @@ -380,24 +381,24 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_shuffle_without_range(self): - self.b.current_playlist.append([ + self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - version = self.b.current_playlist.version.get() - result = self.h.handle_request(u'shuffle') - self.assert_(version < self.b.current_playlist.version.get()) + version = self.backend.current_playlist.version.get() + result = self.dispatcher.handle_request(u'shuffle') + self.assert_(version < self.backend.current_playlist.version.get()) self.assert_(u'OK' in result) def test_shuffle_with_open_range(self): - self.b.current_playlist.append([ + self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - version = self.b.current_playlist.version.get() - result = self.h.handle_request(u'shuffle "4:"') - self.assert_(version < self.b.current_playlist.version.get()) - tracks = self.b.current_playlist.tracks.get() + version = self.backend.current_playlist.version.get() + result = self.dispatcher.handle_request(u'shuffle "4:"') + self.assert_(version < self.backend.current_playlist.version.get()) + tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'a') self.assertEqual(tracks[1].name, 'b') self.assertEqual(tracks[2].name, 'c') @@ -405,14 +406,14 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_shuffle_with_closed_range(self): - self.b.current_playlist.append([ + self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - version = self.b.current_playlist.version.get() - result = self.h.handle_request(u'shuffle "1:3"') - self.assert_(version < self.b.current_playlist.version.get()) - tracks = self.b.current_playlist.tracks.get() + version = self.backend.current_playlist.version.get() + result = self.dispatcher.handle_request(u'shuffle "1:3"') + self.assert_(version < self.backend.current_playlist.version.get()) + tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'a') self.assertEqual(tracks[3].name, 'd') self.assertEqual(tracks[4].name, 'e') @@ -420,12 +421,12 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_swap(self): - self.b.current_playlist.append([ + self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.h.handle_request(u'swap "1" "4"') - tracks = self.b.current_playlist.tracks.get() + result = self.dispatcher.handle_request(u'swap "1" "4"') + tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'a') self.assertEqual(tracks[1].name, 'e') self.assertEqual(tracks[2].name, 'c') @@ -435,12 +436,12 @@ class CurrentPlaylistHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_swapid(self): - self.b.current_playlist.append([ + self.backend.current_playlist.append([ Track(name='a'), Track(name='b'), Track(name='c'), Track(name='d'), Track(name='e'), Track(name='f'), ]) - result = self.h.handle_request(u'swapid "1" "4"') - tracks = self.b.current_playlist.tracks.get() + result = self.dispatcher.handle_request(u'swapid "1" "4"') + tracks = self.backend.current_playlist.tracks.get() self.assertEqual(tracks[0].name, 'a') self.assertEqual(tracks[1].name, 'e') self.assertEqual(tracks[2].name, 'c') diff --git a/tests/frontends/mpd/dispatcher_test.py b/tests/frontends/mpd/dispatcher_test.py index 77e0ddf0..1d8efe64 100644 --- a/tests/frontends/mpd/dispatcher_test.py +++ b/tests/frontends/mpd/dispatcher_test.py @@ -1,19 +1,19 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import dispatcher +from mopidy.frontends.mpd.dispatcher import MpdDispatcher from mopidy.frontends.mpd.exceptions import MpdAckError from mopidy.frontends.mpd.protocol import request_handlers, handle_pattern from mopidy.mixers.dummy import DummyMixer class MpdDispatcherTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend.start().proxy() + self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = MpdDispatcher() def tearDown(self): - self.b.stop().get() + self.backend.stop().get() self.mixer.stop().get() def test_register_same_pattern_twice_fails(self): @@ -27,7 +27,7 @@ class MpdDispatcherTest(unittest.TestCase): def test_finding_handler_for_unknown_command_raises_exception(self): try: - self.h.find_handler('an_unknown_command with args') + self.dispatcher.find_handler('an_unknown_command with args') self.fail('Should raise exception') except MpdAckError as e: self.assertEqual(e.get_mpd_ack(), @@ -37,18 +37,18 @@ class MpdDispatcherTest(unittest.TestCase): expected_handler = lambda x: None request_handlers['known_command (?P.+)'] = \ expected_handler - (handler, kwargs) = self.h.find_handler('known_command an_arg') + (handler, kwargs) = self.dispatcher.find_handler('known_command an_arg') self.assertEqual(handler, expected_handler) self.assert_('arg1' in kwargs) self.assertEqual(kwargs['arg1'], 'an_arg') def test_handling_unknown_request_yields_error(self): - result = self.h.handle_request('an unhandled request') + result = self.dispatcher.handle_request('an unhandled request') self.assertEqual(result[0], u'ACK [5@0] {} unknown command "an"') def test_handling_known_request(self): expected = 'magic' request_handlers['known request'] = lambda x: expected - result = self.h.handle_request('known request') + result = self.dispatcher.handle_request('known request') self.assert_(u'OK' in result) self.assert_(expected in result) diff --git a/tests/frontends/mpd/music_db_test.py b/tests/frontends/mpd/music_db_test.py index fa5634be..3793db9e 100644 --- a/tests/frontends/mpd/music_db_test.py +++ b/tests/frontends/mpd/music_db_test.py @@ -1,390 +1,412 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import dispatcher +from mopidy.frontends.mpd.dispatcher import MpdDispatcher from mopidy.mixers.dummy import DummyMixer class MusicDatabaseHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend.start().proxy() + self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = MpdDispatcher() def tearDown(self): - self.b.stop().get() + self.backend.stop().get() self.mixer.stop().get() def test_count(self): - result = self.h.handle_request(u'count "tag" "needle"') + result = self.dispatcher.handle_request(u'count "tag" "needle"') self.assert_(u'songs: 0' in result) self.assert_(u'playtime: 0' in result) self.assert_(u'OK' in result) def test_findadd(self): - result = self.h.handle_request(u'findadd "album" "what"') + result = self.dispatcher.handle_request(u'findadd "album" "what"') self.assert_(u'OK' in result) def test_listall(self): - result = self.h.handle_request(u'listall "file:///dev/urandom"') + result = self.dispatcher.handle_request( + u'listall "file:///dev/urandom"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_listallinfo(self): - result = self.h.handle_request(u'listallinfo "file:///dev/urandom"') + result = self.dispatcher.handle_request( + u'listallinfo "file:///dev/urandom"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_lsinfo_without_path_returns_same_as_listplaylists(self): - lsinfo_result = self.h.handle_request(u'lsinfo') - listplaylists_result = self.h.handle_request(u'listplaylists') + lsinfo_result = self.dispatcher.handle_request(u'lsinfo') + listplaylists_result = self.dispatcher.handle_request(u'listplaylists') self.assertEqual(lsinfo_result, listplaylists_result) def test_lsinfo_with_empty_path_returns_same_as_listplaylists(self): - lsinfo_result = self.h.handle_request(u'lsinfo ""') - listplaylists_result = self.h.handle_request(u'listplaylists') + lsinfo_result = self.dispatcher.handle_request(u'lsinfo ""') + listplaylists_result = self.dispatcher.handle_request(u'listplaylists') self.assertEqual(lsinfo_result, listplaylists_result) def test_lsinfo_for_root_returns_same_as_listplaylists(self): - lsinfo_result = self.h.handle_request(u'lsinfo "/"') - listplaylists_result = self.h.handle_request(u'listplaylists') + lsinfo_result = self.dispatcher.handle_request(u'lsinfo "/"') + listplaylists_result = self.dispatcher.handle_request(u'listplaylists') self.assertEqual(lsinfo_result, listplaylists_result) def test_update_without_uri(self): - result = self.h.handle_request(u'update') + result = self.dispatcher.handle_request(u'update') self.assert_(u'OK' in result) self.assert_(u'updating_db: 0' in result) def test_update_with_uri(self): - result = self.h.handle_request(u'update "file:///dev/urandom"') + result = self.dispatcher.handle_request(u'update "file:///dev/urandom"') self.assert_(u'OK' in result) self.assert_(u'updating_db: 0' in result) def test_rescan_without_uri(self): - result = self.h.handle_request(u'rescan') + result = self.dispatcher.handle_request(u'rescan') self.assert_(u'OK' in result) self.assert_(u'updating_db: 0' in result) def test_rescan_with_uri(self): - result = self.h.handle_request(u'rescan "file:///dev/urandom"') + result = self.dispatcher.handle_request(u'rescan "file:///dev/urandom"') self.assert_(u'OK' in result) self.assert_(u'updating_db: 0' in result) class MusicDatabaseFindTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend.start().proxy() + self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = MpdDispatcher() def tearDown(self): - self.b.stop().get() + self.backend.stop().get() self.mixer.stop().get() def test_find_album(self): - result = self.h.handle_request(u'find "album" "what"') + result = self.dispatcher.handle_request(u'find "album" "what"') self.assert_(u'OK' in result) def test_find_album_without_quotes(self): - result = self.h.handle_request(u'find album "what"') + result = self.dispatcher.handle_request(u'find album "what"') self.assert_(u'OK' in result) def test_find_artist(self): - result = self.h.handle_request(u'find "artist" "what"') + result = self.dispatcher.handle_request(u'find "artist" "what"') self.assert_(u'OK' in result) def test_find_artist_without_quotes(self): - result = self.h.handle_request(u'find artist "what"') + result = self.dispatcher.handle_request(u'find artist "what"') self.assert_(u'OK' in result) def test_find_title(self): - result = self.h.handle_request(u'find "title" "what"') + result = self.dispatcher.handle_request(u'find "title" "what"') self.assert_(u'OK' in result) def test_find_title_without_quotes(self): - result = self.h.handle_request(u'find title "what"') + result = self.dispatcher.handle_request(u'find title "what"') self.assert_(u'OK' in result) def test_find_date(self): - result = self.h.handle_request(u'find "date" "2002-01-01"') + result = self.dispatcher.handle_request(u'find "date" "2002-01-01"') self.assert_(u'OK' in result) def test_find_date_without_quotes(self): - result = self.h.handle_request(u'find date "2002-01-01"') + result = self.dispatcher.handle_request(u'find date "2002-01-01"') self.assert_(u'OK' in result) def test_find_date_with_capital_d_and_incomplete_date(self): - result = self.h.handle_request(u'find Date "2005"') + result = self.dispatcher.handle_request(u'find Date "2005"') self.assert_(u'OK' in result) def test_find_else_should_fail(self): - result = self.h.handle_request(u'find "somethingelse" "what"') + result = self.dispatcher.handle_request(u'find "somethingelse" "what"') self.assertEqual(result[0], u'ACK [2@0] {find} incorrect arguments') def test_find_album_and_artist(self): - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'find album "album_what" artist "artist_what"') self.assert_(u'OK' in result) class MusicDatabaseListTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend.start().proxy() + self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = MpdDispatcher() def tearDown(self): - self.b.stop().get() + self.backend.stop().get() self.mixer.stop().get() def test_list_foo_returns_ack(self): - result = self.h.handle_request(u'list "foo"') + result = self.dispatcher.handle_request(u'list "foo"') self.assertEqual(result[0], u'ACK [2@0] {list} incorrect arguments') ### Artist def test_list_artist_with_quotes(self): - result = self.h.handle_request(u'list "artist"') + result = self.dispatcher.handle_request(u'list "artist"') self.assert_(u'OK' in result) def test_list_artist_without_quotes(self): - result = self.h.handle_request(u'list artist') + result = self.dispatcher.handle_request(u'list artist') self.assert_(u'OK' in result) def test_list_artist_without_quotes_and_capitalized(self): - result = self.h.handle_request(u'list Artist') + result = self.dispatcher.handle_request(u'list Artist') self.assert_(u'OK' in result) def test_list_artist_with_query_of_one_token(self): - result = self.h.handle_request(u'list "artist" "anartist"') + result = self.dispatcher.handle_request(u'list "artist" "anartist"') self.assertEqual(result[0], u'ACK [2@0] {list} should be "Album" for 3 arguments') def test_list_artist_with_unknown_field_in_query_returns_ack(self): - result = self.h.handle_request(u'list "artist" "foo" "bar"') + result = self.dispatcher.handle_request(u'list "artist" "foo" "bar"') self.assertEqual(result[0], u'ACK [2@0] {list} not able to parse args') def test_list_artist_by_artist(self): - result = self.h.handle_request(u'list "artist" "artist" "anartist"') + result = self.dispatcher.handle_request( + u'list "artist" "artist" "anartist"') self.assert_(u'OK' in result) def test_list_artist_by_album(self): - result = self.h.handle_request(u'list "artist" "album" "analbum"') + result = self.dispatcher.handle_request( + u'list "artist" "album" "analbum"') self.assert_(u'OK' in result) def test_list_artist_by_full_date(self): - result = self.h.handle_request(u'list "artist" "date" "2001-01-01"') + result = self.dispatcher.handle_request( + u'list "artist" "date" "2001-01-01"') self.assert_(u'OK' in result) def test_list_artist_by_year(self): - result = self.h.handle_request(u'list "artist" "date" "2001"') + result = self.dispatcher.handle_request( + u'list "artist" "date" "2001"') self.assert_(u'OK' in result) def test_list_artist_by_genre(self): - result = self.h.handle_request(u'list "artist" "genre" "agenre"') + result = self.dispatcher.handle_request( + u'list "artist" "genre" "agenre"') self.assert_(u'OK' in result) def test_list_artist_by_artist_and_album(self): - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'list "artist" "artist" "anartist" "album" "analbum"') self.assert_(u'OK' in result) ### Album def test_list_album_with_quotes(self): - result = self.h.handle_request(u'list "album"') + result = self.dispatcher.handle_request(u'list "album"') self.assert_(u'OK' in result) def test_list_album_without_quotes(self): - result = self.h.handle_request(u'list album') + result = self.dispatcher.handle_request(u'list album') self.assert_(u'OK' in result) def test_list_album_without_quotes_and_capitalized(self): - result = self.h.handle_request(u'list Album') + result = self.dispatcher.handle_request(u'list Album') self.assert_(u'OK' in result) def test_list_album_with_artist_name(self): - result = self.h.handle_request(u'list "album" "anartist"') + result = self.dispatcher.handle_request(u'list "album" "anartist"') self.assert_(u'OK' in result) def test_list_album_by_artist(self): - result = self.h.handle_request(u'list "album" "artist" "anartist"') + result = self.dispatcher.handle_request( + u'list "album" "artist" "anartist"') self.assert_(u'OK' in result) def test_list_album_by_album(self): - result = self.h.handle_request(u'list "album" "album" "analbum"') + result = self.dispatcher.handle_request( + u'list "album" "album" "analbum"') self.assert_(u'OK' in result) def test_list_album_by_full_date(self): - result = self.h.handle_request(u'list "album" "date" "2001-01-01"') + result = self.dispatcher.handle_request( + u'list "album" "date" "2001-01-01"') self.assert_(u'OK' in result) def test_list_album_by_year(self): - result = self.h.handle_request(u'list "album" "date" "2001"') + result = self.dispatcher.handle_request( + u'list "album" "date" "2001"') self.assert_(u'OK' in result) def test_list_album_by_genre(self): - result = self.h.handle_request(u'list "album" "genre" "agenre"') + result = self.dispatcher.handle_request( + u'list "album" "genre" "agenre"') self.assert_(u'OK' in result) def test_list_album_by_artist_and_album(self): - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'list "album" "artist" "anartist" "album" "analbum"') self.assert_(u'OK' in result) ### Date def test_list_date_with_quotes(self): - result = self.h.handle_request(u'list "date"') + result = self.dispatcher.handle_request(u'list "date"') self.assert_(u'OK' in result) def test_list_date_without_quotes(self): - result = self.h.handle_request(u'list date') + result = self.dispatcher.handle_request(u'list date') self.assert_(u'OK' in result) def test_list_date_without_quotes_and_capitalized(self): - result = self.h.handle_request(u'list Date') + result = self.dispatcher.handle_request(u'list Date') self.assert_(u'OK' in result) def test_list_date_with_query_of_one_token(self): - result = self.h.handle_request(u'list "date" "anartist"') + result = self.dispatcher.handle_request(u'list "date" "anartist"') self.assertEqual(result[0], u'ACK [2@0] {list} should be "Album" for 3 arguments') def test_list_date_by_artist(self): - result = self.h.handle_request(u'list "date" "artist" "anartist"') + result = self.dispatcher.handle_request( + u'list "date" "artist" "anartist"') self.assert_(u'OK' in result) def test_list_date_by_album(self): - result = self.h.handle_request(u'list "date" "album" "analbum"') + result = self.dispatcher.handle_request( + u'list "date" "album" "analbum"') self.assert_(u'OK' in result) def test_list_date_by_full_date(self): - result = self.h.handle_request(u'list "date" "date" "2001-01-01"') + result = self.dispatcher.handle_request( + u'list "date" "date" "2001-01-01"') self.assert_(u'OK' in result) def test_list_date_by_year(self): - result = self.h.handle_request(u'list "date" "date" "2001"') + result = self.dispatcher.handle_request(u'list "date" "date" "2001"') self.assert_(u'OK' in result) def test_list_date_by_genre(self): - result = self.h.handle_request(u'list "date" "genre" "agenre"') + result = self.dispatcher.handle_request(u'list "date" "genre" "agenre"') self.assert_(u'OK' in result) def test_list_date_by_artist_and_album(self): - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'list "date" "artist" "anartist" "album" "analbum"') self.assert_(u'OK' in result) ### Genre def test_list_genre_with_quotes(self): - result = self.h.handle_request(u'list "genre"') + result = self.dispatcher.handle_request(u'list "genre"') self.assert_(u'OK' in result) def test_list_genre_without_quotes(self): - result = self.h.handle_request(u'list genre') + result = self.dispatcher.handle_request(u'list genre') self.assert_(u'OK' in result) def test_list_genre_without_quotes_and_capitalized(self): - result = self.h.handle_request(u'list Genre') + result = self.dispatcher.handle_request(u'list Genre') self.assert_(u'OK' in result) def test_list_genre_with_query_of_one_token(self): - result = self.h.handle_request(u'list "genre" "anartist"') + result = self.dispatcher.handle_request(u'list "genre" "anartist"') self.assertEqual(result[0], u'ACK [2@0] {list} should be "Album" for 3 arguments') def test_list_genre_by_artist(self): - result = self.h.handle_request(u'list "genre" "artist" "anartist"') + result = self.dispatcher.handle_request( + u'list "genre" "artist" "anartist"') self.assert_(u'OK' in result) def test_list_genre_by_album(self): - result = self.h.handle_request(u'list "genre" "album" "analbum"') + result = self.dispatcher.handle_request( + u'list "genre" "album" "analbum"') self.assert_(u'OK' in result) def test_list_genre_by_full_date(self): - result = self.h.handle_request(u'list "genre" "date" "2001-01-01"') + result = self.dispatcher.handle_request( + u'list "genre" "date" "2001-01-01"') self.assert_(u'OK' in result) def test_list_genre_by_year(self): - result = self.h.handle_request(u'list "genre" "date" "2001"') + result = self.dispatcher.handle_request( + u'list "genre" "date" "2001"') self.assert_(u'OK' in result) def test_list_genre_by_genre(self): - result = self.h.handle_request(u'list "genre" "genre" "agenre"') + result = self.dispatcher.handle_request( + u'list "genre" "genre" "agenre"') self.assert_(u'OK' in result) def test_list_genre_by_artist_and_album(self): - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'list "genre" "artist" "anartist" "album" "analbum"') self.assert_(u'OK' in result) class MusicDatabaseSearchTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend.start().proxy() + self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = MpdDispatcher() def tearDown(self): - self.b.stop().get() + self.backend.stop().get() self.mixer.stop().get() def test_search_album(self): - result = self.h.handle_request(u'search "album" "analbum"') + result = self.dispatcher.handle_request(u'search "album" "analbum"') self.assert_(u'OK' in result) def test_search_album_without_quotes(self): - result = self.h.handle_request(u'search album "analbum"') + result = self.dispatcher.handle_request(u'search album "analbum"') self.assert_(u'OK' in result) def test_search_artist(self): - result = self.h.handle_request(u'search "artist" "anartist"') + result = self.dispatcher.handle_request(u'search "artist" "anartist"') self.assert_(u'OK' in result) def test_search_artist_without_quotes(self): - result = self.h.handle_request(u'search artist "anartist"') + result = self.dispatcher.handle_request(u'search artist "anartist"') self.assert_(u'OK' in result) def test_search_filename(self): - result = self.h.handle_request(u'search "filename" "afilename"') + result = self.dispatcher.handle_request( + u'search "filename" "afilename"') self.assert_(u'OK' in result) def test_search_filename_without_quotes(self): - result = self.h.handle_request(u'search filename "afilename"') + result = self.dispatcher.handle_request(u'search filename "afilename"') self.assert_(u'OK' in result) def test_search_title(self): - result = self.h.handle_request(u'search "title" "atitle"') + result = self.dispatcher.handle_request(u'search "title" "atitle"') self.assert_(u'OK' in result) def test_search_title_without_quotes(self): - result = self.h.handle_request(u'search title "atitle"') + result = self.dispatcher.handle_request(u'search title "atitle"') self.assert_(u'OK' in result) def test_search_any(self): - result = self.h.handle_request(u'search "any" "anything"') + result = self.dispatcher.handle_request(u'search "any" "anything"') self.assert_(u'OK' in result) def test_search_any_without_quotes(self): - result = self.h.handle_request(u'search any "anything"') + result = self.dispatcher.handle_request(u'search any "anything"') self.assert_(u'OK' in result) def test_search_date(self): - result = self.h.handle_request(u'search "date" "2002-01-01"') + result = self.dispatcher.handle_request(u'search "date" "2002-01-01"') self.assert_(u'OK' in result) def test_search_date_without_quotes(self): - result = self.h.handle_request(u'search date "2002-01-01"') + result = self.dispatcher.handle_request(u'search date "2002-01-01"') self.assert_(u'OK' in result) def test_search_date_with_capital_d_and_incomplete_date(self): - result = self.h.handle_request(u'search Date "2005"') + result = self.dispatcher.handle_request(u'search Date "2005"') self.assert_(u'OK' in result) def test_search_else_should_fail(self): - result = self.h.handle_request(u'search "sometype" "something"') + result = self.dispatcher.handle_request( + u'search "sometype" "something"') self.assertEqual(result[0], u'ACK [2@0] {search} incorrect arguments') diff --git a/tests/frontends/mpd/playback_test.py b/tests/frontends/mpd/playback_test.py index 8601aa9c..e80943d6 100644 --- a/tests/frontends/mpd/playback_test.py +++ b/tests/frontends/mpd/playback_test.py @@ -2,7 +2,7 @@ import unittest from mopidy.backends.base import PlaybackController from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import dispatcher +from mopidy.frontends.mpd.dispatcher import MpdDispatcher from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track @@ -14,393 +14,378 @@ STOPPED = PlaybackController.STOPPED class PlaybackOptionsHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend.start().proxy() + self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = MpdDispatcher() def tearDown(self): - self.b.stop().get() + self.backend.stop().get() self.mixer.stop().get() def test_consume_off(self): - result = self.h.handle_request(u'consume "0"') - self.assertFalse(self.b.playback.consume.get()) + result = self.dispatcher.handle_request(u'consume "0"') + self.assertFalse(self.backend.playback.consume.get()) self.assert_(u'OK' in result) def test_consume_off_without_quotes(self): - result = self.h.handle_request(u'consume 0') - self.assertFalse(self.b.playback.consume.get()) + result = self.dispatcher.handle_request(u'consume 0') + self.assertFalse(self.backend.playback.consume.get()) self.assert_(u'OK' in result) def test_consume_on(self): - result = self.h.handle_request(u'consume "1"') - self.assertTrue(self.b.playback.consume.get()) + result = self.dispatcher.handle_request(u'consume "1"') + self.assertTrue(self.backend.playback.consume.get()) self.assert_(u'OK' in result) def test_consume_on_without_quotes(self): - result = self.h.handle_request(u'consume 1') - self.assertTrue(self.b.playback.consume.get()) + result = self.dispatcher.handle_request(u'consume 1') + self.assertTrue(self.backend.playback.consume.get()) self.assert_(u'OK' in result) def test_crossfade(self): - result = self.h.handle_request(u'crossfade "10"') + result = self.dispatcher.handle_request(u'crossfade "10"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_random_off(self): - result = self.h.handle_request(u'random "0"') - self.assertFalse(self.b.playback.random.get()) + result = self.dispatcher.handle_request(u'random "0"') + self.assertFalse(self.backend.playback.random.get()) self.assert_(u'OK' in result) def test_random_off_without_quotes(self): - result = self.h.handle_request(u'random 0') - self.assertFalse(self.b.playback.random.get()) + result = self.dispatcher.handle_request(u'random 0') + self.assertFalse(self.backend.playback.random.get()) self.assert_(u'OK' in result) def test_random_on(self): - result = self.h.handle_request(u'random "1"') - self.assertTrue(self.b.playback.random.get()) + result = self.dispatcher.handle_request(u'random "1"') + self.assertTrue(self.backend.playback.random.get()) self.assert_(u'OK' in result) def test_random_on_without_quotes(self): - result = self.h.handle_request(u'random 1') - self.assertTrue(self.b.playback.random.get()) + result = self.dispatcher.handle_request(u'random 1') + self.assertTrue(self.backend.playback.random.get()) self.assert_(u'OK' in result) def test_repeat_off(self): - result = self.h.handle_request(u'repeat "0"') - self.assertFalse(self.b.playback.repeat.get()) + result = self.dispatcher.handle_request(u'repeat "0"') + self.assertFalse(self.backend.playback.repeat.get()) self.assert_(u'OK' in result) def test_repeat_off_without_quotes(self): - result = self.h.handle_request(u'repeat 0') - self.assertFalse(self.b.playback.repeat.get()) + result = self.dispatcher.handle_request(u'repeat 0') + self.assertFalse(self.backend.playback.repeat.get()) self.assert_(u'OK' in result) def test_repeat_on(self): - result = self.h.handle_request(u'repeat "1"') - self.assertTrue(self.b.playback.repeat.get()) + result = self.dispatcher.handle_request(u'repeat "1"') + self.assertTrue(self.backend.playback.repeat.get()) self.assert_(u'OK' in result) def test_repeat_on_without_quotes(self): - result = self.h.handle_request(u'repeat 1') - self.assertTrue(self.b.playback.repeat.get()) + result = self.dispatcher.handle_request(u'repeat 1') + self.assertTrue(self.backend.playback.repeat.get()) self.assert_(u'OK' in result) def test_setvol_below_min(self): - result = self.h.handle_request(u'setvol "-10"') + result = self.dispatcher.handle_request(u'setvol "-10"') self.assert_(u'OK' in result) self.assertEqual(0, self.mixer.volume.get()) def test_setvol_min(self): - result = self.h.handle_request(u'setvol "0"') + result = self.dispatcher.handle_request(u'setvol "0"') self.assert_(u'OK' in result) self.assertEqual(0, self.mixer.volume.get()) def test_setvol_middle(self): - result = self.h.handle_request(u'setvol "50"') + result = self.dispatcher.handle_request(u'setvol "50"') self.assert_(u'OK' in result) self.assertEqual(50, self.mixer.volume.get()) def test_setvol_max(self): - result = self.h.handle_request(u'setvol "100"') + result = self.dispatcher.handle_request(u'setvol "100"') self.assert_(u'OK' in result) self.assertEqual(100, self.mixer.volume.get()) def test_setvol_above_max(self): - result = self.h.handle_request(u'setvol "110"') + result = self.dispatcher.handle_request(u'setvol "110"') self.assert_(u'OK' in result) self.assertEqual(100, self.mixer.volume.get()) def test_setvol_plus_is_ignored(self): - result = self.h.handle_request(u'setvol "+10"') + result = self.dispatcher.handle_request(u'setvol "+10"') self.assert_(u'OK' in result) self.assertEqual(10, self.mixer.volume.get()) def test_setvol_without_quotes(self): - result = self.h.handle_request(u'setvol 50') + result = self.dispatcher.handle_request(u'setvol 50') self.assert_(u'OK' in result) self.assertEqual(50, self.mixer.volume.get()) def test_single_off(self): - result = self.h.handle_request(u'single "0"') - self.assertFalse(self.b.playback.single.get()) + result = self.dispatcher.handle_request(u'single "0"') + self.assertFalse(self.backend.playback.single.get()) self.assert_(u'OK' in result) def test_single_off_without_quotes(self): - result = self.h.handle_request(u'single 0') - self.assertFalse(self.b.playback.single.get()) + result = self.dispatcher.handle_request(u'single 0') + self.assertFalse(self.backend.playback.single.get()) self.assert_(u'OK' in result) def test_single_on(self): - result = self.h.handle_request(u'single "1"') - self.assertTrue(self.b.playback.single.get()) + result = self.dispatcher.handle_request(u'single "1"') + self.assertTrue(self.backend.playback.single.get()) self.assert_(u'OK' in result) def test_single_on_without_quotes(self): - result = self.h.handle_request(u'single 1') - self.assertTrue(self.b.playback.single.get()) + result = self.dispatcher.handle_request(u'single 1') + self.assertTrue(self.backend.playback.single.get()) self.assert_(u'OK' in result) def test_replay_gain_mode_off(self): - result = self.h.handle_request(u'replay_gain_mode "off"') + result = self.dispatcher.handle_request(u'replay_gain_mode "off"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_replay_gain_mode_track(self): - result = self.h.handle_request(u'replay_gain_mode "track"') + result = self.dispatcher.handle_request(u'replay_gain_mode "track"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_replay_gain_mode_album(self): - result = self.h.handle_request(u'replay_gain_mode "album"') + result = self.dispatcher.handle_request(u'replay_gain_mode "album"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_replay_gain_status_default(self): expected = u'off' - result = self.h.handle_request(u'replay_gain_status') + result = self.dispatcher.handle_request(u'replay_gain_status') self.assert_(u'OK' in result) self.assert_(expected in result) def test_replay_gain_status_off(self): - raise SkipTest - expected = u'off' - self.h._replay_gain_mode(expected) - result = self.h.handle_request(u'replay_gain_status') - self.assert_(u'OK' in result) - self.assert_(expected in result) + raise SkipTest # TODO def test_replay_gain_status_track(self): - raise SkipTest - expected = u'track' - self.h._replay_gain_mode(expected) - result = self.h.handle_request(u'replay_gain_status') - self.assert_(u'OK' in result) - self.assert_(expected in result) + raise SkipTest # TODO def test_replay_gain_status_album(self): - raise SkipTest - expected = u'album' - self.h._replay_gain_mode(expected) - result = self.h.handle_request(u'replay_gain_status') - self.assert_(u'OK' in result) - self.assert_(expected in result) + raise SkipTest # TODO class PlaybackControlHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend.start().proxy() + self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = MpdDispatcher() def tearDown(self): - self.b.stop().get() + self.backend.stop().get() self.mixer.stop().get() def test_next(self): - result = self.h.handle_request(u'next') + result = self.dispatcher.handle_request(u'next') self.assert_(u'OK' in result) def test_pause_off(self): - self.b.current_playlist.append([Track()]) - self.h.handle_request(u'play "0"') - self.h.handle_request(u'pause "1"') - result = self.h.handle_request(u'pause "0"') + self.backend.current_playlist.append([Track()]) + self.dispatcher.handle_request(u'play "0"') + self.dispatcher.handle_request(u'pause "1"') + result = self.dispatcher.handle_request(u'pause "0"') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) + self.assertEqual(PLAYING, self.backend.playback.state.get()) def test_pause_on(self): - self.b.current_playlist.append([Track()]) - self.h.handle_request(u'play "0"') - result = self.h.handle_request(u'pause "1"') + self.backend.current_playlist.append([Track()]) + self.dispatcher.handle_request(u'play "0"') + result = self.dispatcher.handle_request(u'pause "1"') self.assert_(u'OK' in result) - self.assertEqual(PAUSED, self.b.playback.state.get()) + self.assertEqual(PAUSED, self.backend.playback.state.get()) def test_pause_toggle(self): - self.b.current_playlist.append([Track()]) - result = self.h.handle_request(u'play "0"') + self.backend.current_playlist.append([Track()]) + result = self.dispatcher.handle_request(u'play "0"') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) - result = self.h.handle_request(u'pause') + self.assertEqual(PLAYING, self.backend.playback.state.get()) + result = self.dispatcher.handle_request(u'pause') self.assert_(u'OK' in result) - self.assertEqual(PAUSED, self.b.playback.state.get()) - result = self.h.handle_request(u'pause') + self.assertEqual(PAUSED, self.backend.playback.state.get()) + result = self.dispatcher.handle_request(u'pause') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) + self.assertEqual(PLAYING, self.backend.playback.state.get()) def test_play_without_pos(self): - self.b.current_playlist.append([Track()]) - self.b.playback.state = PAUSED - result = self.h.handle_request(u'play') + self.backend.current_playlist.append([Track()]) + self.backend.playback.state = PAUSED + result = self.dispatcher.handle_request(u'play') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) + self.assertEqual(PLAYING, self.backend.playback.state.get()) def test_play_with_pos(self): - self.b.current_playlist.append([Track()]) - result = self.h.handle_request(u'play "0"') + self.backend.current_playlist.append([Track()]) + result = self.dispatcher.handle_request(u'play "0"') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) + self.assertEqual(PLAYING, self.backend.playback.state.get()) def test_play_with_pos_without_quotes(self): - self.b.current_playlist.append([Track()]) - result = self.h.handle_request(u'play 0') + self.backend.current_playlist.append([Track()]) + result = self.dispatcher.handle_request(u'play 0') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) + self.assertEqual(PLAYING, self.backend.playback.state.get()) def test_play_with_pos_out_of_bounds(self): - self.b.current_playlist.append([]) - result = self.h.handle_request(u'play "0"') + self.backend.current_playlist.append([]) + result = self.dispatcher.handle_request(u'play "0"') self.assertEqual(result[0], u'ACK [2@0] {play} Bad song index') - self.assertEqual(STOPPED, self.b.playback.state.get()) + self.assertEqual(STOPPED, self.backend.playback.state.get()) def test_play_minus_one_plays_first_in_playlist_if_no_current_track(self): - self.assertEqual(self.b.playback.current_track.get(), None) - self.b.current_playlist.append([Track(uri='a'), Track(uri='b')]) - result = self.h.handle_request(u'play "-1"') + self.assertEqual(self.backend.playback.current_track.get(), None) + self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')]) + result = self.dispatcher.handle_request(u'play "-1"') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) - self.assertEqual(self.b.playback.current_track.get().uri, 'a') + self.assertEqual(PLAYING, self.backend.playback.state.get()) + self.assertEqual(self.backend.playback.current_track.get().uri, 'a') def test_play_minus_one_plays_current_track_if_current_track_is_set(self): - self.b.current_playlist.append([Track(uri='a'), Track(uri='b')]) - self.assertEqual(self.b.playback.current_track.get(), None) - self.b.playback.play() - self.b.playback.next() - self.b.playback.stop() - self.assertNotEqual(self.b.playback.current_track.get(), None) - result = self.h.handle_request(u'play "-1"') + self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')]) + self.assertEqual(self.backend.playback.current_track.get(), None) + self.backend.playback.play() + self.backend.playback.next() + self.backend.playback.stop() + self.assertNotEqual(self.backend.playback.current_track.get(), None) + result = self.dispatcher.handle_request(u'play "-1"') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) - self.assertEqual(self.b.playback.current_track.get().uri, 'b') + self.assertEqual(PLAYING, self.backend.playback.state.get()) + self.assertEqual(self.backend.playback.current_track.get().uri, 'b') def test_play_minus_one_on_empty_playlist_does_not_ack(self): - self.b.current_playlist.clear() - result = self.h.handle_request(u'play "-1"') + self.backend.current_playlist.clear() + result = self.dispatcher.handle_request(u'play "-1"') self.assert_(u'OK' in result) - self.assertEqual(STOPPED, self.b.playback.state.get()) - self.assertEqual(self.b.playback.current_track.get(), None) + self.assertEqual(STOPPED, self.backend.playback.state.get()) + self.assertEqual(self.backend.playback.current_track.get(), None) def test_play_minus_is_ignored_if_playing(self): - self.b.current_playlist.append([Track(length=40000)]) - self.b.playback.seek(30000) - self.assert_(self.b.playback.time_position.get() >= 30000) - self.assertEquals(PLAYING, self.b.playback.state.get()) - result = self.h.handle_request(u'play "-1"') + self.backend.current_playlist.append([Track(length=40000)]) + self.backend.playback.seek(30000) + self.assert_(self.backend.playback.time_position.get() >= 30000) + self.assertEquals(PLAYING, self.backend.playback.state.get()) + result = self.dispatcher.handle_request(u'play "-1"') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) - self.assert_(self.b.playback.time_position.get() >= 30000) + self.assertEqual(PLAYING, self.backend.playback.state.get()) + self.assert_(self.backend.playback.time_position.get() >= 30000) def test_play_minus_one_resumes_if_paused(self): - self.b.current_playlist.append([Track(length=40000)]) - self.b.playback.seek(30000) - self.assert_(self.b.playback.time_position.get() >= 30000) - self.assertEquals(PLAYING, self.b.playback.state.get()) - self.b.playback.pause() - self.assertEquals(PAUSED, self.b.playback.state.get()) - result = self.h.handle_request(u'play "-1"') + self.backend.current_playlist.append([Track(length=40000)]) + self.backend.playback.seek(30000) + self.assert_(self.backend.playback.time_position.get() >= 30000) + self.assertEquals(PLAYING, self.backend.playback.state.get()) + self.backend.playback.pause() + self.assertEquals(PAUSED, self.backend.playback.state.get()) + result = self.dispatcher.handle_request(u'play "-1"') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) - self.assert_(self.b.playback.time_position.get() >= 30000) + self.assertEqual(PLAYING, self.backend.playback.state.get()) + self.assert_(self.backend.playback.time_position.get() >= 30000) def test_playid(self): - self.b.current_playlist.append([Track()]) - result = self.h.handle_request(u'playid "0"') + self.backend.current_playlist.append([Track()]) + result = self.dispatcher.handle_request(u'playid "0"') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) + self.assertEqual(PLAYING, self.backend.playback.state.get()) def test_playid_minus_one_plays_first_in_playlist_if_no_current_track(self): - self.assertEqual(self.b.playback.current_track.get(), None) - self.b.current_playlist.append([Track(uri='a'), Track(uri='b')]) - result = self.h.handle_request(u'playid "-1"') + self.assertEqual(self.backend.playback.current_track.get(), None) + self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')]) + result = self.dispatcher.handle_request(u'playid "-1"') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) - self.assertEqual(self.b.playback.current_track.get().uri, 'a') + self.assertEqual(PLAYING, self.backend.playback.state.get()) + self.assertEqual(self.backend.playback.current_track.get().uri, 'a') def test_playid_minus_one_plays_current_track_if_current_track_is_set(self): - self.b.current_playlist.append([Track(uri='a'), Track(uri='b')]) - self.assertEqual(self.b.playback.current_track.get(), None) - self.b.playback.play() - self.b.playback.next() - self.b.playback.stop() - self.assertNotEqual(self.b.playback.current_track.get(), None) - result = self.h.handle_request(u'playid "-1"') + self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')]) + self.assertEqual(self.backend.playback.current_track.get(), None) + self.backend.playback.play() + self.backend.playback.next() + self.backend.playback.stop() + self.assertNotEqual(self.backend.playback.current_track.get(), None) + result = self.dispatcher.handle_request(u'playid "-1"') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) - self.assertEqual(self.b.playback.current_track.get().uri, 'b') + self.assertEqual(PLAYING, self.backend.playback.state.get()) + self.assertEqual(self.backend.playback.current_track.get().uri, 'b') def test_playid_minus_one_on_empty_playlist_does_not_ack(self): - self.b.current_playlist.clear() - result = self.h.handle_request(u'playid "-1"') + self.backend.current_playlist.clear() + result = self.dispatcher.handle_request(u'playid "-1"') self.assert_(u'OK' in result) - self.assertEqual(STOPPED, self.b.playback.state.get()) - self.assertEqual(self.b.playback.current_track.get(), None) + self.assertEqual(STOPPED, self.backend.playback.state.get()) + self.assertEqual(self.backend.playback.current_track.get(), None) def test_playid_minus_is_ignored_if_playing(self): - self.b.current_playlist.append([Track(length=40000)]) - self.b.playback.seek(30000) - self.assert_(self.b.playback.time_position.get() >= 30000) - self.assertEquals(PLAYING, self.b.playback.state.get()) - result = self.h.handle_request(u'playid "-1"') + self.backend.current_playlist.append([Track(length=40000)]) + self.backend.playback.seek(30000) + self.assert_(self.backend.playback.time_position.get() >= 30000) + self.assertEquals(PLAYING, self.backend.playback.state.get()) + result = self.dispatcher.handle_request(u'playid "-1"') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) - self.assert_(self.b.playback.time_position.get() >= 30000) + self.assertEqual(PLAYING, self.backend.playback.state.get()) + self.assert_(self.backend.playback.time_position.get() >= 30000) def test_playid_minus_one_resumes_if_paused(self): - self.b.current_playlist.append([Track(length=40000)]) - self.b.playback.seek(30000) - self.assert_(self.b.playback.time_position.get() >= 30000) - self.assertEquals(PLAYING, self.b.playback.state.get()) - self.b.playback.pause() - self.assertEquals(PAUSED, self.b.playback.state.get()) - result = self.h.handle_request(u'playid "-1"') + self.backend.current_playlist.append([Track(length=40000)]) + self.backend.playback.seek(30000) + self.assert_(self.backend.playback.time_position.get() >= 30000) + self.assertEquals(PLAYING, self.backend.playback.state.get()) + self.backend.playback.pause() + self.assertEquals(PAUSED, self.backend.playback.state.get()) + result = self.dispatcher.handle_request(u'playid "-1"') self.assert_(u'OK' in result) - self.assertEqual(PLAYING, self.b.playback.state.get()) - self.assert_(self.b.playback.time_position.get() >= 30000) + self.assertEqual(PLAYING, self.backend.playback.state.get()) + self.assert_(self.backend.playback.time_position.get() >= 30000) def test_playid_which_does_not_exist(self): - self.b.current_playlist.append([Track()]) - result = self.h.handle_request(u'playid "12345"') + self.backend.current_playlist.append([Track()]) + result = self.dispatcher.handle_request(u'playid "12345"') self.assertEqual(result[0], u'ACK [50@0] {playid} No such song') def test_previous(self): - result = self.h.handle_request(u'previous') + result = self.dispatcher.handle_request(u'previous') self.assert_(u'OK' in result) def test_seek(self): - self.b.current_playlist.append([Track(length=40000)]) - self.h.handle_request(u'seek "0"') - result = self.h.handle_request(u'seek "0" "30"') + self.backend.current_playlist.append([Track(length=40000)]) + self.dispatcher.handle_request(u'seek "0"') + result = self.dispatcher.handle_request(u'seek "0" "30"') self.assert_(u'OK' in result) - self.assert_(self.b.playback.time_position >= 30000) + self.assert_(self.backend.playback.time_position >= 30000) def test_seek_with_songpos(self): seek_track = Track(uri='2', length=40000) - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(uri='1', length=40000), seek_track]) - result = self.h.handle_request(u'seek "1" "30"') + result = self.dispatcher.handle_request(u'seek "1" "30"') self.assert_(u'OK' in result) - self.assertEqual(self.b.playback.current_track.get(), seek_track) + self.assertEqual(self.backend.playback.current_track.get(), seek_track) def test_seek_without_quotes(self): - self.b.current_playlist.append([Track(length=40000)]) - self.h.handle_request(u'seek 0') - result = self.h.handle_request(u'seek 0 30') + self.backend.current_playlist.append([Track(length=40000)]) + self.dispatcher.handle_request(u'seek 0') + result = self.dispatcher.handle_request(u'seek 0 30') self.assert_(u'OK' in result) - self.assert_(self.b.playback.time_position.get() >= 30000) + self.assert_(self.backend.playback.time_position.get() >= 30000) def test_seekid(self): - self.b.current_playlist.append([Track(length=40000)]) - result = self.h.handle_request(u'seekid "0" "30"') + self.backend.current_playlist.append([Track(length=40000)]) + result = self.dispatcher.handle_request(u'seekid "0" "30"') self.assert_(u'OK' in result) - self.assert_(self.b.playback.time_position.get() >= 30000) + self.assert_(self.backend.playback.time_position.get() >= 30000) def test_seekid_with_cpid(self): seek_track = Track(uri='2', length=40000) - self.b.current_playlist.append( + self.backend.current_playlist.append( [Track(length=40000), seek_track]) - result = self.h.handle_request(u'seekid "1" "30"') + result = self.dispatcher.handle_request(u'seekid "1" "30"') self.assert_(u'OK' in result) - self.assertEqual(self.b.playback.current_cpid.get(), 1) - self.assertEqual(self.b.playback.current_track.get(), seek_track) + self.assertEqual(self.backend.playback.current_cpid.get(), 1) + self.assertEqual(self.backend.playback.current_track.get(), seek_track) def test_stop(self): - result = self.h.handle_request(u'stop') + result = self.dispatcher.handle_request(u'stop') self.assert_(u'OK' in result) - self.assertEqual(STOPPED, self.b.playback.state.get()) + self.assertEqual(STOPPED, self.backend.playback.state.get()) diff --git a/tests/frontends/mpd/reflection_test.py b/tests/frontends/mpd/reflection_test.py index be95c49b..c5cde1bb 100644 --- a/tests/frontends/mpd/reflection_test.py +++ b/tests/frontends/mpd/reflection_test.py @@ -1,21 +1,21 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import dispatcher +from mopidy.frontends.mpd.dispatcher import MpdDispatcher from mopidy.mixers.dummy import DummyMixer class ReflectionHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend.start().proxy() + self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = MpdDispatcher() def tearDown(self): - self.b.stop().get() + self.backend.stop().get() self.mixer.stop().get() def test_commands_returns_list_of_all_commands(self): - result = self.h.handle_request(u'commands') + result = self.dispatcher.handle_request(u'commands') # Check if some random commands are included self.assert_(u'command: commands' in result) self.assert_(u'command: play' in result) @@ -30,19 +30,19 @@ class ReflectionHandlerTest(unittest.TestCase): self.assert_(u'OK' in result) def test_decoders(self): - result = self.h.handle_request(u'decoders') + result = self.dispatcher.handle_request(u'decoders') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_notcommands_returns_only_ok(self): - result = self.h.handle_request(u'notcommands') + result = self.dispatcher.handle_request(u'notcommands') self.assertEqual(1, len(result)) self.assert_(u'OK' in result) def test_tagtypes(self): - result = self.h.handle_request(u'tagtypes') + result = self.dispatcher.handle_request(u'tagtypes') self.assert_(u'OK' in result) def test_urlhandlers(self): - result = self.h.handle_request(u'urlhandlers') + result = self.dispatcher.handle_request(u'urlhandlers') self.assert_(u'OK' in result) self.assert_(u'handler: dummy:' in result) diff --git a/tests/frontends/mpd/stickers_test.py b/tests/frontends/mpd/stickers_test.py index 83d43792..86ac8aec 100644 --- a/tests/frontends/mpd/stickers_test.py +++ b/tests/frontends/mpd/stickers_test.py @@ -1,45 +1,45 @@ import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import dispatcher +from mopidy.frontends.mpd.dispatcher import MpdDispatcher from mopidy.mixers.dummy import DummyMixer class StickersHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend.start().proxy() + self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = MpdDispatcher() def tearDown(self): - self.b.stop().get() + self.backend.stop().get() self.mixer.stop().get() def test_sticker_get(self): - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'sticker get "song" "file:///dev/urandom" "a_name"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_sticker_set(self): - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'sticker set "song" "file:///dev/urandom" "a_name" "a_value"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_sticker_delete_with_name(self): - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'sticker delete "song" "file:///dev/urandom" "a_name"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_sticker_delete_without_name(self): - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'sticker delete "song" "file:///dev/urandom"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_sticker_list(self): - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'sticker list "song" "file:///dev/urandom"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_sticker_find(self): - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'sticker find "song" "file:///dev/urandom" "a_name"') self.assert_(u'ACK [0@0] {} Not implemented' in result) diff --git a/tests/frontends/mpd/stored_playlists_test.py b/tests/frontends/mpd/stored_playlists_test.py index e981c9ed..04bab6f1 100644 --- a/tests/frontends/mpd/stored_playlists_test.py +++ b/tests/frontends/mpd/stored_playlists_test.py @@ -2,64 +2,64 @@ import datetime as dt import unittest from mopidy.backends.dummy import DummyBackend -from mopidy.frontends.mpd import dispatcher +from mopidy.frontends.mpd.dispatcher import MpdDispatcher from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track, Playlist class StoredPlaylistsHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend.start().proxy() + self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.h = dispatcher.MpdDispatcher() + self.dispatcher = MpdDispatcher() def tearDown(self): - self.b.stop().get() + self.backend.stop().get() self.mixer.stop().get() def test_listplaylist(self): - self.b.stored_playlists.playlists = [ + self.backend.stored_playlists.playlists = [ Playlist(name='name', tracks=[Track(uri='file:///dev/urandom')])] - result = self.h.handle_request(u'listplaylist "name"') + result = self.dispatcher.handle_request(u'listplaylist "name"') self.assert_(u'file: file:///dev/urandom' in result) self.assert_(u'OK' in result) def test_listplaylist_fails_if_no_playlist_is_found(self): - result = self.h.handle_request(u'listplaylist "name"') + result = self.dispatcher.handle_request(u'listplaylist "name"') self.assertEqual(result[0], u'ACK [50@0] {listplaylist} No such playlist') def test_listplaylistinfo(self): - self.b.stored_playlists.playlists = [ + self.backend.stored_playlists.playlists = [ Playlist(name='name', tracks=[Track(uri='file:///dev/urandom')])] - result = self.h.handle_request(u'listplaylistinfo "name"') + result = self.dispatcher.handle_request(u'listplaylistinfo "name"') self.assert_(u'file: file:///dev/urandom' in result) self.assert_(u'Track: 0' in result) self.assert_(u'Pos: 0' not in result) self.assert_(u'OK' in result) def test_listplaylistinfo_fails_if_no_playlist_is_found(self): - result = self.h.handle_request(u'listplaylistinfo "name"') + result = self.dispatcher.handle_request(u'listplaylistinfo "name"') self.assertEqual(result[0], u'ACK [50@0] {listplaylistinfo} No such playlist') def test_listplaylists(self): last_modified = dt.datetime(2001, 3, 17, 13, 41, 17, 12345) - self.b.stored_playlists.playlists = [Playlist(name='a', + self.backend.stored_playlists.playlists = [Playlist(name='a', last_modified=last_modified)] - result = self.h.handle_request(u'listplaylists') + result = self.dispatcher.handle_request(u'listplaylists') self.assert_(u'playlist: a' in result) # Date without microseconds and with time zone information self.assert_(u'Last-Modified: 2001-03-17T13:41:17Z' in result) self.assert_(u'OK' in result) def test_load_known_playlist_appends_to_current_playlist(self): - self.b.current_playlist.append([Track(uri='a'), Track(uri='b')]) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 2) - self.b.stored_playlists.playlists = [Playlist(name='A-list', + self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')]) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 2) + self.backend.stored_playlists.playlists = [Playlist(name='A-list', tracks=[Track(uri='c'), Track(uri='d'), Track(uri='e')])] - result = self.h.handle_request(u'load "A-list"') + result = self.dispatcher.handle_request(u'load "A-list"') self.assert_(u'OK' in result) - tracks = self.b.current_playlist.tracks.get() + tracks = self.backend.current_playlist.tracks.get() self.assertEqual(len(tracks), 5) self.assertEqual(tracks[0].uri, 'a') self.assertEqual(tracks[1].uri, 'b') @@ -68,35 +68,35 @@ class StoredPlaylistsHandlerTest(unittest.TestCase): self.assertEqual(tracks[4].uri, 'e') def test_load_unknown_playlist_acks(self): - result = self.h.handle_request(u'load "unknown playlist"') + result = self.dispatcher.handle_request(u'load "unknown playlist"') self.assert_(u'ACK [50@0] {load} No such playlist' in result) - self.assertEqual(len(self.b.current_playlist.tracks.get()), 0) + self.assertEqual(len(self.backend.current_playlist.tracks.get()), 0) def test_playlistadd(self): - result = self.h.handle_request( + result = self.dispatcher.handle_request( u'playlistadd "name" "file:///dev/urandom"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_playlistclear(self): - result = self.h.handle_request(u'playlistclear "name"') + result = self.dispatcher.handle_request(u'playlistclear "name"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_playlistdelete(self): - result = self.h.handle_request(u'playlistdelete "name" "5"') + result = self.dispatcher.handle_request(u'playlistdelete "name" "5"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_playlistmove(self): - result = self.h.handle_request(u'playlistmove "name" "5" "10"') + result = self.dispatcher.handle_request(u'playlistmove "name" "5" "10"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_rename(self): - result = self.h.handle_request(u'rename "old_name" "new_name"') + result = self.dispatcher.handle_request(u'rename "old_name" "new_name"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_rm(self): - result = self.h.handle_request(u'rm "name"') + result = self.dispatcher.handle_request(u'rm "name"') self.assert_(u'ACK [0@0] {} Not implemented' in result) def test_save(self): - result = self.h.handle_request(u'save "name"') + result = self.dispatcher.handle_request(u'save "name"') self.assert_(u'ACK [0@0] {} Not implemented' in result) From d4ab666b213d203f38fc06aa8192d4063faee0f5 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 16:57:48 +0200 Subject: [PATCH 14/48] Update outdated docstrings in MpdSession --- mopidy/frontends/mpd/session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/frontends/mpd/session.py b/mopidy/frontends/mpd/session.py index 5a473eca..6308e66e 100644 --- a/mopidy/frontends/mpd/session.py +++ b/mopidy/frontends/mpd/session.py @@ -45,7 +45,7 @@ class MpdSession(asynchat.async_chat): logger.warning(u'Received invalid data: %s', e) def handle_request(self, request): - """Handle request by sending it to the MPD frontend.""" + """Handle request using the MPD command handlers.""" if not self.authenticated: (self.authenticated, response) = self.check_password(request) if response is not None: @@ -56,7 +56,7 @@ class MpdSession(asynchat.async_chat): self.handle_response(response) def handle_response(self, response): - """Handle response from the MPD frontend.""" + """Handle response from the MPD command handlers.""" self.send_response(LINE_TERMINATOR.join(response)) def send_response(self, output): From acde68159ad16f5912807f5ac26db2ba542a6e23 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 17:06:32 +0200 Subject: [PATCH 15/48] Use @property and memoization to delay backend/mixer proxy creation until it is needed --- mopidy/frontends/mpd/dispatcher.py | 32 +++++++++++++++++++----------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 87a7112c..2eb0805c 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -91,23 +91,31 @@ class MpdContext(object): #: The current :class:`MpdDispatcher`. dispatcher = None - #: The backend. An instance of :class:`mopidy.backends.base.Backend`. - backend = None - - #: The mixer. An instance of :class:`mopidy.mixers.base.BaseMixer`. - mixer = None - def __init__(self, dispatcher): self.dispatcher = dispatcher - self.backend = self._get_backend() - self.mixer = self._get_mixer() + self._backend = None + self._mixer = None - def _get_backend(self): + @property + def backend(self): + """ + The backend. An instance of :class:`mopidy.backends.base.Backend`. + """ + if self._backend is not None: + return self._backend backend_refs = ActorRegistry.get_by_class(Backend) assert len(backend_refs) == 1, 'Expected exactly one running backend.' - return backend_refs[0].proxy() + self._backend = backend_refs[0].proxy() + return self._backend - def _get_mixer(self): + @property + def mixer(self): + """ + The mixer. An instance of :class:`mopidy.mixers.base.BaseMixer`. + """ + if self._mixer is not None: + return self._mixer mixer_refs = ActorRegistry.get_by_class(BaseMixer) assert len(mixer_refs) == 1, 'Expected exactly one running mixer.' - return mixer_refs[0].proxy() + self._mixer = mixer_refs[0].proxy() + return self._mixer From 82381720e57354236e1f65f69847ccc0adf6084f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 17:12:41 +0200 Subject: [PATCH 16/48] Add the current MpdSession to the MpdContext --- mopidy/frontends/mpd/dispatcher.py | 10 +++++++--- mopidy/frontends/mpd/session.py | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 2eb0805c..e513a4d1 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -25,10 +25,10 @@ class MpdDispatcher(object): # XXX Consider merging MpdDispatcher into MpdSession - def __init__(self): + def __init__(self, session=None): self.command_list = False self.command_list_ok = False - self.context = MpdContext(self) + self.context = MpdContext(self, session=session) def handle_request(self, request, command_list_index=None): """Dispatch incoming requests to the correct handler.""" @@ -91,8 +91,12 @@ class MpdContext(object): #: The current :class:`MpdDispatcher`. dispatcher = None - def __init__(self, dispatcher): + #: The current :class:`mopidy.frontends.mpd.session.MpdSession`. + session = None + + def __init__(self, dispatcher, session=None): self.dispatcher = dispatcher + self.session = session self._backend = None self._mixer = None diff --git a/mopidy/frontends/mpd/session.py b/mopidy/frontends/mpd/session.py index 6308e66e..4ac09a68 100644 --- a/mopidy/frontends/mpd/session.py +++ b/mopidy/frontends/mpd/session.py @@ -22,7 +22,7 @@ class MpdSession(asynchat.async_chat): self.input_buffer = [] self.authenticated = False self.set_terminator(LINE_TERMINATOR.encode(ENCODING)) - self.dispatcher = MpdDispatcher() + self.dispatcher = MpdDispatcher(session=self) def start(self): """Start a new client session.""" From 30d6d6f29e2e45806d0ff7c8edcc2a06565a3c77 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 17:18:06 +0200 Subject: [PATCH 17/48] Close client connection on MPD command 'close' --- mopidy/frontends/mpd/protocol/connection.py | 2 +- tests/frontends/mpd/connection_test.py | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/mopidy/frontends/mpd/protocol/connection.py b/mopidy/frontends/mpd/protocol/connection.py index 48e5ebd4..d6a1dc7c 100644 --- a/mopidy/frontends/mpd/protocol/connection.py +++ b/mopidy/frontends/mpd/protocol/connection.py @@ -11,7 +11,7 @@ def close(context): Closes the connection to MPD. """ - pass # TODO + context.session.close() @handle_pattern(r'^kill$') def kill(context): diff --git a/tests/frontends/mpd/connection_test.py b/tests/frontends/mpd/connection_test.py index e34265d4..84794257 100644 --- a/tests/frontends/mpd/connection_test.py +++ b/tests/frontends/mpd/connection_test.py @@ -1,23 +1,28 @@ +import mock import unittest from mopidy import settings from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd.dispatcher import MpdDispatcher +from mopidy.frontends.mpd.session import MpdSession from mopidy.mixers.dummy import DummyMixer class ConnectionHandlerTest(unittest.TestCase): def setUp(self): self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() - self.dispatcher = MpdDispatcher() + self.session = mock.Mock(spec=MpdSession) + self.dispatcher = MpdDispatcher(session=self.session) def tearDown(self): self.backend.stop().get() self.mixer.stop().get() settings.runtime.clear() - def test_close(self): + def test_close_closes_the_client_connection(self): result = self.dispatcher.handle_request(u'close') + self.assert_(self.session.close.called, + u'Should call close() on MpdSession') self.assert_(u'OK' in result) def test_empty_request(self): From 4db9c0139b42cdaf7c22ac2032b5edfec9394ce4 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 17:34:19 +0200 Subject: [PATCH 18/48] Update TODO for MPD command 'kill' --- mopidy/frontends/mpd/protocol/connection.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mopidy/frontends/mpd/protocol/connection.py b/mopidy/frontends/mpd/protocol/connection.py index d6a1dc7c..2b030eb0 100644 --- a/mopidy/frontends/mpd/protocol/connection.py +++ b/mopidy/frontends/mpd/protocol/connection.py @@ -22,7 +22,9 @@ def kill(context): Kills MPD. """ - pass # TODO + # TODO We do not want to allow remote killing of Mopidy. We should throw an + # MPD exception here. Maybe using ACK_ERROR_PERMISSION. + pass @handle_pattern(r'^password "(?P[^"]+)"$') def password_(context, password): From 9545da4b4e7cc2360063f3162064e31466ef7a63 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 22:37:05 +0200 Subject: [PATCH 19/48] Refactor MPD dispatcher --- mopidy/frontends/mpd/dispatcher.py | 62 ++++++++++++++------------ tests/frontends/mpd/dispatcher_test.py | 4 +- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 423e8701..7b322419 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -27,8 +27,6 @@ class MpdDispatcher(object): back to the MPD session. """ - # XXX Consider merging MpdDispatcher into MpdSession - def __init__(self, session=None): self.command_list = False self.command_list_ok = False @@ -40,24 +38,26 @@ class MpdDispatcher(object): self.command_list.append(request) return None try: - (handler, kwargs) = self.find_handler(request) - result = handler(self.context, **kwargs) + result = self._call_handler(request) except MpdAckError as e: if command_list_index is not None: e.index = command_list_index - return self.handle_response(e.get_mpd_ack(), add_ok=False) + return self._format_response(e.get_mpd_ack(), add_ok=False) except ActorDeadError as e: logger.warning(u'Tried to communicate with dead actor.') mpd_error = MpdSystemError(e.message) - return self.handle_response(mpd_error.get_mpd_ack(), add_ok=False) + return self._format_response(mpd_error.get_mpd_ack(), add_ok=False) if request in (u'command_list_begin', u'command_list_ok_begin'): return None if command_list_index is not None: - return self.handle_response(result, add_ok=False) - return self.handle_response(result) + return self._format_response(result, add_ok=False) + return self._format_response(result) - def find_handler(self, request): - """Find the correct handler for a request.""" + def _call_handler(self, request): + (handler, kwargs) = self._find_handler(request) + return handler(self.context, **kwargs) + + def _find_handler(self, request): for pattern in request_handlers: matches = re.match(pattern, request) if matches is not None: @@ -67,28 +67,34 @@ class MpdDispatcher(object): raise MpdArgError(u'incorrect arguments', command=command) raise MpdUnknownCommand(command=command) - def handle_response(self, result, add_ok=True): - """Format the response from a request handler.""" + def _format_response(self, result, add_ok=True): response = [] - if result is None: - result = [] - elif isinstance(result, set): - result = list(result) - elif not isinstance(result, list): - result = [result] - for line in flatten(result): - if isinstance(line, dict): - for (key, value) in line.items(): - response.append(u'%s: %s' % (key, value)) - elif isinstance(line, tuple): - (key, value) = line - response.append(u'%s: %s' % (key, value)) - else: - response.append(line) - if add_ok and (not response or not response[-1].startswith(u'ACK')): + for element in self._listify_result(result): + response.extend(self._format_lines(element)) + if add_ok and (not response or not self._has_error(response)): response.append(u'OK') return response + def _listify_result(self, result): + if result is None: + return [] + if isinstance(result, set): + return flatten(list(result)) + if not isinstance(result, list): + return [result] + return flatten(result) + + def _format_lines(self, line): + if isinstance(line, dict): + return [u'%s: %s' % (key, value) for (key, value) in line.items()] + if isinstance(line, tuple): + (key, value) = line + return [u'%s: %s' % (key, value)] + return [line] + + def _has_error(self, response): + return bool(response) and response[-1].startswith(u'ACK') + class MpdContext(object): """ diff --git a/tests/frontends/mpd/dispatcher_test.py b/tests/frontends/mpd/dispatcher_test.py index 1d8efe64..7dd70834 100644 --- a/tests/frontends/mpd/dispatcher_test.py +++ b/tests/frontends/mpd/dispatcher_test.py @@ -27,7 +27,7 @@ class MpdDispatcherTest(unittest.TestCase): def test_finding_handler_for_unknown_command_raises_exception(self): try: - self.dispatcher.find_handler('an_unknown_command with args') + self.dispatcher._find_handler('an_unknown_command with args') self.fail('Should raise exception') except MpdAckError as e: self.assertEqual(e.get_mpd_ack(), @@ -37,7 +37,7 @@ class MpdDispatcherTest(unittest.TestCase): expected_handler = lambda x: None request_handlers['known_command (?P.+)'] = \ expected_handler - (handler, kwargs) = self.dispatcher.find_handler('known_command an_arg') + (handler, kwargs) = self.dispatcher._find_handler('known_command an_arg') self.assertEqual(handler, expected_handler) self.assert_('arg1' in kwargs) self.assertEqual(kwargs['arg1'], 'an_arg') From 4036b7dd8ebec61b8f3c9b950a359df1cba17e02 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 22:54:19 +0200 Subject: [PATCH 20/48] Add missing inheritance diagrams for the MPD docs --- docs/modules/frontends/mpd.rst | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/modules/frontends/mpd.rst b/docs/modules/frontends/mpd.rst index 35128e70..6120c2a6 100644 --- a/docs/modules/frontends/mpd.rst +++ b/docs/modules/frontends/mpd.rst @@ -2,6 +2,8 @@ :mod:`mopidy.frontends.mpd` -- MPD server ***************************************** +.. inheritance-diagram:: mopidy.frontends.mpd + .. automodule:: mopidy.frontends.mpd :synopsis: MPD frontend :members: @@ -11,28 +13,30 @@ MPD server ========== +.. inheritance-diagram:: mopidy.frontends.mpd.server + .. automodule:: mopidy.frontends.mpd.server :synopsis: MPD server :members: :undoc-members: -.. inheritance-diagram:: mopidy.frontends.mpd.server - MPD session =========== +.. inheritance-diagram:: mopidy.frontends.mpd.session + .. automodule:: mopidy.frontends.mpd.session :synopsis: MPD client session :members: :undoc-members: -.. inheritance-diagram:: mopidy.frontends.mpd.session - MPD dispatcher ============== +.. inheritance-diagram:: mopidy.frontends.mpd.dispatcher + .. automodule:: mopidy.frontends.mpd.dispatcher :synopsis: MPD request dispatcher :members: From c84639b1977f47aaee6bf6970102c4c50d4bbc32 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 23:25:00 +0200 Subject: [PATCH 21/48] Add MpdPermissionError exception --- mopidy/frontends/mpd/exceptions.py | 6 ++++++ tests/frontends/mpd/exception_test.py | 11 +++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/mopidy/frontends/mpd/exceptions.py b/mopidy/frontends/mpd/exceptions.py index df90aed7..c6020658 100644 --- a/mopidy/frontends/mpd/exceptions.py +++ b/mopidy/frontends/mpd/exceptions.py @@ -42,6 +42,12 @@ class MpdPasswordError(MpdAckError): super(MpdPasswordError, self).__init__(*args, **kwargs) self.error_code = MpdAckError.ACK_ERROR_PASSWORD +class MpdPermissionError(MpdAckError): + def __init__(self, *args, **kwargs): + super(MpdPermissionError, self).__init__(*args, **kwargs) + self.message = u'you don\'t have permission for "%s"' % self.command + self.error_code = MpdAckError.ACK_ERROR_PERMISSION + class MpdUnknownCommand(MpdAckError): def __init__(self, *args, **kwargs): super(MpdUnknownCommand, self).__init__(*args, **kwargs) diff --git a/tests/frontends/mpd/exception_test.py b/tests/frontends/mpd/exception_test.py index 9b1b47a2..6750189b 100644 --- a/tests/frontends/mpd/exception_test.py +++ b/tests/frontends/mpd/exception_test.py @@ -1,7 +1,7 @@ import unittest -from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdUnknownCommand, - MpdSystemError, MpdNotImplemented) +from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdPermissionError, + MpdUnknownCommand, MpdSystemError, MpdNotImplemented) class MpdExceptionsTest(unittest.TestCase): def test_key_error_wrapped_in_mpd_ack_error(self): @@ -43,3 +43,10 @@ class MpdExceptionsTest(unittest.TestCase): except MpdSystemError as e: self.assertEqual(e.get_mpd_ack(), u'ACK [52@0] {} foo') + + def test_mpd_permission_error(self): + try: + raise MpdPermissionError(command='foo') + except MpdPermissionError as e: + self.assertEqual(e.get_mpd_ack(), + u'ACK [4@0] {foo} you don\'t have permission for "foo"') From 7f7d79b21fa0721b7b645c957c35c0f891bfe36d Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 23:31:52 +0200 Subject: [PATCH 22/48] Return permission error on use of MPD command 'kill' --- mopidy/frontends/mpd/protocol/connection.py | 7 +++---- tests/frontends/mpd/connection_test.py | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mopidy/frontends/mpd/protocol/connection.py b/mopidy/frontends/mpd/protocol/connection.py index 2b030eb0..5722a83c 100644 --- a/mopidy/frontends/mpd/protocol/connection.py +++ b/mopidy/frontends/mpd/protocol/connection.py @@ -1,6 +1,7 @@ from mopidy import settings from mopidy.frontends.mpd.protocol import handle_pattern -from mopidy.frontends.mpd.exceptions import MpdPasswordError +from mopidy.frontends.mpd.exceptions import (MpdPasswordError, + MpdPermissionError) @handle_pattern(r'^close$') def close(context): @@ -22,9 +23,7 @@ def kill(context): Kills MPD. """ - # TODO We do not want to allow remote killing of Mopidy. We should throw an - # MPD exception here. Maybe using ACK_ERROR_PERMISSION. - pass + raise MpdPermissionError(command=u'kill') @handle_pattern(r'^password "(?P[^"]+)"$') def password_(context, password): diff --git a/tests/frontends/mpd/connection_test.py b/tests/frontends/mpd/connection_test.py index 84794257..bc995a5e 100644 --- a/tests/frontends/mpd/connection_test.py +++ b/tests/frontends/mpd/connection_test.py @@ -31,7 +31,7 @@ class ConnectionHandlerTest(unittest.TestCase): def test_kill(self): result = self.dispatcher.handle_request(u'kill') - self.assert_(u'OK' in result) + self.assert_(u'ACK [4@0] {kill} you don\'t have permission for "kill"' in result) def test_valid_password_is_accepted(self): settings.MPD_SERVER_PASSWORD = u'topsecret' From c9506ca7e1d37fcc9f6f0743ab2b7941cd7afdd1 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 23:32:19 +0200 Subject: [PATCH 23/48] List 'kill' MPD command in 'notcommands' instead of 'commands' --- mopidy/frontends/mpd/protocol/reflection.py | 11 ++++++++++- tests/frontends/mpd/reflection_test.py | 7 +++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/mopidy/frontends/mpd/protocol/reflection.py b/mopidy/frontends/mpd/protocol/reflection.py index 181dce54..2b319c5e 100644 --- a/mopidy/frontends/mpd/protocol/reflection.py +++ b/mopidy/frontends/mpd/protocol/reflection.py @@ -17,6 +17,9 @@ def commands(context): sorted_commands = sorted(list(mpd_commands)) + # No permission to use + sorted_commands.remove('kill') + # Not shown by MPD in its command list sorted_commands.remove('command_list_begin') sorted_commands.remove('command_list_ok_begin') @@ -59,7 +62,13 @@ def notcommands(context): # authenticated, 'notcommands' should list all the commands the client does # not have access to. To implement this we need access to the session # object to check if the client is authenticated or not. - pass + + commands = [] + + # No permission to use + commands.append('kill') + + return [('command', c) for c in sorted(commands)] @handle_pattern(r'^tagtypes$') def tagtypes(context): diff --git a/tests/frontends/mpd/reflection_test.py b/tests/frontends/mpd/reflection_test.py index c5cde1bb..adc34338 100644 --- a/tests/frontends/mpd/reflection_test.py +++ b/tests/frontends/mpd/reflection_test.py @@ -20,6 +20,8 @@ class ReflectionHandlerTest(unittest.TestCase): self.assert_(u'command: commands' in result) self.assert_(u'command: play' in result) self.assert_(u'command: status' in result) + # Check if commands you do not have access to are not present + self.assert_(u'command: kill' not in result) # Check if the blacklisted commands are not present self.assert_(u'command: command_list_begin' not in result) self.assert_(u'command: command_list_ok_begin' not in result) @@ -33,9 +35,10 @@ class ReflectionHandlerTest(unittest.TestCase): result = self.dispatcher.handle_request(u'decoders') self.assert_(u'ACK [0@0] {} Not implemented' in result) - def test_notcommands_returns_only_ok(self): + def test_notcommands_returns_only_kill_and_ok(self): result = self.dispatcher.handle_request(u'notcommands') - self.assertEqual(1, len(result)) + self.assertEqual(2, len(result)) + self.assert_(u'command: kill' in result) self.assert_(u'OK' in result) def test_tagtypes(self): From 6b71a7acb5680f7641034104e6197becfcd26197 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 23:39:34 +0200 Subject: [PATCH 24/48] Update changelog with MPD improvements --- docs/changes.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/changes.rst b/docs/changes.rst index b9df87df..37ce22c1 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -29,6 +29,17 @@ No description yet. - Replace not decodable characters returned from Spotify instead of throwing an exception, as we won't try to figure out the encoding of non-UTF-8-data. +- MPD frontend: + + - Refactoring and cleanup. Most notably, all request handlers now get an + instance of :class:`mopidy.frontends.mpd.dispatcher.MpdContext` as the + first argument. The new class contains reference to any object in Mopidy + the MPD protocol implementation should need access to. + + - Close the client connection when the command ``close`` is received. + + - Do not allow access to the command ``kill``. + v0.4.1 (2011-05-06) =================== From bf175a3dce0a194677190e194667ad17acacf5f7 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Fri, 3 Jun 2011 23:58:24 +0200 Subject: [PATCH 25/48] Rename one letter variable in tests --- tests/frontends/mpd/status_test.py | 46 +++++++++++++++--------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/frontends/mpd/status_test.py b/tests/frontends/mpd/status_test.py index 7fa9d8de..a7ed921f 100644 --- a/tests/frontends/mpd/status_test.py +++ b/tests/frontends/mpd/status_test.py @@ -13,13 +13,13 @@ STOPPED = PlaybackController.STOPPED class StatusHandlerTest(unittest.TestCase): def setUp(self): - self.b = DummyBackend.start().proxy() + self.backend = DummyBackend.start().proxy() self.mixer = DummyMixer.start().proxy() self.dispatcher = MpdDispatcher() self.context = self.dispatcher.context def tearDown(self): - self.b.stop().get() + self.backend.stop().get() self.mixer.stop().get() def test_clearerror(self): @@ -28,8 +28,8 @@ class StatusHandlerTest(unittest.TestCase): def test_currentsong(self): track = Track() - self.b.current_playlist.append([track]) - self.b.playback.play() + self.backend.current_playlist.append([track]) + self.backend.playback.play() result = self.dispatcher.handle_request(u'currentsong') self.assert_(u'file: ' in result) self.assert_(u'Time: 0' in result) @@ -100,7 +100,7 @@ class StatusHandlerTest(unittest.TestCase): self.assertEqual(int(result['repeat']), 0) def test_status_method_contains_repeat_is_1(self): - self.b.playback.repeat = 1 + self.backend.playback.repeat = 1 result = dict(status.status(self.context)) self.assert_('repeat' in result) self.assertEqual(int(result['repeat']), 1) @@ -111,7 +111,7 @@ class StatusHandlerTest(unittest.TestCase): self.assertEqual(int(result['random']), 0) def test_status_method_contains_random_is_1(self): - self.b.playback.random = 1 + self.backend.playback.random = 1 result = dict(status.status(self.context)) self.assert_('random' in result) self.assertEqual(int(result['random']), 1) @@ -127,7 +127,7 @@ class StatusHandlerTest(unittest.TestCase): self.assertEqual(int(result['consume']), 0) def test_status_method_contains_consume_is_1(self): - self.b.playback.consume = 1 + self.backend.playback.consume = 1 result = dict(status.status(self.context)) self.assert_('consume' in result) self.assertEqual(int(result['consume']), 1) @@ -148,41 +148,41 @@ class StatusHandlerTest(unittest.TestCase): self.assert_(int(result['xfade']) >= 0) def test_status_method_contains_state_is_play(self): - self.b.playback.state = PLAYING + self.backend.playback.state = PLAYING result = dict(status.status(self.context)) self.assert_('state' in result) self.assertEqual(result['state'], 'play') def test_status_method_contains_state_is_stop(self): - self.b.playback.state = STOPPED + self.backend.playback.state = STOPPED result = dict(status.status(self.context)) self.assert_('state' in result) self.assertEqual(result['state'], 'stop') def test_status_method_contains_state_is_pause(self): - self.b.playback.state = PLAYING - self.b.playback.state = PAUSED + self.backend.playback.state = PLAYING + self.backend.playback.state = PAUSED result = dict(status.status(self.context)) self.assert_('state' in result) self.assertEqual(result['state'], 'pause') def test_status_method_when_playlist_loaded_contains_song(self): - self.b.current_playlist.append([Track()]) - self.b.playback.play() + self.backend.current_playlist.append([Track()]) + self.backend.playback.play() result = dict(status.status(self.context)) self.assert_('song' in result) self.assert_(int(result['song']) >= 0) def test_status_method_when_playlist_loaded_contains_cpid_as_songid(self): - self.b.current_playlist.append([Track()]) - self.b.playback.play() + self.backend.current_playlist.append([Track()]) + self.backend.playback.play() result = dict(status.status(self.context)) self.assert_('songid' in result) self.assertEqual(int(result['songid']), 0) def test_status_method_when_playing_contains_time_with_no_length(self): - self.b.current_playlist.append([Track(length=None)]) - self.b.playback.play() + self.backend.current_playlist.append([Track(length=None)]) + self.backend.playback.play() result = dict(status.status(self.context)) self.assert_('time' in result) (position, total) = result['time'].split(':') @@ -191,8 +191,8 @@ class StatusHandlerTest(unittest.TestCase): self.assert_(position <= total) def test_status_method_when_playing_contains_time_with_length(self): - self.b.current_playlist.append([Track(length=10000)]) - self.b.playback.play() + self.backend.current_playlist.append([Track(length=10000)]) + self.backend.playback.play() result = dict(status.status(self.context)) self.assert_('time' in result) (position, total) = result['time'].split(':') @@ -201,15 +201,15 @@ class StatusHandlerTest(unittest.TestCase): self.assert_(position <= total) def test_status_method_when_playing_contains_elapsed(self): - self.b.playback.state = PAUSED - self.b.playback.play_time_accumulated = 59123 + self.backend.playback.state = PAUSED + self.backend.playback.play_time_accumulated = 59123 result = dict(status.status(self.context)) self.assert_('elapsed' in result) self.assertEqual(int(result['elapsed']), 59123) def test_status_method_when_playing_contains_bitrate(self): - self.b.current_playlist.append([Track(bitrate=320)]) - self.b.playback.play() + self.backend.current_playlist.append([Track(bitrate=320)]) + self.backend.playback.play() result = dict(status.status(self.context)) self.assert_('bitrate' in result) self.assertEqual(int(result['bitrate']), 320) From 1db84dcccada3e696e8974ed1762c0ab6b5c9a6c Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 00:33:57 +0200 Subject: [PATCH 26/48] Refactor MpdDispatcher.handle_request --- mopidy/frontends/mpd/dispatcher.py | 30 +++++++++++-------- mopidy/frontends/mpd/protocol/command_list.py | 4 +-- tests/frontends/mpd/command_list_test.py | 4 +-- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 7b322419..8a28714a 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -32,27 +32,33 @@ class MpdDispatcher(object): self.command_list_ok = False self.context = MpdContext(self, session=session) - def handle_request(self, request, command_list_index=None): + def handle_request(self, request, current_command_list_index=None): """Dispatch incoming requests to the correct handler.""" - if self.command_list is not False and request != u'command_list_end': + if self._is_receiving_command_list(request): self.command_list.append(request) return None + try: - result = self._call_handler(request) + try: + result = self._call_handler(request) + except ActorDeadError as e: + logger.warning(u'Tried to communicate with dead actor.') + raise MpdSystemError(e.message) except MpdAckError as e: - if command_list_index is not None: - e.index = command_list_index + if current_command_list_index is not None: + e.index = current_command_list_index return self._format_response(e.get_mpd_ack(), add_ok=False) - except ActorDeadError as e: - logger.warning(u'Tried to communicate with dead actor.') - mpd_error = MpdSystemError(e.message) - return self._format_response(mpd_error.get_mpd_ack(), add_ok=False) - if request in (u'command_list_begin', u'command_list_ok_begin'): - return None - if command_list_index is not None: + + if (request in (u'command_list_begin', u'command_list_ok_begin') + or current_command_list_index is not None): return self._format_response(result, add_ok=False) + return self._format_response(result) + def _is_receiving_command_list(self, request): + return (self.command_list is not False + and request != u'command_list_end') + def _call_handler(self, request): (handler, kwargs) = self._find_handler(request) return handler(self.context, **kwargs) diff --git a/mopidy/frontends/mpd/protocol/command_list.py b/mopidy/frontends/mpd/protocol/command_list.py index 78fccec6..ffdf58d7 100644 --- a/mopidy/frontends/mpd/protocol/command_list.py +++ b/mopidy/frontends/mpd/protocol/command_list.py @@ -32,9 +32,9 @@ def command_list_end(context): (command_list_ok, context.dispatcher.command_list_ok) = ( context.dispatcher.command_list_ok, False) result = [] - for i, command in enumerate(command_list): + for index, command in enumerate(command_list): response = context.dispatcher.handle_request( - command, command_list_index=i) + command, current_command_list_index=index) if response is not None: result.append(response) if response and response[-1].startswith(u'ACK'): diff --git a/tests/frontends/mpd/command_list_test.py b/tests/frontends/mpd/command_list_test.py index 3537ee77..542d1265 100644 --- a/tests/frontends/mpd/command_list_test.py +++ b/tests/frontends/mpd/command_list_test.py @@ -16,7 +16,7 @@ class CommandListsTest(unittest.TestCase): def test_command_list_begin(self): result = self.dispatcher.handle_request(u'command_list_begin') - self.assert_(result is None) + self.assertEquals(result, []) def test_command_list_end(self): self.dispatcher.handle_request(u'command_list_begin') @@ -47,7 +47,7 @@ class CommandListsTest(unittest.TestCase): def test_command_list_ok_begin(self): result = self.dispatcher.handle_request(u'command_list_ok_begin') - self.assert_(result is None) + self.assertEquals(result, []) def test_command_list_ok_with_ping(self): self.dispatcher.handle_request(u'command_list_ok_begin') From a68bdae7511ef319db0f6103d426d769837a31a9 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 00:42:51 +0200 Subject: [PATCH 27/48] Move MPD authentication check from MpdSession to MpdDispatcher --- mopidy/frontends/mpd/dispatcher.py | 30 +++++++++++ mopidy/frontends/mpd/session.py | 28 ---------- tests/frontends/mpd/authentication_test.py | 63 ++++++++++++++++++++++ tests/frontends/mpd/server_test.py | 49 ----------------- 4 files changed, 93 insertions(+), 77 deletions(-) create mode 100644 tests/frontends/mpd/authentication_test.py diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 8a28714a..5365c8bd 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -4,6 +4,7 @@ import re from pykka import ActorDeadError from pykka.registry import ActorRegistry +from mopidy import settings from mopidy.backends.base import Backend from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdArgError, MpdUnknownCommand, MpdSystemError) @@ -28,12 +29,18 @@ class MpdDispatcher(object): """ def __init__(self, session=None): + self.authenticated = False self.command_list = False self.command_list_ok = False self.context = MpdContext(self, session=session) def handle_request(self, request, current_command_list_index=None): """Dispatch incoming requests to the correct handler.""" + if not self.authenticated: + (self.authenticated, result) = self._check_password(request) + if result: + return result + if self._is_receiving_command_list(request): self.command_list.append(request) return None @@ -55,6 +62,29 @@ class MpdDispatcher(object): return self._format_response(result) + def _check_password(self, request): + """ + Takes any request and tries to authenticate the client using it. + + :rtype: a two-tuple containing (is_authenticated, response_message). If + the response_message is :class:`None`, normal processing should + continue, even though the client may not be authenticated. + """ + if settings.MPD_SERVER_PASSWORD is None: + return (True, None) + command = request.split(' ')[0] + if command == 'password': + if request == 'password "%s"' % settings.MPD_SERVER_PASSWORD: + return (True, [u'OK']) + else: + return (False, [u'ACK [3@0] {password} incorrect password']) + if command in ('close', 'commands', 'notcommands', 'ping'): + return (False, None) + else: + return (False, + [u'ACK [4@0] {%(c)s} you don\'t have permission for "%(c)s"' % + {'c': command}]) + def _is_receiving_command_list(self, request): return (self.command_list is not False and request != u'command_list_end') diff --git a/mopidy/frontends/mpd/session.py b/mopidy/frontends/mpd/session.py index 4ac09a68..42058eb7 100644 --- a/mopidy/frontends/mpd/session.py +++ b/mopidy/frontends/mpd/session.py @@ -46,11 +46,6 @@ class MpdSession(asynchat.async_chat): def handle_request(self, request): """Handle request using the MPD command handlers.""" - if not self.authenticated: - (self.authenticated, response) = self.check_password(request) - if response is not None: - self.send_response(response) - return response = self.dispatcher.handle_request(request) if response is not None: self.handle_response(response) @@ -66,26 +61,3 @@ class MpdSession(asynchat.async_chat): output = u'%s%s' % (output, LINE_TERMINATOR) data = output.encode(ENCODING) self.push(data) - - def check_password(self, request): - """ - Takes any request and tries to authenticate the client using it. - - :rtype: a two-tuple containing (is_authenticated, response_message). If - the response_message is :class:`None`, normal processing should - continue, even though the client may not be authenticated. - """ - if settings.MPD_SERVER_PASSWORD is None: - return (True, None) - command = request.split(' ')[0] - if command == 'password': - if request == 'password "%s"' % settings.MPD_SERVER_PASSWORD: - return (True, u'OK') - else: - return (False, u'ACK [3@0] {password} incorrect password') - if command in ('close', 'commands', 'notcommands', 'ping'): - return (False, None) - else: - return (False, - u'ACK [4@0] {%(c)s} you don\'t have permission for "%(c)s"' % - {'c': command}) diff --git a/tests/frontends/mpd/authentication_test.py b/tests/frontends/mpd/authentication_test.py new file mode 100644 index 00000000..d795d726 --- /dev/null +++ b/tests/frontends/mpd/authentication_test.py @@ -0,0 +1,63 @@ +import mock +import unittest + +from mopidy import settings +from mopidy.frontends.mpd.dispatcher import MpdDispatcher +from mopidy.frontends.mpd.session import MpdSession + +class AuthenticationTest(unittest.TestCase): + def setUp(self): + self.session = mock.Mock(spec=MpdSession) + self.dispatcher = MpdDispatcher(session=self.session) + + def tearDown(self): + settings.runtime.clear() + + def test_authentication_with_valid_password_is_accepted(self): + settings.MPD_SERVER_PASSWORD = u'topsecret' + response = self.dispatcher.handle_request(u'password "topsecret"') + self.assertTrue(self.dispatcher.authenticated) + self.assert_(u'OK' in response) + + def test_authentication_with_invalid_password_is_not_accepted(self): + settings.MPD_SERVER_PASSWORD = u'topsecret' + response = self.dispatcher.handle_request(u'password "secret"') + self.assertFalse(self.dispatcher.authenticated) + self.assert_(u'ACK [3@0] {password} incorrect password' in response) + + def test_authentication_with_anything_when_password_check_turned_off(self): + settings.MPD_SERVER_PASSWORD = None + response = self.dispatcher.handle_request(u'any request at all') + self.assertTrue(self.dispatcher.authenticated) + self.assert_('ACK [5@0] {} unknown command "any"' in response) + + def test_anything_when_not_authenticated_should_fail(self): + settings.MPD_SERVER_PASSWORD = u'topsecret' + response = self.dispatcher.handle_request(u'any request at all') + self.assertFalse(self.dispatcher.authenticated) + self.assert_( + u'ACK [4@0] {any} you don\'t have permission for "any"' in response) + + def test_close_is_allowed_without_authentication(self): + settings.MPD_SERVER_PASSWORD = u'topsecret' + response = self.dispatcher.handle_request(u'close') + self.assertFalse(self.dispatcher.authenticated) + self.assert_(u'OK' in response) + + def test_commands_is_allowed_without_authentication(self): + settings.MPD_SERVER_PASSWORD = u'topsecret' + response = self.dispatcher.handle_request(u'commands') + self.assertFalse(self.dispatcher.authenticated) + self.assert_(u'OK' in response) + + def test_notcommands_is_allowed_without_authentication(self): + settings.MPD_SERVER_PASSWORD = u'topsecret' + response = self.dispatcher.handle_request(u'notcommands') + self.assertFalse(self.dispatcher.authenticated) + self.assert_(u'OK' in response) + + def test_ping_is_allowed_without_authentication(self): + settings.MPD_SERVER_PASSWORD = u'topsecret' + response = self.dispatcher.handle_request(u'ping') + self.assertFalse(self.dispatcher.authenticated) + self.assert_(u'OK' in response) diff --git a/tests/frontends/mpd/server_test.py b/tests/frontends/mpd/server_test.py index 32e90450..ee363aea 100644 --- a/tests/frontends/mpd/server_test.py +++ b/tests/frontends/mpd/server_test.py @@ -44,52 +44,3 @@ class MpdSessionTest(unittest.TestCase): self.session.input_buffer = ['\xff'] self.session.found_terminator() self.assertEqual(len(self.session.input_buffer), 0) - - def test_authentication_with_valid_password_is_accepted(self): - settings.MPD_SERVER_PASSWORD = u'topsecret' - authed, response = self.session.check_password(u'password "topsecret"') - self.assertTrue(authed) - self.assertEqual(u'OK', response) - - def test_authentication_with_invalid_password_is_not_accepted(self): - settings.MPD_SERVER_PASSWORD = u'topsecret' - authed, response = self.session.check_password(u'password "secret"') - self.assertFalse(authed) - self.assertEqual(u'ACK [3@0] {password} incorrect password', response) - - def test_authentication_with_anything_when_password_check_turned_off(self): - settings.MPD_SERVER_PASSWORD = None - authed, response = self.session.check_password(u'any request at all') - self.assertTrue(authed) - self.assertEqual(None, response) - - def test_anything_when_not_authenticated_should_fail(self): - settings.MPD_SERVER_PASSWORD = u'topsecret' - authed, response = self.session.check_password(u'any request at all') - self.assertFalse(authed) - self.assertEqual( - u'ACK [4@0] {any} you don\'t have permission for "any"', response) - - def test_close_is_allowed_without_authentication(self): - settings.MPD_SERVER_PASSWORD = u'topsecret' - authed, response = self.session.check_password(u'close') - self.assertFalse(authed) - self.assertEqual(None, response) - - def test_commands_is_allowed_without_authentication(self): - settings.MPD_SERVER_PASSWORD = u'topsecret' - authed, response = self.session.check_password(u'commands') - self.assertFalse(authed) - self.assertEqual(None, response) - - def test_notcommands_is_allowed_without_authentication(self): - settings.MPD_SERVER_PASSWORD = u'topsecret' - authed, response = self.session.check_password(u'notcommands') - self.assertFalse(authed) - self.assertEqual(None, response) - - def test_ping_is_allowed_without_authentication(self): - settings.MPD_SERVER_PASSWORD = u'topsecret' - authed, response = self.session.check_password(u'ping') - self.assertFalse(authed) - self.assertEqual(None, response) From 3fe276f32a2d9e42d1e3974d9ec0e5af5b4969c7 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 00:54:02 +0200 Subject: [PATCH 28/48] Refactor MpdSession --- mopidy/frontends/mpd/session.py | 42 +++++++++++++++------------------ 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/mopidy/frontends/mpd/session.py b/mopidy/frontends/mpd/session.py index 42058eb7..7226e0eb 100644 --- a/mopidy/frontends/mpd/session.py +++ b/mopidy/frontends/mpd/session.py @@ -25,39 +25,35 @@ class MpdSession(asynchat.async_chat): self.dispatcher = MpdDispatcher(session=self) def start(self): - """Start a new client session.""" - self.send_response(u'OK MPD %s' % VERSION) + """Called by asynchat when a new client connects.""" + self.send_response([u'OK MPD %s' % VERSION]) def collect_incoming_data(self, data): - """Collect incoming data into buffer until a terminator is found.""" + """Called by asynchat when new data arrives.""" self.input_buffer.append(data) def found_terminator(self): - """Handle request when a terminator is found.""" + """Called by asynchat when a terminator is found in incoming data.""" data = ''.join(self.input_buffer).strip() self.input_buffer = [] try: - request = data.decode(ENCODING) - logger.debug(u'Input from [%s]:%s: %s', self.client_address, - self.client_port, indent(request)) - self.handle_request(request) + self.send_response(self.handle_request(data)) except UnicodeDecodeError as e: logger.warning(u'Received invalid data: %s', e) def handle_request(self, request): - """Handle request using the MPD command handlers.""" - response = self.dispatcher.handle_request(request) + """Handle the request using the MPD command handlers.""" + request = request.decode(ENCODING) + logger.debug(u'Request from [%s]:%s: %s', self.client_address, + self.client_port, indent(request)) + return self.dispatcher.handle_request(request) + + def send_response(self, response): + """Format a response from the MPD command handlers and send it to the client.""" if response is not None: - self.handle_response(response) - - def handle_response(self, response): - """Handle response from the MPD command handlers.""" - self.send_response(LINE_TERMINATOR.join(response)) - - def send_response(self, output): - """Send a response to the client.""" - logger.debug(u'Output to [%s]:%s: %s', self.client_address, - self.client_port, indent(output)) - output = u'%s%s' % (output, LINE_TERMINATOR) - data = output.encode(ENCODING) - self.push(data) + response = LINE_TERMINATOR.join(response) + logger.debug(u'Response to [%s]:%s: %s', self.client_address, + self.client_port, indent(response)) + response = u'%s%s' % (response, LINE_TERMINATOR) + data = response.encode(ENCODING) + self.push(data) From 08f085fd8d36082870bdde1cef7b593f9ebf4137 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 02:21:14 +0200 Subject: [PATCH 29/48] Refactor MpdDispatcher to use a filter model, like Java Servlets. Password authentication handling becomes much cleaner. --- mopidy/frontends/mpd/dispatcher.py | 132 ++++++++++-------- mopidy/frontends/mpd/protocol/command_list.py | 13 +- mopidy/frontends/mpd/protocol/connection.py | 7 +- tests/frontends/mpd/authentication_test.py | 2 +- tests/frontends/mpd/command_list_test.py | 1 + 5 files changed, 88 insertions(+), 67 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 5365c8bd..05ae976d 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -7,7 +7,7 @@ from pykka.registry import ActorRegistry from mopidy import settings from mopidy.backends.base import Backend from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdArgError, - MpdUnknownCommand, MpdSystemError) + MpdPermissionError, MpdPasswordError, MpdSystemError, MpdUnknownCommand) from mopidy.frontends.mpd.protocol import mpd_commands, request_handlers # Do not remove the following import. The protocol modules must be imported to # get them registered as request handlers. @@ -32,63 +32,82 @@ class MpdDispatcher(object): self.authenticated = False self.command_list = False self.command_list_ok = False + self.command_list_index = None self.context = MpdContext(self, session=session) def handle_request(self, request, current_command_list_index=None): """Dispatch incoming requests to the correct handler.""" - if not self.authenticated: - (self.authenticated, result) = self._check_password(request) - if result: - return result + self.command_list_index = current_command_list_index + response = [] + filter_chain = [ + self._catch_mpd_ack_errors_filter, + self._authenticate_filter, + self._command_list_filter, + self._add_ok_filter, + self._call_handler_filter, + ] + return self._call_next_filter(request, response, filter_chain) + + def _catch_mpd_ack_errors_filter(self, request, response, filter_chain): + try: + return self._call_next_filter(request, response, filter_chain) + except MpdAckError as mpd_ack_error: + if self.command_list_index is not None: + mpd_ack_error.index = self.command_list_index + return [mpd_ack_error.get_mpd_ack()] + + + def _authenticate_filter(self, request, response, filter_chain): + if self.authenticated or settings.MPD_SERVER_PASSWORD is None: + return self._call_next_filter(request, response, filter_chain) + else: + command = request.split(' ')[0] + if command in ('close', 'commands', 'notcommands', 'password', 'ping'): + return self._call_next_filter(request, response, filter_chain) + else: + raise MpdPermissionError(command=command) + + + def _command_list_filter(self, request, response, filter_chain): if self._is_receiving_command_list(request): self.command_list.append(request) - return None - - try: - try: - result = self._call_handler(request) - except ActorDeadError as e: - logger.warning(u'Tried to communicate with dead actor.') - raise MpdSystemError(e.message) - except MpdAckError as e: - if current_command_list_index is not None: - e.index = current_command_list_index - return self._format_response(e.get_mpd_ack(), add_ok=False) - - if (request in (u'command_list_begin', u'command_list_ok_begin') - or current_command_list_index is not None): - return self._format_response(result, add_ok=False) - - return self._format_response(result) - - def _check_password(self, request): - """ - Takes any request and tries to authenticate the client using it. - - :rtype: a two-tuple containing (is_authenticated, response_message). If - the response_message is :class:`None`, normal processing should - continue, even though the client may not be authenticated. - """ - if settings.MPD_SERVER_PASSWORD is None: - return (True, None) - command = request.split(' ')[0] - if command == 'password': - if request == 'password "%s"' % settings.MPD_SERVER_PASSWORD: - return (True, [u'OK']) - else: - return (False, [u'ACK [3@0] {password} incorrect password']) - if command in ('close', 'commands', 'notcommands', 'ping'): - return (False, None) + return [] else: - return (False, - [u'ACK [4@0] {%(c)s} you don\'t have permission for "%(c)s"' % - {'c': command}]) + response = self._call_next_filter(request, response, filter_chain) + if (self._is_receiving_command_list(request) or + self._is_processing_command_list(request)): + if response and response[-1] == u'OK': + response = response[:-1] + return response def _is_receiving_command_list(self, request): return (self.command_list is not False and request != u'command_list_end') + def _is_processing_command_list(self, request): + return (self.command_list_index is not None + and request != u'command_list_end') + + + def _add_ok_filter(self, request, response, filter_chain): + response = self._call_next_filter(request, response, filter_chain) + if not self._has_error(response): + response.append(u'OK') + return response + + def _has_error(self, response): + return response and response[-1].startswith(u'ACK') + + + def _call_handler_filter(self, request, response, filter_chain): + try: + response = self._format_response(self._call_handler(request)) + return self._call_next_filter(request, response, filter_chain) + except ActorDeadError as e: + logger.warning(u'Tried to communicate with dead actor.') + raise MpdSystemError(e.message) + def _call_handler(self, request): (handler, kwargs) = self._find_handler(request) return handler(self.context, **kwargs) @@ -103,13 +122,19 @@ class MpdDispatcher(object): raise MpdArgError(u'incorrect arguments', command=command) raise MpdUnknownCommand(command=command) - def _format_response(self, result, add_ok=True): - response = [] - for element in self._listify_result(result): - response.extend(self._format_lines(element)) - if add_ok and (not response or not self._has_error(response)): - response.append(u'OK') - return response + + def _call_next_filter(self, request, response, filter_chain): + if filter_chain: + next_filter = filter_chain.pop(0) + return next_filter(request, response, filter_chain) + else: + return response + + def _format_response(self, response): + formatted_response = [] + for element in self._listify_result(response): + formatted_response.extend(self._format_lines(element)) + return formatted_response def _listify_result(self, result): if result is None: @@ -128,9 +153,6 @@ class MpdDispatcher(object): return [u'%s: %s' % (key, value)] return [line] - def _has_error(self, response): - return bool(response) and response[-1].startswith(u'ACK') - class MpdContext(object): """ diff --git a/mopidy/frontends/mpd/protocol/command_list.py b/mopidy/frontends/mpd/protocol/command_list.py index ffdf58d7..a8a0c317 100644 --- a/mopidy/frontends/mpd/protocol/command_list.py +++ b/mopidy/frontends/mpd/protocol/command_list.py @@ -31,17 +31,16 @@ def command_list_end(context): context.dispatcher.command_list, False) (command_list_ok, context.dispatcher.command_list_ok) = ( context.dispatcher.command_list_ok, False) - result = [] + command_list_response = [] for index, command in enumerate(command_list): response = context.dispatcher.handle_request( command, current_command_list_index=index) - if response is not None: - result.append(response) - if response and response[-1].startswith(u'ACK'): - return result + command_list_response.extend(response) + if command_list_response and command_list_response[-1].startswith(u'ACK'): + return command_list_response if command_list_ok: - response.append(u'list_OK') - return result + command_list_response.append(u'list_OK') + return command_list_response @handle_pattern(r'^command_list_ok_begin$') def command_list_ok_begin(context): diff --git a/mopidy/frontends/mpd/protocol/connection.py b/mopidy/frontends/mpd/protocol/connection.py index 5722a83c..99944ac2 100644 --- a/mopidy/frontends/mpd/protocol/connection.py +++ b/mopidy/frontends/mpd/protocol/connection.py @@ -35,10 +35,9 @@ def password_(context, password): This is used for authentication with the server. ``PASSWORD`` is simply the plaintext password. """ - # You will not get to this code without being authenticated. This is for - # when you are already authenticated, and are sending additional 'password' - # requests. - if settings.MPD_SERVER_PASSWORD != password: + if password == settings.MPD_SERVER_PASSWORD: + context.dispatcher.authenticated = True + else: raise MpdPasswordError(u'incorrect password', command=u'password') @handle_pattern(r'^ping$') diff --git a/tests/frontends/mpd/authentication_test.py b/tests/frontends/mpd/authentication_test.py index d795d726..03fc5718 100644 --- a/tests/frontends/mpd/authentication_test.py +++ b/tests/frontends/mpd/authentication_test.py @@ -28,7 +28,7 @@ class AuthenticationTest(unittest.TestCase): def test_authentication_with_anything_when_password_check_turned_off(self): settings.MPD_SERVER_PASSWORD = None response = self.dispatcher.handle_request(u'any request at all') - self.assertTrue(self.dispatcher.authenticated) + self.assertFalse(self.dispatcher.authenticated) self.assert_('ACK [5@0] {} unknown command "any"' in response) def test_anything_when_not_authenticated_should_fail(self): diff --git a/tests/frontends/mpd/command_list_test.py b/tests/frontends/mpd/command_list_test.py index 542d1265..8fd4c828 100644 --- a/tests/frontends/mpd/command_list_test.py +++ b/tests/frontends/mpd/command_list_test.py @@ -43,6 +43,7 @@ class CommandListsTest(unittest.TestCase): self.dispatcher.handle_request(u'play') # Known command self.dispatcher.handle_request(u'paly') # Unknown command result = self.dispatcher.handle_request(u'command_list_end') + self.assertEqual(len(result), 1, result) self.assertEqual(result[0], u'ACK [5@1] {} unknown command "paly"') def test_command_list_ok_begin(self): From 6d1bac0d72e0d2100d1d167bf50c7ff8febbc196 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 17:06:17 +0200 Subject: [PATCH 30/48] Reorder methods and add comments to divide sections --- mopidy/frontends/mpd/dispatcher.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 05ae976d..65346275 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -48,6 +48,15 @@ class MpdDispatcher(object): ] return self._call_next_filter(request, response, filter_chain) + def _call_next_filter(self, request, response, filter_chain): + if filter_chain: + next_filter = filter_chain.pop(0) + return next_filter(request, response, filter_chain) + else: + return response + + + ### Filter: catch MPD ACK errors def _catch_mpd_ack_errors_filter(self, request, response, filter_chain): try: @@ -58,6 +67,8 @@ class MpdDispatcher(object): return [mpd_ack_error.get_mpd_ack()] + ### Filter: authenticate + def _authenticate_filter(self, request, response, filter_chain): if self.authenticated or settings.MPD_SERVER_PASSWORD is None: return self._call_next_filter(request, response, filter_chain) @@ -69,6 +80,8 @@ class MpdDispatcher(object): raise MpdPermissionError(command=command) + ### Filter: command list + def _command_list_filter(self, request, response, filter_chain): if self._is_receiving_command_list(request): self.command_list.append(request) @@ -90,6 +103,8 @@ class MpdDispatcher(object): and request != u'command_list_end') + ### Filter: add OK + def _add_ok_filter(self, request, response, filter_chain): response = self._call_next_filter(request, response, filter_chain) if not self._has_error(response): @@ -100,6 +115,8 @@ class MpdDispatcher(object): return response and response[-1].startswith(u'ACK') + ### Filter: call handler + def _call_handler_filter(self, request, response, filter_chain): try: response = self._format_response(self._call_handler(request)) @@ -122,14 +139,6 @@ class MpdDispatcher(object): raise MpdArgError(u'incorrect arguments', command=command) raise MpdUnknownCommand(command=command) - - def _call_next_filter(self, request, response, filter_chain): - if filter_chain: - next_filter = filter_chain.pop(0) - return next_filter(request, response, filter_chain) - else: - return response - def _format_response(self, response): formatted_response = [] for element in self._listify_result(response): From 939a8f40d6e680c67528d531c3fd7ae1905b6187 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 17:18:46 +0200 Subject: [PATCH 31/48] No need for splitting init and start of MpdSession --- mopidy/frontends/mpd/server.py | 2 +- mopidy/frontends/mpd/session.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/mopidy/frontends/mpd/server.py b/mopidy/frontends/mpd/server.py index 1be46ef4..20e0073f 100644 --- a/mopidy/frontends/mpd/server.py +++ b/mopidy/frontends/mpd/server.py @@ -60,7 +60,7 @@ class MpdServer(asyncore.dispatcher): (client_socket, client_socket_address) = self.accept() logger.info(u'MPD client connection from [%s]:%s', client_socket_address[0], client_socket_address[1]) - MpdSession(self, client_socket, client_socket_address).start() + MpdSession(self, client_socket, client_socket_address) def handle_close(self): """Handle end of client connection.""" diff --git a/mopidy/frontends/mpd/session.py b/mopidy/frontends/mpd/session.py index 7226e0eb..5a86dfdb 100644 --- a/mopidy/frontends/mpd/session.py +++ b/mopidy/frontends/mpd/session.py @@ -23,9 +23,6 @@ class MpdSession(asynchat.async_chat): self.authenticated = False self.set_terminator(LINE_TERMINATOR.encode(ENCODING)) self.dispatcher = MpdDispatcher(session=self) - - def start(self): - """Called by asynchat when a new client connects.""" self.send_response([u'OK MPD %s' % VERSION]) def collect_incoming_data(self, data): From 68a671414cd2777047df5567c66b8e0c0c1c847e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 17:38:03 +0200 Subject: [PATCH 32/48] Update MpdServer's docstrings --- mopidy/frontends/mpd/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/frontends/mpd/server.py b/mopidy/frontends/mpd/server.py index 20e0073f..a2d9cefe 100644 --- a/mopidy/frontends/mpd/server.py +++ b/mopidy/frontends/mpd/server.py @@ -56,14 +56,14 @@ class MpdServer(asyncore.dispatcher): sys.exit(1) def handle_accept(self): - """Handle new client connection.""" + """Called by asyncore when a new client connects.""" (client_socket, client_socket_address) = self.accept() logger.info(u'MPD client connection from [%s]:%s', client_socket_address[0], client_socket_address[1]) MpdSession(self, client_socket, client_socket_address) def handle_close(self): - """Handle end of client connection.""" + """Called by asyncore when the socket is closed.""" self.close() def _format_hostname(self, hostname): From e6294ec8694a739b73ccc6fb5cc892bb3125021f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 18:24:06 +0200 Subject: [PATCH 33/48] Fix pylint warnings in MPD frontend --- mopidy/frontends/mpd/dispatcher.py | 7 ++++--- mopidy/frontends/mpd/protocol/command_list.py | 3 ++- mopidy/frontends/mpd/server.py | 3 ++- mopidy/frontends/mpd/session.py | 6 ++++-- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 65346275..996c3714 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -7,7 +7,7 @@ from pykka.registry import ActorRegistry from mopidy import settings from mopidy.backends.base import Backend from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdArgError, - MpdPermissionError, MpdPasswordError, MpdSystemError, MpdUnknownCommand) + MpdPermissionError, MpdSystemError, MpdUnknownCommand) from mopidy.frontends.mpd.protocol import mpd_commands, request_handlers # Do not remove the following import. The protocol modules must be imported to # get them registered as request handlers. @@ -74,7 +74,8 @@ class MpdDispatcher(object): return self._call_next_filter(request, response, filter_chain) else: command = request.split(' ')[0] - if command in ('close', 'commands', 'notcommands', 'password', 'ping'): + if command in ( + 'close', 'commands', 'notcommands', 'password', 'ping'): return self._call_next_filter(request, response, filter_chain) else: raise MpdPermissionError(command=command) @@ -142,7 +143,7 @@ class MpdDispatcher(object): def _format_response(self, response): formatted_response = [] for element in self._listify_result(response): - formatted_response.extend(self._format_lines(element)) + formatted_response.extend(self._format_lines(element)) return formatted_response def _listify_result(self, result): diff --git a/mopidy/frontends/mpd/protocol/command_list.py b/mopidy/frontends/mpd/protocol/command_list.py index a8a0c317..f2c51578 100644 --- a/mopidy/frontends/mpd/protocol/command_list.py +++ b/mopidy/frontends/mpd/protocol/command_list.py @@ -36,7 +36,8 @@ def command_list_end(context): response = context.dispatcher.handle_request( command, current_command_list_index=index) command_list_response.extend(response) - if command_list_response and command_list_response[-1].startswith(u'ACK'): + if (command_list_response and + command_list_response[-1].startswith(u'ACK')): return command_list_response if command_list_ok: command_list_response.append(u'list_OK') diff --git a/mopidy/frontends/mpd/server.py b/mopidy/frontends/mpd/server.py index a2d9cefe..4e651ddb 100644 --- a/mopidy/frontends/mpd/server.py +++ b/mopidy/frontends/mpd/server.py @@ -52,7 +52,8 @@ class MpdServer(asyncore.dispatcher): self._format_hostname(settings.MPD_SERVER_HOSTNAME), settings.MPD_SERVER_PORT) except IOError, e: - logger.error(u'MPD server startup failed: %s' % str(e).decode('utf-8')) + logger.error(u'MPD server startup failed: %s' % + str(e).decode('utf-8')) sys.exit(1) def handle_accept(self): diff --git a/mopidy/frontends/mpd/session.py b/mopidy/frontends/mpd/session.py index 5a86dfdb..53f4cab7 100644 --- a/mopidy/frontends/mpd/session.py +++ b/mopidy/frontends/mpd/session.py @@ -1,7 +1,6 @@ import asynchat import logging -from mopidy import settings from mopidy.frontends.mpd.dispatcher import MpdDispatcher from mopidy.frontends.mpd.protocol import ENCODING, LINE_TERMINATOR, VERSION from mopidy.utils.log import indent @@ -46,7 +45,10 @@ class MpdSession(asynchat.async_chat): return self.dispatcher.handle_request(request) def send_response(self, response): - """Format a response from the MPD command handlers and send it to the client.""" + """ + Format a response from the MPD command handlers and send it to the + client. + """ if response is not None: response = LINE_TERMINATOR.join(response) logger.debug(u'Response to [%s]:%s: %s', self.client_address, From a08885bb9598956dfa2393d187d8a12fef562043 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 18:43:30 +0200 Subject: [PATCH 34/48] Use a MpdCommand namedtuple in the mopidy.frontends.mpd.protocol.mpd_commands list --- mopidy/frontends/mpd/dispatcher.py | 8 +++---- mopidy/frontends/mpd/protocol/__init__.py | 4 +++- mopidy/frontends/mpd/protocol/reflection.py | 24 ++++++++++----------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 996c3714..405ca03d 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -135,10 +135,10 @@ class MpdDispatcher(object): matches = re.match(pattern, request) if matches is not None: return (request_handlers[pattern], matches.groupdict()) - command = request.split(' ')[0] - if command in mpd_commands: - raise MpdArgError(u'incorrect arguments', command=command) - raise MpdUnknownCommand(command=command) + command_name = request.split(' ')[0] + if command_name in [command.name for command in mpd_commands]: + raise MpdArgError(u'incorrect arguments', command=command_name) + raise MpdUnknownCommand(command=command_name) def _format_response(self, response): formatted_response = [] diff --git a/mopidy/frontends/mpd/protocol/__init__.py b/mopidy/frontends/mpd/protocol/__init__.py index 6689f627..76aad687 100644 --- a/mopidy/frontends/mpd/protocol/__init__.py +++ b/mopidy/frontends/mpd/protocol/__init__.py @@ -10,6 +10,7 @@ implement our own MPD server which is compatible with the numerous existing `MPD clients `_. """ +from collections import namedtuple import re #: The MPD protocol uses UTF-8 for encoding all data. @@ -21,6 +22,7 @@ LINE_TERMINATOR = u'\n' #: The MPD protocol version is 0.16.0. VERSION = u'0.16.0' +MpdCommand = namedtuple('MpdCommand', ['name']) mpd_commands = set() request_handlers = {} @@ -45,7 +47,7 @@ def handle_pattern(pattern): def decorator(func): match = re.search('([a-z_]+)', pattern) if match is not None: - mpd_commands.add(match.group()) + mpd_commands.add(MpdCommand(name=match.group())) if pattern in request_handlers: raise ValueError(u'Tried to redefine handler for %s with %s' % ( pattern, func)) diff --git a/mopidy/frontends/mpd/protocol/reflection.py b/mopidy/frontends/mpd/protocol/reflection.py index 2b319c5e..6e0a2f6c 100644 --- a/mopidy/frontends/mpd/protocol/reflection.py +++ b/mopidy/frontends/mpd/protocol/reflection.py @@ -15,20 +15,20 @@ def commands(context): # have access to. To implement this we need access to the session object to # check if the client is authenticated or not. - sorted_commands = sorted(list(mpd_commands)) + command_names = [command.name for command in mpd_commands] # No permission to use - sorted_commands.remove('kill') + command_names.remove('kill') # Not shown by MPD in its command list - sorted_commands.remove('command_list_begin') - sorted_commands.remove('command_list_ok_begin') - sorted_commands.remove('command_list_end') - sorted_commands.remove('idle') - sorted_commands.remove('noidle') - sorted_commands.remove('sticker') + command_names.remove('command_list_begin') + command_names.remove('command_list_ok_begin') + command_names.remove('command_list_end') + command_names.remove('idle') + command_names.remove('noidle') + command_names.remove('sticker') - return [('command', c) for c in sorted_commands] + return [('command', command_name) for command_name in sorted(command_names)] @handle_pattern(r'^decoders$') def decoders(context): @@ -63,12 +63,12 @@ def notcommands(context): # not have access to. To implement this we need access to the session # object to check if the client is authenticated or not. - commands = [] + command_names = [] # No permission to use - commands.append('kill') + command_names.append('kill') - return [('command', c) for c in sorted(commands)] + return [('command', command_name) for command_name in sorted(command_names)] @handle_pattern(r'^tagtypes$') def tagtypes(context): From e68d715d3e1d07418237c9f3d4c93b7ab4da9c18 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 18:51:18 +0200 Subject: [PATCH 35/48] Rename handle_pattern decorator to handle_request --- mopidy/frontends/mpd/protocol/__init__.py | 6 +-- mopidy/frontends/mpd/protocol/audio_output.py | 8 +-- mopidy/frontends/mpd/protocol/command_list.py | 8 +-- mopidy/frontends/mpd/protocol/connection.py | 10 ++-- .../mpd/protocol/current_playlist.py | 52 +++++++++--------- mopidy/frontends/mpd/protocol/empty.py | 4 +- mopidy/frontends/mpd/protocol/music_db.py | 24 ++++----- mopidy/frontends/mpd/protocol/playback.py | 54 +++++++++---------- mopidy/frontends/mpd/protocol/reflection.py | 12 ++--- mopidy/frontends/mpd/protocol/status.py | 16 +++--- mopidy/frontends/mpd/protocol/stickers.py | 12 ++--- .../mpd/protocol/stored_playlists.py | 24 ++++----- tests/frontends/mpd/dispatcher_test.py | 6 +-- 13 files changed, 118 insertions(+), 118 deletions(-) diff --git a/mopidy/frontends/mpd/protocol/__init__.py b/mopidy/frontends/mpd/protocol/__init__.py index 76aad687..24ce1cac 100644 --- a/mopidy/frontends/mpd/protocol/__init__.py +++ b/mopidy/frontends/mpd/protocol/__init__.py @@ -26,9 +26,9 @@ MpdCommand = namedtuple('MpdCommand', ['name']) mpd_commands = set() request_handlers = {} -def handle_pattern(pattern): +def handle_request(pattern): """ - Decorator for connecting command handlers to command patterns. + Decorator for connecting command handlers to command requests. If you use named groups in the pattern, the decorated method will get the groups as keyword arguments. If the group is optional, remember to give the @@ -37,7 +37,7 @@ def handle_pattern(pattern): For example, if the command is ``do that thing`` the ``what`` argument will be ``this thing``:: - @handle_pattern('^do (?P.+)$') + @handle_request('^do (?P.+)$') def do(what): ... diff --git a/mopidy/frontends/mpd/protocol/audio_output.py b/mopidy/frontends/mpd/protocol/audio_output.py index 6111332a..7147963a 100644 --- a/mopidy/frontends/mpd/protocol/audio_output.py +++ b/mopidy/frontends/mpd/protocol/audio_output.py @@ -1,7 +1,7 @@ -from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.protocol import handle_request from mopidy.frontends.mpd.exceptions import MpdNotImplemented -@handle_pattern(r'^disableoutput "(?P\d+)"$') +@handle_request(r'^disableoutput "(?P\d+)"$') def disableoutput(context, outputid): """ *musicpd.org, audio output section:* @@ -12,7 +12,7 @@ def disableoutput(context, outputid): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^enableoutput "(?P\d+)"$') +@handle_request(r'^enableoutput "(?P\d+)"$') def enableoutput(context, outputid): """ *musicpd.org, audio output section:* @@ -23,7 +23,7 @@ def enableoutput(context, outputid): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^outputs$') +@handle_request(r'^outputs$') def outputs(context): """ *musicpd.org, audio output section:* diff --git a/mopidy/frontends/mpd/protocol/command_list.py b/mopidy/frontends/mpd/protocol/command_list.py index f2c51578..37e5c93d 100644 --- a/mopidy/frontends/mpd/protocol/command_list.py +++ b/mopidy/frontends/mpd/protocol/command_list.py @@ -1,7 +1,7 @@ -from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.protocol import handle_request from mopidy.frontends.mpd.exceptions import MpdUnknownCommand -@handle_pattern(r'^command_list_begin$') +@handle_request(r'^command_list_begin$') def command_list_begin(context): """ *musicpd.org, command list section:* @@ -21,7 +21,7 @@ def command_list_begin(context): context.dispatcher.command_list = [] context.dispatcher.command_list_ok = False -@handle_pattern(r'^command_list_end$') +@handle_request(r'^command_list_end$') def command_list_end(context): """See :meth:`command_list_begin()`.""" if context.dispatcher.command_list is False: @@ -43,7 +43,7 @@ def command_list_end(context): command_list_response.append(u'list_OK') return command_list_response -@handle_pattern(r'^command_list_ok_begin$') +@handle_request(r'^command_list_ok_begin$') def command_list_ok_begin(context): """See :meth:`command_list_begin()`.""" context.dispatcher.command_list = [] diff --git a/mopidy/frontends/mpd/protocol/connection.py b/mopidy/frontends/mpd/protocol/connection.py index 99944ac2..d5c2d80c 100644 --- a/mopidy/frontends/mpd/protocol/connection.py +++ b/mopidy/frontends/mpd/protocol/connection.py @@ -1,9 +1,9 @@ from mopidy import settings -from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.protocol import handle_request from mopidy.frontends.mpd.exceptions import (MpdPasswordError, MpdPermissionError) -@handle_pattern(r'^close$') +@handle_request(r'^close$') def close(context): """ *musicpd.org, connection section:* @@ -14,7 +14,7 @@ def close(context): """ context.session.close() -@handle_pattern(r'^kill$') +@handle_request(r'^kill$') def kill(context): """ *musicpd.org, connection section:* @@ -25,7 +25,7 @@ def kill(context): """ raise MpdPermissionError(command=u'kill') -@handle_pattern(r'^password "(?P[^"]+)"$') +@handle_request(r'^password "(?P[^"]+)"$') def password_(context, password): """ *musicpd.org, connection section:* @@ -40,7 +40,7 @@ def password_(context, password): else: raise MpdPasswordError(u'incorrect password', command=u'password') -@handle_pattern(r'^ping$') +@handle_request(r'^ping$') def ping(context): """ *musicpd.org, connection section:* diff --git a/mopidy/frontends/mpd/protocol/current_playlist.py b/mopidy/frontends/mpd/protocol/current_playlist.py index e73e0a9c..82e096a0 100644 --- a/mopidy/frontends/mpd/protocol/current_playlist.py +++ b/mopidy/frontends/mpd/protocol/current_playlist.py @@ -1,9 +1,9 @@ from mopidy.frontends.mpd.exceptions import (MpdArgError, MpdNoExistError, MpdNotImplemented) -from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.protocol import handle_request from mopidy.frontends.mpd.translator import tracks_to_mpd_format -@handle_pattern(r'^add "(?P[^"]*)"$') +@handle_request(r'^add "(?P[^"]*)"$') def add(context, uri): """ *musicpd.org, current playlist section:* @@ -28,7 +28,7 @@ def add(context, uri): raise MpdNoExistError( u'directory or file not found', command=u'add') -@handle_pattern(r'^addid "(?P[^"]*)"( "(?P\d+)")*$') +@handle_request(r'^addid "(?P[^"]*)"( "(?P\d+)")*$') def addid(context, uri, songpos=None): """ *musicpd.org, current playlist section:* @@ -61,7 +61,7 @@ def addid(context, uri, songpos=None): at_position=songpos).get() return ('Id', cp_track[0]) -@handle_pattern(r'^delete "(?P\d+):(?P\d+)*"$') +@handle_request(r'^delete "(?P\d+):(?P\d+)*"$') def delete_range(context, start, end=None): """ *musicpd.org, current playlist section:* @@ -81,7 +81,7 @@ def delete_range(context, start, end=None): for (cpid, _) in cp_tracks: context.backend.current_playlist.remove(cpid=cpid) -@handle_pattern(r'^delete "(?P\d+)"$') +@handle_request(r'^delete "(?P\d+)"$') def delete_songpos(context, songpos): """See :meth:`delete_range`""" try: @@ -91,7 +91,7 @@ def delete_songpos(context, songpos): except IndexError: raise MpdArgError(u'Bad song index', command=u'delete') -@handle_pattern(r'^deleteid "(?P\d+)"$') +@handle_request(r'^deleteid "(?P\d+)"$') def deleteid(context, cpid): """ *musicpd.org, current playlist section:* @@ -108,7 +108,7 @@ def deleteid(context, cpid): except LookupError: raise MpdNoExistError(u'No such song', command=u'deleteid') -@handle_pattern(r'^clear$') +@handle_request(r'^clear$') def clear(context): """ *musicpd.org, current playlist section:* @@ -119,7 +119,7 @@ def clear(context): """ context.backend.current_playlist.clear() -@handle_pattern(r'^move "(?P\d+):(?P\d+)*" "(?P\d+)"$') +@handle_request(r'^move "(?P\d+):(?P\d+)*" "(?P\d+)"$') def move_range(context, start, to, end=None): """ *musicpd.org, current playlist section:* @@ -136,14 +136,14 @@ def move_range(context, start, to, end=None): to = int(to) context.backend.current_playlist.move(start, end, to) -@handle_pattern(r'^move "(?P\d+)" "(?P\d+)"$') +@handle_request(r'^move "(?P\d+)" "(?P\d+)"$') def move_songpos(context, songpos, to): """See :meth:`move_range`.""" songpos = int(songpos) to = int(to) context.backend.current_playlist.move(songpos, songpos + 1, to) -@handle_pattern(r'^moveid "(?P\d+)" "(?P\d+)"$') +@handle_request(r'^moveid "(?P\d+)" "(?P\d+)"$') def moveid(context, cpid, to): """ *musicpd.org, current playlist section:* @@ -161,7 +161,7 @@ def moveid(context, cpid, to): cp_track) context.backend.current_playlist.move(position, position + 1, to) -@handle_pattern(r'^playlist$') +@handle_request(r'^playlist$') def playlist(context): """ *musicpd.org, current playlist section:* @@ -176,8 +176,8 @@ def playlist(context): """ return playlistinfo(context) -@handle_pattern(r'^playlistfind (?P[^"]+) "(?P[^"]+)"$') -@handle_pattern(r'^playlistfind "(?P[^"]+)" "(?P[^"]+)"$') +@handle_request(r'^playlistfind (?P[^"]+) "(?P[^"]+)"$') +@handle_request(r'^playlistfind "(?P[^"]+)" "(?P[^"]+)"$') def playlistfind(context, tag, needle): """ *musicpd.org, current playlist section:* @@ -201,7 +201,7 @@ def playlistfind(context, tag, needle): return None raise MpdNotImplemented # TODO -@handle_pattern(r'^playlistid( "(?P\d+)")*$') +@handle_request(r'^playlistid( "(?P\d+)")*$') def playlistid(context, cpid=None): """ *musicpd.org, current playlist section:* @@ -226,9 +226,9 @@ def playlistid(context, cpid=None): return tracks_to_mpd_format( context.backend.current_playlist.tracks.get(), cpids=cpids) -@handle_pattern(r'^playlistinfo$') -@handle_pattern(r'^playlistinfo "(?P-?\d+)"$') -@handle_pattern(r'^playlistinfo "(?P\d+):(?P\d+)*"$') +@handle_request(r'^playlistinfo$') +@handle_request(r'^playlistinfo "(?P-?\d+)"$') +@handle_request(r'^playlistinfo "(?P\d+):(?P\d+)*"$') def playlistinfo(context, songpos=None, start=None, end=None): """ @@ -276,8 +276,8 @@ def playlistinfo(context, songpos=None, context.backend.current_playlist.tracks.get(), start, end, cpids=cpids) -@handle_pattern(r'^playlistsearch "(?P[^"]+)" "(?P[^"]+)"$') -@handle_pattern(r'^playlistsearch (?P\S+) "(?P[^"]+)"$') +@handle_request(r'^playlistsearch "(?P[^"]+)" "(?P[^"]+)"$') +@handle_request(r'^playlistsearch (?P\S+) "(?P[^"]+)"$') def playlistsearch(context, tag, needle): """ *musicpd.org, current playlist section:* @@ -294,8 +294,8 @@ def playlistsearch(context, tag, needle): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^plchanges (?P-?\d+)$') -@handle_pattern(r'^plchanges "(?P-?\d+)"$') +@handle_request(r'^plchanges (?P-?\d+)$') +@handle_request(r'^plchanges "(?P-?\d+)"$') def plchanges(context, version): """ *musicpd.org, current playlist section:* @@ -318,7 +318,7 @@ def plchanges(context, version): return tracks_to_mpd_format( context.backend.current_playlist.tracks.get(), cpids=cpids) -@handle_pattern(r'^plchangesposid "(?P\d+)"$') +@handle_request(r'^plchangesposid "(?P\d+)"$') def plchangesposid(context, version): """ *musicpd.org, current playlist section:* @@ -341,8 +341,8 @@ def plchangesposid(context, version): result.append((u'Id', cpid)) return result -@handle_pattern(r'^shuffle$') -@handle_pattern(r'^shuffle "(?P\d+):(?P\d+)*"$') +@handle_request(r'^shuffle$') +@handle_request(r'^shuffle "(?P\d+):(?P\d+)*"$') def shuffle(context, start=None, end=None): """ *musicpd.org, current playlist section:* @@ -358,7 +358,7 @@ def shuffle(context, start=None, end=None): end = int(end) context.backend.current_playlist.shuffle(start, end) -@handle_pattern(r'^swap "(?P\d+)" "(?P\d+)"$') +@handle_request(r'^swap "(?P\d+)" "(?P\d+)"$') def swap(context, songpos1, songpos2): """ *musicpd.org, current playlist section:* @@ -379,7 +379,7 @@ def swap(context, songpos1, songpos2): context.backend.current_playlist.clear() context.backend.current_playlist.append(tracks) -@handle_pattern(r'^swapid "(?P\d+)" "(?P\d+)"$') +@handle_request(r'^swapid "(?P\d+)" "(?P\d+)"$') def swapid(context, cpid1, cpid2): """ *musicpd.org, current playlist section:* diff --git a/mopidy/frontends/mpd/protocol/empty.py b/mopidy/frontends/mpd/protocol/empty.py index b84f08f4..0e418551 100644 --- a/mopidy/frontends/mpd/protocol/empty.py +++ b/mopidy/frontends/mpd/protocol/empty.py @@ -1,6 +1,6 @@ -from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.protocol import handle_request -@handle_pattern(r'^$') +@handle_request(r'^$') def empty(context): """The original MPD server returns ``OK`` on an empty request.""" pass diff --git a/mopidy/frontends/mpd/protocol/music_db.py b/mopidy/frontends/mpd/protocol/music_db.py index 0183d471..0343b3ab 100644 --- a/mopidy/frontends/mpd/protocol/music_db.py +++ b/mopidy/frontends/mpd/protocol/music_db.py @@ -1,7 +1,7 @@ import re import shlex -from mopidy.frontends.mpd.protocol import handle_pattern, stored_playlists +from mopidy.frontends.mpd.protocol import handle_request, stored_playlists from mopidy.frontends.mpd.exceptions import MpdArgError, MpdNotImplemented def _build_query(mpd_query): @@ -28,7 +28,7 @@ def _build_query(mpd_query): query[field] = [what] return query -@handle_pattern(r'^count "(?P[^"]+)" "(?P[^"]*)"$') +@handle_request(r'^count "(?P[^"]+)" "(?P[^"]*)"$') def count(context, tag, needle): """ *musicpd.org, music database section:* @@ -40,7 +40,7 @@ def count(context, tag, needle): """ return [('songs', 0), ('playtime', 0)] # TODO -@handle_pattern(r'^find ' +@handle_request(r'^find ' r'(?P("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ilename|' r'[Tt]itle|[Aa]ny)"? "[^"]+"\s?)+)$') def find(context, mpd_query): @@ -70,7 +70,7 @@ def find(context, mpd_query): query = _build_query(mpd_query) return context.backend.library.find_exact(**query).get().mpd_format() -@handle_pattern(r'^findadd ' +@handle_request(r'^findadd ' r'(?P("?([Aa]lbum|[Aa]rtist|[Ff]ilename|[Tt]itle|[Aa]ny)"? ' '"[^"]+"\s?)+)$') def findadd(context, query): @@ -86,7 +86,7 @@ def findadd(context, query): # TODO Add result to current playlist #result = context.find(query) -@handle_pattern(r'^list "?(?P([Aa]rtist|[Aa]lbum|[Dd]ate|[Gg]enre))"?' +@handle_request(r'^list "?(?P([Aa]rtist|[Aa]lbum|[Dd]ate|[Gg]enre))"?' '( (?P.*))?$') def list_(context, field, mpd_query=None): """ @@ -237,7 +237,7 @@ def _list_date(context, query): dates.add((u'Date', track.date.strftime('%Y-%m-%d'))) return dates -@handle_pattern(r'^listall "(?P[^"]+)"') +@handle_request(r'^listall "(?P[^"]+)"') def listall(context, uri): """ *musicpd.org, music database section:* @@ -248,7 +248,7 @@ def listall(context, uri): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^listallinfo "(?P[^"]+)"') +@handle_request(r'^listallinfo "(?P[^"]+)"') def listallinfo(context, uri): """ *musicpd.org, music database section:* @@ -260,8 +260,8 @@ def listallinfo(context, uri): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^lsinfo$') -@handle_pattern(r'^lsinfo "(?P[^"]*)"$') +@handle_request(r'^lsinfo$') +@handle_request(r'^lsinfo "(?P[^"]*)"$') def lsinfo(context, uri=None): """ *musicpd.org, music database section:* @@ -282,7 +282,7 @@ def lsinfo(context, uri=None): return stored_playlists.listplaylists(context) raise MpdNotImplemented # TODO -@handle_pattern(r'^rescan( "(?P[^"]+)")*$') +@handle_request(r'^rescan( "(?P[^"]+)")*$') def rescan(context, uri=None): """ *musicpd.org, music database section:* @@ -293,7 +293,7 @@ def rescan(context, uri=None): """ return update(context, uri, rescan_unmodified_files=True) -@handle_pattern(r'^search ' +@handle_request(r'^search ' r'(?P("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ilename|' r'[Tt]itle|[Aa]ny)"? "[^"]+"\s?)+)$') def search(context, mpd_query): @@ -326,7 +326,7 @@ def search(context, mpd_query): query = _build_query(mpd_query) return context.backend.library.search(**query).get().mpd_format() -@handle_pattern(r'^update( "(?P[^"]+)")*$') +@handle_request(r'^update( "(?P[^"]+)")*$') def update(context, uri=None, rescan_unmodified_files=False): """ *musicpd.org, music database section:* diff --git a/mopidy/frontends/mpd/protocol/playback.py b/mopidy/frontends/mpd/protocol/playback.py index b8646a8b..63cfe649 100644 --- a/mopidy/frontends/mpd/protocol/playback.py +++ b/mopidy/frontends/mpd/protocol/playback.py @@ -1,10 +1,10 @@ from mopidy.backends.base import PlaybackController -from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.protocol import handle_request from mopidy.frontends.mpd.exceptions import (MpdArgError, MpdNoExistError, MpdNotImplemented) -@handle_pattern(r'^consume (?P[01])$') -@handle_pattern(r'^consume "(?P[01])"$') +@handle_request(r'^consume (?P[01])$') +@handle_request(r'^consume "(?P[01])"$') def consume(context, state): """ *musicpd.org, playback section:* @@ -20,7 +20,7 @@ def consume(context, state): else: context.backend.playback.consume = False -@handle_pattern(r'^crossfade "(?P\d+)"$') +@handle_request(r'^crossfade "(?P\d+)"$') def crossfade(context, seconds): """ *musicpd.org, playback section:* @@ -32,7 +32,7 @@ def crossfade(context, seconds): seconds = int(seconds) raise MpdNotImplemented # TODO -@handle_pattern(r'^next$') +@handle_request(r'^next$') def next_(context): """ *musicpd.org, playback section:* @@ -89,8 +89,8 @@ def next_(context): """ return context.backend.playback.next().get() -@handle_pattern(r'^pause$') -@handle_pattern(r'^pause "(?P[01])"$') +@handle_request(r'^pause$') +@handle_request(r'^pause "(?P[01])"$') def pause(context, state=None): """ *musicpd.org, playback section:* @@ -115,7 +115,7 @@ def pause(context, state=None): else: context.backend.playback.resume() -@handle_pattern(r'^play$') +@handle_request(r'^play$') def play(context): """ The original MPD server resumes from the paused state on ``play`` @@ -123,8 +123,8 @@ def play(context): """ return context.backend.playback.play().get() -@handle_pattern(r'^playid "(?P\d+)"$') -@handle_pattern(r'^playid "(?P-1)"$') +@handle_request(r'^playid "(?P\d+)"$') +@handle_request(r'^playid "(?P-1)"$') def playid(context, cpid): """ *musicpd.org, playback section:* @@ -151,8 +151,8 @@ def playid(context, cpid): except LookupError: raise MpdNoExistError(u'No such song', command=u'playid') -@handle_pattern(r'^play (?P-?\d+)$') -@handle_pattern(r'^play "(?P-?\d+)"$') +@handle_request(r'^play (?P-?\d+)$') +@handle_request(r'^play "(?P-?\d+)"$') def playpos(context, songpos): """ *musicpd.org, playback section:* @@ -197,7 +197,7 @@ def _play_minus_one(context): else: return # Fail silently -@handle_pattern(r'^previous$') +@handle_request(r'^previous$') def previous(context): """ *musicpd.org, playback section:* @@ -243,8 +243,8 @@ def previous(context): """ return context.backend.playback.previous().get() -@handle_pattern(r'^random (?P[01])$') -@handle_pattern(r'^random "(?P[01])"$') +@handle_request(r'^random (?P[01])$') +@handle_request(r'^random "(?P[01])"$') def random(context, state): """ *musicpd.org, playback section:* @@ -258,8 +258,8 @@ def random(context, state): else: context.backend.playback.random = False -@handle_pattern(r'^repeat (?P[01])$') -@handle_pattern(r'^repeat "(?P[01])"$') +@handle_request(r'^repeat (?P[01])$') +@handle_request(r'^repeat "(?P[01])"$') def repeat(context, state): """ *musicpd.org, playback section:* @@ -273,7 +273,7 @@ def repeat(context, state): else: context.backend.playback.repeat = False -@handle_pattern(r'^replay_gain_mode "(?P(off|track|album))"$') +@handle_request(r'^replay_gain_mode "(?P(off|track|album))"$') def replay_gain_mode(context, mode): """ *musicpd.org, playback section:* @@ -289,7 +289,7 @@ def replay_gain_mode(context, mode): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^replay_gain_status$') +@handle_request(r'^replay_gain_status$') def replay_gain_status(context): """ *musicpd.org, playback section:* @@ -301,8 +301,8 @@ def replay_gain_status(context): """ return u'off' # TODO -@handle_pattern(r'^seek (?P\d+) (?P\d+)$') -@handle_pattern(r'^seek "(?P\d+)" "(?P\d+)"$') +@handle_request(r'^seek (?P\d+) (?P\d+)$') +@handle_request(r'^seek "(?P\d+)" "(?P\d+)"$') def seek(context, songpos, seconds): """ *musicpd.org, playback section:* @@ -320,7 +320,7 @@ def seek(context, songpos, seconds): playpos(context, songpos) context.backend.playback.seek(int(seconds) * 1000) -@handle_pattern(r'^seekid "(?P\d+)" "(?P\d+)"$') +@handle_request(r'^seekid "(?P\d+)" "(?P\d+)"$') def seekid(context, cpid, seconds): """ *musicpd.org, playback section:* @@ -333,8 +333,8 @@ def seekid(context, cpid, seconds): playid(context, cpid) context.backend.playback.seek(int(seconds) * 1000) -@handle_pattern(r'^setvol (?P[-+]*\d+)$') -@handle_pattern(r'^setvol "(?P[-+]*\d+)"$') +@handle_request(r'^setvol (?P[-+]*\d+)$') +@handle_request(r'^setvol "(?P[-+]*\d+)"$') def setvol(context, volume): """ *musicpd.org, playback section:* @@ -354,8 +354,8 @@ def setvol(context, volume): volume = 100 context.mixer.volume = volume -@handle_pattern(r'^single (?P[01])$') -@handle_pattern(r'^single "(?P[01])"$') +@handle_request(r'^single (?P[01])$') +@handle_request(r'^single "(?P[01])"$') def single(context, state): """ *musicpd.org, playback section:* @@ -371,7 +371,7 @@ def single(context, state): else: context.backend.playback.single = False -@handle_pattern(r'^stop$') +@handle_request(r'^stop$') def stop(context): """ *musicpd.org, playback section:* diff --git a/mopidy/frontends/mpd/protocol/reflection.py b/mopidy/frontends/mpd/protocol/reflection.py index 6e0a2f6c..94d8ce83 100644 --- a/mopidy/frontends/mpd/protocol/reflection.py +++ b/mopidy/frontends/mpd/protocol/reflection.py @@ -1,7 +1,7 @@ -from mopidy.frontends.mpd.protocol import handle_pattern, mpd_commands +from mopidy.frontends.mpd.protocol import handle_request, mpd_commands from mopidy.frontends.mpd.exceptions import MpdNotImplemented -@handle_pattern(r'^commands$') +@handle_request(r'^commands$') def commands(context): """ *musicpd.org, reflection section:* @@ -30,7 +30,7 @@ def commands(context): return [('command', command_name) for command_name in sorted(command_names)] -@handle_pattern(r'^decoders$') +@handle_request(r'^decoders$') def decoders(context): """ *musicpd.org, reflection section:* @@ -49,7 +49,7 @@ def decoders(context): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^notcommands$') +@handle_request(r'^notcommands$') def notcommands(context): """ *musicpd.org, reflection section:* @@ -70,7 +70,7 @@ def notcommands(context): return [('command', command_name) for command_name in sorted(command_names)] -@handle_pattern(r'^tagtypes$') +@handle_request(r'^tagtypes$') def tagtypes(context): """ *musicpd.org, reflection section:* @@ -81,7 +81,7 @@ def tagtypes(context): """ pass # TODO -@handle_pattern(r'^urlhandlers$') +@handle_request(r'^urlhandlers$') def urlhandlers(context): """ *musicpd.org, reflection section:* diff --git a/mopidy/frontends/mpd/protocol/status.py b/mopidy/frontends/mpd/protocol/status.py index 4f9e00cd..58fefa11 100644 --- a/mopidy/frontends/mpd/protocol/status.py +++ b/mopidy/frontends/mpd/protocol/status.py @@ -1,8 +1,8 @@ from mopidy.backends.base import PlaybackController -from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.protocol import handle_request from mopidy.frontends.mpd.exceptions import MpdNotImplemented -@handle_pattern(r'^clearerror$') +@handle_request(r'^clearerror$') def clearerror(context): """ *musicpd.org, status section:* @@ -14,7 +14,7 @@ def clearerror(context): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^currentsong$') +@handle_request(r'^currentsong$') def currentsong(context): """ *musicpd.org, status section:* @@ -30,8 +30,8 @@ def currentsong(context): position=context.backend.playback.current_playlist_position.get(), cpid=current_cp_track[0]) -@handle_pattern(r'^idle$') -@handle_pattern(r'^idle (?P.+)$') +@handle_request(r'^idle$') +@handle_request(r'^idle (?P.+)$') def idle(context, subsystems=None): """ *musicpd.org, status section:* @@ -67,12 +67,12 @@ def idle(context, subsystems=None): """ pass # TODO -@handle_pattern(r'^noidle$') +@handle_request(r'^noidle$') def noidle(context): """See :meth:`_status_idle`.""" pass # TODO -@handle_pattern(r'^stats$') +@handle_request(r'^stats$') def stats(context): """ *musicpd.org, status section:* @@ -98,7 +98,7 @@ def stats(context): 'playtime': 0, # TODO } -@handle_pattern(r'^status$') +@handle_request(r'^status$') def status(context): """ *musicpd.org, status section:* diff --git a/mopidy/frontends/mpd/protocol/stickers.py b/mopidy/frontends/mpd/protocol/stickers.py index c1b7be16..c3663ff1 100644 --- a/mopidy/frontends/mpd/protocol/stickers.py +++ b/mopidy/frontends/mpd/protocol/stickers.py @@ -1,7 +1,7 @@ -from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.protocol import handle_request from mopidy.frontends.mpd.exceptions import MpdNotImplemented -@handle_pattern(r'^sticker delete "(?P[^"]+)" ' +@handle_request(r'^sticker delete "(?P[^"]+)" ' r'"(?P[^"]+)"( "(?P[^"]+)")*$') def sticker_delete(context, field, uri, name=None): """ @@ -14,7 +14,7 @@ def sticker_delete(context, field, uri, name=None): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^sticker find "(?P[^"]+)" "(?P[^"]+)" ' +@handle_request(r'^sticker find "(?P[^"]+)" "(?P[^"]+)" ' r'"(?P[^"]+)"$') def sticker_find(context, field, uri, name): """ @@ -28,7 +28,7 @@ def sticker_find(context, field, uri, name): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^sticker get "(?P[^"]+)" "(?P[^"]+)" ' +@handle_request(r'^sticker get "(?P[^"]+)" "(?P[^"]+)" ' r'"(?P[^"]+)"$') def sticker_get(context, field, uri, name): """ @@ -40,7 +40,7 @@ def sticker_get(context, field, uri, name): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^sticker list "(?P[^"]+)" "(?P[^"]+)"$') +@handle_request(r'^sticker list "(?P[^"]+)" "(?P[^"]+)"$') def sticker_list(context, field, uri): """ *musicpd.org, sticker section:* @@ -51,7 +51,7 @@ def sticker_list(context, field, uri): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^sticker set "(?P[^"]+)" "(?P[^"]+)" ' +@handle_request(r'^sticker set "(?P[^"]+)" "(?P[^"]+)" ' r'"(?P[^"]+)" "(?P[^"]+)"$') def sticker_set(context, field, uri, name, value): """ diff --git a/mopidy/frontends/mpd/protocol/stored_playlists.py b/mopidy/frontends/mpd/protocol/stored_playlists.py index 764a8e12..0a157f66 100644 --- a/mopidy/frontends/mpd/protocol/stored_playlists.py +++ b/mopidy/frontends/mpd/protocol/stored_playlists.py @@ -1,9 +1,9 @@ import datetime as dt -from mopidy.frontends.mpd.protocol import handle_pattern +from mopidy.frontends.mpd.protocol import handle_request from mopidy.frontends.mpd.exceptions import MpdNoExistError, MpdNotImplemented -@handle_pattern(r'^listplaylist "(?P[^"]+)"$') +@handle_request(r'^listplaylist "(?P[^"]+)"$') def listplaylist(context, name): """ *musicpd.org, stored playlists section:* @@ -24,7 +24,7 @@ def listplaylist(context, name): except LookupError: raise MpdNoExistError(u'No such playlist', command=u'listplaylist') -@handle_pattern(r'^listplaylistinfo "(?P[^"]+)"$') +@handle_request(r'^listplaylistinfo "(?P[^"]+)"$') def listplaylistinfo(context, name): """ *musicpd.org, stored playlists section:* @@ -45,7 +45,7 @@ def listplaylistinfo(context, name): raise MpdNoExistError( u'No such playlist', command=u'listplaylistinfo') -@handle_pattern(r'^listplaylists$') +@handle_request(r'^listplaylists$') def listplaylists(context): """ *musicpd.org, stored playlists section:* @@ -79,7 +79,7 @@ def listplaylists(context): result.append((u'Last-Modified', last_modified)) return result -@handle_pattern(r'^load "(?P[^"]+)"$') +@handle_request(r'^load "(?P[^"]+)"$') def load(context, name): """ *musicpd.org, stored playlists section:* @@ -98,7 +98,7 @@ def load(context, name): except LookupError: raise MpdNoExistError(u'No such playlist', command=u'load') -@handle_pattern(r'^playlistadd "(?P[^"]+)" "(?P[^"]+)"$') +@handle_request(r'^playlistadd "(?P[^"]+)" "(?P[^"]+)"$') def playlistadd(context, name, uri): """ *musicpd.org, stored playlists section:* @@ -111,7 +111,7 @@ def playlistadd(context, name, uri): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^playlistclear "(?P[^"]+)"$') +@handle_request(r'^playlistclear "(?P[^"]+)"$') def playlistclear(context, name): """ *musicpd.org, stored playlists section:* @@ -122,7 +122,7 @@ def playlistclear(context, name): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^playlistdelete "(?P[^"]+)" "(?P\d+)"$') +@handle_request(r'^playlistdelete "(?P[^"]+)" "(?P\d+)"$') def playlistdelete(context, name, songpos): """ *musicpd.org, stored playlists section:* @@ -133,7 +133,7 @@ def playlistdelete(context, name, songpos): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^playlistmove "(?P[^"]+)" ' +@handle_request(r'^playlistmove "(?P[^"]+)" ' r'"(?P\d+)" "(?P\d+)"$') def playlistmove(context, name, from_pos, to_pos): """ @@ -152,7 +152,7 @@ def playlistmove(context, name, from_pos, to_pos): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^rename "(?P[^"]+)" "(?P[^"]+)"$') +@handle_request(r'^rename "(?P[^"]+)" "(?P[^"]+)"$') def rename(context, old_name, new_name): """ *musicpd.org, stored playlists section:* @@ -163,7 +163,7 @@ def rename(context, old_name, new_name): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^rm "(?P[^"]+)"$') +@handle_request(r'^rm "(?P[^"]+)"$') def rm(context, name): """ *musicpd.org, stored playlists section:* @@ -174,7 +174,7 @@ def rm(context, name): """ raise MpdNotImplemented # TODO -@handle_pattern(r'^save "(?P[^"]+)"$') +@handle_request(r'^save "(?P[^"]+)"$') def save(context, name): """ *musicpd.org, stored playlists section:* diff --git a/tests/frontends/mpd/dispatcher_test.py b/tests/frontends/mpd/dispatcher_test.py index 7dd70834..7708ce31 100644 --- a/tests/frontends/mpd/dispatcher_test.py +++ b/tests/frontends/mpd/dispatcher_test.py @@ -3,7 +3,7 @@ import unittest from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd.dispatcher import MpdDispatcher from mopidy.frontends.mpd.exceptions import MpdAckError -from mopidy.frontends.mpd.protocol import request_handlers, handle_pattern +from mopidy.frontends.mpd.protocol import request_handlers, handle_request from mopidy.mixers.dummy import DummyMixer class MpdDispatcherTest(unittest.TestCase): @@ -19,8 +19,8 @@ class MpdDispatcherTest(unittest.TestCase): def test_register_same_pattern_twice_fails(self): func = lambda: None try: - handle_pattern('a pattern')(func) - handle_pattern('a pattern')(func) + handle_request('a pattern')(func) + handle_request('a pattern')(func) self.fail('Registering a pattern twice shoulde raise ValueError') except ValueError: pass From d5a13ae1ca51c4955f39be50ed02e987d1c8c3cb Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 18:55:54 +0200 Subject: [PATCH 36/48] Add auth_required=True to handle_request, and add it to the MpdCommand object stashed in mpd_commands --- mopidy/frontends/mpd/protocol/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mopidy/frontends/mpd/protocol/__init__.py b/mopidy/frontends/mpd/protocol/__init__.py index 24ce1cac..dc6cfb89 100644 --- a/mopidy/frontends/mpd/protocol/__init__.py +++ b/mopidy/frontends/mpd/protocol/__init__.py @@ -22,11 +22,11 @@ LINE_TERMINATOR = u'\n' #: The MPD protocol version is 0.16.0. VERSION = u'0.16.0' -MpdCommand = namedtuple('MpdCommand', ['name']) +MpdCommand = namedtuple('MpdCommand', ['name', 'auth_required']) mpd_commands = set() request_handlers = {} -def handle_request(pattern): +def handle_request(pattern, auth_required=True): """ Decorator for connecting command handlers to command requests. @@ -47,7 +47,8 @@ def handle_request(pattern): def decorator(func): match = re.search('([a-z_]+)', pattern) if match is not None: - mpd_commands.add(MpdCommand(name=match.group())) + mpd_commands.add( + MpdCommand(name=match.group(), auth_required=auth_required)) if pattern in request_handlers: raise ValueError(u'Tried to redefine handler for %s with %s' % ( pattern, func)) From 601a0f0a455441628c313ac1f8f3b456611b0cb2 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 19:02:20 +0200 Subject: [PATCH 37/48] You are always authenticated when MPD_SERVER_PASSWORD==None --- mopidy/frontends/mpd/dispatcher.py | 5 ++++- tests/frontends/mpd/authentication_test.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 405ca03d..4ae1e184 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -70,7 +70,10 @@ class MpdDispatcher(object): ### Filter: authenticate def _authenticate_filter(self, request, response, filter_chain): - if self.authenticated or settings.MPD_SERVER_PASSWORD is None: + if self.authenticated: + return self._call_next_filter(request, response, filter_chain) + elif settings.MPD_SERVER_PASSWORD is None: + self.authenticated = True return self._call_next_filter(request, response, filter_chain) else: command = request.split(' ')[0] diff --git a/tests/frontends/mpd/authentication_test.py b/tests/frontends/mpd/authentication_test.py index 03fc5718..d795d726 100644 --- a/tests/frontends/mpd/authentication_test.py +++ b/tests/frontends/mpd/authentication_test.py @@ -28,7 +28,7 @@ class AuthenticationTest(unittest.TestCase): def test_authentication_with_anything_when_password_check_turned_off(self): settings.MPD_SERVER_PASSWORD = None response = self.dispatcher.handle_request(u'any request at all') - self.assertFalse(self.dispatcher.authenticated) + self.assertTrue(self.dispatcher.authenticated) self.assert_('ACK [5@0] {} unknown command "any"' in response) def test_anything_when_not_authenticated_should_fail(self): From 3ac987ee475a41b17b8265dc7375fb140b33550e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 19:16:07 +0200 Subject: [PATCH 38/48] Move definition of what commands are allowed without authentication from the auth filter to the request handlers --- mopidy/frontends/mpd/dispatcher.py | 10 ++++++---- mopidy/frontends/mpd/protocol/connection.py | 6 +++--- mopidy/frontends/mpd/protocol/reflection.py | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 4ae1e184..29ceacd1 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -76,12 +76,14 @@ class MpdDispatcher(object): self.authenticated = True return self._call_next_filter(request, response, filter_chain) else: - command = request.split(' ')[0] - if command in ( - 'close', 'commands', 'notcommands', 'password', 'ping'): + command_name = request.split(' ')[0] + command_names_not_requiring_auth = [ + command.name for command in mpd_commands + if not command.auth_required] + if command_name in command_names_not_requiring_auth: return self._call_next_filter(request, response, filter_chain) else: - raise MpdPermissionError(command=command) + raise MpdPermissionError(command=command_name) ### Filter: command list diff --git a/mopidy/frontends/mpd/protocol/connection.py b/mopidy/frontends/mpd/protocol/connection.py index d5c2d80c..ff230173 100644 --- a/mopidy/frontends/mpd/protocol/connection.py +++ b/mopidy/frontends/mpd/protocol/connection.py @@ -3,7 +3,7 @@ from mopidy.frontends.mpd.protocol import handle_request from mopidy.frontends.mpd.exceptions import (MpdPasswordError, MpdPermissionError) -@handle_request(r'^close$') +@handle_request(r'^close$', auth_required=False) def close(context): """ *musicpd.org, connection section:* @@ -25,7 +25,7 @@ def kill(context): """ raise MpdPermissionError(command=u'kill') -@handle_request(r'^password "(?P[^"]+)"$') +@handle_request(r'^password "(?P[^"]+)"$', auth_required=False) def password_(context, password): """ *musicpd.org, connection section:* @@ -40,7 +40,7 @@ def password_(context, password): else: raise MpdPasswordError(u'incorrect password', command=u'password') -@handle_request(r'^ping$') +@handle_request(r'^ping$', auth_required=False) def ping(context): """ *musicpd.org, connection section:* diff --git a/mopidy/frontends/mpd/protocol/reflection.py b/mopidy/frontends/mpd/protocol/reflection.py index 94d8ce83..fd47e57e 100644 --- a/mopidy/frontends/mpd/protocol/reflection.py +++ b/mopidy/frontends/mpd/protocol/reflection.py @@ -1,7 +1,7 @@ from mopidy.frontends.mpd.protocol import handle_request, mpd_commands from mopidy.frontends.mpd.exceptions import MpdNotImplemented -@handle_request(r'^commands$') +@handle_request(r'^commands$', auth_required=False) def commands(context): """ *musicpd.org, reflection section:* @@ -49,7 +49,7 @@ def decoders(context): """ raise MpdNotImplemented # TODO -@handle_request(r'^notcommands$') +@handle_request(r'^notcommands$', auth_required=False) def notcommands(context): """ *musicpd.org, reflection section:* From c52d5c9388fc7d3d86ad78bfc793d780f3ec26a2 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 19:20:53 +0200 Subject: [PATCH 39/48] Document mopidy.frontends.mpd.protoocol.mpd_commands as a part of the MPD frontend API --- mopidy/frontends/mpd/protocol/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mopidy/frontends/mpd/protocol/__init__.py b/mopidy/frontends/mpd/protocol/__init__.py index dc6cfb89..f0b56a57 100644 --- a/mopidy/frontends/mpd/protocol/__init__.py +++ b/mopidy/frontends/mpd/protocol/__init__.py @@ -23,7 +23,10 @@ LINE_TERMINATOR = u'\n' VERSION = u'0.16.0' MpdCommand = namedtuple('MpdCommand', ['name', 'auth_required']) + +#: List of all available commands, represented as :class:`MpdCommand` objects. mpd_commands = set() + request_handlers = {} def handle_request(pattern, auth_required=True): From 528f5996601e89c03b28deeb96fd66e5afb6363d Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 19:25:38 +0200 Subject: [PATCH 40/48] Remove _-prefix from BaseMixer.{get_volume,set_volume} as they are public members of the API, intended to be overridden --- mopidy/mixers/alsa.py | 4 ++-- mopidy/mixers/base.py | 11 ++++++----- mopidy/mixers/denon.py | 4 ++-- mopidy/mixers/dummy.py | 4 ++-- mopidy/mixers/gstreamer_software.py | 4 ++-- mopidy/mixers/nad.py | 4 ++-- mopidy/mixers/osa.py | 4 ++-- 7 files changed, 18 insertions(+), 17 deletions(-) diff --git a/mopidy/mixers/alsa.py b/mopidy/mixers/alsa.py index 6329bbbb..ae4bd031 100644 --- a/mopidy/mixers/alsa.py +++ b/mopidy/mixers/alsa.py @@ -51,9 +51,9 @@ class AlsaMixer(ThreadingActor, BaseMixer): return [settings.MIXER_ALSA_CONTROL] return [u'Master', u'PCM'] - def _get_volume(self): + def get_volume(self): # FIXME does not seem to see external volume changes. return self._mixer.getvolume()[0] - def _set_volume(self, volume): + def set_volume(self, volume): self._mixer.setvolume(volume) diff --git a/mopidy/mixers/base.py b/mopidy/mixers/base.py index 74996cb6..ec3d8ae5 100644 --- a/mopidy/mixers/base.py +++ b/mopidy/mixers/base.py @@ -17,9 +17,10 @@ class BaseMixer(object): 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: + volume = self.get_volume() + if volume is None: return None - return int(self._get_volume() / self.amplification_factor) + return int(volume / self.amplification_factor) @volume.setter def volume(self, volume): @@ -28,9 +29,9 @@ class BaseMixer(object): volume = 0 elif volume > 100: volume = 100 - self._set_volume(volume) + self.set_volume(volume) - def _get_volume(self): + def get_volume(self): """ Return volume as integer in range [0, 100]. :class:`None` if unknown. @@ -38,7 +39,7 @@ class BaseMixer(object): """ raise NotImplementedError - def _set_volume(self, volume): + def set_volume(self, volume): """ Set volume as integer in range [0, 100]. diff --git a/mopidy/mixers/denon.py b/mopidy/mixers/denon.py index 3922d20a..d0dc5f54 100644 --- a/mopidy/mixers/denon.py +++ b/mopidy/mixers/denon.py @@ -35,14 +35,14 @@ class DenonMixer(ThreadingActor, BaseMixer): from serial import Serial self._device = Serial(port=settings.MIXER_EXT_PORT, timeout=0.2) - def _get_volume(self): + def get_volume(self): self._ensure_open_device() self._device.write('MV?\r') vol = str(self._device.readline()[2:4]) logger.debug(u'_get_volume() = %s' % vol) return self._levels.index(vol) - def _set_volume(self, volume): + def set_volume(self, volume): # Clamp according to Denon-spec if volume > 99: volume = 99 diff --git a/mopidy/mixers/dummy.py b/mopidy/mixers/dummy.py index 186bc7aa..23f96c4c 100644 --- a/mopidy/mixers/dummy.py +++ b/mopidy/mixers/dummy.py @@ -8,8 +8,8 @@ class DummyMixer(ThreadingActor, BaseMixer): def __init__(self): self._volume = None - def _get_volume(self): + def get_volume(self): return self._volume - def _set_volume(self, volume): + def set_volume(self, volume): self._volume = volume diff --git a/mopidy/mixers/gstreamer_software.py b/mopidy/mixers/gstreamer_software.py index 87602772..523c3387 100644 --- a/mopidy/mixers/gstreamer_software.py +++ b/mopidy/mixers/gstreamer_software.py @@ -15,8 +15,8 @@ class GStreamerSoftwareMixer(ThreadingActor, BaseMixer): assert len(output_refs) == 1, 'Expected exactly one running output.' self.output = output_refs[0].proxy() - def _get_volume(self): + def get_volume(self): return self.output.get_volume().get() - def _set_volume(self, volume): + def set_volume(self, volume): self.output.set_volume(volume).get() diff --git a/mopidy/mixers/nad.py b/mopidy/mixers/nad.py index 62f38bb7..4dbf27be 100644 --- a/mopidy/mixers/nad.py +++ b/mopidy/mixers/nad.py @@ -40,10 +40,10 @@ class NadMixer(ThreadingActor, BaseMixer): self._volume_cache = None self._nad_talker = NadTalker.start().proxy() - def _get_volume(self): + def get_volume(self): return self._volume_cache - def _set_volume(self, volume): + def set_volume(self, volume): self._volume_cache = volume self._nad_talker.set_volume(volume) diff --git a/mopidy/mixers/osa.py b/mopidy/mixers/osa.py index 53983095..bd97d790 100644 --- a/mopidy/mixers/osa.py +++ b/mopidy/mixers/osa.py @@ -28,7 +28,7 @@ class OsaMixer(ThreadingActor, BaseMixer): and self._last_update is not None and (int(time.time() - self._last_update) < self.CACHE_TTL)) - def _get_volume(self): + def get_volume(self): if not self._valid_cache(): try: self._cache = int(Popen( @@ -40,7 +40,7 @@ class OsaMixer(ThreadingActor, BaseMixer): self._last_update = int(time.time()) return self._cache - def _set_volume(self, volume): + def set_volume(self, volume): Popen(['osascript', '-e', 'set volume output volume %d' % volume]) self._cache = volume self._last_update = int(time.time()) From d0573aa7e851198857d75bfa322c872001bf4c77 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 19:28:19 +0200 Subject: [PATCH 41/48] docs: Do not autodoc private members any longer. The need really went away with the separation of BackendControllers and BackendProviders --- docs/autodoc_private_members.py | 10 ---------- docs/conf.py | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) delete mode 100644 docs/autodoc_private_members.py diff --git a/docs/autodoc_private_members.py b/docs/autodoc_private_members.py deleted file mode 100644 index 9cb2e49b..00000000 --- a/docs/autodoc_private_members.py +++ /dev/null @@ -1,10 +0,0 @@ -def setup(app): - app.connect('autodoc-skip-member', autodoc_private_members_with_doc) - -def autodoc_private_members_with_doc(app, what, name, obj, skip, options): - if not skip: - return skip - if (name.startswith('_') and obj.__doc__ is not None - and not (name.startswith('__') and name.endswith('__'))): - return False - return skip diff --git a/docs/conf.py b/docs/conf.py index 7ae3c126..aeada340 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -25,7 +25,7 @@ import mopidy # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'autodoc_private_members', +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.graphviz', 'sphinx.ext.inheritance_diagram', 'sphinx.ext.extlinks', 'sphinx.ext.viewcode'] From 6c68b17b45c1e65411d13f49e4238857feb0a141 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 19:44:08 +0200 Subject: [PATCH 42/48] Fix 'commands' and 'notcommands' for unauthenticated users Use newly gained access to the current user's authentication state and the command handler's auth_required flag to give correct 'commands' and 'notcommands' output to unauthenticated users when password authentication is activated. --- docs/changes.rst | 4 ++ mopidy/frontends/mpd/protocol/reflection.py | 43 ++++++++++++--------- tests/frontends/mpd/reflection_test.py | 28 ++++++++++++++ 3 files changed, 56 insertions(+), 19 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 37ce22c1..b4d56711 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -40,6 +40,10 @@ No description yet. - Do not allow access to the command ``kill``. + - ``commands`` and ``notcommands`` now have correct output if password + authentication is turned on, but the connected user has not been + authenticated yet. + v0.4.1 (2011-05-06) =================== diff --git a/mopidy/frontends/mpd/protocol/reflection.py b/mopidy/frontends/mpd/protocol/reflection.py index fd47e57e..920f48a5 100644 --- a/mopidy/frontends/mpd/protocol/reflection.py +++ b/mopidy/frontends/mpd/protocol/reflection.py @@ -10,23 +10,29 @@ def commands(context): Shows which commands the current user has access to. """ - # FIXME When password auth is turned on and the client is not - # authenticated, 'commands' should list only the commands the client does - # have access to. To implement this we need access to the session object to - # check if the client is authenticated or not. - - command_names = [command.name for command in mpd_commands] + if context.dispatcher.authenticated: + command_names = [command.name for command in mpd_commands] + else: + command_names = [command.name for command in mpd_commands + if not command.auth_required] # No permission to use - command_names.remove('kill') + if 'kill' in command_names: + command_names.remove('kill') # Not shown by MPD in its command list - command_names.remove('command_list_begin') - command_names.remove('command_list_ok_begin') - command_names.remove('command_list_end') - command_names.remove('idle') - command_names.remove('noidle') - command_names.remove('sticker') + if 'command_list_begin' in command_names: + command_names.remove('command_list_begin') + if 'command_list_ok_begin' in command_names: + command_names.remove('command_list_ok_begin') + if 'command_list_end' in command_names: + command_names.remove('command_list_end') + if 'idle' in command_names: + command_names.remove('idle') + if 'noidle' in command_names: + command_names.remove('noidle') + if 'sticker' in command_names: + command_names.remove('sticker') return [('command', command_name) for command_name in sorted(command_names)] @@ -58,12 +64,11 @@ def notcommands(context): Shows which commands the current user does not have access to. """ - # FIXME When password auth is turned on and the client is not - # authenticated, 'notcommands' should list all the commands the client does - # not have access to. To implement this we need access to the session - # object to check if the client is authenticated or not. - - command_names = [] + if context.dispatcher.authenticated: + command_names = [] + else: + command_names = [command.name for command in mpd_commands + if command.auth_required] # No permission to use command_names.append('kill') diff --git a/tests/frontends/mpd/reflection_test.py b/tests/frontends/mpd/reflection_test.py index adc34338..2abf5acc 100644 --- a/tests/frontends/mpd/reflection_test.py +++ b/tests/frontends/mpd/reflection_test.py @@ -1,5 +1,6 @@ import unittest +from mopidy import settings from mopidy.backends.dummy import DummyBackend from mopidy.frontends.mpd.dispatcher import MpdDispatcher from mopidy.mixers.dummy import DummyMixer @@ -11,6 +12,7 @@ class ReflectionHandlerTest(unittest.TestCase): self.dispatcher = MpdDispatcher() def tearDown(self): + settings.runtime.clear() self.backend.stop().get() self.mixer.stop().get() @@ -31,6 +33,19 @@ class ReflectionHandlerTest(unittest.TestCase): self.assert_(u'command: sticker' not in result) self.assert_(u'OK' in result) + def test_commands_show_less_if_auth_required_and_not_authed(self): + settings.MPD_SERVER_PASSWORD = u'secret' + result = self.dispatcher.handle_request(u'commands') + # Not requiring auth + self.assert_(u'command: close' in result, result) + self.assert_(u'command: commands' in result, result) + self.assert_(u'command: notcommands' in result, result) + self.assert_(u'command: password' in result, result) + self.assert_(u'command: ping' in result, result) + # Requiring auth + self.assert_(u'command: play' not in result, result) + self.assert_(u'command: status' not in result, result) + def test_decoders(self): result = self.dispatcher.handle_request(u'decoders') self.assert_(u'ACK [0@0] {} Not implemented' in result) @@ -41,6 +56,19 @@ class ReflectionHandlerTest(unittest.TestCase): self.assert_(u'command: kill' in result) self.assert_(u'OK' in result) + def test_notcommands_returns_more_if_auth_required_and_not_authed(self): + settings.MPD_SERVER_PASSWORD = u'secret' + result = self.dispatcher.handle_request(u'notcommands') + # Not requiring auth + self.assert_(u'command: close' not in result, result) + self.assert_(u'command: commands' not in result, result) + self.assert_(u'command: notcommands' not in result, result) + self.assert_(u'command: password' not in result, result) + self.assert_(u'command: ping' not in result, result) + # Requiring auth + self.assert_(u'command: play' in result, result) + self.assert_(u'command: status' in result, result) + def test_tagtypes(self): result = self.dispatcher.handle_request(u'tagtypes') self.assert_(u'OK' in result) From 958983113dac0dfeaeaa92dd2ef6d519af3ee747 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 20:25:02 +0200 Subject: [PATCH 43/48] Improve 'status' performance by about 30% by sending requests for all needed data at once, block for all of them to arrive, and then return the result. This reduces the number of thread switches needed. --- mopidy/frontends/mpd/protocol/status.py | 127 ++++++++++++++---------- 1 file changed, 72 insertions(+), 55 deletions(-) diff --git a/mopidy/frontends/mpd/protocol/status.py b/mopidy/frontends/mpd/protocol/status.py index 58fefa11..bf0c4f08 100644 --- a/mopidy/frontends/mpd/protocol/status.py +++ b/mopidy/frontends/mpd/protocol/status.py @@ -1,3 +1,5 @@ +import pykka.future + from mopidy.backends.base import PlaybackController from mopidy.frontends.mpd.protocol import handle_request from mopidy.frontends.mpd.exceptions import MpdNotImplemented @@ -130,65 +132,80 @@ def status(context): - ``updatings_db``: job id - ``error``: if there is an error, returns message here """ + futures = { + 'current_playlist.tracks': context.backend.current_playlist.tracks, + 'current_playlist.version': context.backend.current_playlist.version, + 'mixer.volume': context.mixer.volume, + 'playback.consume': context.backend.playback.consume, + 'playback.random': context.backend.playback.random, + 'playback.repeat': context.backend.playback.repeat, + 'playback.single': context.backend.playback.single, + 'playback.state': context.backend.playback.state, + 'playback.current_cp_track': context.backend.playback.current_cp_track, + 'playback.current_playlist_position': + context.backend.playback.current_playlist_position, + 'playback.time_position': context.backend.playback.time_position, + } + pykka.future.get_all(futures.values()) result = [ - ('volume', _status_volume(context)), - ('repeat', _status_repeat(context)), - ('random', _status_random(context)), - ('single', _status_single(context)), - ('consume', _status_consume(context)), - ('playlist', _status_playlist_version(context)), - ('playlistlength', _status_playlist_length(context)), - ('xfade', _status_xfade(context)), - ('state', _status_state(context)), + ('volume', _status_volume(futures)), + ('repeat', _status_repeat(futures)), + ('random', _status_random(futures)), + ('single', _status_single(futures)), + ('consume', _status_consume(futures)), + ('playlist', _status_playlist_version(futures)), + ('playlistlength', _status_playlist_length(futures)), + ('xfade', _status_xfade(futures)), + ('state', _status_state(futures)), ] - if context.backend.playback.current_track.get() is not None: - result.append(('song', _status_songpos(context))) - result.append(('songid', _status_songid(context))) - if context.backend.playback.state.get() in (PlaybackController.PLAYING, + if futures['playback.current_cp_track'].get() is not None: + result.append(('song', _status_songpos(futures))) + result.append(('songid', _status_songid(futures))) + if futures['playback.state'].get() in (PlaybackController.PLAYING, PlaybackController.PAUSED): - result.append(('time', _status_time(context))) - result.append(('elapsed', _status_time_elapsed(context))) - result.append(('bitrate', _status_bitrate(context))) + result.append(('time', _status_time(futures))) + result.append(('elapsed', _status_time_elapsed(futures))) + result.append(('bitrate', _status_bitrate(futures))) return result -def _status_bitrate(context): - current_track = context.backend.playback.current_track.get() - if current_track is not None: - return current_track.bitrate +def _status_bitrate(futures): + current_cp_track = futures['playback.current_cp_track'].get() + if current_cp_track is not None: + return current_cp_track[1].bitrate -def _status_consume(context): - if context.backend.playback.consume.get(): +def _status_consume(futures): + if futures['playback.consume'].get(): return 1 else: return 0 -def _status_playlist_length(context): - return len(context.backend.current_playlist.tracks.get()) +def _status_playlist_length(futures): + return len(futures['current_playlist.tracks'].get()) -def _status_playlist_version(context): - return context.backend.current_playlist.version.get() +def _status_playlist_version(futures): + return futures['current_playlist.version'].get() -def _status_random(context): - return int(context.backend.playback.random.get()) +def _status_random(futures): + return int(futures['playback.random'].get()) -def _status_repeat(context): - return int(context.backend.playback.repeat.get()) +def _status_repeat(futures): + return int(futures['playback.repeat'].get()) -def _status_single(context): - return int(context.backend.playback.single.get()) +def _status_single(futures): + return int(futures['playback.single'].get()) -def _status_songid(context): - current_cpid = context.backend.playback.current_cpid.get() - if current_cpid is not None: - return current_cpid +def _status_songid(futures): + current_cp_track = futures['playback.current_cp_track'].get() + if current_cp_track is not None: + return current_cp_track[0] else: - return _status_songpos(context) + return _status_songpos(futures) -def _status_songpos(context): - return context.backend.playback.current_playlist_position.get() +def _status_songpos(futures): + return futures['playback.current_playlist_position'].get() -def _status_state(context): - state = context.backend.playback.state.get() +def _status_state(futures): + state = futures['playback.state'].get() if state == PlaybackController.PLAYING: return u'play' elif state == PlaybackController.STOPPED: @@ -196,28 +213,28 @@ def _status_state(context): elif state == PlaybackController.PAUSED: return u'pause' -def _status_time(context): - return u'%s:%s' % (_status_time_elapsed(context) // 1000, - _status_time_total(context) // 1000) +def _status_time(futures): + return u'%s:%s' % (_status_time_elapsed(futures) // 1000, + _status_time_total(futures) // 1000) -def _status_time_elapsed(context): - return context.backend.playback.time_position.get() +def _status_time_elapsed(futures): + return futures['playback.time_position'].get() -def _status_time_total(context): - current_track = context.backend.playback.current_track.get() - if current_track is None: +def _status_time_total(futures): + current_cp_track = futures['playback.current_cp_track'].get() + if current_cp_track is None: return 0 - elif current_track.length is None: + elif current_cp_track[1].length is None: return 0 else: - return current_track.length + return current_cp_track[1].length -def _status_volume(context): - volume = context.mixer.volume.get() +def _status_volume(futures): + volume = futures['mixer.volume'].get() if volume is not None: return volume else: return 0 -def _status_xfade(context): - return 0 # TODO +def _status_xfade(futures): + return 0 # Not supported From caedac252eb8da7d2c12d888bbdd4ec843eb5767 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 20:45:03 +0200 Subject: [PATCH 44/48] Move import into methods where needed to avoid import loops when importing mopidy.models --- mopidy/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mopidy/models.py b/mopidy/models.py index ef60ebbe..2ede4352 100644 --- a/mopidy/models.py +++ b/mopidy/models.py @@ -1,4 +1,3 @@ -from mopidy.frontends.mpd import translator class ImmutableObject(object): """ @@ -183,6 +182,7 @@ class Track(ImmutableObject): super(Track, self).__init__(*args, **kwargs) def mpd_format(self, *args, **kwargs): + from mopidy.frontends.mpd import translator return translator.track_to_mpd_format(self, *args, **kwargs) @@ -222,4 +222,5 @@ class Playlist(ImmutableObject): return len(self.tracks) def mpd_format(self, *args, **kwargs): + from mopidy.frontends.mpd import translator return translator.playlist_to_mpd_format(self, *args, **kwargs) From 3f97f3f78b4bfabff53c3201ae24ba7e6d8550e6 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sat, 4 Jun 2011 20:46:34 +0200 Subject: [PATCH 45/48] Convert cp_track from tuple to namedtuple, to get more readable code --- mopidy/backends/base/current_playlist.py | 4 +++- mopidy/backends/base/playback.py | 8 ++++---- .../frontends/mpd/protocol/current_playlist.py | 4 ++-- mopidy/frontends/mpd/protocol/status.py | 12 ++++++------ mopidy/models.py | 4 ++++ tests/backends/base/current_playlist.py | 8 ++++---- tests/models_test.py | 17 ++++++++++++++++- 7 files changed, 39 insertions(+), 18 deletions(-) diff --git a/mopidy/backends/base/current_playlist.py b/mopidy/backends/base/current_playlist.py index ffdce176..2633f166 100644 --- a/mopidy/backends/base/current_playlist.py +++ b/mopidy/backends/base/current_playlist.py @@ -2,6 +2,8 @@ from copy import copy import logging import random +from mopidy.models import CpTrack + logger = logging.getLogger('mopidy.backends.base') class CurrentPlaylistController(object): @@ -66,7 +68,7 @@ class CurrentPlaylistController(object): """ assert at_position <= len(self._cp_tracks), \ u'at_position can not be greater than playlist length' - cp_track = (self.version, track) + cp_track = CpTrack(self.version, track) if at_position is not None: self._cp_tracks.insert(at_position, cp_track) else: diff --git a/mopidy/backends/base/playback.py b/mopidy/backends/base/playback.py index 88ae141d..4ea7a13f 100644 --- a/mopidy/backends/base/playback.py +++ b/mopidy/backends/base/playback.py @@ -80,12 +80,12 @@ class PlaybackController(object): def _get_cpid(self, cp_track): if cp_track is None: return None - return cp_track[0] + return cp_track.cpid def _get_track(self, cp_track): if cp_track is None: return None - return cp_track[1] + return cp_track.track @property def current_cpid(self): @@ -331,7 +331,7 @@ class PlaybackController(object): self.stop(clear_current_track=True) if self.consume: - self.backend.current_playlist.remove(cpid=original_cp_track[0]) + self.backend.current_playlist.remove(cpid=original_cp_track.cpid) def on_current_playlist_change(self): """ @@ -389,7 +389,7 @@ class PlaybackController(object): self.state = self.STOPPED self.current_cp_track = cp_track self.state = self.PLAYING - if not self.provider.play(cp_track[1]): + if not self.provider.play(cp_track.track): # Track is not playable if self.random and self._shuffled: self._shuffled.remove(cp_track) diff --git a/mopidy/frontends/mpd/protocol/current_playlist.py b/mopidy/frontends/mpd/protocol/current_playlist.py index 82e096a0..8e26013d 100644 --- a/mopidy/frontends/mpd/protocol/current_playlist.py +++ b/mopidy/frontends/mpd/protocol/current_playlist.py @@ -59,7 +59,7 @@ def addid(context, uri, songpos=None): raise MpdArgError(u'Bad song index', command=u'addid') cp_track = context.backend.current_playlist.add(track, at_position=songpos).get() - return ('Id', cp_track[0]) + return ('Id', cp_track.cpid) @handle_request(r'^delete "(?P\d+):(?P\d+)*"$') def delete_range(context, start, end=None): @@ -217,7 +217,7 @@ def playlistid(context, cpid=None): cp_track = context.backend.current_playlist.get(cpid=cpid).get() position = context.backend.current_playlist.cp_tracks.get().index( cp_track) - return cp_track[1].mpd_format(position=position, cpid=cpid) + return cp_track.track.mpd_format(position=position, cpid=cpid) except LookupError: raise MpdNoExistError(u'No such song', command=u'playlistid') else: diff --git a/mopidy/frontends/mpd/protocol/status.py b/mopidy/frontends/mpd/protocol/status.py index bf0c4f08..abbb8d7f 100644 --- a/mopidy/frontends/mpd/protocol/status.py +++ b/mopidy/frontends/mpd/protocol/status.py @@ -28,9 +28,9 @@ def currentsong(context): """ current_cp_track = context.backend.playback.current_cp_track.get() if current_cp_track is not None: - return current_cp_track[1].mpd_format( + return current_cp_track.track.mpd_format( position=context.backend.playback.current_playlist_position.get(), - cpid=current_cp_track[0]) + cpid=current_cp_track.cpid) @handle_request(r'^idle$') @handle_request(r'^idle (?P.+)$') @@ -171,7 +171,7 @@ def status(context): def _status_bitrate(futures): current_cp_track = futures['playback.current_cp_track'].get() if current_cp_track is not None: - return current_cp_track[1].bitrate + return current_cp_track.track.bitrate def _status_consume(futures): if futures['playback.consume'].get(): @@ -197,7 +197,7 @@ def _status_single(futures): def _status_songid(futures): current_cp_track = futures['playback.current_cp_track'].get() if current_cp_track is not None: - return current_cp_track[0] + return current_cp_track.cpid else: return _status_songpos(futures) @@ -224,10 +224,10 @@ def _status_time_total(futures): current_cp_track = futures['playback.current_cp_track'].get() if current_cp_track is None: return 0 - elif current_cp_track[1].length is None: + elif current_cp_track.track.length is None: return 0 else: - return current_cp_track[1].length + return current_cp_track.track.length def _status_volume(futures): volume = futures['mixer.volume'].get() diff --git a/mopidy/models.py b/mopidy/models.py index 2ede4352..ed323b71 100644 --- a/mopidy/models.py +++ b/mopidy/models.py @@ -1,3 +1,4 @@ +from collections import namedtuple class ImmutableObject(object): """ @@ -128,6 +129,9 @@ class Album(ImmutableObject): super(Album, self).__init__(*args, **kwargs) +CpTrack = namedtuple('CpTrack', ['cpid', 'track']) + + class Track(ImmutableObject): """ :param uri: track URI diff --git a/tests/backends/base/current_playlist.py b/tests/backends/base/current_playlist.py index 427ce76d..b84391af 100644 --- a/tests/backends/base/current_playlist.py +++ b/tests/backends/base/current_playlist.py @@ -23,14 +23,14 @@ class CurrentPlaylistControllerTest(object): cp_track = self.controller.add(track) self.assertEqual(track, self.controller.tracks[-1]) self.assertEqual(cp_track, self.controller.cp_tracks[-1]) - self.assertEqual(track, cp_track[1]) + self.assertEqual(track, cp_track.track) def test_add_at_position(self): for track in self.tracks[:-1]: cp_track = self.controller.add(track, 0) self.assertEqual(track, self.controller.tracks[0]) self.assertEqual(cp_track, self.controller.cp_tracks[0]) - self.assertEqual(track, cp_track[1]) + self.assertEqual(track, cp_track.track) @populate_playlist def test_add_at_position_outside_of_playlist(self): @@ -40,12 +40,12 @@ class CurrentPlaylistControllerTest(object): @populate_playlist def test_get_by_cpid(self): cp_track = self.controller.cp_tracks[1] - self.assertEqual(cp_track, self.controller.get(cpid=cp_track[0])) + self.assertEqual(cp_track, self.controller.get(cpid=cp_track.cpid)) @populate_playlist def test_get_by_uri(self): cp_track = self.controller.cp_tracks[1] - self.assertEqual(cp_track, self.controller.get(uri=cp_track[1].uri)) + self.assertEqual(cp_track, self.controller.get(uri=cp_track.track.uri)) @populate_playlist def test_get_by_uri_raises_error_for_invalid_uri(self): diff --git a/tests/models_test.py b/tests/models_test.py index afbf9d50..637a8209 100644 --- a/tests/models_test.py +++ b/tests/models_test.py @@ -1,7 +1,7 @@ import datetime as dt import unittest -from mopidy.models import Artist, Album, Track, Playlist +from mopidy.models import Artist, Album, CpTrack, Track, Playlist from tests import SkipTest @@ -274,6 +274,21 @@ class AlbumTest(unittest.TestCase): self.assertNotEqual(hash(album1), hash(album2)) +class CpTrackTest(unittest.TestCase): + def setUp(self): + self.cpid = 123 + self.track = Track() + self.cp_track = CpTrack(self.cpid, self.track) + + def test_cp_track_can_be_accessed_as_a_tuple(self): + self.assertEqual(self.cpid, self.cp_track[0]) + self.assertEqual(self.track, self.cp_track[1]) + + def test_cp_track_can_be_accessed_by_attribute_names(self): + self.assertEqual(self.cpid, self.cp_track.cpid) + self.assertEqual(self.track, self.cp_track.track) + + class TrackTest(unittest.TestCase): def test_uri(self): uri = u'an_uri' From 03dbbeb9cf3aebe67bedeb5e1b13aff08dc3d3f3 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 6 Jun 2011 18:51:10 +0200 Subject: [PATCH 46/48] Ignore .idea --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3fed7452..21adc7af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ *.pyc *.swp .coverage +.idea .noseids .tox MANIFEST From 30afee49c5c5ed1c03d85b6b50c84e60e3e0c5c0 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 7 Jun 2011 01:55:36 +0200 Subject: [PATCH 47/48] Import entire exceptions module to make import list shorter --- mopidy/frontends/mpd/dispatcher.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/mopidy/frontends/mpd/dispatcher.py b/mopidy/frontends/mpd/dispatcher.py index 29ceacd1..91cdc5e7 100644 --- a/mopidy/frontends/mpd/dispatcher.py +++ b/mopidy/frontends/mpd/dispatcher.py @@ -6,8 +6,7 @@ from pykka.registry import ActorRegistry from mopidy import settings from mopidy.backends.base import Backend -from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdArgError, - MpdPermissionError, MpdSystemError, MpdUnknownCommand) +from mopidy.frontends.mpd import exceptions from mopidy.frontends.mpd.protocol import mpd_commands, request_handlers # Do not remove the following import. The protocol modules must be imported to # get them registered as request handlers. @@ -61,7 +60,7 @@ class MpdDispatcher(object): def _catch_mpd_ack_errors_filter(self, request, response, filter_chain): try: return self._call_next_filter(request, response, filter_chain) - except MpdAckError as mpd_ack_error: + except exceptions.MpdAckError as mpd_ack_error: if self.command_list_index is not None: mpd_ack_error.index = self.command_list_index return [mpd_ack_error.get_mpd_ack()] @@ -83,7 +82,7 @@ class MpdDispatcher(object): if command_name in command_names_not_requiring_auth: return self._call_next_filter(request, response, filter_chain) else: - raise MpdPermissionError(command=command_name) + raise exceptions.MpdPermissionError(command=command_name) ### Filter: command list @@ -129,7 +128,7 @@ class MpdDispatcher(object): return self._call_next_filter(request, response, filter_chain) except ActorDeadError as e: logger.warning(u'Tried to communicate with dead actor.') - raise MpdSystemError(e.message) + raise exceptions.MpdSystemError(e.message) def _call_handler(self, request): (handler, kwargs) = self._find_handler(request) @@ -142,8 +141,9 @@ class MpdDispatcher(object): return (request_handlers[pattern], matches.groupdict()) command_name = request.split(' ')[0] if command_name in [command.name for command in mpd_commands]: - raise MpdArgError(u'incorrect arguments', command=command_name) - raise MpdUnknownCommand(command=command_name) + raise exceptions.MpdArgError(u'incorrect arguments', + command=command_name) + raise exceptions.MpdUnknownCommand(command=command_name) def _format_response(self, response): formatted_response = [] From 92937bd3ac90b8582634f95f4c29c5a8050a4b42 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 7 Jun 2011 02:26:58 +0200 Subject: [PATCH 48/48] Make the error_code a class attribute on MPD exceptions --- mopidy/frontends/mpd/exceptions.py | 33 ++++++++++++--------------- tests/frontends/mpd/exception_test.py | 5 ++-- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/mopidy/frontends/mpd/exceptions.py b/mopidy/frontends/mpd/exceptions.py index c6020658..661d6905 100644 --- a/mopidy/frontends/mpd/exceptions.py +++ b/mopidy/frontends/mpd/exceptions.py @@ -16,10 +16,11 @@ class MpdAckError(MopidyException): ACK_ERROR_PLAYER_SYNC = 55 ACK_ERROR_EXIST = 56 - def __init__(self, message=u'', error_code=0, index=0, command=u''): - super(MpdAckError, self).__init__(message, error_code, index, command) + error_code = 0 + + def __init__(self, message=u'', index=0, command=u''): + super(MpdAckError, self).__init__(message, index, command) self.message = message - self.error_code = error_code self.index = index self.command = command @@ -30,42 +31,38 @@ class MpdAckError(MopidyException): ACK [%(error_code)i@%(index)i] {%(command)s} description """ return u'ACK [%i@%i] {%s} %s' % ( - self.error_code, self.index, self.command, self.message) + self.__class__.error_code, self.index, self.command, self.message) class MpdArgError(MpdAckError): - def __init__(self, *args, **kwargs): - super(MpdArgError, self).__init__(*args, **kwargs) - self.error_code = MpdAckError.ACK_ERROR_ARG + error_code = MpdAckError.ACK_ERROR_ARG class MpdPasswordError(MpdAckError): - def __init__(self, *args, **kwargs): - super(MpdPasswordError, self).__init__(*args, **kwargs) - self.error_code = MpdAckError.ACK_ERROR_PASSWORD + error_code = MpdAckError.ACK_ERROR_PASSWORD class MpdPermissionError(MpdAckError): + error_code = MpdAckError.ACK_ERROR_PERMISSION + def __init__(self, *args, **kwargs): super(MpdPermissionError, self).__init__(*args, **kwargs) self.message = u'you don\'t have permission for "%s"' % self.command - self.error_code = MpdAckError.ACK_ERROR_PERMISSION class MpdUnknownCommand(MpdAckError): + error_code = MpdAckError.ACK_ERROR_UNKNOWN + def __init__(self, *args, **kwargs): super(MpdUnknownCommand, self).__init__(*args, **kwargs) self.message = u'unknown command "%s"' % self.command self.command = u'' - self.error_code = MpdAckError.ACK_ERROR_UNKNOWN class MpdNoExistError(MpdAckError): - def __init__(self, *args, **kwargs): - super(MpdNoExistError, self).__init__(*args, **kwargs) - self.error_code = MpdAckError.ACK_ERROR_NO_EXIST + error_code = MpdAckError.ACK_ERROR_NO_EXIST class MpdSystemError(MpdAckError): - def __init__(self, *args, **kwargs): - super(MpdSystemError, self).__init__(*args, **kwargs) - self.error_code = MpdAckError.ACK_ERROR_SYSTEM + error_code = MpdAckError.ACK_ERROR_SYSTEM class MpdNotImplemented(MpdAckError): + error_code = 0 + def __init__(self, *args, **kwargs): super(MpdNotImplemented, self).__init__(*args, **kwargs) self.message = u'Not implemented' diff --git a/tests/frontends/mpd/exception_test.py b/tests/frontends/mpd/exception_test.py index 6750189b..df2cd65e 100644 --- a/tests/frontends/mpd/exception_test.py +++ b/tests/frontends/mpd/exception_test.py @@ -25,10 +25,9 @@ class MpdExceptionsTest(unittest.TestCase): def test_get_mpd_ack_with_values(self): try: - raise MpdAckError('A description', error_code=6, index=7, - command='foo') + raise MpdAckError('A description', index=7, command='foo') except MpdAckError as e: - self.assertEqual(e.get_mpd_ack(), u'ACK [6@7] {foo} A description') + self.assertEqual(e.get_mpd_ack(), u'ACK [0@7] {foo} A description') def test_mpd_unknown_command(self): try: