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)