From 35a31d3d2758c8a06dcf22a1585f4398a086c83a Mon Sep 17 00:00:00 2001 From: Lasse Bigum Date: Sun, 13 Oct 2013 07:39:34 +0200 Subject: [PATCH 01/16] Add support for 'count ..' and 'find .. track X' --- mopidy/backends/local/library.py | 2 +- mopidy/frontends/mpd/protocol/music_db.py | 13 +++++++++++-- mopidy/frontends/mpd/translator.py | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/mopidy/backends/local/library.py b/mopidy/backends/local/library.py index 0de63faf..24145e9a 100644 --- a/mopidy/backends/local/library.py +++ b/mopidy/backends/local/library.py @@ -58,7 +58,7 @@ class LocalLibraryProvider(base.BaseLibraryProvider): q = value.strip() uri_filter = lambda t: q == t.uri - track_filter = lambda t: q == t.name + track_filter = lambda t: int(q) == t.track_no album_filter = lambda t: q == getattr(t, 'album', Album()).name artist_filter = lambda t: filter( lambda a: q == a.name, t.artists) diff --git a/mopidy/frontends/mpd/protocol/music_db.py b/mopidy/frontends/mpd/protocol/music_db.py index f81d57ee..20d53a94 100644 --- a/mopidy/frontends/mpd/protocol/music_db.py +++ b/mopidy/frontends/mpd/protocol/music_db.py @@ -11,7 +11,7 @@ from mopidy.frontends.mpd.protocol import handle_request, stored_playlists QUERY_RE = ( r'(?P("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ile|[Ff]ilename|' - r'[Tt]itle|[Aa]ny)"? "[^"]*"\s?)+)$') + r'[Tt]itle|[Tt]rack|[Aa]ny)"? "[^"]*"\s?)+)$') def _get_field(field, search_results): @@ -54,7 +54,16 @@ def count(context, mpd_query): - does not add quotes around the tag argument. - use multiple tag-needle pairs to make more specific searches. """ - return [('songs', 0), ('playtime', 0)] # TODO + try: + query = translator.query_from_mpd_search_format(mpd_query) + except ValueError: + return + results = context.core.library.find_exact(**query).get() + result_tracks = [] + + result_tracks = _get_tracks(results) + return [('songs', len(result_tracks)), + ('playtime', sum(track.length for track in result_tracks) / 1000)] @handle_request(r'^find ' + QUERY_RE) diff --git a/mopidy/frontends/mpd/translator.py b/mopidy/frontends/mpd/translator.py index df3338ba..8f70320d 100644 --- a/mopidy/frontends/mpd/translator.py +++ b/mopidy/frontends/mpd/translator.py @@ -195,7 +195,7 @@ def query_from_mpd_search_format(mpd_query): query_parts = re.findall(query_pattern, mpd_query) query_part_pattern = ( r'"?(?P([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ile|[Ff]ilename|' - r'[Tt]itle|[Aa]ny))"? "(?P[^"]+)"') + r'[Tt]itle|[Tt]rack|[Aa]ny))"? "(?P[^"]+)"') query = {} for query_part in query_parts: m = re.match(query_part_pattern, query_part) From df32e7fcd0c2560f6508d0d7760642c52b88f6c1 Mon Sep 17 00:00:00 2001 From: Lasse Bigum Date: Sun, 13 Oct 2013 10:24:19 +0200 Subject: [PATCH 02/16] Error checking before type-cast --- mopidy/backends/local/library.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mopidy/backends/local/library.py b/mopidy/backends/local/library.py index 24145e9a..73b4bf50 100644 --- a/mopidy/backends/local/library.py +++ b/mopidy/backends/local/library.py @@ -58,7 +58,8 @@ class LocalLibraryProvider(base.BaseLibraryProvider): q = value.strip() uri_filter = lambda t: q == t.uri - track_filter = lambda t: int(q) == t.track_no + track_filter = lambda t: q == int(t.track_no) \ + if t.track_no.isdigit() else None album_filter = lambda t: q == getattr(t, 'album', Album()).name artist_filter = lambda t: filter( lambda a: q == a.name, t.artists) From 1975694d08f9367bc82e70ee37b859dc738b8370 Mon Sep 17 00:00:00 2001 From: Lasse Bigum Date: Sun, 13 Oct 2013 13:28:13 +0200 Subject: [PATCH 03/16] Changed track to track_no, fix code, add tests Added testcases for track and count --- mopidy/backends/local/library.py | 23 ++++-- mopidy/frontends/mpd/protocol/music_db.py | 2 - mopidy/frontends/mpd/translator.py | 2 + tests/backends/local/library_test.py | 72 +++++++++++++++++-- tests/data/library_tag_cache | 3 + tests/frontends/mpd/protocol/music_db_test.py | 37 ++++++++++ 6 files changed, 127 insertions(+), 12 deletions(-) diff --git a/mopidy/backends/local/library.py b/mopidy/backends/local/library.py index 73b4bf50..bec5b0d0 100644 --- a/mopidy/backends/local/library.py +++ b/mopidy/backends/local/library.py @@ -55,23 +55,28 @@ class LocalLibraryProvider(base.BaseLibraryProvider): values = [values] # FIXME this is bound to be slow for large libraries for value in values: - q = value.strip() + if field != 'track_no': + q = value.strip() + else: + q = value uri_filter = lambda t: q == t.uri - track_filter = lambda t: q == int(t.track_no) \ - if t.track_no.isdigit() else None + track_filter = lambda t: q == t.name album_filter = lambda t: q == getattr(t, 'album', Album()).name artist_filter = lambda t: filter( lambda a: q == a.name, t.artists) albumartist_filter = lambda t: any([ q == a.name for a in getattr(t.album, 'artists', [])]) + track_no_filter = lambda t: q == t.track_no date_filter = lambda t: q == t.date any_filter = lambda t: ( track_filter(t) or album_filter(t) or artist_filter(t) or albumartist_filter(t) or + track_no_filter(t) or + date_filter(t)) or uri_filter(t)) if field == 'uri': @@ -86,6 +91,8 @@ class LocalLibraryProvider(base.BaseLibraryProvider): result_tracks = filter(albumartist_filter, result_tracks) elif field == 'date': result_tracks = filter(date_filter, result_tracks) + elif field == 'track_no': + result_tracks = filter(track_no_filter, result_tracks) elif field == 'any': result_tracks = filter(any_filter, result_tracks) else: @@ -106,7 +113,10 @@ class LocalLibraryProvider(base.BaseLibraryProvider): values = [values] # FIXME this is bound to be slow for large libraries for value in values: - q = value.strip().lower() + if field != 'track_no': + q = value.strip().lower() + else: + q = value uri_filter = lambda t: q in t.uri.lower() track_filter = lambda t: q in t.name.lower() @@ -117,12 +127,15 @@ class LocalLibraryProvider(base.BaseLibraryProvider): albumartist_filter = lambda t: any([ q in a.name.lower() for a in getattr(t.album, 'artists', [])]) + track_no_filter = lambda t: q == t.track_no date_filter = lambda t: t.date and t.date.startswith(q) any_filter = lambda t: ( track_filter(t) or album_filter(t) or artist_filter(t) or albumartist_filter(t) or + track_no_filter(t) or + date_filter(t)) or uri_filter(t)) if field == 'uri': @@ -137,6 +150,8 @@ class LocalLibraryProvider(base.BaseLibraryProvider): result_tracks = filter(albumartist_filter, result_tracks) elif field == 'date': result_tracks = filter(date_filter, result_tracks) + elif field == 'track_no': + result_tracks = filter(track_no_filter, result_tracks) elif field == 'any': result_tracks = filter(any_filter, result_tracks) else: diff --git a/mopidy/frontends/mpd/protocol/music_db.py b/mopidy/frontends/mpd/protocol/music_db.py index 20d53a94..3f4d460f 100644 --- a/mopidy/frontends/mpd/protocol/music_db.py +++ b/mopidy/frontends/mpd/protocol/music_db.py @@ -59,8 +59,6 @@ def count(context, mpd_query): except ValueError: return results = context.core.library.find_exact(**query).get() - result_tracks = [] - result_tracks = _get_tracks(results) return [('songs', len(result_tracks)), ('playtime', sum(track.length for track in result_tracks) / 1000)] diff --git a/mopidy/frontends/mpd/translator.py b/mopidy/frontends/mpd/translator.py index 8f70320d..ec6dec4b 100644 --- a/mopidy/frontends/mpd/translator.py +++ b/mopidy/frontends/mpd/translator.py @@ -202,6 +202,8 @@ def query_from_mpd_search_format(mpd_query): field = m.groupdict()['field'].lower() if field == 'title': field = 'track' + if field == 'track': + field = 'track_no' elif field in ('file', 'filename'): field = 'uri' field = str(field) # Needed for kwargs keys on OS X and Windows diff --git a/tests/backends/local/library_test.py b/tests/backends/local/library_test.py index 6b0cd6f6..7077e65c 100644 --- a/tests/backends/local/library_test.py +++ b/tests/backends/local/library_test.py @@ -29,15 +29,15 @@ class LocalLibraryProviderTest(unittest.TestCase): Track( uri='local:track:path1', name='track1', artists=[artists[0]], album=albums[0], - date='2001-02-03', length=4000), + date='2001-02-03', length=4000, track_no=1), Track( uri='local:track:path2', name='track2', artists=[artists[1]], album=albums[1], - date='2002', length=4000), + date='2002', length=4000, track_no=2), Track( uri='local:track:path3', name='track3', artists=[artists[3]], album=albums[2], - date='2003', length=4000), + date='2003', length=4000, track_no=3), ] config = { @@ -87,6 +87,18 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.find_exact(album=['unknown artist']) self.assertEqual(list(result[0].tracks), []) + result = self.library.find_exact(date=['1990']) + self.assertEqual(list(result[0].tracks), []) + + result = self.library.find_exact(track_no=[9]) + self.assertEqual(list(result[0].tracks), []) + + result = self.library.find_exact(uri=['fake uri']) + self.assertEqual(list(result[0].tracks), []) + + result = self.library.find_exact(any=['unknown any']) + self.assertEqual(list(result[0].tracks), []) + def test_find_exact_uri(self): track_1_uri = 'local:track:path1' result = self.library.find_exact(uri=track_1_uri) @@ -96,6 +108,22 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.find_exact(uri=track_2_uri) self.assertEqual(list(result[0].tracks), self.tracks[1:2]) + def test_find_exact_any(self): + result = self.library.find_exact(any=['track1']) + self.assertEqual(list(result[0].tracks), self.tracks[:1]) + + result = self.library.find_exact(any=['track2']) + self.assertEqual(list(result[0].tracks), self.tracks[1:2]) + + result = self.library.find_exact(any=['2002']) + self.assertEqual(list(result[0].tracks), self.tracks[1:2]) + + result = self.library.find_exact(any=['album1']) + self.assertEqual(list(result[0].tracks), self.tracks[:1]) + + result = self.library.find_exact(any=['artist2']) + self.assertEqual(list(result[0].tracks), self.tracks[1:2]) + def test_find_exact_track(self): result = self.library.find_exact(track=['track1']) self.assertEqual(list(result[0].tracks), self.tracks[:1]) @@ -130,6 +158,13 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.find_exact(albumartist=['artist3']) self.assertEqual(list(result[0].tracks), [self.tracks[2]]) + def test_find_exact_track_no(self): + result = self.library.find_exact(track_no=[1]) + self.assertEqual(list(result[0].tracks), self.tracks[:1]) + + result = self.library.find_exact(track_no=[2]) + self.assertEqual(list(result[0].tracks), self.tracks[1:2]) + def test_find_exact_date(self): result = self.library.find_exact(date=['2001']) self.assertEqual(list(result[0].tracks), []) @@ -175,16 +210,28 @@ class LocalLibraryProviderTest(unittest.TestCase): test = lambda: self.library.find_exact(album=['']) self.assertRaises(LookupError, test) - def test_search_no_hits(self): - result = self.library.search(track=['unknown track']) - self.assertEqual(list(result[0].tracks), []) + test = lambda: self.library.find_exact(track_no=[]) + self.assertRaises(LookupError, test) + test = lambda: self.library.find_exact(date=['']) + self.assertRaises(LookupError, test) + + test = lambda: self.library.find_exact(any=['']) + self.assertRaises(LookupError, test) + + def test_search_no_hits(self): result = self.library.search(artist=['unknown artist']) self.assertEqual(list(result[0].tracks), []) result = self.library.search(album=['unknown artist']) self.assertEqual(list(result[0].tracks), []) + result = self.library.search(track_no=[9]) + self.assertEqual(list(result[0].tracks), []) + + result = self.library.search(date=['unknown']) + self.assertEqual(list(result[0].tracks), []) + result = self.library.search(uri=['unknown']) self.assertEqual(list(result[0].tracks), []) @@ -245,6 +292,13 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.search(date=['2002']) self.assertEqual(list(result[0].tracks), self.tracks[1:2]) + def test_search_track_no(self): + result = self.library.search(track_no=[1]) + self.assertEqual(list(result[0].tracks), self.tracks[:1]) + + result = self.library.search(track_no=[2]) + self.assertEqual(list(result[0].tracks), self.tracks[1:2]) + def test_search_any(self): # Matches on track artist result = self.library.search(any=['Tist1']) @@ -254,6 +308,9 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.search(any=['Rack1']) self.assertEqual(list(result[0].tracks), self.tracks[:1]) + result = self.library.search(any=['Rack2']) + self.assertEqual(list(result[0].tracks), self.tracks[1:2]) + # Matches on track album result = self.library.search(any=['Bum1']) self.assertEqual(list(result[0].tracks), self.tracks[:1]) @@ -280,6 +337,9 @@ class LocalLibraryProviderTest(unittest.TestCase): test = lambda: self.library.search(album=['']) self.assertRaises(LookupError, test) + test = lambda: self.library.search(date=['']) + self.assertRaises(LookupError, test) + test = lambda: self.library.search(uri=['']) self.assertRaises(LookupError, test) diff --git a/tests/data/library_tag_cache b/tests/data/library_tag_cache index 2272a114..e9e87c1b 100644 --- a/tests/data/library_tag_cache +++ b/tests/data/library_tag_cache @@ -9,6 +9,7 @@ Artist: artist1 Title: track1 Album: album1 Date: 2001-02-03 +Track: 1 Time: 4 key: key2 file: /path2 @@ -16,6 +17,7 @@ Artist: artist2 Title: track2 Album: album2 Date: 2002 +Track: 2 Time: 4 key: key3 file: /path3 @@ -24,5 +26,6 @@ AlbumArtist: artist3 Title: track3 Album: album3 Date: 2003 +Track: 3 Time: 4 songList end diff --git a/tests/frontends/mpd/protocol/music_db_test.py b/tests/frontends/mpd/protocol/music_db_test.py index eaa5da06..203fc020 100644 --- a/tests/frontends/mpd/protocol/music_db_test.py +++ b/tests/frontends/mpd/protocol/music_db_test.py @@ -24,6 +24,23 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase): self.assertInResponse('playtime: 0') self.assertInResponse('OK') + def test_count_correct_length(self): + self.backend.library.dummy_search_result = SearchResult( + tracks=[Track(name='A', date="2001", length=4000), + Track(name='B', date="2001", length=50000), + Track(name='C', date="2001", length=600000), + Track(name='D', date="2009", length=60000) + ]) + self.sendRequest('count "date" "2009"') + self.assertInResponse('songs: 1') + self.assertInResponse('playtime: 4000') + self.assertInResponse('OK') + + self.sendRequest('count "date" "2001"') + self.assertInResponse('songs: 3') + self.assertInResponse('playtime: 654000') + self.assertInResponse('OK') + def test_findadd(self): self.backend.library.dummy_find_exact_result = SearchResult( tracks=[Track(uri='dummy:a', name='A')]) @@ -235,6 +252,14 @@ class MusicDatabaseFindTest(protocol.BaseTestCase): self.sendRequest('find title "what"') self.assertInResponse('OK') + def test_find_track_no(self): + self.sendRequest('find "track" "what"') + self.assertInResponse('OK') + + def test_find_track_no_without_quotes(self): + self.sendRequest('find track "what"') + self.assertInResponse('OK') + def test_find_date(self): self.sendRequest('find "date" "2002-01-01"') self.assertInResponse('OK') @@ -593,6 +618,18 @@ class MusicDatabaseSearchTest(protocol.BaseTestCase): self.sendRequest('search "any" ""') self.assertInResponse('OK') + def test_search_track_no(self): + self.sendRequest('search "track" "anything"') + self.assertInResponse('OK') + + def test_search_track_no_without_quotes(self): + self.sendRequest('search track "anything"') + self.assertInResponse('OK') + + def test_search_track_no_without_filter_value(self): + self.sendRequest('search "track" ""') + self.assertInResponse('OK') + def test_search_date(self): self.sendRequest('search "date" "2002-01-01"') self.assertInResponse('OK') From 6f761a03f0d538c08751b084dbf753f31410250d Mon Sep 17 00:00:00 2001 From: Lasse Bigum Date: Sun, 13 Oct 2013 13:36:52 +0200 Subject: [PATCH 04/16] Return MpdArgError if count parsing fails --- mopidy/frontends/mpd/protocol/music_db.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/frontends/mpd/protocol/music_db.py b/mopidy/frontends/mpd/protocol/music_db.py index 3f4d460f..1bcbf84c 100644 --- a/mopidy/frontends/mpd/protocol/music_db.py +++ b/mopidy/frontends/mpd/protocol/music_db.py @@ -5,7 +5,7 @@ import itertools from mopidy.models import Track from mopidy.frontends.mpd import translator -from mopidy.frontends.mpd.exceptions import MpdNotImplemented +from mopidy.frontends.mpd.exceptions import MpdNotImplemented, MpdArgError from mopidy.frontends.mpd.protocol import handle_request, stored_playlists @@ -57,7 +57,7 @@ def count(context, mpd_query): try: query = translator.query_from_mpd_search_format(mpd_query) except ValueError: - return + raise MpdArgError('incorrect arguments', command='count') results = context.core.library.find_exact(**query).get() result_tracks = _get_tracks(results) return [('songs', len(result_tracks)), From 6028d6766d92ee13bbf6edae135655995863581a Mon Sep 17 00:00:00 2001 From: Lasse Bigum Date: Sun, 20 Oct 2013 23:35:31 +0200 Subject: [PATCH 05/16] Fix tabs->spaces and extra ')' --- mopidy/backends/local/library.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mopidy/backends/local/library.py b/mopidy/backends/local/library.py index bec5b0d0..1cb8534e 100644 --- a/mopidy/backends/local/library.py +++ b/mopidy/backends/local/library.py @@ -76,7 +76,7 @@ class LocalLibraryProvider(base.BaseLibraryProvider): artist_filter(t) or albumartist_filter(t) or track_no_filter(t) or - date_filter(t)) or + date_filter(t) or uri_filter(t)) if field == 'uri': @@ -135,7 +135,7 @@ class LocalLibraryProvider(base.BaseLibraryProvider): artist_filter(t) or albumartist_filter(t) or track_no_filter(t) or - date_filter(t)) or + date_filter(t) or uri_filter(t)) if field == 'uri': From 95046b4852f6630df6a98f415fc0a3f15171ebb3 Mon Sep 17 00:00:00 2001 From: Lasse Bigum Date: Tue, 22 Oct 2013 00:12:32 +0200 Subject: [PATCH 06/16] Fix small bug and fix test --- mopidy/frontends/mpd/translator.py | 2 +- tests/frontends/mpd/protocol/music_db_test.py | 24 +++++++++++-------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/mopidy/frontends/mpd/translator.py b/mopidy/frontends/mpd/translator.py index ec6dec4b..80bfb60a 100644 --- a/mopidy/frontends/mpd/translator.py +++ b/mopidy/frontends/mpd/translator.py @@ -202,7 +202,7 @@ def query_from_mpd_search_format(mpd_query): field = m.groupdict()['field'].lower() if field == 'title': field = 'track' - if field == 'track': + elif field == 'track': field = 'track_no' elif field in ('file', 'filename'): field = 'uri' diff --git a/tests/frontends/mpd/protocol/music_db_test.py b/tests/frontends/mpd/protocol/music_db_test.py index 203fc020..0d4dcaac 100644 --- a/tests/frontends/mpd/protocol/music_db_test.py +++ b/tests/frontends/mpd/protocol/music_db_test.py @@ -25,20 +25,24 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase): self.assertInResponse('OK') def test_count_correct_length(self): - self.backend.library.dummy_search_result = SearchResult( - tracks=[Track(name='A', date="2001", length=4000), - Track(name='B', date="2001", length=50000), - Track(name='C', date="2001", length=600000), - Track(name='D', date="2009", length=60000) - ]) - self.sendRequest('count "date" "2009"') + # Count the lone track + self.backend.library.dummy_find_exact_result = SearchResult( + tracks=[Track(uri='dummy:a', name="foo", date="2001", length=4000)] + ) + self.sendRequest('count "title" "foo"') self.assertInResponse('songs: 1') - self.assertInResponse('playtime: 4000') + self.assertInResponse('playtime: 4') self.assertInResponse('OK') + # Count multiple tracks + self.backend.library.dummy_find_exact_result = SearchResult( + tracks=[Track(uri='dummy:b', date="2001", length=50000), + Track(uri='dummy:c', date="2001", length=600000) + ] + ) self.sendRequest('count "date" "2001"') - self.assertInResponse('songs: 3') - self.assertInResponse('playtime: 654000') + self.assertInResponse('songs: 2') + self.assertInResponse('playtime: 650') self.assertInResponse('OK') def test_findadd(self): From 457c39fc78de912e7cdbd8a15f5c8d5b9fdc28c0 Mon Sep 17 00:00:00 2001 From: Lasse Bigum Date: Tue, 22 Oct 2013 23:40:38 +0200 Subject: [PATCH 07/16] Remove duplicate test group, merge it into existing --- tests/backends/local/library_test.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/tests/backends/local/library_test.py b/tests/backends/local/library_test.py index 7077e65c..9c3bc13c 100644 --- a/tests/backends/local/library_test.py +++ b/tests/backends/local/library_test.py @@ -108,22 +108,6 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.find_exact(uri=track_2_uri) self.assertEqual(list(result[0].tracks), self.tracks[1:2]) - def test_find_exact_any(self): - result = self.library.find_exact(any=['track1']) - self.assertEqual(list(result[0].tracks), self.tracks[:1]) - - result = self.library.find_exact(any=['track2']) - self.assertEqual(list(result[0].tracks), self.tracks[1:2]) - - result = self.library.find_exact(any=['2002']) - self.assertEqual(list(result[0].tracks), self.tracks[1:2]) - - result = self.library.find_exact(any=['album1']) - self.assertEqual(list(result[0].tracks), self.tracks[:1]) - - result = self.library.find_exact(any=['artist2']) - self.assertEqual(list(result[0].tracks), self.tracks[1:2]) - def test_find_exact_track(self): result = self.library.find_exact(track=['track1']) self.assertEqual(list(result[0].tracks), self.tracks[:1]) @@ -180,10 +164,16 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.find_exact(any=['artist1']) self.assertEqual(list(result[0].tracks), self.tracks[:1]) + result = self.library.find_exact(any=['artist2']) + self.assertEqual(list(result[0].tracks), self.tracks[1:2]) + # Matches on track result = self.library.find_exact(any=['track1']) self.assertEqual(list(result[0].tracks), self.tracks[:1]) + result = self.library.find_exact(any=['track2']) + self.assertEqual(list(result[0].tracks), self.tracks[1:2]) + # Matches on track album result = self.library.find_exact(any=['album1']) self.assertEqual(list(result[0].tracks), self.tracks[:1]) @@ -192,6 +182,10 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.find_exact(any=['artist3']) self.assertEqual(list(result[0].tracks), self.tracks[2:3]) + # Matches on track year + result = self.library.find_exact(any=['2002']) + self.assertEqual(list(result[0].tracks), self.tracks[1:2]) + # Matches on URI result = self.library.find_exact(any=['local:track:path1']) self.assertEqual(list(result[0].tracks), self.tracks[:1]) From d5cb4282d97068845fda65940bf09a84c7b100f4 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 27 Oct 2013 11:38:01 +0100 Subject: [PATCH 08/16] config: Add preprocessor for preserving comments when editing configs. Adds markers to configs files that ensures configparser won't mangle comments in the files. Will be combined with a postprocessor that undoes these changes. --- mopidy/config/__init__.py | 37 ++++++++++++++ tests/config/config_test.py | 96 +++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) diff --git a/mopidy/config/__init__.py b/mopidy/config/__init__.py index 0767b50c..8d68c7f3 100644 --- a/mopidy/config/__init__.py +++ b/mopidy/config/__init__.py @@ -2,8 +2,10 @@ from __future__ import unicode_literals import ConfigParser as configparser import io +import itertools import logging import os.path +import re from mopidy.config import keyring from mopidy.config.schemas import * # noqa @@ -145,6 +147,41 @@ def _format(config, comments, schemas, display): return b'\n'.join(output) +def _preprocess(string): + """Convert a raw config into a form that preserves comments etc.""" + results = ['[__COMMENTS__]'] + counter = itertools.count(0) + + section_re = re.compile(r'^(\[[^\]]+\])\s*(.+)$') + blank_line_re = re.compile(r'^\s*$') + comment_re = re.compile(r'^(#|;)') + inline_comment_re = re.compile(r' ;') + + def newlines(match): + return '__BLANK%d__ =' % next(counter) + + def comments(match): + if match.group(1) == '#': + return '__HASH%d__ =' % next(counter) + elif match.group(1) == ';': + return '__SEMICOLON%d__ =' % next(counter) + + def inlinecomments(match): + return '\n__INLINE%d__ =' % next(counter) + + def sections(match): + return '%s\n__SECTION%d__ = %s' % ( + match.group(1), next(counter), match.group(2)) + + for line in string.splitlines(): + line = blank_line_re.sub(newlines, line) + line = section_re.sub(sections, line) + line = comment_re.sub(comments, line) + line = inline_comment_re.sub(inlinecomments, line) + results.append(line) + return '\n'.join(results) + + class Proxy(collections.Mapping): def __init__(self, data): self._data = data diff --git a/tests/config/config_test.py b/tests/config/config_test.py index c40baa87..16769513 100644 --- a/tests/config/config_test.py +++ b/tests/config/config_test.py @@ -106,3 +106,99 @@ class ValidateTest(unittest.TestCase): self.assertEqual({'foo': {'bar': 'bad'}}, errors) # TODO: add more tests + + +INPUT_CONFIG = """# comments before first section should work + +[section] anything goes ; after the [] block it seems. +; this is a valid comment +this-should-equal-baz = baz ; as this is a comment +this-should-equal-everything = baz # as this is not a comment + +# this is also a comment ; and the next line should be a blank comment. +; +# foo # = should all be treated as a comment. +""" + +PROCESSED_CONFIG = """[__COMMENTS__] +__HASH0__ = comments before first section should work +__BLANK1__ = +[section] +__SECTION2__ = anything goes +__INLINE3__ = after the [] block it seems. +__SEMICOLON4__ = this is a valid comment +this-should-equal-baz = baz +__INLINE5__ = as this is a comment +this-should-equal-everything = baz # as this is not a comment +__BLANK6__ = +__HASH7__ = this is also a comment +__INLINE8__ = and the next line should be a blank comment. +__SEMICOLON9__ = +__HASH10__ = foo # = should all be treated as a comment.""" + + +class ProcessorTest(unittest.TestCase): + maxDiff = None # Show entire diff. + + def test_preprocessor_empty_config(self): + result = config._preprocess('') + self.assertEqual(result, '[__COMMENTS__]') + + def test_preprocessor_plain_section(self): + result = config._preprocess('[section]\nfoo = bar') + self.assertEqual(result, '[__COMMENTS__]\n' + '[section]\n' + 'foo = bar') + + def test_preprocessor_initial_comments(self): + result = config._preprocess('; foobar') + self.assertEqual(result, '[__COMMENTS__]\n' + '__SEMICOLON0__ = foobar') + + result = config._preprocess('# foobar') + self.assertEqual(result, '[__COMMENTS__]\n' + '__HASH0__ = foobar') + + result = config._preprocess('; foo\n# bar') + self.assertEqual(result, '[__COMMENTS__]\n' + '__SEMICOLON0__ = foo\n' + '__HASH1__ = bar') + + def test_preprocessor_initial_comment_inline_handling(self): + result = config._preprocess('; foo ; bar ; baz') + self.assertEqual(result, '[__COMMENTS__]\n' + '__SEMICOLON0__ = foo\n' + '__INLINE1__ = bar\n' + '__INLINE2__ = baz') + + def test_preprocessor_inline_semicolon_comment(self): + result = config._preprocess('[section]\nfoo = bar ; baz') + self.assertEqual(result, '[__COMMENTS__]\n' + '[section]\n' + 'foo = bar\n' + '__INLINE0__ = baz') + + def test_preprocessor_no_inline_hash_comment(self): + result = config._preprocess('[section]\nfoo = bar # baz') + self.assertEqual(result, '[__COMMENTS__]\n' + '[section]\n' + 'foo = bar # baz') + + def test_preprocessor_section_extra_text(self): + result = config._preprocess('[section] foobar') + self.assertEqual(result, '[__COMMENTS__]\n' + '[section]\n' + '__SECTION0__ = foobar') + + def test_preprocessor_section_extra_text_inline_semicolon(self): + result = config._preprocess('[section] foobar ; baz') + self.assertEqual(result, '[__COMMENTS__]\n' + '[section]\n' + '__SECTION0__ = foobar\n' + '__INLINE1__ = baz') + + def test_preprocessor_conversion(self): + """Tests all of the above cases at once.""" + result = config._preprocess(INPUT_CONFIG) + self.assertEqual(result, PROCESSED_CONFIG) + From 73f91710e14bc72c7de2f5ffc2b21042c43dc422 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 27 Oct 2013 12:30:02 +0100 Subject: [PATCH 09/16] config: Add postprocessor for converting config back. Idea forward from here is that once we have a config sub command that we expose a setting config values which will: 1. Run the preprocessor on the file to edit. 2. Load it into config parser. 3. Modify the value. 4. Write it to a io.ByteString 5. Run the postprocessor 6. Save the file with comments etc intact. --- mopidy/config/__init__.py | 16 ++++++- tests/config/config_test.py | 89 +++++++++++++++++++++++++++++++------ 2 files changed, 90 insertions(+), 15 deletions(-) diff --git a/mopidy/config/__init__.py b/mopidy/config/__init__.py index 8d68c7f3..6d66e253 100644 --- a/mopidy/config/__init__.py +++ b/mopidy/config/__init__.py @@ -147,7 +147,7 @@ def _format(config, comments, schemas, display): return b'\n'.join(output) -def _preprocess(string): +def _preprocess(config_string): """Convert a raw config into a form that preserves comments etc.""" results = ['[__COMMENTS__]'] counter = itertools.count(0) @@ -173,7 +173,7 @@ def _preprocess(string): return '%s\n__SECTION%d__ = %s' % ( match.group(1), next(counter), match.group(2)) - for line in string.splitlines(): + for line in config_string.splitlines(): line = blank_line_re.sub(newlines, line) line = section_re.sub(sections, line) line = comment_re.sub(comments, line) @@ -182,6 +182,18 @@ def _preprocess(string): return '\n'.join(results) +def _postprocess(config_string): + """Converts a preprocessed config back to original form.""" + flags = re.IGNORECASE | re.MULTILINE + result = re.sub(r'^\[__COMMENTS__\](\n|$)', '', config_string, flags=flags) + result = re.sub(r'\n__INLINE\d+__ =(.*)$', ' ;\g<1>', result, flags=flags) + result = re.sub(r'^__HASH\d+__ =(.*)$', '#\g<1>', result, flags=flags) + result = re.sub(r'^__SEMICOLON\d+__ =(.*)$', ';\g<1>', result, flags=flags) + result = re.sub(r'\n__SECTION\d+__ =(.*)$', '\g<1>', result, flags=flags) + result = re.sub(r'^__BLANK\d+__ =$', '', result, flags=flags) + return result + + class Proxy(collections.Mapping): def __init__(self, data): self._data = data diff --git a/tests/config/config_test.py b/tests/config/config_test.py index 16769513..fceb293d 100644 --- a/tests/config/config_test.py +++ b/tests/config/config_test.py @@ -117,8 +117,7 @@ this-should-equal-everything = baz # as this is not a comment # this is also a comment ; and the next line should be a blank comment. ; -# foo # = should all be treated as a comment. -""" +# foo # = should all be treated as a comment.""" PROCESSED_CONFIG = """[__COMMENTS__] __HASH0__ = comments before first section should work @@ -137,20 +136,20 @@ __SEMICOLON9__ = __HASH10__ = foo # = should all be treated as a comment.""" -class ProcessorTest(unittest.TestCase): - maxDiff = None # Show entire diff. +class PreProcessorTest(unittest.TestCase): + maxDiff = None # Show entire diff. - def test_preprocessor_empty_config(self): + def test_empty_config(self): result = config._preprocess('') self.assertEqual(result, '[__COMMENTS__]') - def test_preprocessor_plain_section(self): + def test_plain_section(self): result = config._preprocess('[section]\nfoo = bar') self.assertEqual(result, '[__COMMENTS__]\n' '[section]\n' 'foo = bar') - def test_preprocessor_initial_comments(self): + def test_initial_comments(self): result = config._preprocess('; foobar') self.assertEqual(result, '[__COMMENTS__]\n' '__SEMICOLON0__ = foobar') @@ -164,41 +163,105 @@ class ProcessorTest(unittest.TestCase): '__SEMICOLON0__ = foo\n' '__HASH1__ = bar') - def test_preprocessor_initial_comment_inline_handling(self): + def test_initial_comment_inline_handling(self): result = config._preprocess('; foo ; bar ; baz') self.assertEqual(result, '[__COMMENTS__]\n' '__SEMICOLON0__ = foo\n' '__INLINE1__ = bar\n' '__INLINE2__ = baz') - def test_preprocessor_inline_semicolon_comment(self): + def test_inline_semicolon_comment(self): result = config._preprocess('[section]\nfoo = bar ; baz') self.assertEqual(result, '[__COMMENTS__]\n' '[section]\n' 'foo = bar\n' '__INLINE0__ = baz') - def test_preprocessor_no_inline_hash_comment(self): + def test_no_inline_hash_comment(self): result = config._preprocess('[section]\nfoo = bar # baz') self.assertEqual(result, '[__COMMENTS__]\n' '[section]\n' 'foo = bar # baz') - def test_preprocessor_section_extra_text(self): + def test_section_extra_text(self): result = config._preprocess('[section] foobar') self.assertEqual(result, '[__COMMENTS__]\n' '[section]\n' '__SECTION0__ = foobar') - def test_preprocessor_section_extra_text_inline_semicolon(self): + def test_section_extra_text_inline_semicolon(self): result = config._preprocess('[section] foobar ; baz') self.assertEqual(result, '[__COMMENTS__]\n' '[section]\n' '__SECTION0__ = foobar\n' '__INLINE1__ = baz') - def test_preprocessor_conversion(self): + def test_conversion(self): """Tests all of the above cases at once.""" result = config._preprocess(INPUT_CONFIG) self.assertEqual(result, PROCESSED_CONFIG) + +class PostProcessorTest(unittest.TestCase): + maxDiff = None # Show entire diff. + + def test_empty_config(self): + result = config._postprocess('[__COMMENTS__]') + self.assertEqual(result, '') + + def test_plain_section(self): + result = config._postprocess('[__COMMENTS__]\n' + '[section]\n' + 'foo = bar') + self.assertEqual(result, '[section]\nfoo = bar') + + def test_initial_comments(self): + result = config._postprocess('[__COMMENTS__]\n' + '__SEMICOLON0__ = foobar') + self.assertEqual(result, '; foobar') + + result = config._postprocess('[__COMMENTS__]\n' + '__HASH0__ = foobar') + self.assertEqual(result, '# foobar') + + result = config._postprocess('[__COMMENTS__]\n' + '__SEMICOLON0__ = foo\n' + '__HASH1__ = bar') + self.assertEqual(result, '; foo\n# bar') + + def test_initial_comment_inline_handling(self): + result = config._postprocess('[__COMMENTS__]\n' + '__SEMICOLON0__ = foo\n' + '__INLINE1__ = bar\n' + '__INLINE2__ = baz') + self.assertEqual(result, '; foo ; bar ; baz') + + def test_inline_semicolon_comment(self): + result = config._postprocess('[__COMMENTS__]\n' + '[section]\n' + 'foo = bar\n' + '__INLINE0__ = baz') + self.assertEqual(result, '[section]\nfoo = bar ; baz') + + def test_no_inline_hash_comment(self): + result = config._preprocess('[section]\nfoo = bar # baz') + self.assertEqual(result, '[__COMMENTS__]\n' + '[section]\n' + 'foo = bar # baz') + + def test_section_extra_text(self): + result = config._postprocess('[__COMMENTS__]\n' + '[section]\n' + '__SECTION0__ = foobar') + self.assertEqual(result, '[section] foobar') + + def test_section_extra_text_inline_semicolon(self): + result = config._postprocess('[__COMMENTS__]\n' + '[section]\n' + '__SECTION0__ = foobar\n' + '__INLINE1__ = baz') + self.assertEqual(result, '[section] foobar ; baz') + + def test_conversion(self): + result = config._postprocess(PROCESSED_CONFIG) + self.assertEqual(result, INPUT_CONFIG) From ecc0bae3447aefb635533bcb9a3b861fbd39bb54 Mon Sep 17 00:00:00 2001 From: Thomas Adamcik Date: Sun, 27 Oct 2013 14:10:56 +0100 Subject: [PATCH 10/16] local: Delete uris in library refresh (fixes #500) Makes sure we remove uri's that can no longer be found in the tag cache. --- mopidy/backends/local/library.py | 5 +++++ tests/backends/local/library_test.py | 30 +++++++++++++++++++++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/mopidy/backends/local/library.py b/mopidy/backends/local/library.py index 0de63faf..f21ac81a 100644 --- a/mopidy/backends/local/library.py +++ b/mopidy/backends/local/library.py @@ -27,9 +27,14 @@ class LocalLibraryProvider(base.BaseLibraryProvider): self._media_dir, self._tag_cache_file) tracks = parse_mpd_tag_cache(self._tag_cache_file, self._media_dir) + uris_to_remove = set(self._uri_mapping) for track in tracks: self._uri_mapping[track.uri] = track + uris_to_remove.discard(track.uri) + + for uri in uris_to_remove: + del self._uri_mapping[uri] logger.info( 'Loaded %d local tracks from %s using %s', diff --git a/tests/backends/local/library_test.py b/tests/backends/local/library_test.py index 6b0cd6f6..09b3febb 100644 --- a/tests/backends/local/library_test.py +++ b/tests/backends/local/library_test.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +import tempfile import unittest import pykka @@ -11,6 +12,8 @@ from mopidy.models import Track, Album, Artist from tests import path_to_data_dir +# TODO: update tests to only use backend, not core. we need a seperate +# core test that does this integration test. class LocalLibraryProviderTest(unittest.TestCase): artists = [ Artist(name='artist1'), @@ -49,7 +52,6 @@ class LocalLibraryProviderTest(unittest.TestCase): } def setUp(self): - self.backend = actor.LocalBackend.start( config=self.config, audio=None).proxy() self.core = core.Core(backends=[self.backend]) @@ -65,9 +67,31 @@ class LocalLibraryProviderTest(unittest.TestCase): def test_refresh_uri(self): pass - @unittest.SkipTest def test_refresh_missing_uri(self): - pass + # Verifies that https://github.com/mopidy/mopidy/issues/500 + # has been fixed. + + tag_cache = tempfile.NamedTemporaryFile() + with open(self.config['local']['tag_cache_file']) as fh: + tag_cache.write(fh.read()) + tag_cache.flush() + + config = {'local': self.config['local'].copy()} + config['local']['tag_cache_file'] = tag_cache.name + backend = actor.LocalBackend(config=config, audio=None) + + # Sanity check that value is in tag cache + result = backend.library.lookup(self.tracks[0].uri) + self.assertEqual(result, self.tracks[0:1]) + + # Clear tag cache and refresh + tag_cache.seek(0) + tag_cache.truncate() + backend.library.refresh() + + # Now it should be gone. + result = backend.library.lookup(self.tracks[0].uri) + self.assertEqual(result, []) def test_lookup(self): tracks = self.library.lookup(self.tracks[0].uri) From 70801c2481680acc10ff603cd6a023b66f5db169 Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 27 Oct 2013 21:22:34 +0100 Subject: [PATCH 11/16] mpd: Fix import order --- mopidy/frontends/mpd/protocol/music_db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mopidy/frontends/mpd/protocol/music_db.py b/mopidy/frontends/mpd/protocol/music_db.py index 1bcbf84c..f0d8b11e 100644 --- a/mopidy/frontends/mpd/protocol/music_db.py +++ b/mopidy/frontends/mpd/protocol/music_db.py @@ -5,7 +5,7 @@ import itertools from mopidy.models import Track from mopidy.frontends.mpd import translator -from mopidy.frontends.mpd.exceptions import MpdNotImplemented, MpdArgError +from mopidy.frontends.mpd.exceptions import MpdArgError, MpdNotImplemented from mopidy.frontends.mpd.protocol import handle_request, stored_playlists From d13d9958b05f897035c98160b72a2fa832cc913b Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 27 Oct 2013 21:22:46 +0100 Subject: [PATCH 12/16] mpd: Formatting --- mopidy/frontends/mpd/protocol/music_db.py | 6 ++++-- tests/frontends/mpd/protocol/music_db_test.py | 13 +++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/mopidy/frontends/mpd/protocol/music_db.py b/mopidy/frontends/mpd/protocol/music_db.py index f0d8b11e..e56c123e 100644 --- a/mopidy/frontends/mpd/protocol/music_db.py +++ b/mopidy/frontends/mpd/protocol/music_db.py @@ -60,8 +60,10 @@ def count(context, mpd_query): raise MpdArgError('incorrect arguments', command='count') results = context.core.library.find_exact(**query).get() result_tracks = _get_tracks(results) - return [('songs', len(result_tracks)), - ('playtime', sum(track.length for track in result_tracks) / 1000)] + return [ + ('songs', len(result_tracks)), + ('playtime', sum(track.length for track in result_tracks) / 1000), + ] @handle_request(r'^find ' + QUERY_RE) diff --git a/tests/frontends/mpd/protocol/music_db_test.py b/tests/frontends/mpd/protocol/music_db_test.py index 0d4dcaac..ea1bd5b1 100644 --- a/tests/frontends/mpd/protocol/music_db_test.py +++ b/tests/frontends/mpd/protocol/music_db_test.py @@ -27,8 +27,9 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase): def test_count_correct_length(self): # Count the lone track self.backend.library.dummy_find_exact_result = SearchResult( - tracks=[Track(uri='dummy:a', name="foo", date="2001", length=4000)] - ) + tracks=[ + Track(uri='dummy:a', name="foo", date="2001", length=4000), + ]) self.sendRequest('count "title" "foo"') self.assertInResponse('songs: 1') self.assertInResponse('playtime: 4') @@ -36,10 +37,10 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase): # Count multiple tracks self.backend.library.dummy_find_exact_result = SearchResult( - tracks=[Track(uri='dummy:b', date="2001", length=50000), - Track(uri='dummy:c', date="2001", length=600000) - ] - ) + tracks=[ + Track(uri='dummy:b', date="2001", length=50000), + Track(uri='dummy:c', date="2001", length=600000), + ]) self.sendRequest('count "date" "2001"') self.assertInResponse('songs: 2') self.assertInResponse('playtime: 650') From c42dd1bf1d78abbeffaa15b5d4190855cdc05f3e Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 27 Oct 2013 21:23:45 +0100 Subject: [PATCH 13/16] mpd: Add 'find track ""' test to match search tests --- tests/frontends/mpd/protocol/music_db_test.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/frontends/mpd/protocol/music_db_test.py b/tests/frontends/mpd/protocol/music_db_test.py index ea1bd5b1..1b115bd6 100644 --- a/tests/frontends/mpd/protocol/music_db_test.py +++ b/tests/frontends/mpd/protocol/music_db_test.py @@ -258,11 +258,15 @@ class MusicDatabaseFindTest(protocol.BaseTestCase): self.assertInResponse('OK') def test_find_track_no(self): - self.sendRequest('find "track" "what"') + self.sendRequest('find "track" "10"') self.assertInResponse('OK') def test_find_track_no_without_quotes(self): - self.sendRequest('find track "what"') + self.sendRequest('find track "10"') + self.assertInResponse('OK') + + def test_find_track_no_without_filter_value(self): + self.sendRequest('find "track" ""') self.assertInResponse('OK') def test_find_date(self): @@ -624,11 +628,11 @@ class MusicDatabaseSearchTest(protocol.BaseTestCase): self.assertInResponse('OK') def test_search_track_no(self): - self.sendRequest('search "track" "anything"') + self.sendRequest('search "track" "10"') self.assertInResponse('OK') def test_search_track_no_without_quotes(self): - self.sendRequest('search track "anything"') + self.sendRequest('search track "10"') self.assertInResponse('OK') def test_search_track_no_without_filter_value(self): From 4a9552fc15da55b1912bf8c86923ac604ad78aed Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 27 Oct 2013 21:25:36 +0100 Subject: [PATCH 14/16] local: Bring the corner case close to the condition --- mopidy/backends/local/library.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mopidy/backends/local/library.py b/mopidy/backends/local/library.py index 0e4a29a2..8a0aac79 100644 --- a/mopidy/backends/local/library.py +++ b/mopidy/backends/local/library.py @@ -60,10 +60,10 @@ class LocalLibraryProvider(base.BaseLibraryProvider): values = [values] # FIXME this is bound to be slow for large libraries for value in values: - if field != 'track_no': - q = value.strip() - else: + if field == 'track_no': q = value + else: + q = value.strip() uri_filter = lambda t: q == t.uri track_filter = lambda t: q == t.name @@ -118,10 +118,10 @@ class LocalLibraryProvider(base.BaseLibraryProvider): values = [values] # FIXME this is bound to be slow for large libraries for value in values: - if field != 'track_no': - q = value.strip().lower() - else: + if field == 'track_no': q = value + else: + q = value.strip().lower() uri_filter = lambda t: q in t.uri.lower() track_filter = lambda t: q in t.name.lower() From f0aff669c6992d2870a383c239f93191af47d8ca Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 27 Oct 2013 21:26:03 +0100 Subject: [PATCH 15/16] local: Order search filters consistently --- mopidy/backends/local/library.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/mopidy/backends/local/library.py b/mopidy/backends/local/library.py index 8a0aac79..2ff0e6d1 100644 --- a/mopidy/backends/local/library.py +++ b/mopidy/backends/local/library.py @@ -76,13 +76,13 @@ class LocalLibraryProvider(base.BaseLibraryProvider): track_no_filter = lambda t: q == t.track_no date_filter = lambda t: q == t.date any_filter = lambda t: ( + uri_filter(t) or track_filter(t) or album_filter(t) or artist_filter(t) or albumartist_filter(t) or track_no_filter(t) or - date_filter(t) or - uri_filter(t)) + date_filter(t)) if field == 'uri': result_tracks = filter(uri_filter, result_tracks) @@ -94,10 +94,10 @@ class LocalLibraryProvider(base.BaseLibraryProvider): result_tracks = filter(artist_filter, result_tracks) elif field == 'albumartist': result_tracks = filter(albumartist_filter, result_tracks) - elif field == 'date': - result_tracks = filter(date_filter, result_tracks) elif field == 'track_no': result_tracks = filter(track_no_filter, result_tracks) + elif field == 'date': + result_tracks = filter(date_filter, result_tracks) elif field == 'any': result_tracks = filter(any_filter, result_tracks) else: @@ -135,13 +135,13 @@ class LocalLibraryProvider(base.BaseLibraryProvider): track_no_filter = lambda t: q == t.track_no date_filter = lambda t: t.date and t.date.startswith(q) any_filter = lambda t: ( + uri_filter(t) or track_filter(t) or album_filter(t) or artist_filter(t) or albumartist_filter(t) or track_no_filter(t) or - date_filter(t) or - uri_filter(t)) + date_filter(t)) if field == 'uri': result_tracks = filter(uri_filter, result_tracks) @@ -153,10 +153,10 @@ class LocalLibraryProvider(base.BaseLibraryProvider): result_tracks = filter(artist_filter, result_tracks) elif field == 'albumartist': result_tracks = filter(albumartist_filter, result_tracks) - elif field == 'date': - result_tracks = filter(date_filter, result_tracks) elif field == 'track_no': result_tracks = filter(track_no_filter, result_tracks) + elif field == 'date': + result_tracks = filter(date_filter, result_tracks) elif field == 'any': result_tracks = filter(any_filter, result_tracks) else: From be6e6c9a936ca4c1523d231311d1009d497a644c Mon Sep 17 00:00:00 2001 From: Stein Magnus Jodal Date: Sun, 27 Oct 2013 21:26:26 +0100 Subject: [PATCH 16/16] local: Readd lost test assertion --- tests/backends/local/library_test.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/backends/local/library_test.py b/tests/backends/local/library_test.py index f58ca769..1cb07451 100644 --- a/tests/backends/local/library_test.py +++ b/tests/backends/local/library_test.py @@ -238,6 +238,9 @@ class LocalLibraryProviderTest(unittest.TestCase): self.assertRaises(LookupError, test) def test_search_no_hits(self): + result = self.library.search(track=['unknown track']) + self.assertEqual(list(result[0].tracks), []) + result = self.library.search(artist=['unknown artist']) self.assertEqual(list(result[0].tracks), []) @@ -247,13 +250,13 @@ class LocalLibraryProviderTest(unittest.TestCase): result = self.library.search(track_no=[9]) self.assertEqual(list(result[0].tracks), []) - result = self.library.search(date=['unknown']) + result = self.library.search(date=['unknown date']) self.assertEqual(list(result[0].tracks), []) - result = self.library.search(uri=['unknown']) + result = self.library.search(uri=['unknown uri']) self.assertEqual(list(result[0].tracks), []) - result = self.library.search(any=['unknown']) + result = self.library.search(any=['unknown anything']) self.assertEqual(list(result[0].tracks), []) def test_search_uri(self):