Merge branch 'develop' into feature/improved-lookup

This commit is contained in:
Stein Magnus Jodal 2012-12-20 19:15:39 +01:00
commit 30ac1896b3
8 changed files with 113 additions and 95 deletions

View File

@ -26,6 +26,10 @@ v0.11.0 (in development)
**MPD frontend**
- Add :attr:`mopidy.settings.MPD_SERVER_CONNECTION_TIMEOUT` setting which
controls how long an MPD client can stay inactive before the connection is
closed by the server.
- Add support for the ``findadd`` command.
- Updated to match the MPD 0.17 protocol (Fixes: :issue:`228`):

View File

@ -120,6 +120,13 @@ class SpotifyLibraryProvider(base.BaseLibraryProvider):
if not query:
return self._get_all_tracks()
if 'uri' in query.keys():
result = []
for uri in query['uri']:
tracks = self.lookup(uri)
result += tracks
return result
spotify_query = self._translate_search_query(query)
logger.debug('Spotify search query: %s' % spotify_query)
@ -160,16 +167,7 @@ class SpotifyLibraryProvider(base.BaseLibraryProvider):
def _translate_search_query(self, mopidy_query):
spotify_query = []
for (field, values) in mopidy_query.iteritems():
if field == 'uri':
tracks = []
for value in values:
track = self.lookup(value)
if track:
tracks.append(track)
return tracks
elif field == 'track':
field = 'title'
elif field == 'date':
if field == 'date':
field = 'year'
if not hasattr(values, '__iter__'):
values = [values]

View File

