Merge pull request #584 from jodal/feature/mpd-regexp-improvements

MPD regexp improvements
This commit is contained in:
Thomas Adamcik 2013-11-19 00:24:14 -08:00
commit 384e5b29b6
16 changed files with 248 additions and 226 deletions

View File

@ -44,10 +44,15 @@ def handle_request(pattern, auth_required=True):
For example, if the command is ``do that thing`` the ``what`` argument will
be ``this thing``::
@handle_request('^do (?P<what>.+)$')
@handle_request('do\ (?P<what>.+)$')
def do(what):
...
Note that the patterns are compiled with the :attr:`re.VERBOSE` flag. Thus,
you must escape any space characters you want to match, but you're also
free to add non-escaped whitespace to format the pattern for easier
reading.
:param pattern: regexp pattern for matching commands
:type pattern: string
"""
@ -56,7 +61,7 @@ def handle_request(pattern, auth_required=True):
if match is not None:
mpd_commands.add(
MpdCommand(name=match.group(), auth_required=auth_required))
compiled_pattern = re.compile(pattern, flags=re.UNICODE)
compiled_pattern = re.compile(pattern, flags=(re.UNICODE | re.VERBOSE))
if compiled_pattern in request_handlers:
raise ValueError('Tried to redefine handler for %s with %s' % (
pattern, func))

View File

@ -4,7 +4,7 @@ from mopidy.frontends.mpd.exceptions import MpdNoExistError
from mopidy.frontends.mpd.protocol import handle_request
@handle_request(r'^disableoutput "(?P<outputid>\d+)"$')
@handle_request(r'disableoutput\ "(?P<outputid>\d+)"$')
def disableoutput(context, outputid):
"""
*musicpd.org, audio output section:*
@ -19,7 +19,7 @@ def disableoutput(context, outputid):
raise MpdNoExistError('No such audio output', command='disableoutput')
@handle_request(r'^enableoutput "(?P<outputid>\d+)"$')
@handle_request(r'enableoutput\ "(?P<outputid>\d+)"$')
def enableoutput(context, outputid):
"""
*musicpd.org, audio output section:*
@ -34,7 +34,7 @@ def enableoutput(context, outputid):
raise MpdNoExistError('No such audio output', command='enableoutput')
@handle_request(r'^outputs$')
@handle_request(r'outputs$')
def outputs(context):
"""
*musicpd.org, audio output section:*

View File

@ -4,7 +4,7 @@ from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
@handle_request(r'^subscribe "(?P<channel>[A-Za-z0-9:._-]+)"$')
@handle_request(r'subscribe\ "(?P<channel>[A-Za-z0-9:._-]+)"$')
def subscribe(context, channel):
"""
*musicpd.org, client to client section:*
@ -18,7 +18,7 @@ def subscribe(context, channel):
raise MpdNotImplemented # TODO
@handle_request(r'^unsubscribe "(?P<channel>[A-Za-z0-9:._-]+)"$')
@handle_request(r'unsubscribe\ "(?P<channel>[A-Za-z0-9:._-]+)"$')
def unsubscribe(context, channel):
"""
*musicpd.org, client to client section:*
@ -30,7 +30,7 @@ def unsubscribe(context, channel):
raise MpdNotImplemented # TODO
@handle_request(r'^channels$')
@handle_request(r'channels$')
def channels(context):
"""
*musicpd.org, client to client section:*
@ -43,7 +43,7 @@ def channels(context):
raise MpdNotImplemented # TODO
@handle_request(r'^readmessages$')
@handle_request(r'readmessages$')
def readmessages(context):
"""
*musicpd.org, client to client section:*
@ -57,7 +57,7 @@ def readmessages(context):
@handle_request(
r'^sendmessage "(?P<channel>[A-Za-z0-9:._-]+)" "(?P<text>[^"]*)"$')
r'sendmessage\ "(?P<channel>[A-Za-z0-9:._-]+)"\ "(?P<text>[^"]*)"$')
def sendmessage(context, channel, text):
"""
*musicpd.org, client to client section:*

View File

@ -4,7 +4,7 @@ from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import MpdUnknownCommand
@handle_request(r'^command_list_begin$')
@handle_request(r'command_list_begin$')
def command_list_begin(context):
"""
*musicpd.org, command list section:*
@ -26,7 +26,7 @@ def command_list_begin(context):
context.dispatcher.command_list = []
@handle_request(r'^command_list_end$')
@handle_request(r'command_list_end$')
def command_list_end(context):
"""See :meth:`command_list_begin()`."""
if not context.dispatcher.command_list_receiving:
@ -49,7 +49,7 @@ def command_list_end(context):
return command_list_response
@handle_request(r'^command_list_ok_begin$')
@handle_request(r'command_list_ok_begin$')
def command_list_ok_begin(context):
"""See :meth:`command_list_begin()`."""
context.dispatcher.command_list_receiving = True

View File

