Merge pull request #551 from jodal/feature/mpd-search-by-albumartist
Add search and find by albumartist to MPD frontend
This commit is contained in:
commit
b36bf007c0
@ -10,8 +10,8 @@ from mopidy.frontends.mpd.protocol import handle_request, stored_playlists
|
||||
|
||||
|
||||
QUERY_RE = (
|
||||
r'(?P<mpd_query>("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ile|[Ff]ilename|'
|
||||
r'[Tt]itle|[Tt]rack|[Aa]ny)"? "[^"]*"\s?)+)$')
|
||||
r'(?P<mpd_query>("?([Aa]lbum|[Aa]rtist|[Aa]lbumartist|[Dd]ate|[Ff]ile|'
|
||||
r'[Ff]ilename|[Tt]itle|[Tt]rack|[Aa]ny)"? "[^"]*"\s?)+)$')
|
||||
|
||||
|
||||
def _get_field(field, search_results):
|
||||
@ -100,7 +100,7 @@ def find(context, mpd_query):
|
||||
return
|
||||
results = context.core.library.find_exact(**query).get()
|
||||
result_tracks = []
|
||||
if 'artist' not in query:
|
||||
if 'artist' not in query and 'albumartist' not in query:
|
||||
result_tracks += [_artist_as_track(a) for a in _get_artists(results)]
|
||||
if 'album' not in query:
|
||||
result_tracks += [_album_as_track(a) for a in _get_albums(results)]
|
||||
|
||||
@ -179,6 +179,48 @@ def query_from_mpd_list_format(field, mpd_query):
|
||||
raise MpdArgError('not able to parse args', command='list')
|
||||
|
||||
|
||||
# XXX The regexps below should be refactored to reuse common patterns here
|
||||
# and in mopidy.frontends.mpd.protocol.music_db.QUERY_RE.
|
||||
|
||||
MPD_SEARCH_QUERY_RE = re.compile(r"""
|
||||
\b # Only begin matching at word bundaries
|
||||
"? # Optional quote around the field type
|
||||
(?: # A non-capturing group for the field type
|
||||
[Aa]lbum
|
||||
| [Aa]rtist
|
||||
| [Aa]lbumartist
|
||||
| [Dd]ate
|
||||
| [Ff]ile
|
||||
| [Ff]ilename
|
||||
| [Tt]itle
|
||||
| [Tt]rack
|
||||
| [Aa]ny
|
||||
)
|
||||
"? # End of optional quote around the field type
|
||||
\s # A single space
|
||||
"[^"]+" # Matching a quoted search string
|
||||
""", re.VERBOSE)
|
||||
|
||||
MPD_SEARCH_QUERY_PART_RE = re.compile(r"""
|
||||
\b # Only begin matching at word bundaries
|
||||
"? # Optional quote around the field type
|
||||
(?P<field>( # A capturing group for the field type
|
||||
[Aa]lbum
|
||||
| [Aa]rtist
|
||||
| [Aa]lbumartist
|
||||
| [Dd]ate
|
||||
| [Ff]ile
|
||||
| [Ff]ilename
|
||||
| [Tt]itle
|
||||
| [Tt]rack
|
||||
| [Aa]ny
|
||||
))
|
||||
"? # End of optional quote around the field type
|
||||
\s # A single space
|
||||
"(?P<what>[^"]+)" # Capturing a quoted search string
|
||||
""", re.VERBOSE)
|
||||
|
||||
|
||||
def query_from_mpd_search_format(mpd_query):
|
||||
"""
|
||||
Parses an MPD ``search`` or ``find`` query and converts it to the Mopidy
|
||||
@ -187,18 +229,10 @@ 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|[Dd]ate|[Ff]ile|[Ff]ilename|'
|
||||
r'[Tt]itle|[Aa]ny)"? "[^"]+"')
|
||||
query_parts = re.findall(query_pattern, mpd_query)
|
||||
query_part_pattern = (
|
||||
r'"?(?P<field>([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ile|[Ff]ilename|'
|
||||
r'[Tt]itle|[Tt]rack|[Aa]ny))"? "(?P<what>[^"]+)"')
|
||||
query_parts = MPD_SEARCH_QUERY_RE.findall(mpd_query)
|
||||
query = {}
|
||||
for query_part in query_parts:
|
||||
m = re.match(query_part_pattern, query_part)
|
||||
m = MPD_SEARCH_QUERY_PART_RE.match(query_part)
|
||||
field = m.groupdict()['field'].lower()
|
||||
if field == 'title':
|
||||
field = 'track'
|
||||
@ -206,7 +240,6 @@ def query_from_mpd_search_format(mpd_query):
|
||||
field = 'track_no'
|
||||
elif field in ('file', 'filename'):
|
||||
field = 'uri'
|
||||
field = str(field) # Needed for kwargs keys on OS X and Windows
|
||||
what = m.groupdict()['what']
|
||||
if not what:
|
||||
raise ValueError
|
||||
|
||||
@ -197,6 +197,26 @@ class MusicDatabaseFindTest(protocol.BaseTestCase):
|
||||
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_find_albumartist_does_not_include_fake_artist_tracks(self):
|
||||
self.backend.library.dummy_find_exact_result = SearchResult(
|
||||
albums=[Album(uri='dummy:album:a', name='A', date='2001')],
|
||||
artists=[Artist(uri='dummy:artist:b', name='B')],
|
||||
tracks=[Track(uri='dummy:track:c', name='C')])
|
||||
|
||||
self.sendRequest('find "albumartist" "foo"')
|
||||
|
||||
self.assertNotInResponse('file: dummy:artist:b')
|
||||
self.assertNotInResponse('Title: Artist: B')
|
||||
|
||||
self.assertInResponse('file: dummy:album:a')
|
||||
self.assertInResponse('Title: Album: A')
|
||||
self.assertInResponse('Date: 2001')
|
||||
|
||||
self.assertInResponse('file: dummy:track:c')
|
||||
self.assertInResponse('Title: C')
|
||||
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_find_artist_and_album_does_not_include_fake_tracks(self):
|
||||
self.backend.library.dummy_find_exact_result = SearchResult(
|
||||
albums=[Album(uri='dummy:album:a', name='A', date='2001')],
|
||||
@ -233,6 +253,14 @@ class MusicDatabaseFindTest(protocol.BaseTestCase):
|
||||
self.sendRequest('find artist "what"')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_find_albumartist(self):
|
||||
self.sendRequest('find "albumartist" "what"')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_find_albumartist_without_quotes(self):
|
||||
self.sendRequest('find albumartist "what"')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_find_filename(self):
|
||||
self.sendRequest('find "filename" "afilename"')
|
||||
self.assertInResponse('OK')
|
||||
@ -579,6 +607,18 @@ class MusicDatabaseSearchTest(protocol.BaseTestCase):
|
||||
self.sendRequest('search "artist" ""')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_search_albumartist(self):
|
||||
self.sendRequest('search "albumartist" "analbumartist"')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_search_albumartist_without_quotes(self):
|
||||
self.sendRequest('search albumartist "analbumartist"')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_search_albumartist_without_filter_value(self):
|
||||
self.sendRequest('search "albumartist" ""')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_search_filename(self):
|
||||
self.sendRequest('search "filename" "afilename"')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user