@ -23,7 +23,8 @@ class MpdFrontend(pykka.ThreadingActor, CoreListener):
network.Server(
hostname, port,
protocol=session.MpdSession, protocol_kwargs={'core': core},
max_connections=settings.MPD_SERVER_MAX_CONNECTIONS)
max_connections=settings.MPD_SERVER_MAX_CONNECTIONS,
timeout=settings.MPD_SERVER_CONNECTION_TIMEOUT)
except IOError as error:
logger.error(
'MPD server startup failed: %s',

View File

@ -1,11 +1,8 @@
from __future__ import unicode_literals
import re
import shlex
from mopidy.frontends.mpd.exceptions import MpdArgError, MpdNotImplemented
from mopidy.frontends.mpd import translator
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
from mopidy.frontends.mpd.protocol import handle_request, stored_playlists
from mopidy.frontends.mpd.translator import tracks_to_mpd_format
QUERY_RE = (
@ -13,35 +10,6 @@ QUERY_RE = (
r'[Tt]itle|[Aa]ny)"? "[^"]*"\s?)+)$')
def _build_query(mpd_query):
"""
Parses a MPD query string and converts it to the Mopidy query format.
"""
query_pattern = (
r'"?(?:[Aa]lbum|[Aa]rtist|[Ff]ile[name]*|[Tt]itle|[Aa]ny)"? "[^"]+"')
query_parts = re.findall(query_pattern, mpd_query)
query_part_pattern = (
r'"?(?P<field>([Aa]lbum|[Aa]rtist|[Ff]ile[name]*|[Tt]itle|[Aa]ny))"? '
r'"(?P<what>[^"]+)"')
query = {}
for query_part in query_parts:
m = re.match(query_part_pattern, query_part)
field = m.groupdict()['field'].lower()
if field == 'title':
field = 'track'
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
if field in query:
query[field].append(what)
else:
query[field] = [what]
return query
@handle_request(r'^count "(?P<tag>[^"]+)" "(?P<needle>[^"]*)"$')
def count(context, tag, needle):
"""
@ -84,11 +52,11 @@ def find(context, mpd_query):
- uses "file" instead of "filename".
"""
try:
query = _build_query(mpd_query)
query = translator.query_from_mpd_search_format(mpd_query)
except ValueError:
return
result = context.core.library.find_exact(**query).get()
return tracks_to_mpd_format(result)
return translator.tracks_to_mpd_format(result)
@handle_request(r'^findadd ' + QUERY_RE)
@ -102,7 +70,7 @@ def findadd(context, mpd_query):
current playlist. Parameters have the same meaning as for ``find``.
"""
try:
query = _build_query(mpd_query)
query = translator.query_from_mpd_search_format(mpd_query)
except ValueError:
return
result = context.core.library.find_exact(**query).get()
@ -196,7 +164,7 @@ def list_(context, field, mpd_query=None):
"""
field = field.lower()
try:
query = _list_build_query(field, mpd_query)
query = translator.query_from_mpd_list_format(field, mpd_query)
except ValueError:
return
if field == 'artist':
@ -209,47 +177,6 @@ def list_(context, field, mpd_query=None):
pass # TODO We don't have genre in our internal data structures yet
def _list_build_query(field, mpd_query):
"""Converts a ``list`` query to a Mopidy query."""
if mpd_query is None:
return {}
try:
# shlex does not seem to be friends with unicode objects
tokens = shlex.split(mpd_query.encode('utf-8'))
except ValueError as error:
if str(error) == 'No closing quotation':
raise MpdArgError('Invalid unquoted character', command='list')
else:
raise
tokens = [t.decode('utf-8') for t in tokens]
if len(tokens) == 1:
if field == 'album':
if not tokens[0]:
raise ValueError
return {'artist': [tokens[0]]}
else:
raise MpdArgError(
'should be "Album" for 3 arguments', command='list')
elif len(tokens) % 2 == 0:
query = {}
while tokens:
key = tokens[0].lower()
key = str(key) # Needed for kwargs keys on OS X and Windows
value = tokens[1]
tokens = tokens[2:]
if key not in ('artist', 'album', 'date', 'genre'):
raise MpdArgError('not able to parse args', command='list')
if not value:
raise ValueError
if key in query:
query[key].append(value)
else:
query[key] = [value]
return query
else:
raise MpdArgError('not able to parse args', command='list')
def _list_artist(context, query):
artists = set()
tracks = context.core.library.find_exact(**query).get()
@ -367,11 +294,11 @@ def search(context, mpd_query):
- uses "file" instead of "filename".
"""
try:
query = _build_query(mpd_query)
query = translator.query_from_mpd_search_format(mpd_query)
except ValueError:
return
result = context.core.library.search(**query).get()
return tracks_to_mpd_format(result)
return translator.tracks_to_mpd_format(result)
@handle_request(r'^searchadd ' + QUERY_RE)
@ -388,7 +315,7 @@ def searchadd(context, mpd_query):
not case sensitive.
"""
try:
query = _build_query(mpd_query)
query = translator.query_from_mpd_search_format(mpd_query)
except ValueError:
return
result = context.core.library.search(**query).get()
@ -411,7 +338,7 @@ def searchaddpl(context, playlist_name, mpd_query):
not case sensitive.
"""
try:
query = _build_query(mpd_query)
query = translator.query_from_mpd_search_format(mpd_query)
except ValueError:
return
result = context.core.library.search(**query).get()

View File

@ -2,10 +2,12 @@ from __future__ import unicode_literals
import os
import re
import shlex
import urllib
from mopidy import settings
from mopidy.frontends.mpd import protocol
from mopidy.frontends.mpd.exceptions import MpdArgError
from mopidy.models import TlTrack
from mopidy.utils.path import mtime as get_mtime, uri_to_path, split_path
@ -134,6 +136,82 @@ def playlist_to_mpd_format(playlist, *args, **kwargs):
return tracks_to_mpd_format(playlist.tracks, *args, **kwargs)
def query_from_mpd_list_format(field, mpd_query):
"""
Converts an MPD ``list`` query to a Mopidy query.
"""
if mpd_query is None:
return {}
try:
# shlex does not seem to be friends with unicode objects
tokens = shlex.split(mpd_query.encode('utf-8'))
except ValueError as error:
if str(error) == 'No closing quotation':
raise MpdArgError('Invalid unquoted character', command='list')
else:
raise
tokens = [t.decode('utf-8') for t in tokens]
if len(tokens) == 1:
if field == 'album':
if not tokens[0]:
raise ValueError
return {'artist': [tokens[0]]}
else:
raise MpdArgError(
'should be "Album" for 3 arguments', command='list')
elif len(tokens) % 2 == 0:
query = {}
while tokens:
key = tokens[0].lower()
key = str(key) # Needed for kwargs keys on OS X and Windows
value = tokens[1]
tokens = tokens[2:]
if key not in ('artist', 'album', 'date', 'genre'):
raise MpdArgError('not able to parse args', command='list')
if not value:
raise ValueError
if key in query:
query[key].append(value)
else:
query[key] = [value]
return query
else:
raise MpdArgError('not able to parse args', command='list')
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_pattern = (
r'"?(?:[Aa]lbum|[Aa]rtist|[Ff]ile[name]*|[Tt]itle|[Aa]ny)"? "[^"]+"')
query_parts = re.findall(query_pattern, mpd_query)
query_part_pattern = (
r'"?(?P<field>([Aa]lbum|[Aa]rtist|[Ff]ile[name]*|[Tt]itle|[Aa]ny))"? '
r'"(?P<what>[^"]+)"')
query = {}
for query_part in query_parts:
m = re.match(query_part_pattern, query_part)
field = m.groupdict()['field'].lower()
if field == 'title':
field = 'track'
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
if field in query:
query[field].append(what)
else:
query[field] = [what]
return query
def tracks_to_tag_cache_format(tracks):
"""
Format list of tracks for output to MPD tag cache

View File

@ -174,6 +174,16 @@ MIXER = 'autoaudiomixer'
#: MIXER_TRACK = None
MIXER_TRACK = None
#: Number of seconds an MPD client can stay inactive before the connection is
#: closed by the server.
#:
#: Used by :mod:`mopidy.frontends.mpd`.
#:
#: Default::
#:
#: MPD_SERVER_CONNECTION_TIMEOUT = 60
MPD_SERVER_CONNECTION_TIMEOUT = 60
#: Which address Mopidy's MPD server should bind to.
#:
#: Used by :mod:`mopidy.frontends.mpd`.

View File

@ -291,7 +291,7 @@ class Connection(object):
return True
def timeout_callback(self):
self.stop('Client timeout out after %s seconds' % self.timeout)
self.stop('Client inactive for %ds; closing connection' % self.timeout)
return False