@ -5,7 +5,7 @@ from mopidy.frontends.mpd.exceptions import (
MpdPasswordError, MpdPermissionError)
@handle_request(r'^close$', auth_required=False)
@handle_request(r'close$', auth_required=False)
def close(context):
"""
*musicpd.org, connection section:*
@ -17,7 +17,7 @@ def close(context):
context.session.close()
@handle_request(r'^kill$')
@handle_request(r'kill$')
def kill(context):
"""
*musicpd.org, connection section:*
@ -29,7 +29,7 @@ def kill(context):
raise MpdPermissionError(command='kill')
@handle_request(r'^password "(?P<password>[^"]+)"$', auth_required=False)
@handle_request(r'password\ "(?P<password>[^"]+)"$', auth_required=False)
def password_(context, password):
"""
*musicpd.org, connection section:*
@ -45,7 +45,7 @@ def password_(context, password):
raise MpdPasswordError('incorrect password', command='password')
@handle_request(r'^ping$', auth_required=False)
@handle_request(r'ping$', auth_required=False)
def ping(context):
"""
*musicpd.org, connection section:*

View File

@ -6,7 +6,7 @@ from mopidy.frontends.mpd.exceptions import (
from mopidy.frontends.mpd.protocol import handle_request
@handle_request(r'^add "(?P<uri>[^"]*)"$')
@handle_request(r'add\ "(?P<uri>[^"]*)"$')
def add(context, uri):
"""
*musicpd.org, current playlist section:*
@ -27,7 +27,7 @@ def add(context, uri):
raise MpdNoExistError('directory or file not found', command='add')
@handle_request(r'^addid "(?P<uri>[^"]*)"( "(?P<songpos>\d+)")*$')
@handle_request(r'addid\ "(?P<uri>[^"]*)"(\ "(?P<songpos>\d+)")*$')
def addid(context, uri, songpos=None):
"""
*musicpd.org, current playlist section:*
@ -58,7 +58,7 @@ def addid(context, uri, songpos=None):
return ('Id', tl_tracks[0].tlid)
@handle_request(r'^delete "(?P<start>\d+):(?P<end>\d+)*"$')
@handle_request(r'delete\ "(?P<start>\d+):(?P<end>\d+)*"$')
def delete_range(context, start, end=None):
"""
*musicpd.org, current playlist section:*
@ -79,7 +79,7 @@ def delete_range(context, start, end=None):
context.core.tracklist.remove(tlid=[tlid])
@handle_request(r'^delete "(?P<songpos>\d+)"$')
@handle_request(r'delete\ "(?P<songpos>\d+)"$')
def delete_songpos(context, songpos):
"""See :meth:`delete_range`"""
try:
@ -91,7 +91,7 @@ def delete_songpos(context, songpos):
raise MpdArgError('Bad song index', command='delete')
@handle_request(r'^deleteid "(?P<tlid>\d+)"$')
@handle_request(r'deleteid\ "(?P<tlid>\d+)"$')
def deleteid(context, tlid):
"""
*musicpd.org, current playlist section:*
@ -106,7 +106,7 @@ def deleteid(context, tlid):
raise MpdNoExistError('No such song', command='deleteid')
@handle_request(r'^clear$')
@handle_request(r'clear$')
def clear(context):
"""
*musicpd.org, current playlist section:*
@ -118,7 +118,7 @@ def clear(context):
context.core.tracklist.clear()
@handle_request(r'^move "(?P<start>\d+):(?P<end>\d+)*" "(?P<to>\d+)"$')
@handle_request(r'move\ "(?P<start>\d+):(?P<end>\d+)*"\ "(?P<to>\d+)"$')
def move_range(context, start, to, end=None):
"""
*musicpd.org, current playlist section:*
@ -136,7 +136,7 @@ def move_range(context, start, to, end=None):
context.core.tracklist.move(start, end, to)
@handle_request(r'^move "(?P<songpos>\d+)" "(?P<to>\d+)"$')
@handle_request(r'move\ "(?P<songpos>\d+)"\ "(?P<to>\d+)"$')
def move_songpos(context, songpos, to):
"""See :meth:`move_range`."""
songpos = int(songpos)
@ -144,7 +144,7 @@ def move_songpos(context, songpos, to):
context.core.tracklist.move(songpos, songpos + 1, to)
@handle_request(r'^moveid "(?P<tlid>\d+)" "(?P<to>\d+)"$')
@handle_request(r'moveid\ "(?P<tlid>\d+)"\ "(?P<to>\d+)"$')
def moveid(context, tlid, to):
"""
*musicpd.org, current playlist section:*
@ -164,7 +164,7 @@ def moveid(context, tlid, to):
context.core.tracklist.move(position, position + 1, to)
@handle_request(r'^playlist$')
@handle_request(r'playlist$')
def playlist(context):
"""
*musicpd.org, current playlist section:*
@ -180,8 +180,7 @@ def playlist(context):
return playlistinfo(context)
@handle_request(r'^playlistfind (?P<tag>[^"]+) "(?P<needle>[^"]+)"$')
@handle_request(r'^playlistfind "(?P<tag>[^"]+)" "(?P<needle>[^"]+)"$')
@handle_request(r'playlistfind\ ("?)(?P<tag>[^"]+)\1\ "(?P<needle>[^"]+)"$')
def playlistfind(context, tag, needle):
"""
*musicpd.org, current playlist section:*
@ -203,7 +202,8 @@ def playlistfind(context, tag, needle):
raise MpdNotImplemented # TODO
@handle_request(r'^playlistid( "(?P<tlid>\d+)")*$')
@handle_request(r'playlistid$')
@handle_request(r'playlistid\ "(?P<tlid>\d+)"$')
def playlistid(context, tlid=None):
"""
*musicpd.org, current playlist section:*
@ -225,9 +225,9 @@ def playlistid(context, tlid=None):
context.core.tracklist.tl_tracks.get())
@handle_request(r'^playlistinfo$')
@handle_request(r'^playlistinfo "(?P<songpos>-?\d+)"$')
@handle_request(r'^playlistinfo "(?P<start>\d+):(?P<end>\d+)*"$')
@handle_request(r'playlistinfo$')
@handle_request(r'playlistinfo\ "(?P<songpos>-?\d+)"$')
@handle_request(r'playlistinfo\ "(?P<start>\d+):(?P<end>\d+)*"$')
def playlistinfo(context, songpos=None, start=None, end=None):
"""
*musicpd.org, current playlist section:*
@ -263,8 +263,7 @@ def playlistinfo(context, songpos=None, start=None, end=None):
return translator.tracks_to_mpd_format(tl_tracks, start, end)
@handle_request(r'^playlistsearch "(?P<tag>[^"]+)" "(?P<needle>[^"]+)"$')
@handle_request(r'^playlistsearch (?P<tag>\w+) "(?P<needle>[^"]+)"$')
@handle_request(r'playlistsearch\ ("?)(?P<tag>\w+)\1\ "(?P<needle>[^"]+)"$')
def playlistsearch(context, tag, needle):
"""
*musicpd.org, current playlist section:*
@ -282,8 +281,7 @@ def playlistsearch(context, tag, needle):
raise MpdNotImplemented # TODO
@handle_request(r'^plchanges (?P<version>-?\d+)$')
@handle_request(r'^plchanges "(?P<version>-?\d+)"$')
@handle_request(r'plchanges\ ("?)(?P<version>-?\d+)\1$')
def plchanges(context, version):
"""
*musicpd.org, current playlist section:*
@ -305,7 +303,7 @@ def plchanges(context, version):
context.core.tracklist.tl_tracks.get())
@handle_request(r'^plchangesposid "(?P<version>\d+)"$')
@handle_request(r'plchangesposid\ "(?P<version>\d+)"$')
def plchangesposid(context, version):
"""
*musicpd.org, current playlist section:*
@ -329,8 +327,8 @@ def plchangesposid(context, version):
return result
@handle_request(r'^shuffle$')
@handle_request(r'^shuffle "(?P<start>\d+):(?P<end>\d+)*"$')
@handle_request(r'shuffle$')
@handle_request(r'shuffle\ "(?P<start>\d+):(?P<end>\d+)*"$')
def shuffle(context, start=None, end=None):
"""
*musicpd.org, current playlist section:*
@ -347,7 +345,7 @@ def shuffle(context, start=None, end=None):
context.core.tracklist.shuffle(start, end)
@handle_request(r'^swap "(?P<songpos1>\d+)" "(?P<songpos2>\d+)"$')
@handle_request(r'swap\ "(?P<songpos1>\d+)"\ "(?P<songpos2>\d+)"$')
def swap(context, songpos1, songpos2):
"""
*musicpd.org, current playlist section:*
@ -369,7 +367,7 @@ def swap(context, songpos1, songpos2):
context.core.tracklist.add(tracks)
@handle_request(r'^swapid "(?P<tlid1>\d+)" "(?P<tlid2>\d+)"$')
@handle_request(r'swapid\ "(?P<tlid1>\d+)"\ "(?P<tlid2>\d+)"$')
def swapid(context, tlid1, tlid2):
"""
*musicpd.org, current playlist section:*

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
from mopidy.frontends.mpd.protocol import handle_request
@handle_request(r'^[ ]*$')
@handle_request(r'[\ ]*$')
def empty(context):
"""The original MPD server returns ``OK`` on an empty request."""
pass

View File

@ -2,6 +2,7 @@ from __future__ import unicode_literals
import functools
import itertools
import re
from mopidy.models import Track
from mopidy.frontends.mpd import translator
@ -9,10 +10,114 @@ from mopidy.frontends.mpd.exceptions import MpdArgError, MpdNotImplemented
from mopidy.frontends.mpd.protocol import handle_request, stored_playlists
QUERY_RE = (
r'(?P<mpd_query>("?([Aa]lbum|[Aa]rtist|[Aa]lbumartist|[Cc]omment|'
r'[Cc]omposer|[Dd]ate|[Ff]ile|[Ff]ilename|[Gg]enre|[Pp]erformer|'
r'[Tt]itle|[Tt]rack|[Aa]ny)"? "[^"]*"\s?)+)$')
LIST_QUERY = r"""
("?) # Optional quote around the field type
(?P<field>( # Field to list in the response
[Aa]lbum
| [Aa]lbumartist
| [Aa]rtist
| [Cc]omposer
| [Dd]ate
| [Gg]enre
| [Pp]erformer
))
\1 # End of optional quote around the field type
(?: # Non-capturing group for optional search query
\ # A single space
(?P<mpd_query>.*)
)?
$
"""
SEARCH_FIELDS = r"""
[Aa]lbum
| [Aa]lbumartist
| [Aa]ny
| [Aa]rtist
| [Cc]omment
| [Cc]omposer
| [Dd]ate
| [Ff]ile
| [Ff]ilename
| [Gg]enre
| [Pp]erformer
| [Tt]itle
| [Tt]rack
"""
# TODO Would be nice to get ("?)...\1 working for the quotes here
SEARCH_QUERY = r"""
(?P<mpd_query>
(?: # Non-capturing group for repeating query pairs
"? # Optional quote around the field type
(?:
""" + SEARCH_FIELDS + r"""
)
"? # End of optional quote around the field type
\ # A single space
"[^"]*" # Matching a quoted search string
\s?
)+
)
$
"""
# TODO Would be nice to get ("?)...\1 working for the quotes here
SEARCH_PAIR_WITHOUT_GROUPS = r"""
\b # Only begin matching at word bundaries
"? # Optional quote around the field type
(?: # A non-capturing group for the field type
""" + SEARCH_FIELDS + """
)
"? # End of optional quote around the field type
\ # A single space
"[^"]+" # Matching a quoted search string
"""
SEARCH_PAIR_WITHOUT_GROUPS_RE = re.compile(
SEARCH_PAIR_WITHOUT_GROUPS, flags=(re.UNICODE | re.VERBOSE))
# TODO Would be nice to get ("?)...\1 working for the quotes here
SEARCH_PAIR_WITH_GROUPS = r"""
\b # Only begin matching at word bundaries
"? # Optional quote around the field type
(?P<field>( # A capturing group for the field type
""" + SEARCH_FIELDS + """
))
"? # End of optional quote around the field type
\ # A single space
"(?P<what>[^"]+)" # Capturing a quoted search string
"""
SEARCH_PAIR_WITH_GROUPS_RE = re.compile(
SEARCH_PAIR_WITH_GROUPS, flags=(re.UNICODE | re.VERBOSE))
def _query_from_mpd_search_format(mpd_query):
"""
Parses an MPD ``search`` or ``find`` query and converts it to the Mopidy
query format.
:param mpd_query: the MPD search query
:type mpd_query: string
"""
pairs = SEARCH_PAIR_WITHOUT_GROUPS_RE.findall(mpd_query)
query = {}
for pair in pairs:
m = SEARCH_PAIR_WITH_GROUPS_RE.match(pair)
field = m.groupdict()['field'].lower()
if field == 'title':
field = 'track_name'
elif field == 'track':
field = 'track_no'
elif field in ('file', 'filename'):
field = 'uri'
what = m.groupdict()['what']
if not what:
raise ValueError
if field in query:
query[field].append(what)
else:
query[field] = [what]
return query
def _get_field(field, search_results):
@ -40,7 +145,7 @@ def _artist_as_track(artist):
artists=[artist])
@handle_request(r'^count ' + QUERY_RE)
@handle_request(r'count\ ' + SEARCH_QUERY)
def count(context, mpd_query):
"""
*musicpd.org, music database section:*
@ -56,7 +161,7 @@ def count(context, mpd_query):
- use multiple tag-needle pairs to make more specific searches.
"""
try:
query = translator.query_from_mpd_search_format(mpd_query)
query = _query_from_mpd_search_format(mpd_query)
except ValueError:
raise MpdArgError('incorrect arguments', command='count')
results = context.core.library.find_exact(**query).get()
@ -67,7 +172,7 @@ def count(context, mpd_query):
]
@handle_request(r'^find ' + QUERY_RE)
@handle_request(r'find\ ' + SEARCH_QUERY)
def find(context, mpd_query):
"""
*musicpd.org, music database section:*
@ -96,7 +201,7 @@ def find(context, mpd_query):
- uses "file" instead of "filename".
"""
try:
query = translator.query_from_mpd_search_format(mpd_query)
query = _query_from_mpd_search_format(mpd_query)
except ValueError:
return
results = context.core.library.find_exact(**query).get()
@ -112,7 +217,7 @@ def find(context, mpd_query):
return translator.tracks_to_mpd_format(result_tracks)
@handle_request(r'^findadd ' + QUERY_RE)
@handle_request(r'findadd\ ' + SEARCH_QUERY)
def findadd(context, mpd_query):
"""
*musicpd.org, music database section:*
@ -123,17 +228,14 @@ def findadd(context, mpd_query):
current playlist. Parameters have the same meaning as for ``find``.
"""
try:
query = translator.query_from_mpd_search_format(mpd_query)
query = _query_from_mpd_search_format(mpd_query)
except ValueError:
return
results = context.core.library.find_exact(**query).get()
context.core.tracklist.add(_get_tracks(results))
@handle_request(
r'^list "?(?P<field>([Aa]rtist|[Aa]lbumartist|[Aa]lbum|[Cc]omposer|'
r'[Dd]ate|[Gg]enre|[Pp]erformer))"?'
r'( (?P<mpd_query>.*))?$')
@handle_request(r'list\ ' + LIST_QUERY)
def list_(context, field, mpd_query=None):
"""
*musicpd.org, music database section:*
@ -305,8 +407,8 @@ def _list_genre(context, query):
return genres
@handle_request(r'^listall$')
@handle_request(r'^listall "(?P<uri>[^"]+)"$')
@handle_request(r'listall$')
@handle_request(r'listall\ "(?P<uri>[^"]+)"$')
def listall(context, uri=None):
"""
*musicpd.org, music database section:*
@ -318,8 +420,8 @@ def listall(context, uri=None):
raise MpdNotImplemented # TODO
@handle_request(r'^listallinfo$')
@handle_request(r'^listallinfo "(?P<uri>[^"]+)"$')
@handle_request(r'listallinfo$')
@handle_request(r'listallinfo\ "(?P<uri>[^"]+)"$')
def listallinfo(context, uri=None):
"""
*musicpd.org, music database section:*
@ -332,8 +434,8 @@ def listallinfo(context, uri=None):
raise MpdNotImplemented # TODO
@handle_request(r'^lsinfo$')
@handle_request(r'^lsinfo "(?P<uri>[^"]*)"$')
@handle_request(r'lsinfo$')
@handle_request(r'lsinfo\ "(?P<uri>[^"]*)"$')
def lsinfo(context, uri=None):
"""
*musicpd.org, music database section:*
@ -355,7 +457,8 @@ def lsinfo(context, uri=None):
raise MpdNotImplemented # TODO
@handle_request(r'^rescan( "(?P<uri>[^"]+)")*$')
@handle_request(r'rescan$')
@handle_request(r'rescan\ "(?P<uri>[^"]+)"$')
def rescan(context, uri=None):
"""
*musicpd.org, music database section:*
@ -367,7 +470,7 @@ def rescan(context, uri=None):
return update(context, uri, rescan_unmodified_files=True)
@handle_request(r'^search ' + QUERY_RE)
@handle_request(r'search\ ' + SEARCH_QUERY)
def search(context, mpd_query):
"""
*musicpd.org, music database section:*
@ -396,7 +499,7 @@ def search(context, mpd_query):
- uses "file" instead of "filename".
"""
try:
query = translator.query_from_mpd_search_format(mpd_query)
query = _query_from_mpd_search_format(mpd_query)
except ValueError:
return
results = context.core.library.search(**query).get()
@ -406,7 +509,7 @@ def search(context, mpd_query):
return translator.tracks_to_mpd_format(artists + albums + tracks)
@handle_request(r'^searchadd ' + QUERY_RE)
@handle_request(r'searchadd\ ' + SEARCH_QUERY)
def searchadd(context, mpd_query):
"""
*musicpd.org, music database section:*
@ -420,14 +523,14 @@ def searchadd(context, mpd_query):
not case sensitive.
"""
try:
query = translator.query_from_mpd_search_format(mpd_query)
query = _query_from_mpd_search_format(mpd_query)
except ValueError:
return
results = context.core.library.search(**query).get()
context.core.tracklist.add(_get_tracks(results))
@handle_request(r'^searchaddpl "(?P<playlist_name>[^"]+)" ' + QUERY_RE)
@handle_request(r'searchaddpl\ "(?P<playlist_name>[^"]+)"\ ' + SEARCH_QUERY)
def searchaddpl(context, playlist_name, mpd_query):
"""
*musicpd.org, music database section:*
@ -443,7 +546,7 @@ def searchaddpl(context, playlist_name, mpd_query):
not case sensitive.
"""
try:
query = translator.query_from_mpd_search_format(mpd_query)
query = _query_from_mpd_search_format(mpd_query)
except ValueError:
return
results = context.core.library.search(**query).get()
@ -456,7 +559,8 @@ def searchaddpl(context, playlist_name, mpd_query):
context.core.playlists.save(playlist)
@handle_request(r'^update( "(?P<uri>[^"]+)")*$')
@handle_request(r'update$')
@handle_request(r'update\ "(?P<uri>[^"]+)"$')
def update(context, uri=None, rescan_unmodified_files=False):
"""
*musicpd.org, music database section:*

View File

@ -6,8 +6,7 @@ from mopidy.frontends.mpd.exceptions import (
MpdArgError, MpdNoExistError, MpdNotImplemented)
@handle_request(r'^consume (?P<state>[01])$')
@handle_request(r'^consume "(?P<state>[01])"$')
@handle_request(r'consume\ ("?)(?P<state>[01])\1$')
def consume(context, state):
"""
*musicpd.org, playback section:*
@ -24,7 +23,7 @@ def consume(context, state):
context.core.tracklist.consume = False
@handle_request(r'^crossfade "(?P<seconds>\d+)"$')
@handle_request(r'crossfade\ "(?P<seconds>\d+)"$')
def crossfade(context, seconds):
"""
*musicpd.org, playback section:*
@ -37,7 +36,7 @@ def crossfade(context, seconds):
raise MpdNotImplemented # TODO
@handle_request(r'^next$')
@handle_request(r'next$')
def next_(context):
"""
*musicpd.org, playback section:*
@ -95,8 +94,8 @@ def next_(context):
return context.core.playback.next().get()
@handle_request(r'^pause$')
@handle_request(r'^pause "(?P<state>[01])"$')
@handle_request(r'pause$')
@handle_request(r'pause\ "(?P<state>[01])"$')
def pause(context, state=None):
"""
*musicpd.org, playback section:*
@ -120,7 +119,7 @@ def pause(context, state=None):
context.core.playback.resume()
@handle_request(r'^play$')
@handle_request(r'play$')
def play(context):
"""
The original MPD server resumes from the paused state on ``play``
@ -129,8 +128,7 @@ def play(context):
return context.core.playback.play().get()
@handle_request(r'^playid (?P<tlid>-?\d+)$')
@handle_request(r'^playid "(?P<tlid>-?\d+)"$')
@handle_request(r'playid\ ("?)(?P<tlid>-?\d+)\1$')
def playid(context, tlid):
"""
*musicpd.org, playback section:*
@ -157,8 +155,7 @@ def playid(context, tlid):
return context.core.playback.play(tl_tracks[0]).get()
@handle_request(r'^play (?P<songpos>-?\d+)$')
@handle_request(r'^play "(?P<songpos>-?\d+)"$')
@handle_request(r'play\ ("?)(?P<songpos>-?\d+)\1$')
def playpos(context, songpos):
"""
*musicpd.org, playback section:*
@ -205,7 +202,7 @@ def _play_minus_one(context):
return # Fail silently
@handle_request(r'^previous$')
@handle_request(r'previous$')
def previous(context):
"""
*musicpd.org, playback section:*
@ -252,8 +249,7 @@ def previous(context):
return context.core.playback.previous().get()
@handle_request(r'^random (?P<state>[01])$')
@handle_request(r'^random "(?P<state>[01])"$')
@handle_request(r'random\ ("?)(?P<state>[01])\1$')
def random(context, state):
"""
*musicpd.org, playback section:*
@ -268,8 +264,7 @@ def random(context, state):
context.core.tracklist.random = False
@handle_request(r'^repeat (?P<state>[01])$')
@handle_request(r'^repeat "(?P<state>[01])"$')
@handle_request(r'repeat\ ("?)(?P<state>[01])\1$')
def repeat(context, state):
"""
*musicpd.org, playback section:*
@ -284,7 +279,7 @@ def repeat(context, state):
context.core.tracklist.repeat = False
@handle_request(r'^replay_gain_mode "(?P<mode>(off|track|album))"$')
@handle_request(r'replay_gain_mode\ "(?P<mode>(off|track|album))"$')
def replay_gain_mode(context, mode):
"""
*musicpd.org, playback section:*
@ -301,7 +296,7 @@ def replay_gain_mode(context, mode):
raise MpdNotImplemented # TODO
@handle_request(r'^replay_gain_status$')
@handle_request(r'replay_gain_status$')
def replay_gain_status(context):
"""
*musicpd.org, playback section:*
@ -314,8 +309,7 @@ def replay_gain_status(context):
return 'off' # TODO
@handle_request(r'^seek (?P<songpos>\d+) (?P<seconds>\d+)$')
@handle_request(r'^seek "(?P<songpos>\d+)" "(?P<seconds>\d+)"$')
@handle_request(r'seek\ ("?)(?P<songpos>\d+)\1\ ("?)(?P<seconds>\d+)\3$')
def seek(context, songpos, seconds):
"""
*musicpd.org, playback section:*
@ -335,7 +329,7 @@ def seek(context, songpos, seconds):
context.core.playback.seek(int(seconds) * 1000).get()
@handle_request(r'^seekid "(?P<tlid>\d+)" "(?P<seconds>\d+)"$')
@handle_request(r'seekid\ "(?P<tlid>\d+)"\ "(?P<seconds>\d+)"$')
def seekid(context, tlid, seconds):
"""
*musicpd.org, playback section:*
@ -350,8 +344,8 @@ def seekid(context, tlid, seconds):
context.core.playback.seek(int(seconds) * 1000).get()
@handle_request(r'^seekcur "(?P<position>\d+)"$')
@handle_request(r'^seekcur "(?P<diff>[-+]\d+)"$')
@handle_request(r'seekcur\ "(?P<position>\d+)"$')
@handle_request(r'seekcur\ "(?P<diff>[-+]\d+)"$')
def seekcur(context, position=None, diff=None):
"""
*musicpd.org, playback section:*
@ -370,8 +364,7 @@ def seekcur(context, position=None, diff=None):
context.core.playback.seek(position).get()
@handle_request(r'^setvol (?P<volume>[-+]*\d+)$')
@handle_request(r'^setvol "(?P<volume>[-+]*\d+)"$')
@handle_request(r'setvol\ ("?)(?P<volume>[-+]*\d+)\1$')
def setvol(context, volume):
"""
*musicpd.org, playback section:*
@ -392,8 +385,7 @@ def setvol(context, volume):
context.core.playback.volume = volume
@handle_request(r'^single (?P<state>[01])$')
@handle_request(r'^single "(?P<state>[01])"$')
@handle_request(r'single\ ("?)(?P<state>[01])\1$')
def single(context, state):
"""
*musicpd.org, playback section:*
@ -410,7 +402,7 @@ def single(context, state):
context.core.tracklist.single = False
@handle_request(r'^stop$')
@handle_request(r'stop$')
def stop(context):
"""
*musicpd.org, playback section:*

View File

@ -4,7 +4,7 @@ from mopidy.frontends.mpd.exceptions import MpdPermissionError
from mopidy.frontends.mpd.protocol import handle_request, mpd_commands
@handle_request(r'^config$', auth_required=False)
@handle_request(r'config$', auth_required=False)
def config(context):
"""
*musicpd.org, reflection section:*
@ -18,7 +18,7 @@ def config(context):
raise MpdPermissionError(command='config')
@handle_request(r'^commands$', auth_required=False)
@handle_request(r'commands$', auth_required=False)
def commands(context):
"""
*musicpd.org, reflection section:*
@ -45,7 +45,7 @@ def commands(context):
('command', command_name) for command_name in sorted(command_names)]
@handle_request(r'^decoders$')
@handle_request(r'decoders$')
def decoders(context):
"""
*musicpd.org, reflection section:*
@ -72,7 +72,7 @@ def decoders(context):
return # TODO
@handle_request(r'^notcommands$', auth_required=False)
@handle_request(r'notcommands$', auth_required=False)
def notcommands(context):
"""
*musicpd.org, reflection section:*
@ -95,7 +95,7 @@ def notcommands(context):
('command', command_name) for command_name in sorted(command_names)]
@handle_request(r'^tagtypes$')
@handle_request(r'tagtypes$')
def tagtypes(context):
"""
*musicpd.org, reflection section:*
@ -107,7 +107,7 @@ def tagtypes(context):
pass # TODO
@handle_request(r'^urlhandlers$')
@handle_request(r'urlhandlers$')
def urlhandlers(context):
"""
*musicpd.org, reflection section:*

View File

@ -13,7 +13,7 @@ SUBSYSTEMS = [
'stored_playlist', 'update']
@handle_request(r'^clearerror$')
@handle_request(r'clearerror$')
def clearerror(context):
"""
*musicpd.org, status section:*
@ -26,7 +26,7 @@ def clearerror(context):
raise MpdNotImplemented # TODO
@handle_request(r'^currentsong$')
@handle_request(r'currentsong$')
def currentsong(context):
"""
*musicpd.org, status section:*
@ -42,8 +42,8 @@ def currentsong(context):
return track_to_mpd_format(tl_track, position=position)
@handle_request(r'^idle$')
@handle_request(r'^idle (?P<subsystems>.+)$')
@handle_request(r'idle$')
@handle_request(r'idle\ (?P<subsystems>.+)$')
def idle(context, subsystems=None):
"""
*musicpd.org, status section:*
@ -100,7 +100,7 @@ def idle(context, subsystems=None):
return response
@handle_request(r'^noidle$')
@handle_request(r'noidle$')
def noidle(context):
"""See :meth:`_status_idle`."""
if not context.subscriptions:
@ -110,7 +110,7 @@ def noidle(context):
context.session.prevent_timeout = False
@handle_request(r'^stats$')
@handle_request(r'stats$')
def stats(context):
"""
*musicpd.org, status section:*
@ -137,7 +137,7 @@ def stats(context):
}
@handle_request(r'^status$')
@handle_request(r'status$')
def status(context):
"""
*musicpd.org, status section:*

