diff --git a/docs/changes.rst b/docs/changes.rst index 3caa02df..8c2087e5 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -24,6 +24,12 @@ v0.11.0 (in development) add "spotify:artist:5TgQ66WuWkoQ2xYxaSTnVP" add "spotify:user:p3.no:playlist:0XX6tamRiqEgh3t6FPFEkw" +**Local backend** + +- Load track dates from tag cache. + +- Add support for searching by track date. + **MPD frontend** - Add :attr:`mopidy.settings.MPD_SERVER_CONNECTION_TIMEOUT` setting which @@ -47,6 +53,8 @@ v0.11.0 (in development) - Add empty stubs for channel commands for client to client communication. +- Add support for search by date. + **Internal changes** *Models:* diff --git a/mopidy/backends/local/library.py b/mopidy/backends/local/library.py index e0e6f423..143c6d84 100644 --- a/mopidy/backends/local/library.py +++ b/mopidy/backends/local/library.py @@ -46,23 +46,26 @@ class LocalLibraryProvider(base.BaseLibraryProvider): for value in values: q = value.strip() + uri_filter = lambda t: q == t.uri 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) - uri_filter = lambda t: q == t.uri + date_filter = lambda t: q == t.date any_filter = lambda t: ( track_filter(t) or album_filter(t) or artist_filter(t) or uri_filter(t)) - if field == 'track': + if field == 'uri': + result_tracks = filter(uri_filter, result_tracks) + elif field == 'track': result_tracks = filter(track_filter, result_tracks) elif field == 'album': result_tracks = filter(album_filter, result_tracks) elif field == 'artist': result_tracks = filter(artist_filter, result_tracks) - elif field == 'uri': - result_tracks = filter(uri_filter, result_tracks) + elif field == 'date': + result_tracks = filter(date_filter, result_tracks) elif field == 'any': result_tracks = filter(any_filter, result_tracks) else: @@ -80,23 +83,26 @@ class LocalLibraryProvider(base.BaseLibraryProvider): for value in values: q = value.strip().lower() + uri_filter = lambda t: q in t.uri.lower() track_filter = lambda t: q in t.name.lower() album_filter = lambda t: q in getattr( t, 'album', Album()).name.lower() artist_filter = lambda t: filter( lambda a: q in a.name.lower(), t.artists) - uri_filter = lambda t: q in t.uri.lower() + 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 uri_filter(t) - if field == 'track': + if field == 'uri': + result_tracks = filter(uri_filter, result_tracks) + elif field == 'track': result_tracks = filter(track_filter, result_tracks) elif field == 'album': result_tracks = filter(album_filter, result_tracks) elif field == 'artist': result_tracks = filter(artist_filter, result_tracks) - elif field == 'uri': - result_tracks = filter(uri_filter, result_tracks) + elif field == 'date': + result_tracks = filter(date_filter, result_tracks) elif field == 'any': result_tracks = filter(any_filter, result_tracks) else: diff --git a/mopidy/backends/local/translator.py b/mopidy/backends/local/translator.py index ff58a16e..390fd92a 100644 --- a/mopidy/backends/local/translator.py +++ b/mopidy/backends/local/translator.py @@ -123,6 +123,9 @@ def _convert_mpd_data(data, tracks, music_dir): if 'title' in data: track_kwargs['name'] = data['title'] + if 'date' in data: + track_kwargs['date'] = data['date'] + if 'musicbrainz_trackid' in data: track_kwargs['musicbrainz_id'] = data['musicbrainz_trackid'] diff --git a/mopidy/frontends/mpd/translator.py b/mopidy/frontends/mpd/translator.py index ef7c8a1c..e26d7dce 100644 --- a/mopidy/frontends/mpd/translator.py +++ b/mopidy/frontends/mpd/translator.py @@ -187,12 +187,15 @@ def query_from_mpd_search_format(mpd_query): :param mpd_query: the MPD search query :type mpd_query: string """ + # XXX The regexps below should be refactored to reuse common patterns here + # and in mopidy.frontends.mpd.protocol.music_db. query_pattern = ( - r'"?(?:[Aa]lbum|[Aa]rtist|[Ff]ile[name]*|[Tt]itle|[Aa]ny)"? "[^"]+"') + r'"?(?:[Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ile|[Ff]ilename|' + r'[Tt]itle|[Aa]ny)"? "[^"]+"') query_parts = re.findall(query_pattern, mpd_query) query_part_pattern = ( - r'"?(?P([Aa]lbum|[Aa]rtist|[Ff]ile[name]*|[Tt]itle|[Aa]ny))"? ' - r'"(?P[^"]+)"') + r'"?(?P([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ile|[Ff]ilename|' + r'[Tt]itle|[Aa]ny))"? "(?P[^"]+)"') query = {} for query_part in query_parts: m = re.match(query_part_pattern, query_part) diff --git a/tests/backends/base/library.py b/tests/backends/base/library.py index 4e9232e5..57aec3c6 100644 --- a/tests/backends/base/library.py +++ b/tests/backends/base/library.py @@ -16,11 +16,12 @@ class LibraryControllerTest(object): Album()] tracks = [ Track( - name='track1', length=4000, artists=artists[:1], - album=albums[0], uri='file://' + path_to_data_dir('uri1')), + uri='file://' + path_to_data_dir('uri1'), name='track1', + artists=artists[:1], album=albums[0], date='2001-02-03', + length=4000), Track( - name='track2', length=4000, artists=artists[1:2], - album=albums[1], uri='file://' + path_to_data_dir('uri2')), + uri='file://' + path_to_data_dir('uri2'), name='track2', + artists=artists[1:2], album=albums[1], date='2002', length=4000), Track()] def setUp(self): @@ -60,11 +61,13 @@ class LibraryControllerTest(object): result = self.library.find_exact(album=['unknown artist']) self.assertEqual(result, []) - def test_find_exact_artist(self): - result = self.library.find_exact(artist=['artist1']) + def test_find_exact_uri(self): + track_1_uri = 'file://' + path_to_data_dir('uri1') + result = self.library.find_exact(uri=track_1_uri) self.assertEqual(result, self.tracks[:1]) - result = self.library.find_exact(artist=['artist2']) + track_2_uri = 'file://' + path_to_data_dir('uri2') + result = self.library.find_exact(uri=track_2_uri) self.assertEqual(result, self.tracks[1:2]) def test_find_exact_track(self): @@ -74,6 +77,13 @@ class LibraryControllerTest(object): result = self.library.find_exact(track=['track2']) self.assertEqual(result, self.tracks[1:2]) + def test_find_exact_artist(self): + result = self.library.find_exact(artist=['artist1']) + self.assertEqual(result, self.tracks[:1]) + + result = self.library.find_exact(artist=['artist2']) + self.assertEqual(result, self.tracks[1:2]) + def test_find_exact_album(self): result = self.library.find_exact(album=['album1']) self.assertEqual(result, self.tracks[:1]) @@ -81,13 +91,14 @@ class LibraryControllerTest(object): result = self.library.find_exact(album=['album2']) self.assertEqual(result, self.tracks[1:2]) - def test_find_exact_uri(self): - track_1_uri = 'file://' + path_to_data_dir('uri1') - result = self.library.find_exact(uri=track_1_uri) + def test_find_exact_date(self): + result = self.library.find_exact(date=['2001']) + self.assertEqual(result, []) + + result = self.library.find_exact(date=['2001-02-03']) self.assertEqual(result, self.tracks[:1]) - track_2_uri = 'file://' + path_to_data_dir('uri2') - result = self.library.find_exact(uri=track_2_uri) + result = self.library.find_exact(date=['2002']) self.assertEqual(result, self.tracks[1:2]) def test_find_exact_wrong_type(self): @@ -120,11 +131,11 @@ class LibraryControllerTest(object): result = self.library.search(any=['unknown']) self.assertEqual(result, []) - def test_search_artist(self): - result = self.library.search(artist=['Tist1']) + def test_search_uri(self): + result = self.library.search(uri=['RI1']) self.assertEqual(result, self.tracks[:1]) - result = self.library.search(artist=['Tist2']) + result = self.library.search(uri=['RI2']) self.assertEqual(result, self.tracks[1:2]) def test_search_track(self): @@ -134,6 +145,13 @@ class LibraryControllerTest(object): result = self.library.search(track=['Rack2']) self.assertEqual(result, self.tracks[1:2]) + def test_search_artist(self): + result = self.library.search(artist=['Tist1']) + self.assertEqual(result, self.tracks[:1]) + + result = self.library.search(artist=['Tist2']) + self.assertEqual(result, self.tracks[1:2]) + def test_search_album(self): result = self.library.search(album=['Bum1']) self.assertEqual(result, self.tracks[:1]) @@ -141,11 +159,17 @@ class LibraryControllerTest(object): result = self.library.search(album=['Bum2']) self.assertEqual(result, self.tracks[1:2]) - def test_search_uri(self): - result = self.library.search(uri=['RI1']) + def test_search_date(self): + result = self.library.search(date=['2001']) self.assertEqual(result, self.tracks[:1]) - result = self.library.search(uri=['RI2']) + result = self.library.search(date=['2001-02-03']) + self.assertEqual(result, self.tracks[:1]) + + result = self.library.search(date=['2001-02-04']) + self.assertEqual(result, []) + + result = self.library.search(date=['2002']) self.assertEqual(result, self.tracks[1:2]) def test_search_any(self): diff --git a/tests/backends/local/translator_test.py b/tests/backends/local/translator_test.py index 90ee849d..61a86672 100644 --- a/tests/backends/local/translator_test.py +++ b/tests/backends/local/translator_test.py @@ -99,8 +99,8 @@ expected_tracks = [] def generate_track(path, ident): uri = path_to_uri(path_to_data_dir(path)) track = Track( - name='trackname', artists=expected_artists, track_no=1, - album=expected_albums[0], length=4000, uri=uri) + uri=uri, name='trackname', artists=expected_artists, + album=expected_albums[0], track_no=1, date='2006', length=4000) expected_tracks.append(track) @@ -126,8 +126,8 @@ class MPDTagCacheToTracksTest(unittest.TestCase): path_to_data_dir('simple_tag_cache'), path_to_data_dir('')) uri = path_to_uri(path_to_data_dir('song1.mp3')) track = Track( - name='trackname', artists=expected_artists, track_no=1, - album=expected_albums[0], length=4000, uri=uri) + uri=uri, name='trackname', artists=expected_artists, track_no=1, + album=expected_albums[0], date='2006', length=4000) self.assertEqual(set([track]), tracks) def test_advanced_cache(self): @@ -182,6 +182,6 @@ class MPDTagCacheToTracksTest(unittest.TestCase): artist = Artist(name='albumartistname') album = expected_albums[0].copy(artists=[artist]) track = Track( - name='trackname', artists=expected_artists, track_no=1, - album=album, length=4000, uri=uri) + uri=uri, name='trackname', artists=expected_artists, track_no=1, + album=album, date='2006', length=4000) self.assertEqual(track, list(tracks)[0]) diff --git a/tests/data/library_tag_cache b/tests/data/library_tag_cache index e090fcbd..50771a0a 100644 --- a/tests/data/library_tag_cache +++ b/tests/data/library_tag_cache @@ -8,12 +8,14 @@ file: /uri1 Artist: artist1 Title: track1 Album: album1 +Date: 2001-02-03 Time: 4 key: uri2 file: /uri2 Artist: artist2 Title: track2 Album: album2 +Date: 2002 Time: 4 key: uri3 file: /uri3 diff --git a/tests/frontends/mpd/translator_test.py b/tests/frontends/mpd/translator_test.py index aa3b77bb..088ae137 100644 --- a/tests/frontends/mpd/translator_test.py +++ b/tests/frontends/mpd/translator_test.py @@ -121,6 +121,20 @@ class PlaylistMpdFormatTest(unittest.TestCase): self.assertEqual(dict(result[0])['Track'], 2) +class QueryFromMpdSearchFormatTest(unittest.TestCase): + def test_dates_are_extracted(self): + result = translator.query_from_mpd_search_format( + 'Date "1974-01-02" Date "1975"') + self.assertEqual(result['date'][0], '1974-01-02') + self.assertEqual(result['date'][1], '1975') + + # TODO Test more mappings + + +class QueryFromMpdListFormatTest(unittest.TestCase): + pass # TODO + + class TracksToTagCacheFormatTest(unittest.TestCase): def setUp(self): settings.LOCAL_MUSIC_PATH = '/dir/subdir'