From 87e489a26ddae4c6c540504414f5d192152b1f34 Mon Sep 17 00:00:00 2001 From: Johannes Knutsen Date: Mon, 16 Aug 2010 23:55:00 +0200 Subject: [PATCH 01/20] rearranged test_next tests --- tests/backends/base.py | 164 ++++++++++++++++++++--------------------- 1 file changed, 82 insertions(+), 82 deletions(-) diff --git a/tests/backends/base.py b/tests/backends/base.py index 31892de2..fa35f382 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -365,6 +365,56 @@ class BasePlaybackControllerTest(object): self.assertEqual(self.playback.state, self.playback.STOPPED) self.assertEqual(self.playback.current_track, None) + @populate_playlist + def test_previous(self): + self.playback.play() + self.playback.next() + self.playback.previous() + self.assertEqual(self.playback.current_track, self.tracks[0]) + + @populate_playlist + def test_previous_more(self): + self.playback.play() # At track 0 + self.playback.next() # At track 1 + self.playback.next() # At track 2 + self.playback.previous() # At track 1 + self.assertEqual(self.playback.current_track, self.tracks[1]) + + @populate_playlist + def test_previous_return_value(self): + self.playback.play() + self.playback.next() + self.assertEqual(self.playback.previous(), None) + + @populate_playlist + def test_previous_does_not_trigger_playback(self): + self.playback.play() + self.playback.next() + self.playback.stop() + self.playback.previous() + self.assertEqual(self.playback.state, self.playback.STOPPED) + + @populate_playlist + def test_previous_at_start_of_playlist(self): + self.playback.previous() + self.assertEqual(self.playback.state, self.playback.STOPPED) + self.assertEqual(self.playback.current_track, None) + + def test_previous_for_empty_playlist(self): + self.playback.previous() + self.assertEqual(self.playback.state, self.playback.STOPPED) + self.assertEqual(self.playback.current_track, None) + + @populate_playlist + def test_previous_skips_to_previous_track_on_failure(self): + # If _play() returns False, it is a failure. + self.playback._play = lambda track: track != self.tracks[1] + self.playback.play(self.current_playlist.cp_tracks[2]) + self.assertEqual(self.playback.current_track, self.tracks[2]) + self.playback.previous() + self.assertNotEqual(self.playback.current_track, self.tracks[1]) + self.assertEqual(self.playback.current_track, self.tracks[0]) + @populate_playlist def test_next(self): self.playback.play() @@ -429,56 +479,6 @@ class BasePlaybackControllerTest(object): self.assertNotEqual(self.playback.current_track, self.tracks[1]) self.assertEqual(self.playback.current_track, self.tracks[2]) - @populate_playlist - def test_previous(self): - self.playback.play() - self.playback.next() - self.playback.previous() - self.assertEqual(self.playback.current_track, self.tracks[0]) - - @populate_playlist - def test_previous_more(self): - self.playback.play() # At track 0 - self.playback.next() # At track 1 - self.playback.next() # At track 2 - self.playback.previous() # At track 1 - self.assertEqual(self.playback.current_track, self.tracks[1]) - - @populate_playlist - def test_previous_return_value(self): - self.playback.play() - self.playback.next() - self.assertEqual(self.playback.previous(), None) - - @populate_playlist - def test_previous_does_not_trigger_playback(self): - self.playback.play() - self.playback.next() - self.playback.stop() - self.playback.previous() - self.assertEqual(self.playback.state, self.playback.STOPPED) - - @populate_playlist - def test_previous_at_start_of_playlist(self): - self.playback.previous() - self.assertEqual(self.playback.state, self.playback.STOPPED) - self.assertEqual(self.playback.current_track, None) - - def test_previous_for_empty_playlist(self): - self.playback.previous() - self.assertEqual(self.playback.state, self.playback.STOPPED) - self.assertEqual(self.playback.current_track, None) - - @populate_playlist - def test_previous_skips_to_previous_track_on_failure(self): - # If _play() returns False, it is a failure. - self.playback._play = lambda track: track != self.tracks[1] - self.playback.play(self.current_playlist.cp_tracks[2]) - self.assertEqual(self.playback.current_track, self.tracks[2]) - self.playback.previous() - self.assertNotEqual(self.playback.current_track, self.tracks[1]) - self.assertEqual(self.playback.current_track, self.tracks[0]) - @populate_playlist def test_next_track_before_play(self): self.assertEqual(self.playback.next_track, self.tracks[0]) @@ -519,6 +519,38 @@ class BasePlaybackControllerTest(object): self.playback.random = True self.assertEqual(self.playback.next_track, self.tracks[2]) + @populate_playlist + def test_next_with_consume(self): + self.playback.consume = True + self.playback.play() + self.playback.next() + self.assert_(self.tracks[0] in self.backend.current_playlist.tracks) + + @populate_playlist + def test_next_with_single_and_repeat(self): + self.playback.single = True + self.playback.repeat = True + self.playback.play() + self.playback.next() + self.assertEqual(self.playback.current_track, self.tracks[1]) + + @populate_playlist + def test_next_with_random(self): + # FIXME feels very fragile + random.seed(1) + self.playback.random = True + self.playback.play() + self.playback.next() + self.assertEqual(self.playback.current_track, self.tracks[1]) + + @populate_playlist + def test_next_track_with_random_after_load_playlist(self): + random.seed(1) + self.playback.random = True + self.assertEqual(self.playback.next_track, self.tracks[2]) + self.backend.current_playlist.load(self.tracks[:1]) + self.assertEqual(self.playback.next_track, self.tracks[1]) + @populate_playlist def test_previous_track_before_play(self): self.assertEqual(self.playback.previous_track, None) @@ -827,13 +859,6 @@ class BasePlaybackControllerTest(object): self.playback.play() self.assertEqual(self.playback.current_track, self.tracks[0]) - @populate_playlist - def test_next_with_consume(self): - self.playback.consume = True - self.playback.play() - self.playback.next() - self.assert_(self.tracks[0] in self.backend.current_playlist.tracks) - @populate_playlist def test_end_of_track_with_consume(self): self.playback.consume = True @@ -841,14 +866,6 @@ class BasePlaybackControllerTest(object): self.playback.end_of_track_callback() self.assert_(self.tracks[0] not in self.backend.current_playlist.tracks) - @populate_playlist - def test_next_with_single_and_repeat(self): - self.playback.single = True - self.playback.repeat = True - self.playback.play() - self.playback.next() - self.assertEqual(self.playback.current_track, self.tracks[1]) - @populate_playlist def test_playlist_is_empty_after_all_tracks_are_played_with_consume(self): self.playback.consume = True @@ -864,15 +881,6 @@ class BasePlaybackControllerTest(object): self.playback.play() self.assertEqual(self.playback.current_track, self.tracks[2]) - @populate_playlist - def test_next_with_random(self): - # FIXME feels very fragile - random.seed(1) - self.playback.random = True - self.playback.play() - self.playback.next() - self.assertEqual(self.playback.current_track, self.tracks[1]) - @populate_playlist def test_previous_with_random(self): random.seed(1) @@ -939,14 +947,6 @@ class BasePlaybackControllerTest(object): self.playback.next() self.assertNotEqual(self.playback.next_track, None) - @populate_playlist - def test_next_track_with_random_after_load_playlist(self): - random.seed(1) - self.playback.random = True - self.assertEqual(self.playback.next_track, self.tracks[2]) - self.backend.current_playlist.load(self.tracks[:1]) - self.assertEqual(self.playback.next_track, self.tracks[1]) - @populate_playlist def test_played_track_during_random_not_played_again(self): self.playback.random = True From 664c731f77d892f67999b03e6ed7adac940d5219 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Mon, 16 Aug 2010 23:57:23 +0200 Subject: [PATCH 02/20] Remove unused variable --- mopidy/frontends/mpd/protocol/stored_playlists.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/frontends/mpd/protocol/stored_playlists.py b/mopidy/frontends/mpd/protocol/stored_playlists.py index 39a2e150..3d7a8710 100644 --- a/mopidy/frontends/mpd/protocol/stored_playlists.py +++ b/mopidy/frontends/mpd/protocol/stored_playlists.py @@ -94,7 +94,7 @@ def load(frontend, name): try: playlist = frontend.backend.stored_playlists.get(name=name) frontend.backend.current_playlist.append(playlist.tracks) - except LookupError as e: + except LookupError: raise MpdNoExistError(u'No such playlist', command=u'load') @handle_pattern(r'^playlistadd "(?P[^"]+)" "(?P[^"]+)"$') From b5026439106df13913443e8e8b342d7bf33c96d6 Mon Sep 17 00:00:00 2001 From: Johannes Knutsen Date: Tue, 17 Aug 2010 00:01:20 +0200 Subject: [PATCH 03/20] added the same tests for end_of_track_callback as was for next --- tests/backends/base.py | 144 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 137 insertions(+), 7 deletions(-) diff --git a/tests/backends/base.py b/tests/backends/base.py index fa35f382..6fb978c9 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -551,6 +551,143 @@ class BasePlaybackControllerTest(object): self.backend.current_playlist.load(self.tracks[:1]) self.assertEqual(self.playback.next_track, self.tracks[1]) + @populate_playlist + def test_end_of_track(self): + self.playback.play() + + old_position = self.playback.current_playlist_position + old_uri = self.playback.current_track.uri + + self.playback.end_of_track_callback() + + self.assertEqual(self.playback.current_playlist_position, + old_position+1) + self.assertNotEqual(self.playback.current_track.uri, old_uri) + + @populate_playlist + def test_end_of_track_return_value(self): + self.playback.play() + self.assertEqual(self.playback.end_of_track_callback(), None) + + @populate_playlist + def test_end_of_track_does_not_trigger_playback(self): + self.playback.end_of_track_callback() + self.assertEqual(self.playback.state, self.playback.STOPPED) + + @populate_playlist + def test_end_of_track_at_end_of_playlist(self): + self.playback.play() + + for i, track in enumerate(self.tracks): + self.assertEqual(self.playback.state, self.playback.PLAYING) + self.assertEqual(self.playback.current_track, track) + self.assertEqual(self.playback.current_playlist_position, i) + + self.playback.end_of_track_callback() + + self.assertEqual(self.playback.state, self.playback.STOPPED) + + @populate_playlist + def test_end_of_track_until_end_of_playlist_and_play_from_start(self): + self.playback.play() + + for track in self.tracks: + self.playback.end_of_track_callback() + + self.assertEqual(self.playback.current_track, None) + self.assertEqual(self.playback.state, self.playback.STOPPED) + + self.playback.play() + self.assertEqual(self.playback.state, self.playback.PLAYING) + self.assertEqual(self.playback.current_track, self.tracks[0]) + + def test_end_of_track_for_empty_playlist(self): + self.playback.end_of_track_callback() + self.assertEqual(self.playback.state, self.playback.STOPPED) + + @populate_playlist + def test_end_of_track_skips_to_next_track_on_failure(self): + # If _play() returns False, it is a failure. + self.playback._play = lambda track: track != self.tracks[1] + self.playback.play() + self.assertEqual(self.playback.current_track, self.tracks[0]) + self.playback.end_of_track_callback() + self.assertNotEqual(self.playback.current_track, self.tracks[1]) + self.assertEqual(self.playback.current_track, self.tracks[2]) + + @populate_playlist + def test_end_of_track_track_before_play(self): + self.assertEqual(self.playback.next_track, self.tracks[0]) + + @populate_playlist + def test_end_of_track_track_during_play(self): + self.playback.play() + self.assertEqual(self.playback.next_track, self.tracks[1]) + + @populate_playlist + def test_end_of_track_track_after_previous(self): + self.playback.play() + self.playback.end_of_track_callback() + self.playback.previous() + self.assertEqual(self.playback.next_track, self.tracks[1]) + + def test_end_of_track_track_empty_playlist(self): + self.assertEqual(self.playback.next_track, None) + + @populate_playlist + def test_end_of_track_track_at_end_of_playlist(self): + self.playback.play() + for track in self.current_playlist.cp_tracks[1:]: + self.playback.end_of_track_callback() + self.assertEqual(self.playback.next_track, None) + + @populate_playlist + def test_end_of_track_track_at_end_of_playlist_with_repeat(self): + self.playback.repeat = True + self.playback.play() + for track in self.tracks[1:]: + self.playback.end_of_track_callback() + self.assertEqual(self.playback.next_track, self.tracks[0]) + + @populate_playlist + def test_end_of_track_track_with_random(self): + random.seed(1) + self.playback.random = True + self.assertEqual(self.playback.next_track, self.tracks[2]) + + + @populate_playlist + def test_end_of_track_with_consume(self): + self.playback.consume = True + self.playback.play() + self.playback.end_of_track_callback() + self.assert_(self.tracks[0] not in self.backend.current_playlist.tracks) + + @populate_playlist + def test_end_of_track_with_single_and_repeat(self): + self.playback.single = True + self.playback.repeat = True + self.playback.play() + self.playback.end_of_track_callback() + self.assertEqual(self.playback.current_track, self.tracks[1]) + + @populate_playlist + def test_end_of_track_with_random(self): + # FIXME feels very fragile + random.seed(1) + self.playback.random = True + self.playback.play() + self.playback.end_of_track_callback() + self.assertEqual(self.playback.current_track, self.tracks[1]) + + @populate_playlist + def test_end_of_track_track_with_random_after_load_playlist(self): + random.seed(1) + self.playback.random = True + self.assertEqual(self.playback.next_track, self.tracks[2]) + self.backend.current_playlist.load(self.tracks[:1]) + self.assertEqual(self.playback.next_track, self.tracks[1]) + @populate_playlist def test_previous_track_before_play(self): self.assertEqual(self.playback.previous_track, None) @@ -859,13 +996,6 @@ class BasePlaybackControllerTest(object): self.playback.play() self.assertEqual(self.playback.current_track, self.tracks[0]) - @populate_playlist - def test_end_of_track_with_consume(self): - self.playback.consume = True - self.playback.play() - self.playback.end_of_track_callback() - self.assert_(self.tracks[0] not in self.backend.current_playlist.tracks) - @populate_playlist def test_playlist_is_empty_after_all_tracks_are_played_with_consume(self): self.playback.consume = True From b32dfee65eea9513d122577125c6edc941e4c2e0 Mon Sep 17 00:00:00 2001 From: Johannes Knutsen Date: Tue, 17 Aug 2010 00:18:46 +0200 Subject: [PATCH 04/20] rename end_of_track_callback to on_end_of_track --- tests/backends/base.py | 46 +++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/backends/base.py b/tests/backends/base.py index 6fb978c9..f341ab48 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -356,7 +356,7 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_current_track_after_completed_playlist(self): self.playback.play(self.current_playlist.cp_tracks[-1]) - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(self.playback.state, self.playback.STOPPED) self.assertEqual(self.playback.current_track, None) @@ -558,7 +558,7 @@ class BasePlaybackControllerTest(object): old_position = self.playback.current_playlist_position old_uri = self.playback.current_track.uri - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(self.playback.current_playlist_position, old_position+1) @@ -567,11 +567,11 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_end_of_track_return_value(self): self.playback.play() - self.assertEqual(self.playback.end_of_track_callback(), None) + self.assertEqual(self.playback.on_end_of_track(), None) @populate_playlist def test_end_of_track_does_not_trigger_playback(self): - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(self.playback.state, self.playback.STOPPED) @populate_playlist @@ -583,7 +583,7 @@ class BasePlaybackControllerTest(object): self.assertEqual(self.playback.current_track, track) self.assertEqual(self.playback.current_playlist_position, i) - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(self.playback.state, self.playback.STOPPED) @@ -592,7 +592,7 @@ class BasePlaybackControllerTest(object): self.playback.play() for track in self.tracks: - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(self.playback.current_track, None) self.assertEqual(self.playback.state, self.playback.STOPPED) @@ -602,7 +602,7 @@ class BasePlaybackControllerTest(object): self.assertEqual(self.playback.current_track, self.tracks[0]) def test_end_of_track_for_empty_playlist(self): - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(self.playback.state, self.playback.STOPPED) @populate_playlist @@ -611,7 +611,7 @@ class BasePlaybackControllerTest(object): self.playback._play = lambda track: track != self.tracks[1] self.playback.play() self.assertEqual(self.playback.current_track, self.tracks[0]) - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertNotEqual(self.playback.current_track, self.tracks[1]) self.assertEqual(self.playback.current_track, self.tracks[2]) @@ -627,7 +627,7 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_end_of_track_track_after_previous(self): self.playback.play() - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.playback.previous() self.assertEqual(self.playback.next_track, self.tracks[1]) @@ -638,7 +638,7 @@ class BasePlaybackControllerTest(object): def test_end_of_track_track_at_end_of_playlist(self): self.playback.play() for track in self.current_playlist.cp_tracks[1:]: - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(self.playback.next_track, None) @populate_playlist @@ -646,7 +646,7 @@ class BasePlaybackControllerTest(object): self.playback.repeat = True self.playback.play() for track in self.tracks[1:]: - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(self.playback.next_track, self.tracks[0]) @populate_playlist @@ -660,7 +660,7 @@ class BasePlaybackControllerTest(object): def test_end_of_track_with_consume(self): self.playback.consume = True self.playback.play() - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assert_(self.tracks[0] not in self.backend.current_playlist.tracks) @populate_playlist @@ -668,7 +668,7 @@ class BasePlaybackControllerTest(object): self.playback.single = True self.playback.repeat = True self.playback.play() - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(self.playback.current_track, self.tracks[1]) @populate_playlist @@ -677,7 +677,7 @@ class BasePlaybackControllerTest(object): random.seed(1) self.playback.random = True self.playback.play() - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(self.playback.current_track, self.tracks[1]) @populate_playlist @@ -763,7 +763,7 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_current_playlist_position_at_end_of_playlist(self): self.playback.play(self.current_playlist.cp_tracks[-1]) - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(self.playback.current_playlist_position, None) def test_on_current_playlist_change_gets_called(self): @@ -780,16 +780,16 @@ class BasePlaybackControllerTest(object): self.assert_(wrapper.called) @populate_playlist - def test_end_of_track_callback_gets_called(self): - end_of_track_callback = self.playback.end_of_track_callback + def test_on_end_of_track_gets_called(self): + on_end_of_track = self.playback.on_end_of_track event = threading.Event() def wrapper(): - result = end_of_track_callback() + result = on_end_of_track() event.set() return result - self.playback.end_of_track_callback = wrapper + self.playback.on_end_of_track = wrapper self.playback.play() self.playback.seek(self.tracks[0].length - 10) @@ -1001,7 +1001,7 @@ class BasePlaybackControllerTest(object): self.playback.consume = True self.playback.play() for i in range(len(self.backend.current_playlist.tracks)): - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(len(self.backend.current_playlist.tracks), 0) @populate_playlist @@ -1024,7 +1024,7 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_end_of_song_starts_next_track(self): self.playback.play() - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(self.playback.current_track, self.tracks[1]) @populate_playlist @@ -1032,13 +1032,13 @@ class BasePlaybackControllerTest(object): self.playback.single = True self.playback.repeat = True self.playback.play() - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(self.playback.current_track, self.tracks[0]) @populate_playlist def test_end_of_playlist_stops(self): self.playback.play(self.current_playlist.cp_tracks[-1]) - self.playback.end_of_track_callback() + self.playback.on_end_of_track() self.assertEqual(self.playback.state, self.playback.STOPPED) def test_repeat_off_by_default(self): From e4edd70c6d419cb6892827fdfcdb10c2b856462b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 17 Aug 2010 00:32:31 +0200 Subject: [PATCH 05/20] Split mopidy.utils into smaller pieces --- mopidy/backends/local/__init__.py | 4 +- .../local/translator.py} | 51 +---------- mopidy/utils/__init__.py | 38 ++++++++ mopidy/utils/path.py | 21 +++++ tests/backends/local/__init__.py | 0 .../{local_test.py => local/backend_test.py} | 2 +- .../local/translator_test.py} | 88 +------------------ tests/outputs/gstreamer_test.py | 6 +- tests/utils/__init__.py | 0 tests/utils/init_test.py | 22 +++++ tests/utils/path_test.py | 71 +++++++++++++++ 11 files changed, 163 insertions(+), 140 deletions(-) rename mopidy/{utils.py => backends/local/translator.py} (68%) create mode 100644 mopidy/utils/__init__.py create mode 100644 mopidy/utils/path.py create mode 100644 tests/backends/local/__init__.py rename tests/backends/{local_test.py => local/backend_test.py} (99%) rename tests/{utils_test.py => backends/local/translator_test.py} (57%) create mode 100644 tests/utils/__init__.py create mode 100644 tests/utils/init_test.py create mode 100644 tests/utils/path_test.py diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 87d2f7c0..434492bf 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -14,10 +14,10 @@ import glob import shutil import threading +from mopidy import settings from mopidy.backends.base import * from mopidy.models import Playlist, Track, Album -from mopidy import settings -from mopidy.utils import parse_m3u, parse_mpd_tag_cache +from .translator import parse_m3u, parse_mpd_tag_cache logger = logging.getLogger(u'mopidy.backends.local') diff --git a/mopidy/utils.py b/mopidy/backends/local/translator.py similarity index 68% rename from mopidy/utils.py rename to mopidy/backends/local/translator.py index bdc0b632..ac69373a 100644 --- a/mopidy/utils.py +++ b/mopidy/backends/local/translator.py @@ -3,57 +3,10 @@ import os import sys import urllib -logger = logging.getLogger('mopidy.utils') +logger = logging.getLogger('mopidy.backends.local.translator') from mopidy.models import Track, Artist, Album - -def flatten(the_list): - result = [] - for element in the_list: - if isinstance(element, list): - result.extend(flatten(element)) - else: - result.append(element) - return result - -def import_module(name): - __import__(name) - return sys.modules[name] - -def get_class(name): - module_name = name[:name.rindex('.')] - class_name = name[name.rindex('.') + 1:] - logger.debug('Loading: %s', name) - try: - module = import_module(module_name) - class_object = getattr(module, class_name) - except (ImportError, AttributeError): - raise ImportError("Couldn't load: %s" % name) - return class_object - -def get_or_create_folder(folder): - folder = os.path.expanduser(folder) - if not os.path.isdir(folder): - logger.info(u'Creating %s', folder) - os.mkdir(folder, 0755) - return folder - -def path_to_uri(*paths): - path = os.path.join(*paths) - #path = os.path.expanduser(path) # FIXME - path = path.encode('utf-8') - if sys.platform == 'win32': - return 'file:' + urllib.pathname2url(path) - return 'file://' + urllib.pathname2url(path) - -def indent(string, places=4, linebreak='\n'): - lines = string.split(linebreak) - if len(lines) == 1: - return string - result = u'' - for line in lines: - result += linebreak + ' ' * places + line - return result +from mopidy.utils.path import path_to_uri def parse_m3u(file_path): """ diff --git a/mopidy/utils/__init__.py b/mopidy/utils/__init__.py new file mode 100644 index 00000000..277d2f3b --- /dev/null +++ b/mopidy/utils/__init__.py @@ -0,0 +1,38 @@ +import logging +import os +import sys + +logger = logging.getLogger('mopidy.utils') + +def flatten(the_list): + result = [] + for element in the_list: + if isinstance(element, list): + result.extend(flatten(element)) + else: + result.append(element) + return result + +def import_module(name): + __import__(name) + return sys.modules[name] + +def get_class(name): + module_name = name[:name.rindex('.')] + class_name = name[name.rindex('.') + 1:] + logger.debug('Loading: %s', name) + try: + module = import_module(module_name) + class_object = getattr(module, class_name) + except (ImportError, AttributeError): + raise ImportError("Couldn't load: %s" % name) + return class_object + +def indent(string, places=4, linebreak='\n'): + lines = string.split(linebreak) + if len(lines) == 1: + return string + result = u'' + for line in lines: + result += linebreak + ' ' * places + line + return result diff --git a/mopidy/utils/path.py b/mopidy/utils/path.py new file mode 100644 index 00000000..002b54c8 --- /dev/null +++ b/mopidy/utils/path.py @@ -0,0 +1,21 @@ +import logging +import os +import sys +import urllib + +logger = logging.getLogger('mopidy.utils.path') + +def get_or_create_folder(folder): + folder = os.path.expanduser(folder) + if not os.path.isdir(folder): + logger.info(u'Creating %s', folder) + os.mkdir(folder, 0755) + return folder + +def path_to_uri(*paths): + path = os.path.join(*paths) + #path = os.path.expanduser(path) # FIXME Waiting for test case? + path = path.encode('utf-8') + if sys.platform == 'win32': + return 'file:' + urllib.pathname2url(path) + return 'file://' + urllib.pathname2url(path) diff --git a/tests/backends/local/__init__.py b/tests/backends/local/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/backends/local_test.py b/tests/backends/local/backend_test.py similarity index 99% rename from tests/backends/local_test.py rename to tests/backends/local/backend_test.py index a5222276..7215e8eb 100644 --- a/tests/backends/local_test.py +++ b/tests/backends/local/backend_test.py @@ -11,7 +11,7 @@ from mopidy import settings from mopidy.backends.local import LocalBackend from mopidy.mixers.dummy import DummyMixer from mopidy.models import Playlist, Track -from mopidy.utils import path_to_uri +from mopidy.utils.path import path_to_uri from tests.backends.base import * from tests import SkipTest, data_folder diff --git a/tests/utils_test.py b/tests/backends/local/translator_test.py similarity index 57% rename from tests/utils_test.py rename to tests/backends/local/translator_test.py index ca44de45..a9fe58d8 100644 --- a/tests/utils_test.py +++ b/tests/backends/local/translator_test.py @@ -1,96 +1,15 @@ -#encoding: utf-8 +# encoding: utf-8 import os -import sys -import shutil import tempfile import unittest -from mopidy.utils import * +from mopidy.utils.path import path_to_uri +from mopidy.backends.local.translator import parse_m3u, parse_mpd_tag_cache from mopidy.models import Track, Artist, Album from tests import SkipTest, data_folder -class GetClassTest(unittest.TestCase): - def test_loading_module_that_does_not_exist(self): - test = lambda: get_class('foo.bar.Baz') - self.assertRaises(ImportError, test) - - def test_loading_class_that_does_not_exist(self): - test = lambda: get_class('unittest.FooBarBaz') - self.assertRaises(ImportError, test) - - def test_import_error_message_contains_complete_class_path(self): - try: - get_class('foo.bar.Baz') - except ImportError as e: - self.assert_('foo.bar.Baz' in str(e)) - - def test_loading_existing_class(self): - cls = get_class('unittest.TestCase') - self.assertEqual(cls.__name__, 'TestCase') - -class GetOrCreateFolderTest(unittest.TestCase): - def setUp(self): - self.parent = tempfile.mkdtemp() - - def tearDown(self): - if os.path.isdir(self.parent): - shutil.rmtree(self.parent) - - def test_creating_folder(self): - folder = os.path.join(self.parent, 'test') - self.assert_(not os.path.exists(folder)) - self.assert_(not os.path.isdir(folder)) - created = get_or_create_folder(folder) - self.assert_(os.path.exists(folder)) - self.assert_(os.path.isdir(folder)) - self.assertEqual(created, folder) - - def test_creating_existing_folder(self): - created = get_or_create_folder(self.parent) - self.assert_(os.path.exists(self.parent)) - self.assert_(os.path.isdir(self.parent)) - self.assertEqual(created, self.parent) - - def test_that_userfolder_is_expanded(self): - raise SkipTest # Not sure how to safely test this - - -class PathToFileURITest(unittest.TestCase): - def test_simple_path(self): - if sys.platform == 'win32': - result = path_to_uri(u'C:/WINDOWS/clock.avi') - self.assertEqual(result, 'file:///C://WINDOWS/clock.avi') - else: - result = path_to_uri(u'/etc/fstab') - self.assertEqual(result, 'file:///etc/fstab') - - def test_folder_and_path(self): - if sys.platform == 'win32': - result = path_to_uri(u'C:/WINDOWS/', u'clock.avi') - self.assertEqual(result, 'file:///C://WINDOWS/clock.avi') - else: - result = path_to_uri(u'/etc', u'fstab') - self.assertEqual(result, u'file:///etc/fstab') - - def test_space_in_path(self): - if sys.platform == 'win32': - result = path_to_uri(u'C:/test this') - self.assertEqual(result, 'file:///C://test%20this') - else: - result = path_to_uri(u'/tmp/test this') - self.assertEqual(result, u'file:///tmp/test%20this') - - def test_unicode_in_path(self): - if sys.platform == 'win32': - result = path_to_uri(u'C:/æøå') - self.assertEqual(result, 'file:///C://%C3%A6%C3%B8%C3%A5') - else: - result = path_to_uri(u'/tmp/æøå') - self.assertEqual(result, u'file:///tmp/%C3%A6%C3%B8%C3%A5') - - song1_path = data_folder('song1.mp3') song2_path = data_folder('song2.mp3') encoded_path = data_folder(u'æøå.mp3') @@ -98,7 +17,6 @@ song1_uri = path_to_uri(song1_path) song2_uri = path_to_uri(song2_path) encoded_uri = path_to_uri(encoded_path) - class M3UToUriTest(unittest.TestCase): def test_empty_file(self): uris = parse_m3u(data_folder('empty.m3u')) diff --git a/tests/outputs/gstreamer_test.py b/tests/outputs/gstreamer_test.py index 62207659..c063aaee 100644 --- a/tests/outputs/gstreamer_test.py +++ b/tests/outputs/gstreamer_test.py @@ -1,15 +1,15 @@ import multiprocessing import unittest -from mopidy.utils import path_to_uri -from mopidy.process import pickle_connection from mopidy.outputs.gstreamer import GStreamerOutput +from mopidy.process import pickle_connection +from mopidy.utils.path import path_to_uri from tests import data_folder, SkipTest class GStreamerOutputTest(unittest.TestCase): def setUp(self): - self.song_uri = path_to_uri(data_folder('song1.wav')) + self.song_uri = path_to_uri(data_folder('song1.wav')) self.output_queue = multiprocessing.Queue() self.core_queue = multiprocessing.Queue() self.output = GStreamerOutput(self.core_queue, self.output_queue) diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/utils/init_test.py b/tests/utils/init_test.py new file mode 100644 index 00000000..fb38e2ea --- /dev/null +++ b/tests/utils/init_test.py @@ -0,0 +1,22 @@ +import unittest + +from mopidy.utils import get_class + +class GetClassTest(unittest.TestCase): + def test_loading_module_that_does_not_exist(self): + test = lambda: get_class('foo.bar.Baz') + self.assertRaises(ImportError, test) + + def test_loading_class_that_does_not_exist(self): + test = lambda: get_class('unittest.FooBarBaz') + self.assertRaises(ImportError, test) + + def test_import_error_message_contains_complete_class_path(self): + try: + get_class('foo.bar.Baz') + except ImportError as e: + self.assert_('foo.bar.Baz' in str(e)) + + def test_loading_existing_class(self): + cls = get_class('unittest.TestCase') + self.assertEqual(cls.__name__, 'TestCase') diff --git a/tests/utils/path_test.py b/tests/utils/path_test.py new file mode 100644 index 00000000..ae63d5c0 --- /dev/null +++ b/tests/utils/path_test.py @@ -0,0 +1,71 @@ +# encoding: utf-8 + +import os +import shutil +import sys +import tempfile +import unittest + +from mopidy.utils.path import get_or_create_folder, path_to_uri + +from tests import SkipTest + +class GetOrCreateFolderTest(unittest.TestCase): + def setUp(self): + self.parent = tempfile.mkdtemp() + + def tearDown(self): + if os.path.isdir(self.parent): + shutil.rmtree(self.parent) + + def test_creating_folder(self): + folder = os.path.join(self.parent, 'test') + self.assert_(not os.path.exists(folder)) + self.assert_(not os.path.isdir(folder)) + created = get_or_create_folder(folder) + self.assert_(os.path.exists(folder)) + self.assert_(os.path.isdir(folder)) + self.assertEqual(created, folder) + + def test_creating_existing_folder(self): + created = get_or_create_folder(self.parent) + self.assert_(os.path.exists(self.parent)) + self.assert_(os.path.isdir(self.parent)) + self.assertEqual(created, self.parent) + + def test_that_userfolder_is_expanded(self): + raise SkipTest # Not sure how to safely test this + + +class PathToFileURITest(unittest.TestCase): + def test_simple_path(self): + if sys.platform == 'win32': + result = path_to_uri(u'C:/WINDOWS/clock.avi') + self.assertEqual(result, 'file:///C://WINDOWS/clock.avi') + else: + result = path_to_uri(u'/etc/fstab') + self.assertEqual(result, 'file:///etc/fstab') + + def test_folder_and_path(self): + if sys.platform == 'win32': + result = path_to_uri(u'C:/WINDOWS/', u'clock.avi') + self.assertEqual(result, 'file:///C://WINDOWS/clock.avi') + else: + result = path_to_uri(u'/etc', u'fstab') + self.assertEqual(result, u'file:///etc/fstab') + + def test_space_in_path(self): + if sys.platform == 'win32': + result = path_to_uri(u'C:/test this') + self.assertEqual(result, 'file:///C://test%20this') + else: + result = path_to_uri(u'/tmp/test this') + self.assertEqual(result, u'file:///tmp/test%20this') + + def test_unicode_in_path(self): + if sys.platform == 'win32': + result = path_to_uri(u'C:/æøå') + self.assertEqual(result, 'file:///C://%C3%A6%C3%B8%C3%A5') + else: + result = path_to_uri(u'/tmp/æøå') + self.assertEqual(result, u'file:///tmp/%C3%A6%C3%B8%C3%A5') From e021863fd807f43f8b910618ee4f2cf9caf48307 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 17 Aug 2010 01:20:17 +0200 Subject: [PATCH 06/20] Fix broken import --- mopidy/__main__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mopidy/__main__.py b/mopidy/__main__.py index c92ce1ed..dd10b1f4 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -11,7 +11,8 @@ sys.path.insert(0, from mopidy import get_version, settings, SettingsError from mopidy.process import CoreProcess -from mopidy.utils import get_class, get_or_create_folder +from mopidy.utils import get_class +from mopidy.utils.path import get_or_create_folder logger = logging.getLogger('mopidy.main') From 7afc74d80b05887a16d44f693d3cc03f3fda6d55 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 17 Aug 2010 01:23:37 +0200 Subject: [PATCH 07/20] Cleanup settings magic --- mopidy/__init__.py | 15 +++----------- mopidy/settings.py | 12 ------------ mopidy/utils/settings.py | 42 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 24 deletions(-) create mode 100644 mopidy/utils/settings.py diff --git a/mopidy/__init__.py b/mopidy/__init__.py index 09d72b26..e3321041 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -2,8 +2,6 @@ import sys if not (2, 6) <= sys.version_info < (3,): sys.exit(u'Mopidy requires Python >= 2.6, < 3') -from mopidy import settings as raw_settings - def get_version(): return u'0.1.0a4' @@ -27,13 +25,6 @@ class MopidyException(Exception): class SettingsError(MopidyException): pass -class Settings(object): - def __getattr__(self, attr): - if attr.isupper() and not hasattr(raw_settings, attr): - raise SettingsError(u'Setting "%s" is not set.' % attr) - value = getattr(raw_settings, attr) - if type(value) != bool and not value: - raise SettingsError(u'Setting "%s" is empty.' % attr) - return value - -settings = Settings() +from mopidy import settings as default_settings_module +from mopidy.utils.settings import SettingsProxy +settings = SettingsProxy(default_settings_module) diff --git a/mopidy/settings.py b/mopidy/settings.py index 67b0c24f..c9e3606e 100644 --- a/mopidy/settings.py +++ b/mopidy/settings.py @@ -7,11 +7,6 @@ Available settings and their default values. file called ``~/.mopidy/settings.py`` and redefine settings there. """ -# Absolute import needed to import ~/.mopidy/settings.py and not ourselves -from __future__ import absolute_import -import os -import sys - #: List of playback backends to use. See :mod:`mopidy.backends` for all #: available backends. #: @@ -172,10 +167,3 @@ SPOTIFY_USERNAME = u'' #: #: Used by :mod:`mopidy.backends.libspotify`. SPOTIFY_PASSWORD = u'' - -# Import user specific settings -dotdir = os.path.expanduser(u'~/.mopidy/') -settings_file = os.path.join(dotdir, u'settings.py') -if os.path.isfile(settings_file): - sys.path.insert(0, dotdir) - from settings import * diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py new file mode 100644 index 00000000..f7209653 --- /dev/null +++ b/mopidy/utils/settings.py @@ -0,0 +1,42 @@ +# Absolute import needed to import ~/.mopidy/settings.py and not ourselves +from __future__ import absolute_import +from copy import copy +import os +import sys + +from mopidy import SettingsError + +class SettingsProxy(object): + def __init__(self, default_settings_module): + self.default_settings = self._get_settings_dict_from_module( + default_settings_module) + self.local_settings = self._get_local_settings() + self.raw_settings = copy(self.default_settings) + self.raw_settings.update(self.local_settings) + + def _get_local_settings(self): + dotdir = os.path.expanduser(u'~/.mopidy/') + settings_file = os.path.join(dotdir, u'settings.py') + if os.path.isfile(settings_file): + sys.path.insert(0, dotdir) + import settings as local_settings_module + return self._get_settings_dict_from_module(local_settings_module) + + def _get_settings_dict_from_module(self, module): + settings = filter(lambda (key, value): self._is_setting(key), + module.__dict__.iteritems()) + return dict(settings) + + def _is_setting(self, name): + return name.isupper() + + def __getattr__(self, attr): + if not self._is_setting(attr): + return + if attr not in self.raw_settings: + raise SettingsError(u'Setting "%s" is not set.' % attr) + value = self.raw_settings[attr] + if type(value) != bool and not value: + raise SettingsError(u'Setting "%s" is empty.' % attr) + return value + From 12e5bc39e310f247f74aa3312610e9600857223c Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 17 Aug 2010 01:45:52 +0200 Subject: [PATCH 08/20] Add settings validation --- docs/changes.rst | 2 ++ mopidy/__init__.py | 1 + mopidy/utils/settings.py | 57 ++++++++++++++++++++++++++++++++++++ tests/utils/settings_test.py | 40 +++++++++++++++++++++++++ 4 files changed, 100 insertions(+) create mode 100644 tests/utils/settings_test.py diff --git a/docs/changes.rst b/docs/changes.rst index 323f899e..9f5efa84 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -31,6 +31,8 @@ greatly improved MPD client support. **Changes** - Exit early if not Python >= 2.6, < 3. +- Validate settings at startup and print useful error messages if the settings + has not been updated or anything is misspelled. - Include Sphinx scripts for building docs, pylintrc, tests and test data in the packages created by ``setup.py`` for i.e. PyPI. - MPD frontend: diff --git a/mopidy/__init__.py b/mopidy/__init__.py index e3321041..9faf31cb 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -28,3 +28,4 @@ class SettingsError(MopidyException): from mopidy import settings as default_settings_module from mopidy.utils.settings import SettingsProxy settings = SettingsProxy(default_settings_module) +settings.validate() diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index f7209653..d06e822a 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -1,6 +1,7 @@ # Absolute import needed to import ~/.mopidy/settings.py and not ourselves from __future__ import absolute_import from copy import copy +import logging import os import sys @@ -40,3 +41,59 @@ class SettingsProxy(object): raise SettingsError(u'Setting "%s" is empty.' % attr) return value + def validate(self): + if self.get_errors(): + sys.exit(self.get_errors_as_string()) + + def get_errors(self): + return validate_settings(self.default_settings, self.local_settings) + + def get_errors_as_string(self): + lines = [u'Errors:'] + for (setting, error) in self.get_errors().iteritems(): + lines.append(u' %s: %s' % (setting, error)) + return '\n'.join(lines) + + +def validate_settings(defaults, settings): + """ + Checks the settings for both errors like misspellings and against a set of + rules for renamed settings, etc. + + Returns of setting names with associated errors. + + :param defaults: Mopidy's default settings + :type defaults: dict + :param settings: the user's local settings + :type settings: dict + :rtype: dict + """ + errors = {} + + changed = { + 'SERVER_HOSTNAME': 'MPD_SERVER_HOSTNAME', + 'SERVER_PORT': 'MPD_SERVER_PORT', + 'SPOTIFY_LIB_APPKEY': None, + } + + for setting, value in settings.iteritems(): + if setting in changed: + if changed[setting] is None: + errors[setting] = u'Deprecated setting. It may be removed.' + else: + errors[setting] = u'Deprecated setting. Use %s.' % ( + changed[setting],) + break + + if setting == 'BACKENDS': + if 'mopidy.backends.despotify.DespotifyBackend' in value: + errors[setting] = (u'Deprecated setting value. ' + + '"mopidy.backends.despotify.DespotifyBackend" is no ' + + 'longer available.') + break + + if setting not in defaults: + errors[setting] = u'Unknown setting. Is it misspelled?' + break + + return errors diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py new file mode 100644 index 00000000..0e1076c9 --- /dev/null +++ b/tests/utils/settings_test.py @@ -0,0 +1,40 @@ +import unittest + +from mopidy.utils.settings import validate_settings + +class ValidateSettingsTest(unittest.TestCase): + def setUp(self): + self.defaults = { + 'MPD_SERVER_HOSTNAME': '::', + 'MPD_SERVER_PORT': 6600, + } + + def test_no_errors_yields_empty_dict(self): + result = validate_settings(self.defaults, {}) + self.assertEqual(result, {}) + + def test_unknown_setting_returns_error(self): + result = validate_settings(self.defaults, + {'MPD_SERVER_HOSTNMAE': '127.0.0.1'}) + self.assertEqual(result['MPD_SERVER_HOSTNMAE'], + u'Unknown setting. Is it misspelled?') + + def test_not_renamed_setting_returns_error(self): + result = validate_settings(self.defaults, + {'SERVER_HOSTNAME': '127.0.0.1'}) + self.assertEqual(result['SERVER_HOSTNAME'], + u'Deprecated setting. Use MPD_SERVER_HOSTNAME.') + + def test_unneeded_settings_returns_error(self): + result = validate_settings(self.defaults, + {'SPOTIFY_LIB_APPKEY': '/tmp/foo'}) + self.assertEqual(result['SPOTIFY_LIB_APPKEY'], + u'Deprecated setting. It may be removed.') + + def test_deprecated_setting_value_returns_error(self): + result = validate_settings(self.defaults, + {'BACKENDS': ('mopidy.backends.despotify.DespotifyBackend',)}) + self.assertEqual(result['BACKENDS'], + u'Deprecated setting value. ' + + '"mopidy.backends.despotify.DespotifyBackend" is no longer ' + + 'available.') From a8c736110fb2c09870aa7ddd24dc4745d127a04b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 17 Aug 2010 01:49:54 +0200 Subject: [PATCH 09/20] Move settings validation from module import to program start --- mopidy/__init__.py | 1 - mopidy/__main__.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/__init__.py b/mopidy/__init__.py index 9faf31cb..e3321041 100644 --- a/mopidy/__init__.py +++ b/mopidy/__init__.py @@ -28,4 +28,3 @@ class SettingsError(MopidyException): from mopidy import settings as default_settings_module from mopidy.utils.settings import SettingsProxy settings = SettingsProxy(default_settings_module) -settings.validate() diff --git a/mopidy/__main__.py b/mopidy/__main__.py index dd10b1f4..1ea05d1f 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -19,6 +19,7 @@ logger = logging.getLogger('mopidy.main') def main(): options = _parse_options() _setup_logging(options.verbosity_level, options.dump) + settings.validate() logger.info('-- Starting Mopidy --') get_or_create_folder('~/.mopidy/') core_queue = multiprocessing.Queue() From 9cb84002bc550c44568f408e20cdfe34ac332841 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 17 Aug 2010 01:58:31 +0200 Subject: [PATCH 10/20] Report multiple settings errors at once --- mopidy/utils/settings.py | 6 +++--- tests/utils/settings_test.py | 5 +++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index d06e822a..78982e8a 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -83,17 +83,17 @@ def validate_settings(defaults, settings): else: errors[setting] = u'Deprecated setting. Use %s.' % ( changed[setting],) - break + continue if setting == 'BACKENDS': if 'mopidy.backends.despotify.DespotifyBackend' in value: errors[setting] = (u'Deprecated setting value. ' + '"mopidy.backends.despotify.DespotifyBackend" is no ' + 'longer available.') - break + continue if setting not in defaults: errors[setting] = u'Unknown setting. Is it misspelled?' - break + continue return errors diff --git a/tests/utils/settings_test.py b/tests/utils/settings_test.py index 0e1076c9..5bf0f9b4 100644 --- a/tests/utils/settings_test.py +++ b/tests/utils/settings_test.py @@ -38,3 +38,8 @@ class ValidateSettingsTest(unittest.TestCase): u'Deprecated setting value. ' + '"mopidy.backends.despotify.DespotifyBackend" is no longer ' + 'available.') + + def test_two_errors_are_both_reported(self): + result = validate_settings(self.defaults, + {'FOO': '', 'BAR': ''}) + self.assertEquals(len(result), 2) From 61059e761049dec719af5eb68c9d11d94c449515 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 17 Aug 2010 02:02:52 +0200 Subject: [PATCH 11/20] Use logging for settings validation errors --- mopidy/utils/settings.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 78982e8a..b478e67e 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -6,6 +6,9 @@ import os import sys from mopidy import SettingsError +from mopidy.utils import indent + +logger = logging.getLogger('mopidy.utils.settings') class SettingsProxy(object): def __init__(self, default_settings_module): @@ -43,15 +46,17 @@ class SettingsProxy(object): def validate(self): if self.get_errors(): - sys.exit(self.get_errors_as_string()) + logger.error(u'Settings validation errors: %s', + indent(self.get_errors_as_string())) + raise SettingsError(u'Settings validation failed.') def get_errors(self): return validate_settings(self.default_settings, self.local_settings) def get_errors_as_string(self): - lines = [u'Errors:'] + lines = [] for (setting, error) in self.get_errors().iteritems(): - lines.append(u' %s: %s' % (setting, error)) + lines.append(u'%s: %s' % (setting, error)) return '\n'.join(lines) From da184ac896831bc67f58a696484488c147634a8f Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 17 Aug 2010 02:34:10 +0200 Subject: [PATCH 12/20] Add '--list-settings' option --- docs/changes.rst | 6 ++++-- mopidy/__main__.py | 4 ++++ mopidy/utils/settings.py | 23 +++++++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/docs/changes.rst b/docs/changes.rst index 9f5efa84..341ef850 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -33,6 +33,8 @@ greatly improved MPD client support. - Exit early if not Python >= 2.6, < 3. - Validate settings at startup and print useful error messages if the settings has not been updated or anything is misspelled. +- Add command line option :option:`--list-settings` to print the currently + active settings. - Include Sphinx scripts for building docs, pylintrc, tests and test data in the packages created by ``setup.py`` for i.e. PyPI. - MPD frontend: @@ -207,8 +209,8 @@ the established pace of at least a release per month. - Improvements to MPD protocol handling, making Mopidy work much better with a group of clients, including ncmpc, MPoD, and Theremin. -- New command line flag ``--dump`` for dumping debug log to ``dump.log`` in the - current directory. +- New command line flag :option:`--dump` for dumping debug log to ``dump.log`` + in the current directory. - New setting :attr:`mopidy.settings.MIXER_ALSA_CONTROL` for forcing what ALSA control :class:`mopidy.mixers.alsa.AlsaMixer` should use. diff --git a/mopidy/__main__.py b/mopidy/__main__.py index 1ea05d1f..a2230180 100644 --- a/mopidy/__main__.py +++ b/mopidy/__main__.py @@ -13,6 +13,7 @@ from mopidy import get_version, settings, SettingsError from mopidy.process import CoreProcess from mopidy.utils import get_class from mopidy.utils.path import get_or_create_folder +from mopidy.utils.settings import list_settings_optparse_callback logger = logging.getLogger('mopidy.main') @@ -42,6 +43,9 @@ def _parse_options(): parser.add_option('--dump', action='store_true', dest='dump', help='dump debug log to file') + parser.add_option('--list-settings', + action='callback', callback=list_settings_optparse_callback, + help='list current settings') return parser.parse_args()[0] def _setup_logging(verbosity_level, dump): diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index b478e67e..34457f05 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -102,3 +102,26 @@ def validate_settings(defaults, settings): continue return errors + +def list_settings_optparse_callback(*args): + """ + Prints a list of all settings. + + Called by optparse when Mopidy is run with the :option:`--list-settings` + option. + """ + from mopidy import settings + errors = settings.get_errors() + lines = [] + for (key, value) in sorted(settings.raw_settings.iteritems()): + default_value = settings.default_settings.get(key) + if key.endswith('PASSWORD'): + value = u'********' + lines.append(u'%s:' % key) + lines.append(u' Value: %s' % repr(value)) + if value != default_value and default_value is not None: + lines.append(u' Default: %s' % repr(default_value)) + if errors.get(key) is not None: + lines.append(u' Error: %s' % errors[key]) + print u'Settings: %s' % indent('\n'.join(lines), places=2) + sys.exit(0) From 7d04550f53e8c287f2c13f71294c001ed6496ada Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Tue, 17 Aug 2010 02:38:55 +0200 Subject: [PATCH 13/20] Remove unused imports --- mopidy/backends/local/translator.py | 2 -- tests/frontends/mpd/stored_playlists_test.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/mopidy/backends/local/translator.py b/mopidy/backends/local/translator.py index ac69373a..87ea15df 100644 --- a/mopidy/backends/local/translator.py +++ b/mopidy/backends/local/translator.py @@ -1,7 +1,5 @@ import logging import os -import sys -import urllib logger = logging.getLogger('mopidy.backends.local.translator') diff --git a/tests/frontends/mpd/stored_playlists_test.py b/tests/frontends/mpd/stored_playlists_test.py index b49ccce1..6e5717af 100644 --- a/tests/frontends/mpd/stored_playlists_test.py +++ b/tests/frontends/mpd/stored_playlists_test.py @@ -6,8 +6,6 @@ from mopidy.frontends.mpd import frontend from mopidy.mixers.dummy import DummyMixer from mopidy.models import Track, Playlist -from tests import SkipTest - class StoredPlaylistsHandlerTest(unittest.TestCase): def setUp(self): self.b = DummyBackend(mixer_class=DummyMixer) From 6b834e6a3353b62079fa1f2bb32425b9479d12ae Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Wed, 18 Aug 2010 00:06:45 +0200 Subject: [PATCH 14/20] Fix loading of local settings when local settings is not present --- mopidy/utils/settings.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mopidy/utils/settings.py b/mopidy/utils/settings.py index 34457f05..18f59df7 100644 --- a/mopidy/utils/settings.py +++ b/mopidy/utils/settings.py @@ -21,9 +21,10 @@ class SettingsProxy(object): def _get_local_settings(self): dotdir = os.path.expanduser(u'~/.mopidy/') settings_file = os.path.join(dotdir, u'settings.py') - if os.path.isfile(settings_file): - sys.path.insert(0, dotdir) - import settings as local_settings_module + if not os.path.isfile(settings_file): + return {} + sys.path.insert(0, dotdir) + import settings as local_settings_module return self._get_settings_dict_from_module(local_settings_module) def _get_settings_dict_from_module(self, module): From 28e1a15ac742b719830e01498e8d097964da8b46 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 18 Aug 2010 00:38:54 +0200 Subject: [PATCH 15/20] Remove SkipTest --- tests/backends/local/backend_test.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/backends/local/backend_test.py b/tests/backends/local/backend_test.py index 7215e8eb..aff84658 100644 --- a/tests/backends/local/backend_test.py +++ b/tests/backends/local/backend_test.py @@ -19,8 +19,6 @@ from tests import SkipTest, data_folder song = data_folder('song%s.wav') generate_song = lambda i: path_to_uri(song % i) -raise SkipTest - # FIXME can be switched to generic test class LocalCurrentPlaylistControllerTest(BaseCurrentPlaylistControllerTest, unittest.TestCase): From 0db797bc125ec608734ea74877bf604cd8298cf4 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 18 Aug 2010 00:39:10 +0200 Subject: [PATCH 16/20] Rename next_track in tests --- tests/backends/base.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/tests/backends/base.py b/tests/backends/base.py index f341ab48..a18eecf8 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -481,29 +481,29 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_next_track_before_play(self): - self.assertEqual(self.playback.next_track, self.tracks[0]) + self.assertEqual(self.playback.track_at_next, self.tracks[0]) @populate_playlist def test_next_track_during_play(self): self.playback.play() - self.assertEqual(self.playback.next_track, self.tracks[1]) + self.assertEqual(self.playback.track_at_next, self.tracks[1]) @populate_playlist def test_next_track_after_previous(self): self.playback.play() self.playback.next() self.playback.previous() - self.assertEqual(self.playback.next_track, self.tracks[1]) + self.assertEqual(self.playback.track_at_next, self.tracks[1]) def test_next_track_empty_playlist(self): - self.assertEqual(self.playback.next_track, None) + self.assertEqual(self.playback.track_at_next, None) @populate_playlist def test_next_track_at_end_of_playlist(self): self.playback.play() for track in self.current_playlist.cp_tracks[1:]: self.playback.next() - self.assertEqual(self.playback.next_track, None) + self.assertEqual(self.playback.track_at_next, None) @populate_playlist def test_next_track_at_end_of_playlist_with_repeat(self): @@ -511,13 +511,13 @@ class BasePlaybackControllerTest(object): self.playback.play() for track in self.tracks[1:]: self.playback.next() - self.assertEqual(self.playback.next_track, self.tracks[0]) + self.assertEqual(self.playback.track_at_next, self.tracks[0]) @populate_playlist def test_next_track_with_random(self): random.seed(1) self.playback.random = True - self.assertEqual(self.playback.next_track, self.tracks[2]) + self.assertEqual(self.playback.track_at_next, self.tracks[2]) @populate_playlist def test_next_with_consume(self): @@ -547,9 +547,9 @@ class BasePlaybackControllerTest(object): def test_next_track_with_random_after_load_playlist(self): random.seed(1) self.playback.random = True - self.assertEqual(self.playback.next_track, self.tracks[2]) + self.assertEqual(self.playback.track_at_next, self.tracks[2]) self.backend.current_playlist.load(self.tracks[:1]) - self.assertEqual(self.playback.next_track, self.tracks[1]) + self.assertEqual(self.playback.track_at_next, self.tracks[1]) @populate_playlist def test_end_of_track(self): @@ -617,29 +617,29 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_end_of_track_track_before_play(self): - self.assertEqual(self.playback.next_track, self.tracks[0]) + self.assertEqual(self.playback.track_at_next, self.tracks[0]) @populate_playlist def test_end_of_track_track_during_play(self): self.playback.play() - self.assertEqual(self.playback.next_track, self.tracks[1]) + self.assertEqual(self.playback.track_at_next, self.tracks[1]) @populate_playlist def test_end_of_track_track_after_previous(self): self.playback.play() self.playback.on_end_of_track() self.playback.previous() - self.assertEqual(self.playback.next_track, self.tracks[1]) + self.assertEqual(self.playback.track_at_next, self.tracks[1]) def test_end_of_track_track_empty_playlist(self): - self.assertEqual(self.playback.next_track, None) + self.assertEqual(self.playback.track_at_next, None) @populate_playlist def test_end_of_track_track_at_end_of_playlist(self): self.playback.play() for track in self.current_playlist.cp_tracks[1:]: self.playback.on_end_of_track() - self.assertEqual(self.playback.next_track, None) + self.assertEqual(self.playback.track_at_next, None) @populate_playlist def test_end_of_track_track_at_end_of_playlist_with_repeat(self): @@ -647,13 +647,13 @@ class BasePlaybackControllerTest(object): self.playback.play() for track in self.tracks[1:]: self.playback.on_end_of_track() - self.assertEqual(self.playback.next_track, self.tracks[0]) + self.assertEqual(self.playback.track_at_next, self.tracks[0]) @populate_playlist def test_end_of_track_track_with_random(self): random.seed(1) self.playback.random = True - self.assertEqual(self.playback.next_track, self.tracks[2]) + self.assertEqual(self.playback.track_at_next, self.tracks[2]) @populate_playlist @@ -684,9 +684,9 @@ class BasePlaybackControllerTest(object): def test_end_of_track_track_with_random_after_load_playlist(self): random.seed(1) self.playback.random = True - self.assertEqual(self.playback.next_track, self.tracks[2]) + self.assertEqual(self.playback.track_at_next, self.tracks[2]) self.backend.current_playlist.load(self.tracks[:1]) - self.assertEqual(self.playback.next_track, self.tracks[1]) + self.assertEqual(self.playback.track_at_next, self.tracks[1]) @populate_playlist def test_previous_track_before_play(self): @@ -1056,14 +1056,14 @@ class BasePlaybackControllerTest(object): self.playback.play() for track in self.tracks[1:]: self.playback.next() - self.assertEqual(self.playback.next_track, None) + self.assertEqual(self.playback.track_at_next, None) @populate_playlist def test_random_until_end_of_playlist_and_play_from_start(self): self.playback.repeat = True for track in self.tracks: self.playback.next() - self.assertNotEqual(self.playback.next_track, None) + self.assertNotEqual(self.playback.track_at_next, None) self.assertEqual(self.playback.state, self.playback.STOPPED) self.playback.play() self.assertEqual(self.playback.state, self.playback.PLAYING) @@ -1075,7 +1075,7 @@ class BasePlaybackControllerTest(object): self.playback.play() for track in self.tracks: self.playback.next() - self.assertNotEqual(self.playback.next_track, None) + self.assertNotEqual(self.playback.track_at_next, None) @populate_playlist def test_played_track_during_random_not_played_again(self): From d01813b72ce33f780fd77506b09431e6862ed86d Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 18 Aug 2010 00:42:26 +0200 Subject: [PATCH 17/20] Rename previous_track in tests --- tests/backends/base.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/backends/base.py b/tests/backends/base.py index a18eecf8..1fee2379 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -690,18 +690,18 @@ class BasePlaybackControllerTest(object): @populate_playlist def test_previous_track_before_play(self): - self.assertEqual(self.playback.previous_track, None) + self.assertEqual(self.playback.track_at_previous, None) @populate_playlist def test_previous_track_after_play(self): self.playback.play() - self.assertEqual(self.playback.previous_track, None) + self.assertEqual(self.playback.track_at_previous, None) @populate_playlist def test_previous_track_after_next(self): self.playback.play() self.playback.next() - self.assertEqual(self.playback.previous_track, self.tracks[0]) + self.assertEqual(self.playback.track_at_previous, self.tracks[0]) @populate_playlist def test_previous_track_after_previous(self): @@ -709,17 +709,17 @@ class BasePlaybackControllerTest(object): self.playback.next() # At track 1 self.playback.next() # At track 2 self.playback.previous() # At track 1 - self.assertEqual(self.playback.previous_track, self.tracks[0]) + self.assertEqual(self.playback.track_at_previous, self.tracks[0]) def test_previous_track_empty_playlist(self): - self.assertEqual(self.playback.previous_track, None) + self.assertEqual(self.playback.track_at_previous, None) @populate_playlist def test_previous_track_with_consume(self): self.playback.consume = True for track in self.tracks: self.playback.next() - self.assertEqual(self.playback.previous_track, + self.assertEqual(self.playback.track_at_previous, self.playback.current_track) @populate_playlist @@ -727,7 +727,7 @@ class BasePlaybackControllerTest(object): self.playback.random = True for track in self.tracks: self.playback.next() - self.assertEqual(self.playback.previous_track, + self.assertEqual(self.playback.track_at_previous, self.playback.current_track) @populate_playlist From e66cf75d054a9ac269a65707667cdc51940b58a5 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 18 Aug 2010 00:43:58 +0200 Subject: [PATCH 18/20] Call renamed callback --- mopidy/backends/local/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/backends/local/__init__.py b/mopidy/backends/local/__init__.py index 434492bf..45e74e5d 100644 --- a/mopidy/backends/local/__init__.py +++ b/mopidy/backends/local/__init__.py @@ -69,7 +69,7 @@ class LocalPlaybackController(BasePlaybackController): def _message(self, bus, message): if message.type == gst.MESSAGE_EOS: - self.end_of_track_callback() + self.on_end_of_track() elif message.type == gst.MESSAGE_ERROR: self._bin.set_state(gst.STATE_NULL) error, debug = message.parse_error() From c0e4454e67c3e6ded3ae573c90d22d83b7965737 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 18 Aug 2010 00:49:26 +0200 Subject: [PATCH 19/20] Remove obsolete search tests --- tests/backends/base.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/backends/base.py b/tests/backends/base.py index 1fee2379..a26c0968 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -1174,21 +1174,6 @@ class BaseStoredPlaylistsControllerTest(object): except LookupError as e: self.assertEqual(u'"name=c" match no playlists', e[0]) - def test_search_returns_empty_list(self): - self.assertEqual([], self.stored.search('test')) - - def test_search_returns_playlist(self): - playlist = self.stored.create('test') - playlists = self.stored.search('test') - self.assert_(playlist in playlists) - - def test_search_returns_mulitple_playlists(self): - playlist1 = self.stored.create('test') - playlist2 = self.stored.create('test2') - playlists = self.stored.search('test') - self.assert_(playlist1 in playlists) - self.assert_(playlist2 in playlists) - def test_lookup(self): raise SkipTest From 785ef04e7b0fea2d5e4d0e44b8c9adaec97ee385 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Wed, 18 Aug 2010 00:53:29 +0200 Subject: [PATCH 20/20] Rename load to append in tests --- tests/backends/base.py | 44 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/backends/base.py b/tests/backends/base.py index a26c0968..eb13af59 100644 --- a/tests/backends/base.py +++ b/tests/backends/base.py @@ -93,12 +93,12 @@ class BaseCurrentPlaylistControllerTest(object): def test_get_by_uri_returns_unique_match(self): track = Track(uri='a') - self.controller.load([Track(uri='z'), track, Track(uri='y')]) + self.controller.append([Track(uri='z'), track, Track(uri='y')]) self.assertEqual(track, self.controller.get(uri='a')[1]) def test_get_by_uri_raises_error_if_multiple_matches(self): track = Track(uri='a') - self.controller.load([Track(uri='z'), track, track]) + self.controller.append([Track(uri='z'), track, track]) try: self.controller.get(uri='a') self.fail(u'Should raise LookupError if multiple matches') @@ -118,7 +118,7 @@ class BaseCurrentPlaylistControllerTest(object): track1 = Track(uri='a', name='x') track2 = Track(uri='b', name='x') track3 = Track(uri='b', name='y') - self.controller.load([track1, track2, track3]) + self.controller.append([track1, track2, track3]) self.assertEqual(track1, self.controller.get(uri='a', name='x')[1]) self.assertEqual(track2, self.controller.get(uri='b', name='x')[1]) self.assertEqual(track3, self.controller.get(uri='b', name='y')[1]) @@ -127,35 +127,35 @@ class BaseCurrentPlaylistControllerTest(object): track1 = Track() track2 = Track(uri='b') track3 = Track() - self.controller.load([track1, track2, track3]) + self.controller.append([track1, track2, track3]) self.assertEqual(track2, self.controller.get(uri='b')[1]) - def test_load_appends_to_the_current_playlist(self): - self.controller.load([Track(uri='a'), Track(uri='b')]) + def test_append_appends_to_the_current_playlist(self): + self.controller.append([Track(uri='a'), Track(uri='b')]) self.assertEqual(len(self.controller.tracks), 2) - self.controller.load([Track(uri='c'), Track(uri='d')]) + self.controller.append([Track(uri='c'), Track(uri='d')]) self.assertEqual(len(self.controller.tracks), 4) self.assertEqual(self.controller.tracks[0].uri, 'a') self.assertEqual(self.controller.tracks[1].uri, 'b') self.assertEqual(self.controller.tracks[2].uri, 'c') self.assertEqual(self.controller.tracks[3].uri, 'd') - def test_load_does_not_reset_version(self): + def test_append_does_not_reset_version(self): version = self.controller.version - self.controller.load([]) + self.controller.append([]) self.assertEqual(self.controller.version, version + 1) @populate_playlist - def test_load_preserves_playing_state(self): + def test_append_preserves_playing_state(self): self.playback.play() track = self.playback.current_track - self.controller.load(self.controller.tracks[1:2]) + self.controller.append(self.controller.tracks[1:2]) self.assertEqual(self.playback.state, self.playback.PLAYING) self.assertEqual(self.playback.current_track, track) @populate_playlist - def test_load_preserves_stopped_state(self): - self.controller.load(self.controller.tracks[1:2]) + def test_append_preserves_stopped_state(self): + self.controller.append(self.controller.tracks[1:2]) self.assertEqual(self.playback.state, self.playback.STOPPED) self.assertEqual(self.playback.current_track, None) @@ -266,7 +266,7 @@ class BaseCurrentPlaylistControllerTest(object): def test_version(self): version = self.controller.version - self.controller.load([]) + self.controller.append([]) self.assert_(version < self.controller.version) @@ -544,11 +544,11 @@ class BasePlaybackControllerTest(object): self.assertEqual(self.playback.current_track, self.tracks[1]) @populate_playlist - def test_next_track_with_random_after_load_playlist(self): + def test_next_track_with_random_after_append_playlist(self): random.seed(1) self.playback.random = True self.assertEqual(self.playback.track_at_next, self.tracks[2]) - self.backend.current_playlist.load(self.tracks[:1]) + self.backend.current_playlist.append(self.tracks[:1]) self.assertEqual(self.playback.track_at_next, self.tracks[1]) @populate_playlist @@ -681,11 +681,11 @@ class BasePlaybackControllerTest(object): self.assertEqual(self.playback.current_track, self.tracks[1]) @populate_playlist - def test_end_of_track_track_with_random_after_load_playlist(self): + def test_end_of_track_track_with_random_after_append_playlist(self): random.seed(1) self.playback.random = True self.assertEqual(self.playback.track_at_next, self.tracks[2]) - self.backend.current_playlist.load(self.tracks[:1]) + self.backend.current_playlist.append(self.tracks[:1]) self.assertEqual(self.playback.track_at_next, self.tracks[1]) @populate_playlist @@ -775,7 +775,7 @@ class BasePlaybackControllerTest(object): wrapper.called = False self.playback.on_current_playlist_change = wrapper - self.backend.current_playlist.load([]) + self.backend.current_playlist.append([]) self.assert_(wrapper.called) @@ -802,14 +802,14 @@ class BasePlaybackControllerTest(object): def test_on_current_playlist_change_when_playing(self): self.playback.play() current_track = self.playback.current_track - self.backend.current_playlist.load([self.tracks[2]]) + self.backend.current_playlist.append([self.tracks[2]]) self.assertEqual(self.playback.state, self.playback.PLAYING) self.assertEqual(self.playback.current_track, current_track) @populate_playlist def test_on_current_playlist_change_when_stopped(self): current_track = self.playback.current_track - self.backend.current_playlist.load([self.tracks[2]]) + self.backend.current_playlist.append([self.tracks[2]]) self.assertEqual(self.playback.state, self.playback.STOPPED) self.assertEqual(self.playback.current_track, None) @@ -818,7 +818,7 @@ class BasePlaybackControllerTest(object): self.playback.play() self.playback.pause() current_track = self.playback.current_track - self.backend.current_playlist.load([self.tracks[2]]) + self.backend.current_playlist.append([self.tracks[2]]) self.assertEqual(self.playback.state, self.backend.playback.PAUSED) self.assertEqual(self.playback.current_track, current_track)