View File

@ -5,8 +5,8 @@ from mopidy.frontends.mpd.exceptions import MpdNotImplemented
@handle_request(
r'^sticker delete "(?P<field>[^"]+)" '
r'"(?P<uri>[^"]+)"( "(?P<name>[^"]+)")*$')
r'sticker\ delete\ "(?P<field>[^"]+)"\ '
r'"(?P<uri>[^"]+)"(\ "(?P<name>[^"]+)")*$')
def sticker_delete(context, field, uri, name=None):
"""
*musicpd.org, sticker section:*
@ -20,7 +20,7 @@ def sticker_delete(context, field, uri, name=None):
@handle_request(
r'^sticker find "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
r'sticker\ find\ "(?P<field>[^"]+)"\ "(?P<uri>[^"]+)"\ '
r'"(?P<name>[^"]+)"$')
def sticker_find(context, field, uri, name):
"""
@ -36,7 +36,7 @@ def sticker_find(context, field, uri, name):
@handle_request(
r'^sticker get "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
r'sticker\ get\ "(?P<field>[^"]+)"\ "(?P<uri>[^"]+)"\ '
r'"(?P<name>[^"]+)"$')
def sticker_get(context, field, uri, name):
"""
@ -49,7 +49,7 @@ def sticker_get(context, field, uri, name):
raise MpdNotImplemented # TODO
@handle_request(r'^sticker list "(?P<field>[^"]+)" "(?P<uri>[^"]+)"$')
@handle_request(r'sticker\ list\ "(?P<field>[^"]+)"\ "(?P<uri>[^"]+)"$')
def sticker_list(context, field, uri):
"""
*musicpd.org, sticker section:*
@ -62,8 +62,8 @@ def sticker_list(context, field, uri):
@handle_request(
r'^sticker set "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
r'"(?P<name>[^"]+)" "(?P<value>[^"]+)"$')
r'sticker\ set\ "(?P<field>[^"]+)"\ "(?P<uri>[^"]+)"\ '
r'"(?P<name>[^"]+)"\ "(?P<value>[^"]+)"$')
def sticker_set(context, field, uri, name, value):
"""
*musicpd.org, sticker section:*

View File

@ -7,8 +7,7 @@ from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.translator import playlist_to_mpd_format
@handle_request(r'^listplaylist (?P<name>\w+)$')
@handle_request(r'^listplaylist "(?P<name>[^"]+)"$')
@handle_request(r'listplaylist\ ("?)(?P<name>[^"]+)\1$')
def listplaylist(context, name):
"""
*musicpd.org, stored playlists section:*
@ -29,8 +28,7 @@ def listplaylist(context, name):
return ['file: %s' % t.uri for t in playlist.tracks]
@handle_request(r'^listplaylistinfo (?P<name>\w+)$')
@handle_request(r'^listplaylistinfo "(?P<name>[^"]+)"$')
@handle_request(r'listplaylistinfo\ ("?)(?P<name>[^"]+)\1$')
def listplaylistinfo(context, name):
"""
*musicpd.org, stored playlists section:*
@ -50,7 +48,7 @@ def listplaylistinfo(context, name):
return playlist_to_mpd_format(playlist)
@handle_request(r'^listplaylists$')
@handle_request(r'listplaylists$')
def listplaylists(context):
"""
*musicpd.org, stored playlists section:*
@ -92,7 +90,8 @@ def listplaylists(context):
return result
@handle_request(r'^load "(?P<name>[^"]+)"( "(?P<start>\d+):(?P<end>\d+)*")*$')
@handle_request(
r'load\ "(?P<name>[^"]+)"(\ "(?P<start>\d+):(?P<end>\d+)*")*$')
def load(context, name, start=None, end=None):
"""
*musicpd.org, stored playlists section:*
@ -124,7 +123,7 @@ def load(context, name, start=None, end=None):
context.core.tracklist.add(playlist.tracks[start:end])
@handle_request(r'^playlistadd "(?P<name>[^"]+)" "(?P<uri>[^"]+)"$')
@handle_request(r'playlistadd\ "(?P<name>[^"]+)"\ "(?P<uri>[^"]+)"$')
def playlistadd(context, name, uri):
"""
*musicpd.org, stored playlists section:*
@ -138,7 +137,7 @@ def playlistadd(context, name, uri):
raise MpdNotImplemented # TODO
@handle_request(r'^playlistclear "(?P<name>[^"]+)"$')
@handle_request(r'playlistclear\ "(?P<name>[^"]+)"$')
def playlistclear(context, name):
"""
*musicpd.org, stored playlists section:*
@ -150,7 +149,7 @@ def playlistclear(context, name):
raise MpdNotImplemented # TODO
@handle_request(r'^playlistdelete "(?P<name>[^"]+)" "(?P<songpos>\d+)"$')
@handle_request(r'playlistdelete\ "(?P<name>[^"]+)"\ "(?P<songpos>\d+)"$')
def playlistdelete(context, name, songpos):
"""
*musicpd.org, stored playlists section:*
@ -163,8 +162,8 @@ def playlistdelete(context, name, songpos):
@handle_request(
r'^playlistmove "(?P<name>[^"]+)" '
r'"(?P<from_pos>\d+)" "(?P<to_pos>\d+)"$')
r'playlistmove\ "(?P<name>[^"]+)"\ '
r'"(?P<from_pos>\d+)"\ "(?P<to_pos>\d+)"$')
def playlistmove(context, name, from_pos, to_pos):
"""
*musicpd.org, stored playlists section:*
@ -183,7 +182,7 @@ def playlistmove(context, name, from_pos, to_pos):
raise MpdNotImplemented # TODO
@handle_request(r'^rename "(?P<old_name>[^"]+)" "(?P<new_name>[^"]+)"$')
@handle_request(r'rename\ "(?P<old_name>[^"]+)"\ "(?P<new_name>[^"]+)"$')
def rename(context, old_name, new_name):
"""
*musicpd.org, stored playlists section:*
@ -195,7 +194,7 @@ def rename(context, old_name, new_name):
raise MpdNotImplemented # TODO
@handle_request(r'^rm "(?P<name>[^"]+)"$')
@handle_request(r'rm\ "(?P<name>[^"]+)"$')
def rm(context, name):
"""
*musicpd.org, stored playlists section:*
@ -207,7 +206,7 @@ def rm(context, name):
raise MpdNotImplemented # TODO
@handle_request(r'^save "(?P<name>[^"]+)"$')
@handle_request(r'save\ "(?P<name>[^"]+)"$')
def save(context, name):
"""
*musicpd.org, stored playlists section:*

