Merge branch 'develop' into feature/improved-lookup
This commit is contained in:
commit
30ac1896b3
@ -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`):
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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`.
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user