View File

@ -199,85 +199,6 @@ 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
| [Cc]omment
| [Cc]omposer
| [Dd]ate
| [Ff]ile
| [Ff]ilename
| [Gg]enre
| [Pp]erformer
| [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
| [Cc]omment
| [Cc]omposer
| [Dd]ate
| [Ff]ile
| [Ff]ilename
| [Gg]enre
| [Pp]erformer
| [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
query format.
:param mpd_query: the MPD search query
:type mpd_query: string
"""
query_parts = MPD_SEARCH_QUERY_RE.findall(mpd_query)
query = {}
for query_part in query_parts:
m = MPD_SEARCH_QUERY_PART_RE.match(query_part)
field = m.groupdict()['field'].lower()
if field == 'title':
field = 'track_name'
elif field == 'track':
field = 'track_no'
elif field in ('file', 'filename'):
field = 'uri'
what = m.groupdict()['what']
if not what:
raise ValueError
if field in query:
query[field].append(what)
else:
query[field] = [what]
return query
# TODO: move to tagcache backend.
def tracks_to_tag_cache_format(tracks, media_dir):
"""

View File

@ -1,10 +1,27 @@
from __future__ import unicode_literals
import unittest
from mopidy.frontends.mpd.protocol import music_db
from mopidy.models import Album, Artist, SearchResult, Track
from tests.frontends.mpd import protocol
class QueryFromMpdSearchFormatTest(unittest.TestCase):
def test_dates_are_extracted(self):
result = music_db._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 MusicDatabaseHandlerTest(protocol.BaseTestCase):
def test_count(self):
self.sendRequest('count "artist" "needle"')

View File

@ -128,20 +128,6 @@ 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):
self.media_dir = '/dir/subdir'