Merge pull request #102 from jodal/feature/mpd-improvements

Lots of small improvements, mostly to the MPD frontend.
This commit is contained in:
Stein Magnus Jodal 2011-06-06 17:28:04 -07:00
commit a472a183e8
46 changed files with 1445 additions and 1221 deletions

View File

@ -1,10 +0,0 @@
def setup(app):
app.connect('autodoc-skip-member', autodoc_private_members_with_doc)
def autodoc_private_members_with_doc(app, what, name, obj, skip, options):
if not skip:
return skip
if (name.startswith('_') and obj.__doc__ is not None
and not (name.startswith('__') and name.endswith('__'))):
return False
return skip

View File

@ -29,6 +29,21 @@ No description yet.
- Replace not decodable characters returned from Spotify instead of throwing an
exception, as we won't try to figure out the encoding of non-UTF-8-data.
- MPD frontend:
- Refactoring and cleanup. Most notably, all request handlers now get an
instance of :class:`mopidy.frontends.mpd.dispatcher.MpdContext` as the
first argument. The new class contains reference to any object in Mopidy
the MPD protocol implementation should need access to.
- Close the client connection when the command ``close`` is received.
- Do not allow access to the command ``kill``.
- ``commands`` and ``notcommands`` now have correct output if password
authentication is turned on, but the connected user has not been
authenticated yet.
v0.4.1 (2011-05-06)
===================

View File

@ -25,7 +25,7 @@ import mopidy
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc', 'autodoc_private_members',
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.graphviz', 'sphinx.ext.inheritance_diagram',
'sphinx.ext.extlinks', 'sphinx.ext.viewcode']

View File

@ -2,6 +2,8 @@
:mod:`mopidy.frontends.mpd` -- MPD server
*****************************************
.. inheritance-diagram:: mopidy.frontends.mpd
.. automodule:: mopidy.frontends.mpd
:synopsis: MPD frontend
:members:
@ -11,28 +13,30 @@
MPD server
==========
.. inheritance-diagram:: mopidy.frontends.mpd.server
.. automodule:: mopidy.frontends.mpd.server
:synopsis: MPD server
:members:
:undoc-members:
.. inheritance-diagram:: mopidy.frontends.mpd.server
MPD session
===========
.. inheritance-diagram:: mopidy.frontends.mpd.session
.. automodule:: mopidy.frontends.mpd.session
:synopsis: MPD client session
:members:
:undoc-members:
.. inheritance-diagram:: mopidy.frontends.mpd.session
MPD dispatcher
==============
.. inheritance-diagram:: mopidy.frontends.mpd.dispatcher
.. automodule:: mopidy.frontends.mpd.dispatcher
:synopsis: MPD request dispatcher
:members:

View File

@ -2,6 +2,8 @@ from copy import copy
import logging
import random
from mopidy.models import CpTrack
logger = logging.getLogger('mopidy.backends.base')
class CurrentPlaylistController(object):
@ -66,7 +68,7 @@ class CurrentPlaylistController(object):
"""
assert at_position <= len(self._cp_tracks), \
u'at_position can not be greater than playlist length'
cp_track = (self.version, track)
cp_track = CpTrack(self.version, track)
if at_position is not None:
self._cp_tracks.insert(at_position, cp_track)
else:

View File

@ -80,12 +80,12 @@ class PlaybackController(object):
def _get_cpid(self, cp_track):
if cp_track is None:
return None
return cp_track[0]
return cp_track.cpid
def _get_track(self, cp_track):
if cp_track is None:
return None
return cp_track[1]
return cp_track.track
@property
def current_cpid(self):
@ -331,7 +331,7 @@ class PlaybackController(object):
self.stop(clear_current_track=True)
if self.consume:
self.backend.current_playlist.remove(cpid=original_cp_track[0])
self.backend.current_playlist.remove(cpid=original_cp_track.cpid)
def on_current_playlist_change(self):
"""
@ -389,7 +389,7 @@ class PlaybackController(object):
self.state = self.STOPPED
self.current_cp_track = cp_track
self.state = self.PLAYING
if not self.provider.play(cp_track[1]):
if not self.provider.play(cp_track.track):
# Track is not playable
if self.random and self._shuffled:
self._shuffled.remove(cp_track)

View File

@ -4,9 +4,9 @@ import re
from pykka import ActorDeadError
from pykka.registry import ActorRegistry
from mopidy import settings
from mopidy.backends.base import Backend
from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdArgError,
MpdUnknownCommand, MpdSystemError)
from mopidy.frontends.mpd import exceptions
from mopidy.frontends.mpd.protocol import mpd_commands, request_handlers
# Do not remove the following import. The protocol modules must be imported to
# get them registered as request handlers.
@ -27,71 +27,186 @@ class MpdDispatcher(object):
back to the MPD session.
"""
# XXX Consider merging MpdDispatcher into MpdSession
def __init__(self):
backend_refs = ActorRegistry.get_by_class(Backend)
assert len(backend_refs) == 1, 'Expected exactly one running backend.'
self.backend = backend_refs[0].proxy()
mixer_refs = ActorRegistry.get_by_class(BaseMixer)
assert len(mixer_refs) == 1, 'Expected exactly one running mixer.'
self.mixer = mixer_refs[0].proxy()
def __init__(self, session=None):
self.authenticated = False
self.command_list = False
self.command_list_ok = False
self.command_list_index = None
self.context = MpdContext(self, session=session)
def handle_request(self, request, command_list_index=None):
def handle_request(self, request, current_command_list_index=None):
"""Dispatch incoming requests to the correct handler."""
if self.command_list is not False and request != u'command_list_end':
self.command_list.append(request)
return None
self.command_list_index = current_command_list_index
response = []
filter_chain = [
self._catch_mpd_ack_errors_filter,
self._authenticate_filter,
self._command_list_filter,
self._add_ok_filter,
self._call_handler_filter,
]
return self._call_next_filter(request, response, filter_chain)
def _call_next_filter(self, request, response, filter_chain):
if filter_chain:
next_filter = filter_chain.pop(0)
return next_filter(request, response, filter_chain)
else:
return response
### Filter: catch MPD ACK errors
def _catch_mpd_ack_errors_filter(self, request, response, filter_chain):
try:
(handler, kwargs) = self.find_handler(request)
result = handler(self, **kwargs)
except MpdAckError as e:
if command_list_index is not None:
e.index = command_list_index
return self.handle_response(e.get_mpd_ack(), add_ok=False)
return self._call_next_filter(request, response, filter_chain)
except exceptions.MpdAckError as mpd_ack_error:
if self.command_list_index is not None:
mpd_ack_error.index = self.command_list_index
return [mpd_ack_error.get_mpd_ack()]
### Filter: authenticate
def _authenticate_filter(self, request, response, filter_chain):
if self.authenticated:
return self._call_next_filter(request, response, filter_chain)
elif settings.MPD_SERVER_PASSWORD is None:
self.authenticated = True
return self._call_next_filter(request, response, filter_chain)
else:
command_name = request.split(' ')[0]
command_names_not_requiring_auth = [
command.name for command in mpd_commands
if not command.auth_required]
if command_name in command_names_not_requiring_auth:
return self._call_next_filter(request, response, filter_chain)
else:
raise exceptions.MpdPermissionError(command=command_name)
### Filter: command list
def _command_list_filter(self, request, response, filter_chain):
if self._is_receiving_command_list(request):
self.command_list.append(request)
return []
else:
response = self._call_next_filter(request, response, filter_chain)
if (self._is_receiving_command_list(request) or
self._is_processing_command_list(request)):
if response and response[-1] == u'OK':
response = response[:-1]
return response
def _is_receiving_command_list(self, request):
return (self.command_list is not False
and request != u'command_list_end')
def _is_processing_command_list(self, request):
return (self.command_list_index is not None
and request != u'command_list_end')
### Filter: add OK
def _add_ok_filter(self, request, response, filter_chain):
response = self._call_next_filter(request, response, filter_chain)
if not self._has_error(response):
response.append(u'OK')
return response
def _has_error(self, response):
return response and response[-1].startswith(u'ACK')
### Filter: call handler
def _call_handler_filter(self, request, response, filter_chain):
try:
response = self._format_response(self._call_handler(request))
return self._call_next_filter(request, response, filter_chain)
except ActorDeadError as e:
logger.warning(u'Tried to communicate with dead actor.')
mpd_error = MpdSystemError(e.message)
return self.handle_response(mpd_error.get_mpd_ack(), add_ok=False)
if request in (u'command_list_begin', u'command_list_ok_begin'):
return None
if command_list_index is not None:
return self.handle_response(result, add_ok=False)
return self.handle_response(result)
raise exceptions.MpdSystemError(e.message)
def find_handler(self, request):
"""Find the correct handler for a request."""
def _call_handler(self, request):
(handler, kwargs) = self._find_handler(request)
return handler(self.context, **kwargs)
def _find_handler(self, request):
for pattern in request_handlers:
matches = re.match(pattern, request)
if matches is not None:
return (request_handlers[pattern], matches.groupdict())
command = request.split(' ')[0]
if command in mpd_commands:
raise MpdArgError(u'incorrect arguments', command=command)
raise MpdUnknownCommand(command=command)
command_name = request.split(' ')[0]
if command_name in [command.name for command in mpd_commands]:
raise exceptions.MpdArgError(u'incorrect arguments',
command=command_name)
raise exceptions.MpdUnknownCommand(command=command_name)
def handle_response(self, result, add_ok=True):
"""Format the response from a request handler."""
response = []
def _format_response(self, response):
formatted_response = []
for element in self._listify_result(response):
formatted_response.extend(self._format_lines(element))
return formatted_response
def _listify_result(self, result):
if result is None:
result = []
elif isinstance(result, set):
result = list(result)
elif not isinstance(result, list):
result = [result]
for line in flatten(result):
if isinstance(line, dict):
for (key, value) in line.items():
response.append(u'%s: %s' % (key, value))
elif isinstance(line, tuple):
(key, value) = line
response.append(u'%s: %s' % (key, value))
else:
response.append(line)
if add_ok and (not response or not response[-1].startswith(u'ACK')):
response.append(u'OK')
return response
return []
if isinstance(result, set):
return flatten(list(result))
if not isinstance(result, list):
return [result]
return flatten(result)
def _format_lines(self, line):
if isinstance(line, dict):
return [u'%s: %s' % (key, value) for (key, value) in line.items()]
if isinstance(line, tuple):
(key, value) = line
return [u'%s: %s' % (key, value)]
return [line]
class MpdContext(object):
"""
This object is passed as the first argument to all MPD command handlers to
give the command handlers access to important parts of Mopidy.
"""
#: The current :class:`MpdDispatcher`.
dispatcher = None
#: The current :class:`mopidy.frontends.mpd.session.MpdSession`.
session = None
def __init__(self, dispatcher, session=None):
self.dispatcher = dispatcher
self.session = session
self._backend = None
self._mixer = None
@property
def backend(self):
"""
The backend. An instance of :class:`mopidy.backends.base.Backend`.
"""
if self._backend is not None:
return self._backend
backend_refs = ActorRegistry.get_by_class(Backend)
assert len(backend_refs) == 1, 'Expected exactly one running backend.'
self._backend = backend_refs[0].proxy()
return self._backend
@property
def mixer(self):
"""
The mixer. An instance of :class:`mopidy.mixers.base.BaseMixer`.
"""
if self._mixer is not None:
return self._mixer
mixer_refs = ActorRegistry.get_by_class(BaseMixer)
assert len(mixer_refs) == 1, 'Expected exactly one running mixer.'
self._mixer = mixer_refs[0].proxy()
return self._mixer

View File

@ -16,10 +16,11 @@ class MpdAckError(MopidyException):
ACK_ERROR_PLAYER_SYNC = 55
ACK_ERROR_EXIST = 56
def __init__(self, message=u'', error_code=0, index=0, command=u''):
super(MpdAckError, self).__init__(message, error_code, index, command)
error_code = 0
def __init__(self, message=u'', index=0, command=u''):
super(MpdAckError, self).__init__(message, index, command)
self.message = message
self.error_code = error_code
self.index = index
self.command = command
@ -30,36 +31,38 @@ class MpdAckError(MopidyException):
ACK [%(error_code)i@%(index)i] {%(command)s} description
"""
return u'ACK [%i@%i] {%s} %s' % (
self.error_code, self.index, self.command, self.message)
self.__class__.error_code, self.index, self.command, self.message)
class MpdArgError(MpdAckError):
def __init__(self, *args, **kwargs):
super(MpdArgError, self).__init__(*args, **kwargs)
self.error_code = MpdAckError.ACK_ERROR_ARG
error_code = MpdAckError.ACK_ERROR_ARG
class MpdPasswordError(MpdAckError):
error_code = MpdAckError.ACK_ERROR_PASSWORD
class MpdPermissionError(MpdAckError):
error_code = MpdAckError.ACK_ERROR_PERMISSION
def __init__(self, *args, **kwargs):
super(MpdPasswordError, self).__init__(*args, **kwargs)
self.error_code = MpdAckError.ACK_ERROR_PASSWORD
super(MpdPermissionError, self).__init__(*args, **kwargs)
self.message = u'you don\'t have permission for "%s"' % self.command
class MpdUnknownCommand(MpdAckError):
error_code = MpdAckError.ACK_ERROR_UNKNOWN
def __init__(self, *args, **kwargs):
super(MpdUnknownCommand, self).__init__(*args, **kwargs)
self.message = u'unknown command "%s"' % self.command
self.command = u''
self.error_code = MpdAckError.ACK_ERROR_UNKNOWN
class MpdNoExistError(MpdAckError):
def __init__(self, *args, **kwargs):
super(MpdNoExistError, self).__init__(*args, **kwargs)
self.error_code = MpdAckError.ACK_ERROR_NO_EXIST
error_code = MpdAckError.ACK_ERROR_NO_EXIST
class MpdSystemError(MpdAckError):
def __init__(self, *args, **kwargs):
super(MpdSystemError, self).__init__(*args, **kwargs)
self.error_code = MpdAckError.ACK_ERROR_SYSTEM
error_code = MpdAckError.ACK_ERROR_SYSTEM
class MpdNotImplemented(MpdAckError):
error_code = 0
def __init__(self, *args, **kwargs):
super(MpdNotImplemented, self).__init__(*args, **kwargs)
self.message = u'Not implemented'

View File

@ -10,6 +10,7 @@ implement our own MPD server which is compatible with the numerous existing
`MPD clients <http://mpd.wikia.com/wiki/Clients>`_.
"""
from collections import namedtuple
import re
#: The MPD protocol uses UTF-8 for encoding all data.
@ -21,12 +22,16 @@ LINE_TERMINATOR = u'\n'
#: The MPD protocol version is 0.16.0.
VERSION = u'0.16.0'
MpdCommand = namedtuple('MpdCommand', ['name', 'auth_required'])
#: List of all available commands, represented as :class:`MpdCommand` objects.
mpd_commands = set()
request_handlers = {}
def handle_pattern(pattern):
def handle_request(pattern, auth_required=True):
"""
Decorator for connecting command handlers to command patterns.
Decorator for connecting command handlers to command requests.
If you use named groups in the pattern, the decorated method will get the
groups as keyword arguments. If the group is optional, remember to give the
@ -35,7 +40,7 @@ def handle_pattern(pattern):
For example, if the command is ``do that thing`` the ``what`` argument will
be ``this thing``::
@handle_pattern('^do (?P<what>.+)$')
@handle_request('^do (?P<what>.+)$')
def do(what):
...
@ -45,7 +50,8 @@ def handle_pattern(pattern):
def decorator(func):
match = re.search('([a-z_]+)', pattern)
if match is not None:
mpd_commands.add(match.group())
mpd_commands.add(
MpdCommand(name=match.group(), auth_required=auth_required))
if pattern in request_handlers:
raise ValueError(u'Tried to redefine handler for %s with %s' % (
pattern, func))

View File

@ -1,8 +1,8 @@
from mopidy.frontends.mpd.protocol import handle_pattern
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
@handle_pattern(r'^disableoutput "(?P<outputid>\d+)"$')
def disableoutput(frontend, outputid):
@handle_request(r'^disableoutput "(?P<outputid>\d+)"$')
def disableoutput(context, outputid):
"""
*musicpd.org, audio output section:*
@ -12,8 +12,8 @@ def disableoutput(frontend, outputid):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^enableoutput "(?P<outputid>\d+)"$')
def enableoutput(frontend, outputid):
@handle_request(r'^enableoutput "(?P<outputid>\d+)"$')
def enableoutput(context, outputid):
"""
*musicpd.org, audio output section:*
@ -23,8 +23,8 @@ def enableoutput(frontend, outputid):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^outputs$')
def outputs(frontend):
@handle_request(r'^outputs$')
def outputs(context):
"""
*musicpd.org, audio output section:*

View File

@ -1,8 +1,8 @@
from mopidy.frontends.mpd.protocol import handle_pattern
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import MpdUnknownCommand
@handle_pattern(r'^command_list_begin$')
def command_list_begin(frontend):
@handle_request(r'^command_list_begin$')
def command_list_begin(context):
"""
*musicpd.org, command list section:*
@ -18,31 +18,33 @@ def command_list_begin(frontend):
returned. If ``command_list_ok_begin`` is used, ``list_OK`` is
returned for each successful command executed in the command list.
"""
frontend.command_list = []
frontend.command_list_ok = False
context.dispatcher.command_list = []
context.dispatcher.command_list_ok = False
@handle_pattern(r'^command_list_end$')
def command_list_end(frontend):
@handle_request(r'^command_list_end$')
def command_list_end(context):
"""See :meth:`command_list_begin()`."""
if frontend.command_list is False:
if context.dispatcher.command_list is False:
# Test for False exactly, and not e.g. empty list
raise MpdUnknownCommand(command='command_list_end')
(command_list, frontend.command_list) = (frontend.command_list, False)
(command_list_ok, frontend.command_list_ok) = (
frontend.command_list_ok, False)
result = []
for i, command in enumerate(command_list):
response = frontend.handle_request(command, command_list_index=i)
if response is not None:
result.append(response)
if response and response[-1].startswith(u'ACK'):
return result
(command_list, context.dispatcher.command_list) = (
context.dispatcher.command_list, False)
(command_list_ok, context.dispatcher.command_list_ok) = (
context.dispatcher.command_list_ok, False)
command_list_response = []
for index, command in enumerate(command_list):
response = context.dispatcher.handle_request(
command, current_command_list_index=index)
command_list_response.extend(response)
if (command_list_response and
command_list_response[-1].startswith(u'ACK')):
return command_list_response
if command_list_ok:
response.append(u'list_OK')
return result
command_list_response.append(u'list_OK')
return command_list_response
@handle_pattern(r'^command_list_ok_begin$')
def command_list_ok_begin(frontend):
@handle_request(r'^command_list_ok_begin$')
def command_list_ok_begin(context):
"""See :meth:`command_list_begin()`."""
frontend.command_list = []
frontend.command_list_ok = True
context.dispatcher.command_list = []
context.dispatcher.command_list_ok = True

View File

@ -1,9 +1,10 @@
from mopidy import settings
from mopidy.frontends.mpd.protocol import handle_pattern
from mopidy.frontends.mpd.exceptions import MpdPasswordError
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import (MpdPasswordError,
MpdPermissionError)
@handle_pattern(r'^close$')
def close(frontend):
@handle_request(r'^close$', auth_required=False)
def close(context):
"""
*musicpd.org, connection section:*
@ -11,10 +12,10 @@ def close(frontend):
Closes the connection to MPD.
"""
pass # TODO
context.session.close()
@handle_pattern(r'^kill$')
def kill(frontend):
@handle_request(r'^kill$')
def kill(context):
"""
*musicpd.org, connection section:*
@ -22,10 +23,10 @@ def kill(frontend):
Kills MPD.
"""
pass # TODO
raise MpdPermissionError(command=u'kill')
@handle_pattern(r'^password "(?P<password>[^"]+)"$')
def password_(frontend, password):
@handle_request(r'^password "(?P<password>[^"]+)"$', auth_required=False)
def password_(context, password):
"""
*musicpd.org, connection section:*
@ -34,14 +35,13 @@ def password_(frontend, password):
This is used for authentication with the server. ``PASSWORD`` is
simply the plaintext password.
"""
# You will not get to this code without being authenticated. This is for
# when you are already authenticated, and are sending additional 'password'
# requests.
if settings.MPD_SERVER_PASSWORD != password:
if password == settings.MPD_SERVER_PASSWORD:
context.dispatcher.authenticated = True
else:
raise MpdPasswordError(u'incorrect password', command=u'password')
@handle_pattern(r'^ping$')
def ping(frontend):
@handle_request(r'^ping$', auth_required=False)
def ping(context):
"""
*musicpd.org, connection section:*

View File

@ -1,10 +1,10 @@
from mopidy.frontends.mpd.exceptions import (MpdArgError, MpdNoExistError,
MpdNotImplemented)
from mopidy.frontends.mpd.protocol import handle_pattern
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.translator import tracks_to_mpd_format
@handle_pattern(r'^add "(?P<uri>[^"]*)"$')
def add(frontend, uri):
@handle_request(r'^add "(?P<uri>[^"]*)"$')
def add(context, uri):
"""
*musicpd.org, current playlist section:*
@ -19,17 +19,17 @@ def add(frontend, uri):
"""
if not uri:
return
for handler_prefix in frontend.backend.uri_handlers.get():
for handler_prefix in context.backend.uri_handlers.get():
if uri.startswith(handler_prefix):
track = frontend.backend.library.lookup(uri).get()
track = context.backend.library.lookup(uri).get()
if track is not None:
frontend.backend.current_playlist.add(track)
context.backend.current_playlist.add(track)
return
raise MpdNoExistError(
u'directory or file not found', command=u'add')
@handle_pattern(r'^addid "(?P<uri>[^"]*)"( "(?P<songpos>\d+)")*$')
def addid(frontend, uri, songpos=None):
@handle_request(r'^addid "(?P<uri>[^"]*)"( "(?P<songpos>\d+)")*$')
def addid(context, uri, songpos=None):
"""
*musicpd.org, current playlist section:*
@ -51,18 +51,18 @@ def addid(frontend, uri, songpos=None):
raise MpdNoExistError(u'No such song', command=u'addid')
if songpos is not None:
songpos = int(songpos)
track = frontend.backend.library.lookup(uri).get()
track = context.backend.library.lookup(uri).get()
if track is None:
raise MpdNoExistError(u'No such song', command=u'addid')
if songpos and songpos > len(
frontend.backend.current_playlist.tracks.get()):
context.backend.current_playlist.tracks.get()):
raise MpdArgError(u'Bad song index', command=u'addid')
cp_track = frontend.backend.current_playlist.add(track,
cp_track = context.backend.current_playlist.add(track,
at_position=songpos).get()
return ('Id', cp_track[0])
return ('Id', cp_track.cpid)
@handle_pattern(r'^delete "(?P<start>\d+):(?P<end>\d+)*"$')
def delete_range(frontend, start, end=None):
@handle_request(r'^delete "(?P<start>\d+):(?P<end>\d+)*"$')
def delete_range(context, start, end=None):
"""
*musicpd.org, current playlist section:*
@ -74,25 +74,25 @@ def delete_range(frontend, start, end=None):
if end is not None:
end = int(end)
else:
end = len(frontend.backend.current_playlist.tracks.get())
cp_tracks = frontend.backend.current_playlist.cp_tracks.get()[start:end]
end = len(context.backend.current_playlist.tracks.get())
cp_tracks = context.backend.current_playlist.cp_tracks.get()[start:end]
if not cp_tracks:
raise MpdArgError(u'Bad song index', command=u'delete')
for (cpid, _) in cp_tracks:
frontend.backend.current_playlist.remove(cpid=cpid)
context.backend.current_playlist.remove(cpid=cpid)
@handle_pattern(r'^delete "(?P<songpos>\d+)"$')
def delete_songpos(frontend, songpos):
@handle_request(r'^delete "(?P<songpos>\d+)"$')
def delete_songpos(context, songpos):
"""See :meth:`delete_range`"""
try:
songpos = int(songpos)
(cpid, _) = frontend.backend.current_playlist.cp_tracks.get()[songpos]
frontend.backend.current_playlist.remove(cpid=cpid)
(cpid, _) = context.backend.current_playlist.cp_tracks.get()[songpos]
context.backend.current_playlist.remove(cpid=cpid)
except IndexError:
raise MpdArgError(u'Bad song index', command=u'delete')
@handle_pattern(r'^deleteid "(?P<cpid>\d+)"$')
def deleteid(frontend, cpid):
@handle_request(r'^deleteid "(?P<cpid>\d+)"$')
def deleteid(context, cpid):
"""
*musicpd.org, current playlist section:*
@ -102,14 +102,14 @@ def deleteid(frontend, cpid):
"""
try:
cpid = int(cpid)
if frontend.backend.playback.current_cpid.get() == cpid:
frontend.backend.playback.next()
return frontend.backend.current_playlist.remove(cpid=cpid).get()
if context.backend.playback.current_cpid.get() == cpid:
context.backend.playback.next()
return context.backend.current_playlist.remove(cpid=cpid).get()
except LookupError:
raise MpdNoExistError(u'No such song', command=u'deleteid')
@handle_pattern(r'^clear$')
def clear(frontend):
@handle_request(r'^clear$')
def clear(context):
"""
*musicpd.org, current playlist section:*
@ -117,10 +117,10 @@ def clear(frontend):
Clears the current playlist.
"""
frontend.backend.current_playlist.clear()
context.backend.current_playlist.clear()
@handle_pattern(r'^move "(?P<start>\d+):(?P<end>\d+)*" "(?P<to>\d+)"$')
def move_range(frontend, start, to, end=None):
@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:*
@ -130,21 +130,21 @@ def move_range(frontend, start, to, end=None):
``TO`` in the playlist.
"""
if end is None:
end = len(frontend.backend.current_playlist.tracks.get())
end = len(context.backend.current_playlist.tracks.get())
start = int(start)
end = int(end)
to = int(to)
frontend.backend.current_playlist.move(start, end, to)
context.backend.current_playlist.move(start, end, to)
@handle_pattern(r'^move "(?P<songpos>\d+)" "(?P<to>\d+)"$')
def move_songpos(frontend, songpos, to):
@handle_request(r'^move "(?P<songpos>\d+)" "(?P<to>\d+)"$')
def move_songpos(context, songpos, to):
"""See :meth:`move_range`."""
songpos = int(songpos)
to = int(to)
frontend.backend.current_playlist.move(songpos, songpos + 1, to)
context.backend.current_playlist.move(songpos, songpos + 1, to)
@handle_pattern(r'^moveid "(?P<cpid>\d+)" "(?P<to>\d+)"$')
def moveid(frontend, cpid, to):
@handle_request(r'^moveid "(?P<cpid>\d+)" "(?P<to>\d+)"$')
def moveid(context, cpid, to):
"""
*musicpd.org, current playlist section:*
@ -156,13 +156,13 @@ def moveid(frontend, cpid, to):
"""
cpid = int(cpid)
to = int(to)
cp_track = frontend.backend.current_playlist.get(cpid=cpid).get()
position = frontend.backend.current_playlist.cp_tracks.get().index(
cp_track = context.backend.current_playlist.get(cpid=cpid).get()
position = context.backend.current_playlist.cp_tracks.get().index(
cp_track)
frontend.backend.current_playlist.move(position, position + 1, to)
context.backend.current_playlist.move(position, position + 1, to)
@handle_pattern(r'^playlist$')
def playlist(frontend):
@handle_request(r'^playlist$')
def playlist(context):
"""
*musicpd.org, current playlist section:*
@ -174,11 +174,11 @@ def playlist(frontend):
Do not use this, instead use ``playlistinfo``.
"""
return playlistinfo(frontend)
return playlistinfo(context)
@handle_pattern(r'^playlistfind (?P<tag>[^"]+) "(?P<needle>[^"]+)"$')
@handle_pattern(r'^playlistfind "(?P<tag>[^"]+)" "(?P<needle>[^"]+)"$')
def playlistfind(frontend, tag, needle):
@handle_request(r'^playlistfind (?P<tag>[^"]+) "(?P<needle>[^"]+)"$')
@handle_request(r'^playlistfind "(?P<tag>[^"]+)" "(?P<needle>[^"]+)"$')
def playlistfind(context, tag, needle):
"""
*musicpd.org, current playlist section:*
@ -192,17 +192,17 @@ def playlistfind(frontend, tag, needle):
"""
if tag == 'filename':
try:
cp_track = frontend.backend.current_playlist.get(uri=needle).get()
cp_track = context.backend.current_playlist.get(uri=needle).get()
(cpid, track) = cp_track
position = frontend.backend.current_playlist.cp_tracks.get().index(
position = context.backend.current_playlist.cp_tracks.get().index(
cp_track)
return track.mpd_format(cpid=cpid, position=position)
except LookupError:
return None
raise MpdNotImplemented # TODO
@handle_pattern(r'^playlistid( "(?P<cpid>\d+)")*$')
def playlistid(frontend, cpid=None):
@handle_request(r'^playlistid( "(?P<cpid>\d+)")*$')
def playlistid(context, cpid=None):
"""
*musicpd.org, current playlist section:*
@ -214,22 +214,22 @@ def playlistid(frontend, cpid=None):
if cpid is not None:
try:
cpid = int(cpid)
cp_track = frontend.backend.current_playlist.get(cpid=cpid).get()
position = frontend.backend.current_playlist.cp_tracks.get().index(
cp_track = context.backend.current_playlist.get(cpid=cpid).get()
position = context.backend.current_playlist.cp_tracks.get().index(
cp_track)
return cp_track[1].mpd_format(position=position, cpid=cpid)
return cp_track.track.mpd_format(position=position, cpid=cpid)
except LookupError:
raise MpdNoExistError(u'No such song', command=u'playlistid')
else:
cpids = [ct[0] for ct in
frontend.backend.current_playlist.cp_tracks.get()]
context.backend.current_playlist.cp_tracks.get()]
return tracks_to_mpd_format(
frontend.backend.current_playlist.tracks.get(), cpids=cpids)
context.backend.current_playlist.tracks.get(), cpids=cpids)
@handle_pattern(r'^playlistinfo$')
@handle_pattern(r'^playlistinfo "(?P<songpos>-?\d+)"$')
@handle_pattern(r'^playlistinfo "(?P<start>\d+):(?P<end>\d+)*"$')
def playlistinfo(frontend, songpos=None,
@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:*
@ -255,30 +255,30 @@ def playlistinfo(frontend, songpos=None,
if start == -1:
end = None
cpids = [ct[0] for ct in
frontend.backend.current_playlist.cp_tracks.get()]
context.backend.current_playlist.cp_tracks.get()]
return tracks_to_mpd_format(
frontend.backend.current_playlist.tracks.get(),
context.backend.current_playlist.tracks.get(),
start, end, cpids=cpids)
else:
if start is None:
start = 0
start = int(start)
if not (0 <= start <= len(
frontend.backend.current_playlist.tracks.get())):
context.backend.current_playlist.tracks.get())):
raise MpdArgError(u'Bad song index', command=u'playlistinfo')
if end is not None:
end = int(end)
if end > len(frontend.backend.current_playlist.tracks.get()):
if end > len(context.backend.current_playlist.tracks.get()):
end = None
cpids = [ct[0] for ct in
frontend.backend.current_playlist.cp_tracks.get()]
context.backend.current_playlist.cp_tracks.get()]
return tracks_to_mpd_format(
frontend.backend.current_playlist.tracks.get(),
context.backend.current_playlist.tracks.get(),
start, end, cpids=cpids)
@handle_pattern(r'^playlistsearch "(?P<tag>[^"]+)" "(?P<needle>[^"]+)"$')
@handle_pattern(r'^playlistsearch (?P<tag>\S+) "(?P<needle>[^"]+)"$')
def playlistsearch(frontend, tag, needle):
@handle_request(r'^playlistsearch "(?P<tag>[^"]+)" "(?P<needle>[^"]+)"$')
@handle_request(r'^playlistsearch (?P<tag>\S+) "(?P<needle>[^"]+)"$')
def playlistsearch(context, tag, needle):
"""
*musicpd.org, current playlist section:*
@ -294,9 +294,9 @@ def playlistsearch(frontend, tag, needle):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^plchanges (?P<version>-?\d+)$')
@handle_pattern(r'^plchanges "(?P<version>-?\d+)"$')
def plchanges(frontend, version):
@handle_request(r'^plchanges (?P<version>-?\d+)$')
@handle_request(r'^plchanges "(?P<version>-?\d+)"$')
def plchanges(context, version):
"""
*musicpd.org, current playlist section:*
@ -312,14 +312,14 @@ def plchanges(frontend, version):
- Calls ``plchanges "-1"`` two times per second to get the entire playlist.
"""
# XXX Naive implementation that returns all tracks as changed
if int(version) < frontend.backend.current_playlist.version:
if int(version) < context.backend.current_playlist.version:
cpids = [ct[0] for ct in
frontend.backend.current_playlist.cp_tracks.get()]
context.backend.current_playlist.cp_tracks.get()]
return tracks_to_mpd_format(
frontend.backend.current_playlist.tracks.get(), cpids=cpids)
context.backend.current_playlist.tracks.get(), cpids=cpids)
@handle_pattern(r'^plchangesposid "(?P<version>\d+)"$')
def plchangesposid(frontend, version):
@handle_request(r'^plchangesposid "(?P<version>\d+)"$')
def plchangesposid(context, version):
"""
*musicpd.org, current playlist section:*
@ -333,17 +333,17 @@ def plchangesposid(frontend, version):
``playlistlength`` returned by status command.
"""
# XXX Naive implementation that returns all tracks as changed
if int(version) != frontend.backend.current_playlist.version.get():
if int(version) != context.backend.current_playlist.version.get():
result = []
for (position, (cpid, _)) in enumerate(
frontend.backend.current_playlist.cp_tracks.get()):
context.backend.current_playlist.cp_tracks.get()):
result.append((u'cpos', position))
result.append((u'Id', cpid))
return result
@handle_pattern(r'^shuffle$')
@handle_pattern(r'^shuffle "(?P<start>\d+):(?P<end>\d+)*"$')
def shuffle(frontend, start=None, end=None):
@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:*
@ -356,10 +356,10 @@ def shuffle(frontend, start=None, end=None):
start = int(start)
if end is not None:
end = int(end)
frontend.backend.current_playlist.shuffle(start, end)
context.backend.current_playlist.shuffle(start, end)
@handle_pattern(r'^swap "(?P<songpos1>\d+)" "(?P<songpos2>\d+)"$')
def swap(frontend, songpos1, songpos2):
@handle_request(r'^swap "(?P<songpos1>\d+)" "(?P<songpos2>\d+)"$')
def swap(context, songpos1, songpos2):
"""
*musicpd.org, current playlist section:*
@ -369,18 +369,18 @@ def swap(frontend, songpos1, songpos2):
"""
songpos1 = int(songpos1)
songpos2 = int(songpos2)
tracks = frontend.backend.current_playlist.tracks.get()
tracks = context.backend.current_playlist.tracks.get()
song1 = tracks[songpos1]
song2 = tracks[songpos2]
del tracks[songpos1]
tracks.insert(songpos1, song2)
del tracks[songpos2]
tracks.insert(songpos2, song1)
frontend.backend.current_playlist.clear()
frontend.backend.current_playlist.append(tracks)
context.backend.current_playlist.clear()
context.backend.current_playlist.append(tracks)
@handle_pattern(r'^swapid "(?P<cpid1>\d+)" "(?P<cpid2>\d+)"$')
def swapid(frontend, cpid1, cpid2):
@handle_request(r'^swapid "(?P<cpid1>\d+)" "(?P<cpid2>\d+)"$')
def swapid(context, cpid1, cpid2):
"""
*musicpd.org, current playlist section:*
@ -390,9 +390,9 @@ def swapid(frontend, cpid1, cpid2):
"""
cpid1 = int(cpid1)
cpid2 = int(cpid2)
cp_track1 = frontend.backend.current_playlist.get(cpid=cpid1).get()
cp_track2 = frontend.backend.current_playlist.get(cpid=cpid2).get()
cp_tracks = frontend.backend.current_playlist.cp_tracks.get()
cp_track1 = context.backend.current_playlist.get(cpid=cpid1).get()
cp_track2 = context.backend.current_playlist.get(cpid=cpid2).get()
cp_tracks = context.backend.current_playlist.cp_tracks.get()
position1 = cp_tracks.index(cp_track1)
position2 = cp_tracks.index(cp_track2)
swap(frontend, position1, position2)
swap(context, position1, position2)

View File

@ -1,6 +1,6 @@
from mopidy.frontends.mpd.protocol import handle_pattern
from mopidy.frontends.mpd.protocol import handle_request
@handle_pattern(r'^$')
def empty(frontend):
@handle_request(r'^$')
def empty(context):
"""The original MPD server returns ``OK`` on an empty request."""
pass

View File

@ -1,7 +1,7 @@
import re
import shlex
from mopidy.frontends.mpd.protocol import handle_pattern, stored_playlists
from mopidy.frontends.mpd.protocol import handle_request, stored_playlists
from mopidy.frontends.mpd.exceptions import MpdArgError, MpdNotImplemented
def _build_query(mpd_query):
@ -28,8 +28,8 @@ def _build_query(mpd_query):
query[field] = [what]
return query
@handle_pattern(r'^count "(?P<tag>[^"]+)" "(?P<needle>[^"]*)"$')
def count(frontend, tag, needle):
@handle_request(r'^count "(?P<tag>[^"]+)" "(?P<needle>[^"]*)"$')
def count(context, tag, needle):
"""
*musicpd.org, music database section:*
@ -40,10 +40,10 @@ def count(frontend, tag, needle):
"""
return [('songs', 0), ('playtime', 0)] # TODO
@handle_pattern(r'^find '
@handle_request(r'^find '
r'(?P<mpd_query>("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ilename|'
r'[Tt]itle|[Aa]ny)"? "[^"]+"\s?)+)$')
def find(frontend, mpd_query):
def find(context, mpd_query):
"""
*musicpd.org, music database section:*
@ -68,12 +68,12 @@ def find(frontend, mpd_query):
- also uses the search type "date".
"""
query = _build_query(mpd_query)
return frontend.backend.library.find_exact(**query).get().mpd_format()
return context.backend.library.find_exact(**query).get().mpd_format()
@handle_pattern(r'^findadd '
@handle_request(r'^findadd '
r'(?P<query>("?([Aa]lbum|[Aa]rtist|[Ff]ilename|[Tt]itle|[Aa]ny)"? '
'"[^"]+"\s?)+)$')
def findadd(frontend, query):
def findadd(context, query):
"""
*musicpd.org, music database section:*
@ -84,11 +84,11 @@ def findadd(frontend, query):
``WHAT`` is what to find.
"""
# TODO Add result to current playlist
#result = frontend.find(query)
#result = context.find(query)
@handle_pattern(r'^list "?(?P<field>([Aa]rtist|[Aa]lbum|[Dd]ate|[Gg]enre))"?'
@handle_request(r'^list "?(?P<field>([Aa]rtist|[Aa]lbum|[Dd]ate|[Gg]enre))"?'
'( (?P<mpd_query>.*))?$')
def list_(frontend, field, mpd_query=None):
def list_(context, field, mpd_query=None):
"""
*musicpd.org, music database section:*
@ -175,11 +175,11 @@ def list_(frontend, field, mpd_query=None):
field = field.lower()
query = _list_build_query(field, mpd_query)
if field == u'artist':
return _list_artist(frontend, query)
return _list_artist(context, query)
elif field == u'album':
return _list_album(frontend, query)
return _list_album(context, query)
elif field == u'date':
return _list_date(frontend, query)
return _list_date(context, query)
elif field == u'genre':
pass # TODO We don't have genre in our internal data structures yet
@ -213,32 +213,32 @@ def _list_build_query(field, mpd_query):
else:
raise MpdArgError(u'not able to parse args', command=u'list')
def _list_artist(frontend, query):
def _list_artist(context, query):
artists = set()
playlist = frontend.backend.library.find_exact(**query).get()
playlist = context.backend.library.find_exact(**query).get()
for track in playlist.tracks:
for artist in track.artists:
artists.add((u'Artist', artist.name))
return artists
def _list_album(frontend, query):
def _list_album(context, query):
albums = set()
playlist = frontend.backend.library.find_exact(**query).get()
playlist = context.backend.library.find_exact(**query).get()
for track in playlist.tracks:
if track.album is not None:
albums.add((u'Album', track.album.name))
return albums
def _list_date(frontend, query):
def _list_date(context, query):
dates = set()
playlist = frontend.backend.library.find_exact(**query).get()
playlist = context.backend.library.find_exact(**query).get()
for track in playlist.tracks:
if track.date is not None:
dates.add((u'Date', track.date.strftime('%Y-%m-%d')))
return dates
@handle_pattern(r'^listall "(?P<uri>[^"]+)"')
def listall(frontend, uri):
@handle_request(r'^listall "(?P<uri>[^"]+)"')
def listall(context, uri):
"""
*musicpd.org, music database section:*
@ -248,8 +248,8 @@ def listall(frontend, uri):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^listallinfo "(?P<uri>[^"]+)"')
def listallinfo(frontend, uri):
@handle_request(r'^listallinfo "(?P<uri>[^"]+)"')
def listallinfo(context, uri):
"""
*musicpd.org, music database section:*
@ -260,9 +260,9 @@ def listallinfo(frontend, uri):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^lsinfo$')
@handle_pattern(r'^lsinfo "(?P<uri>[^"]*)"$')
def lsinfo(frontend, uri=None):
@handle_request(r'^lsinfo$')
@handle_request(r'^lsinfo "(?P<uri>[^"]*)"$')
def lsinfo(context, uri=None):
"""
*musicpd.org, music database section:*
@ -279,11 +279,11 @@ def lsinfo(frontend, uri=None):
""``, and ``lsinfo "/"``.
"""
if uri is None or uri == u'/' or uri == u'':
return stored_playlists.listplaylists(frontend)
return stored_playlists.listplaylists(context)
raise MpdNotImplemented # TODO
@handle_pattern(r'^rescan( "(?P<uri>[^"]+)")*$')
def rescan(frontend, uri=None):
@handle_request(r'^rescan( "(?P<uri>[^"]+)")*$')
def rescan(context, uri=None):
"""
*musicpd.org, music database section:*
@ -291,12 +291,12 @@ def rescan(frontend, uri=None):
Same as ``update``, but also rescans unmodified files.
"""
return update(frontend, uri, rescan_unmodified_files=True)
return update(context, uri, rescan_unmodified_files=True)
@handle_pattern(r'^search '
@handle_request(r'^search '
r'(?P<mpd_query>("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ilename|'
r'[Tt]itle|[Aa]ny)"? "[^"]+"\s?)+)$')
def search(frontend, mpd_query):
def search(context, mpd_query):
"""
*musicpd.org, music database section:*
@ -324,10 +324,10 @@ def search(frontend, mpd_query):
- also uses the search type "date".
"""
query = _build_query(mpd_query)
return frontend.backend.library.search(**query).get().mpd_format()
return context.backend.library.search(**query).get().mpd_format()
@handle_pattern(r'^update( "(?P<uri>[^"]+)")*$')
def update(frontend, uri=None, rescan_unmodified_files=False):
@handle_request(r'^update( "(?P<uri>[^"]+)")*$')
def update(context, uri=None, rescan_unmodified_files=False):
"""
*musicpd.org, music database section:*

View File

@ -1,11 +1,11 @@
from mopidy.backends.base import PlaybackController
from mopidy.frontends.mpd.protocol import handle_pattern
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import (MpdArgError, MpdNoExistError,
MpdNotImplemented)
@handle_pattern(r'^consume (?P<state>[01])$')
@handle_pattern(r'^consume "(?P<state>[01])"$')
def consume(frontend, state):
@handle_request(r'^consume (?P<state>[01])$')
@handle_request(r'^consume "(?P<state>[01])"$')
def consume(context, state):
"""
*musicpd.org, playback section:*
@ -16,12 +16,12 @@ def consume(frontend, state):
playlist.
"""
if int(state):
frontend.backend.playback.consume = True
context.backend.playback.consume = True
else:
frontend.backend.playback.consume = False
context.backend.playback.consume = False
@handle_pattern(r'^crossfade "(?P<seconds>\d+)"$')
def crossfade(frontend, seconds):
@handle_request(r'^crossfade "(?P<seconds>\d+)"$')
def crossfade(context, seconds):
"""
*musicpd.org, playback section:*
@ -32,8 +32,8 @@ def crossfade(frontend, seconds):
seconds = int(seconds)
raise MpdNotImplemented # TODO
@handle_pattern(r'^next$')
def next_(frontend):
@handle_request(r'^next$')
def next_(context):
"""
*musicpd.org, playback section:*
@ -87,11 +87,11 @@ def next_(frontend):
order as the first time.
"""
return frontend.backend.playback.next().get()
return context.backend.playback.next().get()
@handle_pattern(r'^pause$')
@handle_pattern(r'^pause "(?P<state>[01])"$')
def pause(frontend, state=None):
@handle_request(r'^pause$')
@handle_request(r'^pause "(?P<state>[01])"$')
def pause(context, state=None):
"""
*musicpd.org, playback section:*
@ -104,28 +104,28 @@ def pause(frontend, state=None):
- Calls ``pause`` without any arguments to toogle pause.
"""
if state is None:
if (frontend.backend.playback.state.get() ==
if (context.backend.playback.state.get() ==
PlaybackController.PLAYING):
frontend.backend.playback.pause()
elif (frontend.backend.playback.state.get() ==
context.backend.playback.pause()
elif (context.backend.playback.state.get() ==
PlaybackController.PAUSED):
frontend.backend.playback.resume()
context.backend.playback.resume()
elif int(state):
frontend.backend.playback.pause()
context.backend.playback.pause()
else:
frontend.backend.playback.resume()
context.backend.playback.resume()
@handle_pattern(r'^play$')
def play(frontend):
@handle_request(r'^play$')
def play(context):
"""
The original MPD server resumes from the paused state on ``play``
without arguments.
"""
return frontend.backend.playback.play().get()
return context.backend.playback.play().get()
@handle_pattern(r'^playid "(?P<cpid>\d+)"$')
@handle_pattern(r'^playid "(?P<cpid>-1)"$')
def playid(frontend, cpid):
@handle_request(r'^playid "(?P<cpid>\d+)"$')
@handle_request(r'^playid "(?P<cpid>-1)"$')
def playid(context, cpid):
"""
*musicpd.org, playback section:*
@ -144,16 +144,16 @@ def playid(frontend, cpid):
"""
cpid = int(cpid)
if cpid == -1:
return _play_minus_one(frontend)
return _play_minus_one(context)
try:
cp_track = frontend.backend.current_playlist.get(cpid=cpid).get()
return frontend.backend.playback.play(cp_track).get()
cp_track = context.backend.current_playlist.get(cpid=cpid).get()
return context.backend.playback.play(cp_track).get()
except LookupError:
raise MpdNoExistError(u'No such song', command=u'playid')
@handle_pattern(r'^play (?P<songpos>-?\d+)$')
@handle_pattern(r'^play "(?P<songpos>-?\d+)"$')
def playpos(frontend, songpos):
@handle_request(r'^play (?P<songpos>-?\d+)$')
@handle_request(r'^play "(?P<songpos>-?\d+)"$')
def playpos(context, songpos):
"""
*musicpd.org, playback section:*
@ -176,29 +176,29 @@ def playpos(frontend, songpos):
"""
songpos = int(songpos)
if songpos == -1:
return _play_minus_one(frontend)
return _play_minus_one(context)
try:
cp_track = frontend.backend.current_playlist.cp_tracks.get()[songpos]
return frontend.backend.playback.play(cp_track).get()
cp_track = context.backend.current_playlist.cp_tracks.get()[songpos]
return context.backend.playback.play(cp_track).get()
except IndexError:
raise MpdArgError(u'Bad song index', command=u'play')
def _play_minus_one(frontend):
if (frontend.backend.playback.state.get() == PlaybackController.PLAYING):
def _play_minus_one(context):
if (context.backend.playback.state.get() == PlaybackController.PLAYING):
return # Nothing to do
elif (frontend.backend.playback.state.get() == PlaybackController.PAUSED):
return frontend.backend.playback.resume().get()
elif frontend.backend.playback.current_cp_track.get() is not None:
cp_track = frontend.backend.playback.current_cp_track.get()
return frontend.backend.playback.play(cp_track).get()
elif frontend.backend.current_playlist.cp_tracks.get():
cp_track = frontend.backend.current_playlist.cp_tracks.get()[0]
return frontend.backend.playback.play(cp_track).get()
elif (context.backend.playback.state.get() == PlaybackController.PAUSED):
return context.backend.playback.resume().get()
elif context.backend.playback.current_cp_track.get() is not None:
cp_track = context.backend.playback.current_cp_track.get()
return context.backend.playback.play(cp_track).get()
elif context.backend.current_playlist.cp_tracks.get():
cp_track = context.backend.current_playlist.cp_tracks.get()[0]
return context.backend.playback.play(cp_track).get()
else:
return # Fail silently
@handle_pattern(r'^previous$')
def previous(frontend):
@handle_request(r'^previous$')
def previous(context):
"""
*musicpd.org, playback section:*
@ -241,11 +241,11 @@ def previous(frontend):
``previous`` should do a seek to time position 0.
"""
return frontend.backend.playback.previous().get()
return context.backend.playback.previous().get()
@handle_pattern(r'^random (?P<state>[01])$')
@handle_pattern(r'^random "(?P<state>[01])"$')
def random(frontend, state):
@handle_request(r'^random (?P<state>[01])$')
@handle_request(r'^random "(?P<state>[01])"$')
def random(context, state):
"""
*musicpd.org, playback section:*
@ -254,13 +254,13 @@ def random(frontend, state):
Sets random state to ``STATE``, ``STATE`` should be 0 or 1.
"""
if int(state):
frontend.backend.playback.random = True
context.backend.playback.random = True
else:
frontend.backend.playback.random = False
context.backend.playback.random = False
@handle_pattern(r'^repeat (?P<state>[01])$')
@handle_pattern(r'^repeat "(?P<state>[01])"$')
def repeat(frontend, state):
@handle_request(r'^repeat (?P<state>[01])$')
@handle_request(r'^repeat "(?P<state>[01])"$')
def repeat(context, state):
"""
*musicpd.org, playback section:*
@ -269,12 +269,12 @@ def repeat(frontend, state):
Sets repeat state to ``STATE``, ``STATE`` should be 0 or 1.
"""
if int(state):
frontend.backend.playback.repeat = True
context.backend.playback.repeat = True
else:
frontend.backend.playback.repeat = False
context.backend.playback.repeat = False
@handle_pattern(r'^replay_gain_mode "(?P<mode>(off|track|album))"$')
def replay_gain_mode(frontend, mode):
@handle_request(r'^replay_gain_mode "(?P<mode>(off|track|album))"$')
def replay_gain_mode(context, mode):
"""
*musicpd.org, playback section:*
@ -289,8 +289,8 @@ def replay_gain_mode(frontend, mode):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^replay_gain_status$')
def replay_gain_status(frontend):
@handle_request(r'^replay_gain_status$')
def replay_gain_status(context):
"""
*musicpd.org, playback section:*
@ -301,9 +301,9 @@ def replay_gain_status(frontend):
"""
return u'off' # TODO
@handle_pattern(r'^seek (?P<songpos>\d+) (?P<seconds>\d+)$')
@handle_pattern(r'^seek "(?P<songpos>\d+)" "(?P<seconds>\d+)"$')
def seek(frontend, songpos, seconds):
@handle_request(r'^seek (?P<songpos>\d+) (?P<seconds>\d+)$')
@handle_request(r'^seek "(?P<songpos>\d+)" "(?P<seconds>\d+)"$')
def seek(context, songpos, seconds):
"""
*musicpd.org, playback section:*
@ -316,12 +316,12 @@ def seek(frontend, songpos, seconds):
- issues ``seek 1 120`` without quotes around the arguments.
"""
if frontend.backend.playback.current_playlist_position != songpos:
playpos(frontend, songpos)
frontend.backend.playback.seek(int(seconds) * 1000)
if context.backend.playback.current_playlist_position != songpos:
playpos(context, songpos)
context.backend.playback.seek(int(seconds) * 1000)
@handle_pattern(r'^seekid "(?P<cpid>\d+)" "(?P<seconds>\d+)"$')
def seekid(frontend, cpid, seconds):
@handle_request(r'^seekid "(?P<cpid>\d+)" "(?P<seconds>\d+)"$')
def seekid(context, cpid, seconds):
"""
*musicpd.org, playback section:*
@ -329,13 +329,13 @@ def seekid(frontend, cpid, seconds):
Seeks to the position ``TIME`` (in seconds) of song ``SONGID``.
"""
if frontend.backend.playback.current_cpid != cpid:
playid(frontend, cpid)
frontend.backend.playback.seek(int(seconds) * 1000)
if context.backend.playback.current_cpid != cpid:
playid(context, cpid)
context.backend.playback.seek(int(seconds) * 1000)
@handle_pattern(r'^setvol (?P<volume>[-+]*\d+)$')
@handle_pattern(r'^setvol "(?P<volume>[-+]*\d+)"$')
def setvol(frontend, volume):
@handle_request(r'^setvol (?P<volume>[-+]*\d+)$')
@handle_request(r'^setvol "(?P<volume>[-+]*\d+)"$')
def setvol(context, volume):
"""
*musicpd.org, playback section:*
@ -352,11 +352,11 @@ def setvol(frontend, volume):
volume = 0
if volume > 100:
volume = 100
frontend.mixer.volume = volume
context.mixer.volume = volume
@handle_pattern(r'^single (?P<state>[01])$')
@handle_pattern(r'^single "(?P<state>[01])"$')
def single(frontend, state):
@handle_request(r'^single (?P<state>[01])$')
@handle_request(r'^single "(?P<state>[01])"$')
def single(context, state):
"""
*musicpd.org, playback section:*
@ -367,12 +367,12 @@ def single(frontend, state):
song is repeated if the ``repeat`` mode is enabled.
"""
if int(state):
frontend.backend.playback.single = True
context.backend.playback.single = True
else:
frontend.backend.playback.single = False
context.backend.playback.single = False
@handle_pattern(r'^stop$')
def stop(frontend):
@handle_request(r'^stop$')
def stop(context):
"""
*musicpd.org, playback section:*
@ -380,4 +380,4 @@ def stop(frontend):
Stops playing.
"""
frontend.backend.playback.stop()
context.backend.playback.stop()

View File

@ -1,8 +1,8 @@
from mopidy.frontends.mpd.protocol import handle_pattern, mpd_commands
from mopidy.frontends.mpd.protocol import handle_request, mpd_commands
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
@handle_pattern(r'^commands$')
def commands(frontend):
@handle_request(r'^commands$', auth_required=False)
def commands(context):
"""
*musicpd.org, reflection section:*
@ -10,25 +10,34 @@ def commands(frontend):
Shows which commands the current user has access to.
"""
# FIXME When password auth is turned on and the client is not
# authenticated, 'commands' should list only the commands the client does
# have access to. To implement this we need access to the session object to
# check if the client is authenticated or not.
if context.dispatcher.authenticated:
command_names = [command.name for command in mpd_commands]
else:
command_names = [command.name for command in mpd_commands
if not command.auth_required]
sorted_commands = sorted(list(mpd_commands))
# No permission to use
if 'kill' in command_names:
command_names.remove('kill')
# Not shown by MPD in its command list
sorted_commands.remove('command_list_begin')
sorted_commands.remove('command_list_ok_begin')
sorted_commands.remove('command_list_end')
sorted_commands.remove('idle')
sorted_commands.remove('noidle')
sorted_commands.remove('sticker')
if 'command_list_begin' in command_names:
command_names.remove('command_list_begin')
if 'command_list_ok_begin' in command_names:
command_names.remove('command_list_ok_begin')
if 'command_list_end' in command_names:
command_names.remove('command_list_end')
if 'idle' in command_names:
command_names.remove('idle')
if 'noidle' in command_names:
command_names.remove('noidle')
if 'sticker' in command_names:
command_names.remove('sticker')
return [('command', c) for c in sorted_commands]
return [('command', command_name) for command_name in sorted(command_names)]
@handle_pattern(r'^decoders$')
def decoders(frontend):
@handle_request(r'^decoders$')
def decoders(context):
"""
*musicpd.org, reflection section:*
@ -46,8 +55,8 @@ def decoders(frontend):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^notcommands$')
def notcommands(frontend):
@handle_request(r'^notcommands$', auth_required=False)
def notcommands(context):
"""
*musicpd.org, reflection section:*
@ -55,14 +64,19 @@ def notcommands(frontend):
Shows which commands the current user does not have access to.
"""
# FIXME When password auth is turned on and the client is not
# authenticated, 'notcommands' should list all the commands the client does
# not have access to. To implement this we need access to the session
# object to check if the client is authenticated or not.
pass
if context.dispatcher.authenticated:
command_names = []
else:
command_names = [command.name for command in mpd_commands
if command.auth_required]
@handle_pattern(r'^tagtypes$')
def tagtypes(frontend):
# No permission to use
command_names.append('kill')
return [('command', command_name) for command_name in sorted(command_names)]
@handle_request(r'^tagtypes$')
def tagtypes(context):
"""
*musicpd.org, reflection section:*
@ -72,8 +86,8 @@ def tagtypes(frontend):
"""
pass # TODO
@handle_pattern(r'^urlhandlers$')
def urlhandlers(frontend):
@handle_request(r'^urlhandlers$')
def urlhandlers(context):
"""
*musicpd.org, reflection section:*
@ -81,4 +95,4 @@ def urlhandlers(frontend):
Gets a list of available URL handlers.
"""
return [(u'handler', uri) for uri in frontend.backend.uri_handlers.get()]
return [(u'handler', uri) for uri in context.backend.uri_handlers.get()]

View File

@ -1,9 +1,11 @@
import pykka.future
from mopidy.backends.base import PlaybackController
from mopidy.frontends.mpd.protocol import handle_pattern
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
@handle_pattern(r'^clearerror$')
def clearerror(frontend):
@handle_request(r'^clearerror$')
def clearerror(context):
"""
*musicpd.org, status section:*
@ -14,8 +16,8 @@ def clearerror(frontend):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^currentsong$')
def currentsong(frontend):
@handle_request(r'^currentsong$')
def currentsong(context):
"""
*musicpd.org, status section:*
@ -24,15 +26,15 @@ def currentsong(frontend):
Displays the song info of the current song (same song that is
identified in status).
"""
current_cp_track = frontend.backend.playback.current_cp_track.get()
current_cp_track = context.backend.playback.current_cp_track.get()
if current_cp_track is not None:
return current_cp_track[1].mpd_format(
position=frontend.backend.playback.current_playlist_position.get(),
cpid=current_cp_track[0])
return current_cp_track.track.mpd_format(
position=context.backend.playback.current_playlist_position.get(),
cpid=current_cp_track.cpid)
@handle_pattern(r'^idle$')
@handle_pattern(r'^idle (?P<subsystems>.+)$')
def idle(frontend, subsystems=None):
@handle_request(r'^idle$')
@handle_request(r'^idle (?P<subsystems>.+)$')
def idle(context, subsystems=None):
"""
*musicpd.org, status section:*
@ -67,13 +69,13 @@ def idle(frontend, subsystems=None):
"""
pass # TODO
@handle_pattern(r'^noidle$')
def noidle(frontend):
@handle_request(r'^noidle$')
def noidle(context):
"""See :meth:`_status_idle`."""
pass # TODO
@handle_pattern(r'^stats$')
def stats(frontend):
@handle_request(r'^stats$')
def stats(context):
"""
*musicpd.org, status section:*
@ -98,8 +100,8 @@ def stats(frontend):
'playtime': 0, # TODO
}
@handle_pattern(r'^status$')
def status(frontend):
@handle_request(r'^status$')
def status(context):
"""
*musicpd.org, status section:*
@ -130,65 +132,80 @@ def status(frontend):
- ``updatings_db``: job id
- ``error``: if there is an error, returns message here
"""
futures = {
'current_playlist.tracks': context.backend.current_playlist.tracks,
'current_playlist.version': context.backend.current_playlist.version,
'mixer.volume': context.mixer.volume,
'playback.consume': context.backend.playback.consume,
'playback.random': context.backend.playback.random,
'playback.repeat': context.backend.playback.repeat,
'playback.single': context.backend.playback.single,
'playback.state': context.backend.playback.state,
'playback.current_cp_track': context.backend.playback.current_cp_track,
'playback.current_playlist_position':
context.backend.playback.current_playlist_position,
'playback.time_position': context.backend.playback.time_position,
}
pykka.future.get_all(futures.values())
result = [
('volume', _status_volume(frontend)),
('repeat', _status_repeat(frontend)),
('random', _status_random(frontend)),
('single', _status_single(frontend)),
('consume', _status_consume(frontend)),
('playlist', _status_playlist_version(frontend)),
('playlistlength', _status_playlist_length(frontend)),
('xfade', _status_xfade(frontend)),
('state', _status_state(frontend)),
('volume', _status_volume(futures)),
('repeat', _status_repeat(futures)),
('random', _status_random(futures)),
('single', _status_single(futures)),
('consume', _status_consume(futures)),
('playlist', _status_playlist_version(futures)),
('playlistlength', _status_playlist_length(futures)),
('xfade', _status_xfade(futures)),
('state', _status_state(futures)),
]
if frontend.backend.playback.current_track.get() is not None:
result.append(('song', _status_songpos(frontend)))
result.append(('songid', _status_songid(frontend)))
if frontend.backend.playback.state.get() in (PlaybackController.PLAYING,
if futures['playback.current_cp_track'].get() is not None:
result.append(('song', _status_songpos(futures)))
result.append(('songid', _status_songid(futures)))
if futures['playback.state'].get() in (PlaybackController.PLAYING,
PlaybackController.PAUSED):
result.append(('time', _status_time(frontend)))
result.append(('elapsed', _status_time_elapsed(frontend)))
result.append(('bitrate', _status_bitrate(frontend)))
result.append(('time', _status_time(futures)))
result.append(('elapsed', _status_time_elapsed(futures)))
result.append(('bitrate', _status_bitrate(futures)))
return result
def _status_bitrate(frontend):
current_track = frontend.backend.playback.current_track.get()
if current_track is not None:
return current_track.bitrate
def _status_bitrate(futures):
current_cp_track = futures['playback.current_cp_track'].get()
if current_cp_track is not None:
return current_cp_track.track.bitrate
def _status_consume(frontend):
if frontend.backend.playback.consume.get():
def _status_consume(futures):
if futures['playback.consume'].get():
return 1
else:
return 0
def _status_playlist_length(frontend):
return len(frontend.backend.current_playlist.tracks.get())
def _status_playlist_length(futures):
return len(futures['current_playlist.tracks'].get())
def _status_playlist_version(frontend):
return frontend.backend.current_playlist.version.get()
def _status_playlist_version(futures):
return futures['current_playlist.version'].get()
def _status_random(frontend):
return int(frontend.backend.playback.random.get())
def _status_random(futures):
return int(futures['playback.random'].get())
def _status_repeat(frontend):
return int(frontend.backend.playback.repeat.get())
def _status_repeat(futures):
return int(futures['playback.repeat'].get())
def _status_single(frontend):
return int(frontend.backend.playback.single.get())
def _status_single(futures):
return int(futures['playback.single'].get())
def _status_songid(frontend):
current_cpid = frontend.backend.playback.current_cpid.get()
if current_cpid is not None:
return current_cpid
def _status_songid(futures):
current_cp_track = futures['playback.current_cp_track'].get()
if current_cp_track is not None:
return current_cp_track.cpid
else:
return _status_songpos(frontend)
return _status_songpos(futures)
def _status_songpos(frontend):
return frontend.backend.playback.current_playlist_position.get()
def _status_songpos(futures):
return futures['playback.current_playlist_position'].get()
def _status_state(frontend):
state = frontend.backend.playback.state.get()
def _status_state(futures):
state = futures['playback.state'].get()
if state == PlaybackController.PLAYING:
return u'play'
elif state == PlaybackController.STOPPED:
@ -196,28 +213,28 @@ def _status_state(frontend):
elif state == PlaybackController.PAUSED:
return u'pause'
def _status_time(frontend):
return u'%s:%s' % (_status_time_elapsed(frontend) // 1000,
_status_time_total(frontend) // 1000)
def _status_time(futures):
return u'%s:%s' % (_status_time_elapsed(futures) // 1000,
_status_time_total(futures) // 1000)
def _status_time_elapsed(frontend):
return frontend.backend.playback.time_position.get()
def _status_time_elapsed(futures):
return futures['playback.time_position'].get()
def _status_time_total(frontend):
current_track = frontend.backend.playback.current_track.get()
if current_track is None:
def _status_time_total(futures):
current_cp_track = futures['playback.current_cp_track'].get()
if current_cp_track is None:
return 0
elif current_track.length is None:
elif current_cp_track.track.length is None:
return 0
else:
return current_track.length
return current_cp_track.track.length
def _status_volume(frontend):
volume = frontend.mixer.volume.get()
def _status_volume(futures):
volume = futures['mixer.volume'].get()
if volume is not None:
return volume
else:
return 0
def _status_xfade(frontend):
return 0 # TODO
def _status_xfade(futures):
return 0 # Not supported

View File

@ -1,9 +1,9 @@
from mopidy.frontends.mpd.protocol import handle_pattern
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
@handle_pattern(r'^sticker delete "(?P<field>[^"]+)" '
@handle_request(r'^sticker delete "(?P<field>[^"]+)" '
r'"(?P<uri>[^"]+)"( "(?P<name>[^"]+)")*$')
def sticker_delete(frontend, field, uri, name=None):
def sticker_delete(context, field, uri, name=None):
"""
*musicpd.org, sticker section:*
@ -14,9 +14,9 @@ def sticker_delete(frontend, field, uri, name=None):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^sticker find "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
@handle_request(r'^sticker find "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
r'"(?P<name>[^"]+)"$')
def sticker_find(frontend, field, uri, name):
def sticker_find(context, field, uri, name):
"""
*musicpd.org, sticker section:*
@ -28,9 +28,9 @@ def sticker_find(frontend, field, uri, name):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^sticker get "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
@handle_request(r'^sticker get "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
r'"(?P<name>[^"]+)"$')
def sticker_get(frontend, field, uri, name):
def sticker_get(context, field, uri, name):
"""
*musicpd.org, sticker section:*
@ -40,8 +40,8 @@ def sticker_get(frontend, field, uri, name):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^sticker list "(?P<field>[^"]+)" "(?P<uri>[^"]+)"$')
def sticker_list(frontend, field, uri):
@handle_request(r'^sticker list "(?P<field>[^"]+)" "(?P<uri>[^"]+)"$')
def sticker_list(context, field, uri):
"""
*musicpd.org, sticker section:*
@ -51,9 +51,9 @@ def sticker_list(frontend, field, uri):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^sticker set "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
@handle_request(r'^sticker set "(?P<field>[^"]+)" "(?P<uri>[^"]+)" '
r'"(?P<name>[^"]+)" "(?P<value>[^"]+)"$')
def sticker_set(frontend, field, uri, name, value):
def sticker_set(context, field, uri, name, value):
"""
*musicpd.org, sticker section:*

View File

@ -1,10 +1,10 @@
import datetime as dt
from mopidy.frontends.mpd.protocol import handle_pattern
from mopidy.frontends.mpd.protocol import handle_request
from mopidy.frontends.mpd.exceptions import MpdNoExistError, MpdNotImplemented
@handle_pattern(r'^listplaylist "(?P<name>[^"]+)"$')
def listplaylist(frontend, name):
@handle_request(r'^listplaylist "(?P<name>[^"]+)"$')
def listplaylist(context, name):
"""
*musicpd.org, stored playlists section:*
@ -19,13 +19,13 @@ def listplaylist(frontend, name):
file: relative/path/to/file3.mp3
"""
try:
playlist = frontend.backend.stored_playlists.get(name=name).get()
playlist = context.backend.stored_playlists.get(name=name).get()
return ['file: %s' % t.uri for t in playlist.tracks]
except LookupError:
raise MpdNoExistError(u'No such playlist', command=u'listplaylist')
@handle_pattern(r'^listplaylistinfo "(?P<name>[^"]+)"$')
def listplaylistinfo(frontend, name):
@handle_request(r'^listplaylistinfo "(?P<name>[^"]+)"$')
def listplaylistinfo(context, name):
"""
*musicpd.org, stored playlists section:*
@ -39,14 +39,14 @@ def listplaylistinfo(frontend, name):
Album, Artist, Track
"""
try:
playlist = frontend.backend.stored_playlists.get(name=name).get()
playlist = context.backend.stored_playlists.get(name=name).get()
return playlist.mpd_format()
except LookupError:
raise MpdNoExistError(
u'No such playlist', command=u'listplaylistinfo')
@handle_pattern(r'^listplaylists$')
def listplaylists(frontend):
@handle_request(r'^listplaylists$')
def listplaylists(context):
"""
*musicpd.org, stored playlists section:*
@ -67,7 +67,7 @@ def listplaylists(frontend):
Last-Modified: 2010-02-06T02:11:08Z
"""
result = []
for playlist in frontend.backend.stored_playlists.playlists.get():
for playlist in context.backend.stored_playlists.playlists.get():
result.append((u'playlist', playlist.name))
last_modified = (playlist.last_modified or
dt.datetime.now()).isoformat()
@ -79,8 +79,8 @@ def listplaylists(frontend):
result.append((u'Last-Modified', last_modified))
return result
@handle_pattern(r'^load "(?P<name>[^"]+)"$')
def load(frontend, name):
@handle_request(r'^load "(?P<name>[^"]+)"$')
def load(context, name):
"""
*musicpd.org, stored playlists section:*
@ -93,13 +93,13 @@ def load(frontend, name):
- ``load`` appends the given playlist to the current playlist.
"""
try:
playlist = frontend.backend.stored_playlists.get(name=name).get()
frontend.backend.current_playlist.append(playlist.tracks)
playlist = context.backend.stored_playlists.get(name=name).get()
context.backend.current_playlist.append(playlist.tracks)
except LookupError:
raise MpdNoExistError(u'No such playlist', command=u'load')
@handle_pattern(r'^playlistadd "(?P<name>[^"]+)" "(?P<uri>[^"]+)"$')
def playlistadd(frontend, name, uri):
@handle_request(r'^playlistadd "(?P<name>[^"]+)" "(?P<uri>[^"]+)"$')
def playlistadd(context, name, uri):
"""
*musicpd.org, stored playlists section:*
@ -111,8 +111,8 @@ def playlistadd(frontend, name, uri):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^playlistclear "(?P<name>[^"]+)"$')
def playlistclear(frontend, name):
@handle_request(r'^playlistclear "(?P<name>[^"]+)"$')
def playlistclear(context, name):
"""
*musicpd.org, stored playlists section:*
@ -122,8 +122,8 @@ def playlistclear(frontend, name):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^playlistdelete "(?P<name>[^"]+)" "(?P<songpos>\d+)"$')
def playlistdelete(frontend, name, songpos):
@handle_request(r'^playlistdelete "(?P<name>[^"]+)" "(?P<songpos>\d+)"$')
def playlistdelete(context, name, songpos):
"""
*musicpd.org, stored playlists section:*
@ -133,9 +133,9 @@ def playlistdelete(frontend, name, songpos):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^playlistmove "(?P<name>[^"]+)" '
@handle_request(r'^playlistmove "(?P<name>[^"]+)" '
r'"(?P<from_pos>\d+)" "(?P<to_pos>\d+)"$')
def playlistmove(frontend, name, from_pos, to_pos):
def playlistmove(context, name, from_pos, to_pos):
"""
*musicpd.org, stored playlists section:*
@ -152,8 +152,8 @@ def playlistmove(frontend, name, from_pos, to_pos):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^rename "(?P<old_name>[^"]+)" "(?P<new_name>[^"]+)"$')
def rename(frontend, old_name, new_name):
@handle_request(r'^rename "(?P<old_name>[^"]+)" "(?P<new_name>[^"]+)"$')
def rename(context, old_name, new_name):
"""
*musicpd.org, stored playlists section:*
@ -163,8 +163,8 @@ def rename(frontend, old_name, new_name):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^rm "(?P<name>[^"]+)"$')
def rm(frontend, name):
@handle_request(r'^rm "(?P<name>[^"]+)"$')
def rm(context, name):
"""
*musicpd.org, stored playlists section:*
@ -174,8 +174,8 @@ def rm(frontend, name):
"""
raise MpdNotImplemented # TODO
@handle_pattern(r'^save "(?P<name>[^"]+)"$')
def save(frontend, name):
@handle_request(r'^save "(?P<name>[^"]+)"$')
def save(context, name):
"""
*musicpd.org, stored playlists section:*

View File

@ -52,18 +52,19 @@ class MpdServer(asyncore.dispatcher):
self._format_hostname(settings.MPD_SERVER_HOSTNAME),
settings.MPD_SERVER_PORT)
except IOError, e:
logger.error(u'MPD server startup failed: %s' % str(e).decode('utf-8'))
logger.error(u'MPD server startup failed: %s' %
str(e).decode('utf-8'))
sys.exit(1)
def handle_accept(self):
"""Handle new client connection."""
"""Called by asyncore when a new client connects."""
(client_socket, client_socket_address) = self.accept()
logger.info(u'MPD client connection from [%s]:%s',
client_socket_address[0], client_socket_address[1])
MpdSession(self, client_socket, client_socket_address).start()
MpdSession(self, client_socket, client_socket_address)
def handle_close(self):
"""Handle end of client connection."""
"""Called by asyncore when the socket is closed."""
self.close()
def _format_hostname(self, hostname):

View File

@ -1,7 +1,6 @@
import asynchat
import logging
from mopidy import settings
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
from mopidy.frontends.mpd.protocol import ENCODING, LINE_TERMINATOR, VERSION
from mopidy.utils.log import indent
@ -22,70 +21,38 @@ class MpdSession(asynchat.async_chat):
self.input_buffer = []
self.authenticated = False
self.set_terminator(LINE_TERMINATOR.encode(ENCODING))
self.dispatcher = MpdDispatcher()
def start(self):
"""Start a new client session."""
self.send_response(u'OK MPD %s' % VERSION)
self.dispatcher = MpdDispatcher(session=self)
self.send_response([u'OK MPD %s' % VERSION])
def collect_incoming_data(self, data):
"""Collect incoming data into buffer until a terminator is found."""
"""Called by asynchat when new data arrives."""
self.input_buffer.append(data)
def found_terminator(self):
"""Handle request when a terminator is found."""
"""Called by asynchat when a terminator is found in incoming data."""
data = ''.join(self.input_buffer).strip()
self.input_buffer = []
try:
request = data.decode(ENCODING)
logger.debug(u'Input from [%s]:%s: %s', self.client_address,
self.client_port, indent(request))
self.handle_request(request)
self.send_response(self.handle_request(data))
except UnicodeDecodeError as e:
logger.warning(u'Received invalid data: %s', e)
def handle_request(self, request):
"""Handle request by sending it to the MPD frontend."""
if not self.authenticated:
(self.authenticated, response) = self.check_password(request)
if response is not None:
self.send_response(response)
return
response = self.dispatcher.handle_request(request)
"""Handle the request using the MPD command handlers."""
request = request.decode(ENCODING)
logger.debug(u'Request from [%s]:%s: %s', self.client_address,
self.client_port, indent(request))
return self.dispatcher.handle_request(request)
def send_response(self, response):
"""
Format a response from the MPD command handlers and send it to the
client.
"""
if response is not None:
self.handle_response(response)
def handle_response(self, response):
"""Handle response from the MPD frontend."""
self.send_response(LINE_TERMINATOR.join(response))
def send_response(self, output):
"""Send a response to the client."""
logger.debug(u'Output to [%s]:%s: %s', self.client_address,
self.client_port, indent(output))
output = u'%s%s' % (output, LINE_TERMINATOR)
data = output.encode(ENCODING)
self.push(data)
def check_password(self, request):
"""
Takes any request and tries to authenticate the client using it.
:rtype: a two-tuple containing (is_authenticated, response_message). If
the response_message is :class:`None`, normal processing should
continue, even though the client may not be authenticated.
"""
if settings.MPD_SERVER_PASSWORD is None:
return (True, None)
command = request.split(' ')[0]
if command == 'password':
if request == 'password "%s"' % settings.MPD_SERVER_PASSWORD:
return (True, u'OK')
else:
return (False, u'ACK [3@0] {password} incorrect password')
if command in ('close', 'commands', 'notcommands', 'ping'):
return (False, None)
else:
return (False,
u'ACK [4@0] {%(c)s} you don\'t have permission for "%(c)s"' %
{'c': command})
response = LINE_TERMINATOR.join(response)
logger.debug(u'Response to [%s]:%s: %s', self.client_address,
self.client_port, indent(response))
response = u'%s%s' % (response, LINE_TERMINATOR)
data = response.encode(ENCODING)
self.push(data)

View File

@ -51,9 +51,9 @@ class AlsaMixer(ThreadingActor, BaseMixer):
return [settings.MIXER_ALSA_CONTROL]
return [u'Master', u'PCM']
def _get_volume(self):
def get_volume(self):
# FIXME does not seem to see external volume changes.
return self._mixer.getvolume()[0]
def _set_volume(self, volume):
def set_volume(self, volume):
self._mixer.setvolume(volume)

View File

@ -17,9 +17,10 @@ class BaseMixer(object):
Integer in range [0, 100]. :class:`None` if unknown. Values below 0 is
equal to 0. Values above 100 is equal to 100.
"""
if self._get_volume() is None:
volume = self.get_volume()
if volume is None:
return None
return int(self._get_volume() / self.amplification_factor)
return int(volume / self.amplification_factor)
@volume.setter
def volume(self, volume):
@ -28,9 +29,9 @@ class BaseMixer(object):
volume = 0
elif volume > 100:
volume = 100
self._set_volume(volume)
self.set_volume(volume)
def _get_volume(self):
def get_volume(self):
"""
Return volume as integer in range [0, 100]. :class:`None` if unknown.
@ -38,7 +39,7 @@ class BaseMixer(object):
"""
raise NotImplementedError
def _set_volume(self, volume):
def set_volume(self, volume):
"""
Set volume as integer in range [0, 100].

View File

@ -35,14 +35,14 @@ class DenonMixer(ThreadingActor, BaseMixer):
from serial import Serial
self._device = Serial(port=settings.MIXER_EXT_PORT, timeout=0.2)
def _get_volume(self):
def get_volume(self):
self._ensure_open_device()
self._device.write('MV?\r')
vol = str(self._device.readline()[2:4])
logger.debug(u'_get_volume() = %s' % vol)
return self._levels.index(vol)
def _set_volume(self, volume):
def set_volume(self, volume):
# Clamp according to Denon-spec
if volume > 99:
volume = 99

View File

@ -8,8 +8,8 @@ class DummyMixer(ThreadingActor, BaseMixer):
def __init__(self):
self._volume = None
def _get_volume(self):
def get_volume(self):
return self._volume
def _set_volume(self, volume):
def set_volume(self, volume):
self._volume = volume

View File

@ -15,8 +15,8 @@ class GStreamerSoftwareMixer(ThreadingActor, BaseMixer):
assert len(output_refs) == 1, 'Expected exactly one running output.'
self.output = output_refs[0].proxy()
def _get_volume(self):
def get_volume(self):
return self.output.get_volume().get()
def _set_volume(self, volume):
def set_volume(self, volume):
self.output.set_volume(volume).get()

View File

@ -40,10 +40,10 @@ class NadMixer(ThreadingActor, BaseMixer):
self._volume_cache = None
self._nad_talker = NadTalker.start().proxy()
def _get_volume(self):
def get_volume(self):
return self._volume_cache
def _set_volume(self, volume):
def set_volume(self, volume):
self._volume_cache = volume
self._nad_talker.set_volume(volume)

View File

@ -28,7 +28,7 @@ class OsaMixer(ThreadingActor, BaseMixer):
and self._last_update is not None
and (int(time.time() - self._last_update) < self.CACHE_TTL))
def _get_volume(self):
def get_volume(self):
if not self._valid_cache():
try:
self._cache = int(Popen(
@ -40,7 +40,7 @@ class OsaMixer(ThreadingActor, BaseMixer):
self._last_update = int(time.time())
return self._cache
def _set_volume(self, volume):
def set_volume(self, volume):
Popen(['osascript', '-e', 'set volume output volume %d' % volume])
self._cache = volume
self._last_update = int(time.time())

View File

@ -1,4 +1,4 @@
from mopidy.frontends.mpd import translator
from collections import namedtuple
class ImmutableObject(object):
"""
@ -129,6 +129,9 @@ class Album(ImmutableObject):
super(Album, self).__init__(*args, **kwargs)
CpTrack = namedtuple('CpTrack', ['cpid', 'track'])
class Track(ImmutableObject):
"""
:param uri: track URI
@ -183,6 +186,7 @@ class Track(ImmutableObject):
super(Track, self).__init__(*args, **kwargs)
def mpd_format(self, *args, **kwargs):
from mopidy.frontends.mpd import translator
return translator.track_to_mpd_format(self, *args, **kwargs)
@ -222,4 +226,5 @@ class Playlist(ImmutableObject):
return len(self.tracks)
def mpd_format(self, *args, **kwargs):
from mopidy.frontends.mpd import translator
return translator.playlist_to_mpd_format(self, *args, **kwargs)

View File

@ -23,14 +23,14 @@ class CurrentPlaylistControllerTest(object):
cp_track = self.controller.add(track)
self.assertEqual(track, self.controller.tracks[-1])
self.assertEqual(cp_track, self.controller.cp_tracks[-1])
self.assertEqual(track, cp_track[1])
self.assertEqual(track, cp_track.track)
def test_add_at_position(self):
for track in self.tracks[:-1]:
cp_track = self.controller.add(track, 0)
self.assertEqual(track, self.controller.tracks[0])
self.assertEqual(cp_track, self.controller.cp_tracks[0])
self.assertEqual(track, cp_track[1])
self.assertEqual(track, cp_track.track)
@populate_playlist
def test_add_at_position_outside_of_playlist(self):
@ -40,12 +40,12 @@ class CurrentPlaylistControllerTest(object):
@populate_playlist
def test_get_by_cpid(self):
cp_track = self.controller.cp_tracks[1]
self.assertEqual(cp_track, self.controller.get(cpid=cp_track[0]))
self.assertEqual(cp_track, self.controller.get(cpid=cp_track.cpid))
@populate_playlist
def test_get_by_uri(self):
cp_track = self.controller.cp_tracks[1]
self.assertEqual(cp_track, self.controller.get(uri=cp_track[1].uri))
self.assertEqual(cp_track, self.controller.get(uri=cp_track.track.uri))
@populate_playlist
def test_get_by_uri_raises_error_for_invalid_uri(self):

View File

@ -1,29 +1,29 @@
import unittest
from mopidy.backends.dummy import DummyBackend
from mopidy.frontends.mpd import dispatcher
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
from mopidy.mixers.dummy import DummyMixer
class AudioOutputHandlerTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.dispatcher = MpdDispatcher()
def tearDown(self):
self.b.stop().get()
self.backend.stop().get()
self.mixer.stop().get()
def test_enableoutput(self):
result = self.h.handle_request(u'enableoutput "0"')
result = self.dispatcher.handle_request(u'enableoutput "0"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_disableoutput(self):
result = self.h.handle_request(u'disableoutput "0"')
result = self.dispatcher.handle_request(u'disableoutput "0"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_outputs(self):
result = self.h.handle_request(u'outputs')
result = self.dispatcher.handle_request(u'outputs')
self.assert_(u'outputid: 0' in result)
self.assert_(u'outputname: None' in result)
self.assert_(u'outputenabled: 1' in result)

View File

@ -0,0 +1,63 @@
import mock
import unittest
from mopidy import settings
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
from mopidy.frontends.mpd.session import MpdSession
class AuthenticationTest(unittest.TestCase):
def setUp(self):
self.session = mock.Mock(spec=MpdSession)
self.dispatcher = MpdDispatcher(session=self.session)
def tearDown(self):
settings.runtime.clear()
def test_authentication_with_valid_password_is_accepted(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
response = self.dispatcher.handle_request(u'password "topsecret"')
self.assertTrue(self.dispatcher.authenticated)
self.assert_(u'OK' in response)
def test_authentication_with_invalid_password_is_not_accepted(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
response = self.dispatcher.handle_request(u'password "secret"')
self.assertFalse(self.dispatcher.authenticated)
self.assert_(u'ACK [3@0] {password} incorrect password' in response)
def test_authentication_with_anything_when_password_check_turned_off(self):
settings.MPD_SERVER_PASSWORD = None
response = self.dispatcher.handle_request(u'any request at all')
self.assertTrue(self.dispatcher.authenticated)
self.assert_('ACK [5@0] {} unknown command "any"' in response)
def test_anything_when_not_authenticated_should_fail(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
response = self.dispatcher.handle_request(u'any request at all')
self.assertFalse(self.dispatcher.authenticated)
self.assert_(
u'ACK [4@0] {any} you don\'t have permission for "any"' in response)
def test_close_is_allowed_without_authentication(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
response = self.dispatcher.handle_request(u'close')
self.assertFalse(self.dispatcher.authenticated)
self.assert_(u'OK' in response)
def test_commands_is_allowed_without_authentication(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
response = self.dispatcher.handle_request(u'commands')
self.assertFalse(self.dispatcher.authenticated)
self.assert_(u'OK' in response)
def test_notcommands_is_allowed_without_authentication(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
response = self.dispatcher.handle_request(u'notcommands')
self.assertFalse(self.dispatcher.authenticated)
self.assert_(u'OK' in response)
def test_ping_is_allowed_without_authentication(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
response = self.dispatcher.handle_request(u'ping')
self.assertFalse(self.dispatcher.authenticated)
self.assert_(u'OK' in response)

View File

@ -8,55 +8,56 @@ class CommandListsTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.dispatcher = dispatcher.MpdDispatcher()
def tearDown(self):
self.b.stop().get()
self.mixer.stop().get()
def test_command_list_begin(self):
result = self.h.handle_request(u'command_list_begin')
self.assert_(result is None)
result = self.dispatcher.handle_request(u'command_list_begin')
self.assertEquals(result, [])
def test_command_list_end(self):
self.h.handle_request(u'command_list_begin')
result = self.h.handle_request(u'command_list_end')
self.dispatcher.handle_request(u'command_list_begin')
result = self.dispatcher.handle_request(u'command_list_end')
self.assert_(u'OK' in result)
def test_command_list_end_without_start_first_is_an_unknown_command(self):
result = self.h.handle_request(u'command_list_end')
result = self.dispatcher.handle_request(u'command_list_end')
self.assertEquals(result[0],
u'ACK [5@0] {} unknown command "command_list_end"')
def test_command_list_with_ping(self):
self.h.handle_request(u'command_list_begin')
self.assertEqual([], self.h.command_list)
self.assertEqual(False, self.h.command_list_ok)
self.h.handle_request(u'ping')
self.assert_(u'ping' in self.h.command_list)
result = self.h.handle_request(u'command_list_end')
self.dispatcher.handle_request(u'command_list_begin')
self.assertEqual([], self.dispatcher.command_list)
self.assertEqual(False, self.dispatcher.command_list_ok)
self.dispatcher.handle_request(u'ping')
self.assert_(u'ping' in self.dispatcher.command_list)
result = self.dispatcher.handle_request(u'command_list_end')
self.assert_(u'OK' in result)
self.assertEqual(False, self.h.command_list)
self.assertEqual(False, self.dispatcher.command_list)
def test_command_list_with_error_returns_ack_with_correct_index(self):
self.h.handle_request(u'command_list_begin')
self.h.handle_request(u'play') # Known command
self.h.handle_request(u'paly') # Unknown command
result = self.h.handle_request(u'command_list_end')
self.dispatcher.handle_request(u'command_list_begin')
self.dispatcher.handle_request(u'play') # Known command
self.dispatcher.handle_request(u'paly') # Unknown command
result = self.dispatcher.handle_request(u'command_list_end')
self.assertEqual(len(result), 1, result)
self.assertEqual(result[0], u'ACK [5@1] {} unknown command "paly"')
def test_command_list_ok_begin(self):
result = self.h.handle_request(u'command_list_ok_begin')
self.assert_(result is None)
result = self.dispatcher.handle_request(u'command_list_ok_begin')
self.assertEquals(result, [])
def test_command_list_ok_with_ping(self):
self.h.handle_request(u'command_list_ok_begin')
self.assertEqual([], self.h.command_list)
self.assertEqual(True, self.h.command_list_ok)
self.h.handle_request(u'ping')
self.assert_(u'ping' in self.h.command_list)
result = self.h.handle_request(u'command_list_end')
self.dispatcher.handle_request(u'command_list_ok_begin')
self.assertEqual([], self.dispatcher.command_list)
self.assertEqual(True, self.dispatcher.command_list_ok)
self.dispatcher.handle_request(u'ping')
self.assert_(u'ping' in self.dispatcher.command_list)
result = self.dispatcher.handle_request(u'command_list_end')
self.assert_(u'list_OK' in result)
self.assert_(u'OK' in result)
self.assertEqual(False, self.h.command_list)
self.assertEqual(False, self.h.command_list_ok)
self.assertEqual(False, self.dispatcher.command_list)
self.assertEqual(False, self.dispatcher.command_list_ok)

View File

@ -1,48 +1,53 @@
import mock
import unittest
from mopidy import settings
from mopidy.backends.dummy import DummyBackend
from mopidy.frontends.mpd import dispatcher
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
from mopidy.frontends.mpd.session import MpdSession
from mopidy.mixers.dummy import DummyMixer
class ConnectionHandlerTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.session = mock.Mock(spec=MpdSession)
self.dispatcher = MpdDispatcher(session=self.session)
def tearDown(self):
self.b.stop().get()
self.backend.stop().get()
self.mixer.stop().get()
settings.runtime.clear()
def test_close(self):
result = self.h.handle_request(u'close')
def test_close_closes_the_client_connection(self):
result = self.dispatcher.handle_request(u'close')
self.assert_(self.session.close.called,
u'Should call close() on MpdSession')
self.assert_(u'OK' in result)
def test_empty_request(self):
result = self.h.handle_request(u'')
result = self.dispatcher.handle_request(u'')
self.assert_(u'OK' in result)
def test_kill(self):
result = self.h.handle_request(u'kill')
self.assert_(u'OK' in result)
result = self.dispatcher.handle_request(u'kill')
self.assert_(u'ACK [4@0] {kill} you don\'t have permission for "kill"' in result)
def test_valid_password_is_accepted(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
result = self.h.handle_request(u'password "topsecret"')
result = self.dispatcher.handle_request(u'password "topsecret"')
self.assert_(u'OK' in result)
def test_invalid_password_is_not_accepted(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
result = self.h.handle_request(u'password "secret"')
result = self.dispatcher.handle_request(u'password "secret"')
self.assert_(u'ACK [3@0] {password} incorrect password' in result)
def test_any_password_is_not_accepted_when_password_check_turned_off(self):
settings.MPD_SERVER_PASSWORD = None
result = self.h.handle_request(u'password "secret"')
result = self.dispatcher.handle_request(u'password "secret"')
self.assert_(u'ACK [3@0] {password} incorrect password' in result)
def test_ping(self):
result = self.h.handle_request(u'ping')
result = self.dispatcher.handle_request(u'ping')
self.assert_(u'OK' in result)

View File

@ -1,160 +1,160 @@
import unittest
from mopidy.backends.dummy import DummyBackend
from mopidy.frontends.mpd import dispatcher
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
from mopidy.mixers.dummy import DummyMixer
from mopidy.models import Track
class CurrentPlaylistHandlerTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.dispatcher = MpdDispatcher()
def tearDown(self):
self.b.stop().get()
self.backend.stop().get()
self.mixer.stop().get()
def test_add(self):
needle = Track(uri='dummy://foo')
self.b.library.provider.dummy_library = [
self.backend.library.provider.dummy_library = [
Track(), Track(), needle, Track()]
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(), Track(), Track(), Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
result = self.h.handle_request(u'add "dummy://foo"')
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5)
result = self.dispatcher.handle_request(u'add "dummy://foo"')
self.assertEqual(len(result), 1)
self.assertEqual(result[0], u'OK')
self.assertEqual(len(self.b.current_playlist.tracks.get()), 6)
self.assertEqual(self.b.current_playlist.tracks.get()[5], needle)
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 6)
self.assertEqual(self.backend.current_playlist.tracks.get()[5], needle)
def test_add_with_uri_not_found_in_library_should_ack(self):
result = self.h.handle_request(u'add "dummy://foo"')
result = self.dispatcher.handle_request(u'add "dummy://foo"')
self.assertEqual(result[0],
u'ACK [50@0] {add} directory or file not found')
def test_add_with_empty_uri_should_add_all_known_tracks_and_ok(self):
result = self.h.handle_request(u'add ""')
result = self.dispatcher.handle_request(u'add ""')
# TODO check that we add all tracks (we currently don't)
self.assert_(u'OK' in result)
def test_addid_without_songpos(self):
needle = Track(uri='dummy://foo')
self.b.library.provider.dummy_library = [
self.backend.library.provider.dummy_library = [
Track(), Track(), needle, Track()]
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(), Track(), Track(), Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
result = self.h.handle_request(u'addid "dummy://foo"')
self.assertEqual(len(self.b.current_playlist.tracks.get()), 6)
self.assertEqual(self.b.current_playlist.tracks.get()[5], needle)
self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks.get()[5][0]
in result)
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5)
result = self.dispatcher.handle_request(u'addid "dummy://foo"')
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 6)
self.assertEqual(self.backend.current_playlist.tracks.get()[5], needle)
self.assert_(u'Id: %d' %
self.backend.current_playlist.cp_tracks.get()[5][0] in result)
self.assert_(u'OK' in result)
def test_addid_with_empty_uri_acks(self):
result = self.h.handle_request(u'addid ""')
result = self.dispatcher.handle_request(u'addid ""')
self.assertEqual(result[0], u'ACK [50@0] {addid} No such song')
def test_addid_with_songpos(self):
needle = Track(uri='dummy://foo')
self.b.library.provider.dummy_library = [
self.backend.library.provider.dummy_library = [
Track(), Track(), needle, Track()]
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(), Track(), Track(), Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
result = self.h.handle_request(u'addid "dummy://foo" "3"')
self.assertEqual(len(self.b.current_playlist.tracks.get()), 6)
self.assertEqual(self.b.current_playlist.tracks.get()[3], needle)
self.assert_(u'Id: %d' % self.b.current_playlist.cp_tracks.get()[3][0]
in result)
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5)
result = self.dispatcher.handle_request(u'addid "dummy://foo" "3"')
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 6)
self.assertEqual(self.backend.current_playlist.tracks.get()[3], needle)
self.assert_(u'Id: %d' %
self.backend.current_playlist.cp_tracks.get()[3][0] in result)
self.assert_(u'OK' in result)
def test_addid_with_songpos_out_of_bounds_should_ack(self):
needle = Track(uri='dummy://foo')
self.b.library.provider.dummy_library = [
self.backend.library.provider.dummy_library = [
Track(), Track(), needle, Track()]
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(), Track(), Track(), Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
result = self.h.handle_request(u'addid "dummy://foo" "6"')
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5)
result = self.dispatcher.handle_request(u'addid "dummy://foo" "6"')
self.assertEqual(result[0], u'ACK [2@0] {addid} Bad song index')
def test_addid_with_uri_not_found_in_library_should_ack(self):
result = self.h.handle_request(u'addid "dummy://foo"')
result = self.dispatcher.handle_request(u'addid "dummy://foo"')
self.assertEqual(result[0], u'ACK [50@0] {addid} No such song')
def test_clear(self):
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(), Track(), Track(), Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
result = self.h.handle_request(u'clear')
self.assertEqual(len(self.b.current_playlist.tracks.get()), 0)
self.assertEqual(self.b.playback.current_track.get(), None)
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5)
result = self.dispatcher.handle_request(u'clear')
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 0)
self.assertEqual(self.backend.playback.current_track.get(), None)
self.assert_(u'OK' in result)
def test_delete_songpos(self):
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(), Track(), Track(), Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
result = self.h.handle_request(u'delete "%d"' %
self.b.current_playlist.cp_tracks.get()[2][0])
self.assertEqual(len(self.b.current_playlist.tracks.get()), 4)
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5)
result = self.dispatcher.handle_request(u'delete "%d"' %
self.backend.current_playlist.cp_tracks.get()[2][0])
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 4)
self.assert_(u'OK' in result)
def test_delete_songpos_out_of_bounds(self):
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(), Track(), Track(), Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
result = self.h.handle_request(u'delete "5"')
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5)
result = self.dispatcher.handle_request(u'delete "5"')
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5)
self.assertEqual(result[0], u'ACK [2@0] {delete} Bad song index')
def test_delete_open_range(self):
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(), Track(), Track(), Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
result = self.h.handle_request(u'delete "1:"')
self.assertEqual(len(self.b.current_playlist.tracks.get()), 1)
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5)
result = self.dispatcher.handle_request(u'delete "1:"')
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 1)
self.assert_(u'OK' in result)
def test_delete_closed_range(self):
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(), Track(), Track(), Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
result = self.h.handle_request(u'delete "1:3"')
self.assertEqual(len(self.b.current_playlist.tracks.get()), 3)
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5)
result = self.dispatcher.handle_request(u'delete "1:3"')
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 3)
self.assert_(u'OK' in result)
def test_delete_range_out_of_bounds(self):
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(), Track(), Track(), Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
result = self.h.handle_request(u'delete "5:7"')
self.assertEqual(len(self.b.current_playlist.tracks.get()), 5)
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5)
result = self.dispatcher.handle_request(u'delete "5:7"')
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 5)
self.assertEqual(result[0], u'ACK [2@0] {delete} Bad song index')
def test_deleteid(self):
self.b.current_playlist.append([Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks.get()), 2)
result = self.h.handle_request(u'deleteid "1"')
self.assertEqual(len(self.b.current_playlist.tracks.get()), 1)
self.backend.current_playlist.append([Track(), Track()])
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 2)
result = self.dispatcher.handle_request(u'deleteid "1"')
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 1)
self.assert_(u'OK' in result)
def test_deleteid_does_not_exist(self):
self.b.current_playlist.append([Track(), Track()])
self.assertEqual(len(self.b.current_playlist.tracks.get()), 2)
result = self.h.handle_request(u'deleteid "12345"')
self.assertEqual(len(self.b.current_playlist.tracks.get()), 2)
self.backend.current_playlist.append([Track(), Track()])
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 2)
result = self.dispatcher.handle_request(u'deleteid "12345"')
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 2)
self.assertEqual(result[0], u'ACK [50@0] {deleteid} No such song')
def test_move_songpos(self):
self.b.current_playlist.append([
self.backend.current_playlist.append([
Track(name='a'), Track(name='b'), Track(name='c'),
Track(name='d'), Track(name='e'), Track(name='f'),
])
result = self.h.handle_request(u'move "1" "0"')
tracks = self.b.current_playlist.tracks.get()
result = self.dispatcher.handle_request(u'move "1" "0"')
tracks = self.backend.current_playlist.tracks.get()
self.assertEqual(tracks[0].name, 'b')
self.assertEqual(tracks[1].name, 'a')
self.assertEqual(tracks[2].name, 'c')
@ -164,12 +164,12 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_move_open_range(self):
self.b.current_playlist.append([
self.backend.current_playlist.append([
Track(name='a'), Track(name='b'), Track(name='c'),
Track(name='d'), Track(name='e'), Track(name='f'),
])
result = self.h.handle_request(u'move "2:" "0"')
tracks = self.b.current_playlist.tracks.get()
result = self.dispatcher.handle_request(u'move "2:" "0"')
tracks = self.backend.current_playlist.tracks.get()
self.assertEqual(tracks[0].name, 'c')
self.assertEqual(tracks[1].name, 'd')
self.assertEqual(tracks[2].name, 'e')
@ -179,12 +179,12 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_move_closed_range(self):
self.b.current_playlist.append([
self.backend.current_playlist.append([
Track(name='a'), Track(name='b'), Track(name='c'),
Track(name='d'), Track(name='e'), Track(name='f'),
])
result = self.h.handle_request(u'move "1:3" "0"')
tracks = self.b.current_playlist.tracks.get()
result = self.dispatcher.handle_request(u'move "1:3" "0"')
tracks = self.backend.current_playlist.tracks.get()
self.assertEqual(tracks[0].name, 'b')
self.assertEqual(tracks[1].name, 'c')
self.assertEqual(tracks[2].name, 'a')
@ -194,12 +194,12 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_moveid(self):
self.b.current_playlist.append([
self.backend.current_playlist.append([
Track(name='a'), Track(name='b'), Track(name='c'),
Track(name='d'), Track(name='e'), Track(name='f'),
])
result = self.h.handle_request(u'moveid "4" "2"')
tracks = self.b.current_playlist.tracks.get()
result = self.dispatcher.handle_request(u'moveid "4" "2"')
tracks = self.backend.current_playlist.tracks.get()
self.assertEqual(tracks[0].name, 'a')
self.assertEqual(tracks[1].name, 'b')
self.assertEqual(tracks[2].name, 'e')
@ -209,30 +209,30 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_playlist_returns_same_as_playlistinfo(self):
playlist_result = self.h.handle_request(u'playlist')
playlistinfo_result = self.h.handle_request(u'playlistinfo')
playlist_result = self.dispatcher.handle_request(u'playlist')
playlistinfo_result = self.dispatcher.handle_request(u'playlistinfo')
self.assertEqual(playlist_result, playlistinfo_result)
def test_playlistfind(self):
result = self.h.handle_request(u'playlistfind "tag" "needle"')
result = self.dispatcher.handle_request(u'playlistfind "tag" "needle"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_playlistfind_by_filename_not_in_current_playlist(self):
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'playlistfind "filename" "file:///dev/null"')
self.assertEqual(len(result), 1)
self.assert_(u'OK' in result)
def test_playlistfind_by_filename_without_quotes(self):
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'playlistfind filename "file:///dev/null"')
self.assertEqual(len(result), 1)
self.assert_(u'OK' in result)
def test_playlistfind_by_filename_in_current_playlist(self):
self.b.current_playlist.append([
self.backend.current_playlist.append([
Track(uri='file:///exists')])
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'playlistfind filename "file:///exists"')
self.assert_(u'file: file:///exists' in result)
self.assert_(u'Id: 0' in result)
@ -240,15 +240,15 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_playlistid_without_songid(self):
self.b.current_playlist.append([Track(name='a'), Track(name='b')])
result = self.h.handle_request(u'playlistid')
self.backend.current_playlist.append([Track(name='a'), Track(name='b')])
result = self.dispatcher.handle_request(u'playlistid')
self.assert_(u'Title: a' in result)
self.assert_(u'Title: b' in result)
self.assert_(u'OK' in result)
def test_playlistid_with_songid(self):
self.b.current_playlist.append([Track(name='a'), Track(name='b')])
result = self.h.handle_request(u'playlistid "1"')
self.backend.current_playlist.append([Track(name='a'), Track(name='b')])
result = self.dispatcher.handle_request(u'playlistid "1"')
self.assert_(u'Title: a' not in result)
self.assert_(u'Id: 0' not in result)
self.assert_(u'Title: b' in result)
@ -256,16 +256,16 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_playlistid_with_not_existing_songid_fails(self):
self.b.current_playlist.append([Track(name='a'), Track(name='b')])
result = self.h.handle_request(u'playlistid "25"')
self.backend.current_playlist.append([Track(name='a'), Track(name='b')])
result = self.dispatcher.handle_request(u'playlistid "25"')
self.assertEqual(result[0], u'ACK [50@0] {playlistid} No such song')
def test_playlistinfo_without_songpos_or_range(self):
self.b.current_playlist.append([
self.backend.current_playlist.append([
Track(name='a'), Track(name='b'), Track(name='c'),
Track(name='d'), Track(name='e'), Track(name='f'),
])
result = self.h.handle_request(u'playlistinfo')
result = self.dispatcher.handle_request(u'playlistinfo')
self.assert_(u'Title: a' in result)
self.assert_(u'Title: b' in result)
self.assert_(u'Title: c' in result)
@ -275,11 +275,11 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_playlistinfo_with_songpos(self):
self.b.current_playlist.append([
self.backend.current_playlist.append([
Track(name='a'), Track(name='b'), Track(name='c'),
Track(name='d'), Track(name='e'), Track(name='f'),
])
result = self.h.handle_request(u'playlistinfo "4"')
result = self.dispatcher.handle_request(u'playlistinfo "4"')
self.assert_(u'Title: a' not in result)
self.assert_(u'Title: b' not in result)
self.assert_(u'Title: c' not in result)
@ -289,16 +289,16 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_playlistinfo_with_negative_songpos_same_as_playlistinfo(self):
result1 = self.h.handle_request(u'playlistinfo "-1"')
result2 = self.h.handle_request(u'playlistinfo')
result1 = self.dispatcher.handle_request(u'playlistinfo "-1"')
result2 = self.dispatcher.handle_request(u'playlistinfo')
self.assertEqual(result1, result2)
def test_playlistinfo_with_open_range(self):
self.b.current_playlist.append([
self.backend.current_playlist.append([
Track(name='a'), Track(name='b'), Track(name='c'),
Track(name='d'), Track(name='e'), Track(name='f'),
])
result = self.h.handle_request(u'playlistinfo "2:"')
result = self.dispatcher.handle_request(u'playlistinfo "2:"')
self.assert_(u'Title: a' not in result)
self.assert_(u'Title: b' not in result)
self.assert_(u'Title: c' in result)
@ -308,11 +308,11 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_playlistinfo_with_closed_range(self):
self.b.current_playlist.append([
self.backend.current_playlist.append([
Track(name='a'), Track(name='b'), Track(name='c'),
Track(name='d'), Track(name='e'), Track(name='f'),
])
result = self.h.handle_request(u'playlistinfo "2:4"')
result = self.dispatcher.handle_request(u'playlistinfo "2:4"')
self.assert_(u'Title: a' not in result)
self.assert_(u'Title: b' not in result)
self.assert_(u'Title: c' in result)
@ -322,52 +322,53 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_playlistinfo_with_too_high_start_of_range_returns_arg_error(self):
result = self.h.handle_request(u'playlistinfo "10:20"')
result = self.dispatcher.handle_request(u'playlistinfo "10:20"')
self.assert_(u'ACK [2@0] {playlistinfo} Bad song index' in result)
def test_playlistinfo_with_too_high_end_of_range_returns_ok(self):
result = self.h.handle_request(u'playlistinfo "0:20"')
result = self.dispatcher.handle_request(u'playlistinfo "0:20"')
self.assert_(u'OK' in result)
def test_playlistsearch(self):
result = self.h.handle_request(u'playlistsearch "any" "needle"')
result = self.dispatcher.handle_request(
u'playlistsearch "any" "needle"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_playlistsearch_without_quotes(self):
result = self.h.handle_request(u'playlistsearch any "needle"')
result = self.dispatcher.handle_request(u'playlistsearch any "needle"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_plchanges(self):
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(name='a'), Track(name='b'), Track(name='c')])
result = self.h.handle_request(u'plchanges "0"')
result = self.dispatcher.handle_request(u'plchanges "0"')
self.assert_(u'Title: a' in result)
self.assert_(u'Title: b' in result)
self.assert_(u'Title: c' in result)
self.assert_(u'OK' in result)
def test_plchanges_with_minus_one_returns_entire_playlist(self):
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(name='a'), Track(name='b'), Track(name='c')])
result = self.h.handle_request(u'plchanges "-1"')
result = self.dispatcher.handle_request(u'plchanges "-1"')
self.assert_(u'Title: a' in result)
self.assert_(u'Title: b' in result)
self.assert_(u'Title: c' in result)
self.assert_(u'OK' in result)
def test_plchanges_without_quotes_works(self):
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(name='a'), Track(name='b'), Track(name='c')])
result = self.h.handle_request(u'plchanges 0')
result = self.dispatcher.handle_request(u'plchanges 0')
self.assert_(u'Title: a' in result)
self.assert_(u'Title: b' in result)
self.assert_(u'Title: c' in result)
self.assert_(u'OK' in result)
def test_plchangesposid(self):
self.b.current_playlist.append([Track(), Track(), Track()])
result = self.h.handle_request(u'plchangesposid "0"')
cp_tracks = self.b.current_playlist.cp_tracks.get()
self.backend.current_playlist.append([Track(), Track(), Track()])
result = self.dispatcher.handle_request(u'plchangesposid "0"')
cp_tracks = self.backend.current_playlist.cp_tracks.get()
self.assert_(u'cpos: 0' in result)
self.assert_(u'Id: %d' % cp_tracks[0][0]
in result)
@ -380,24 +381,24 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_shuffle_without_range(self):
self.b.current_playlist.append([
self.backend.current_playlist.append([
Track(name='a'), Track(name='b'), Track(name='c'),
Track(name='d'), Track(name='e'), Track(name='f'),
])
version = self.b.current_playlist.version.get()
result = self.h.handle_request(u'shuffle')
self.assert_(version < self.b.current_playlist.version.get())
version = self.backend.current_playlist.version.get()
result = self.dispatcher.handle_request(u'shuffle')
self.assert_(version < self.backend.current_playlist.version.get())
self.assert_(u'OK' in result)
def test_shuffle_with_open_range(self):
self.b.current_playlist.append([
self.backend.current_playlist.append([
Track(name='a'), Track(name='b'), Track(name='c'),
Track(name='d'), Track(name='e'), Track(name='f'),
])
version = self.b.current_playlist.version.get()
result = self.h.handle_request(u'shuffle "4:"')
self.assert_(version < self.b.current_playlist.version.get())
tracks = self.b.current_playlist.tracks.get()
version = self.backend.current_playlist.version.get()
result = self.dispatcher.handle_request(u'shuffle "4:"')
self.assert_(version < self.backend.current_playlist.version.get())
tracks = self.backend.current_playlist.tracks.get()
self.assertEqual(tracks[0].name, 'a')
self.assertEqual(tracks[1].name, 'b')
self.assertEqual(tracks[2].name, 'c')
@ -405,14 +406,14 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_shuffle_with_closed_range(self):
self.b.current_playlist.append([
self.backend.current_playlist.append([
Track(name='a'), Track(name='b'), Track(name='c'),
Track(name='d'), Track(name='e'), Track(name='f'),
])
version = self.b.current_playlist.version.get()
result = self.h.handle_request(u'shuffle "1:3"')
self.assert_(version < self.b.current_playlist.version.get())
tracks = self.b.current_playlist.tracks.get()
version = self.backend.current_playlist.version.get()
result = self.dispatcher.handle_request(u'shuffle "1:3"')
self.assert_(version < self.backend.current_playlist.version.get())
tracks = self.backend.current_playlist.tracks.get()
self.assertEqual(tracks[0].name, 'a')
self.assertEqual(tracks[3].name, 'd')
self.assertEqual(tracks[4].name, 'e')
@ -420,12 +421,12 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_swap(self):
self.b.current_playlist.append([
self.backend.current_playlist.append([
Track(name='a'), Track(name='b'), Track(name='c'),
Track(name='d'), Track(name='e'), Track(name='f'),
])
result = self.h.handle_request(u'swap "1" "4"')
tracks = self.b.current_playlist.tracks.get()
result = self.dispatcher.handle_request(u'swap "1" "4"')
tracks = self.backend.current_playlist.tracks.get()
self.assertEqual(tracks[0].name, 'a')
self.assertEqual(tracks[1].name, 'e')
self.assertEqual(tracks[2].name, 'c')
@ -435,12 +436,12 @@ class CurrentPlaylistHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_swapid(self):
self.b.current_playlist.append([
self.backend.current_playlist.append([
Track(name='a'), Track(name='b'), Track(name='c'),
Track(name='d'), Track(name='e'), Track(name='f'),
])
result = self.h.handle_request(u'swapid "1" "4"')
tracks = self.b.current_playlist.tracks.get()
result = self.dispatcher.handle_request(u'swapid "1" "4"')
tracks = self.backend.current_playlist.tracks.get()
self.assertEqual(tracks[0].name, 'a')
self.assertEqual(tracks[1].name, 'e')
self.assertEqual(tracks[2].name, 'c')

View File

@ -1,33 +1,33 @@
import unittest
from mopidy.backends.dummy import DummyBackend
from mopidy.frontends.mpd import dispatcher
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
from mopidy.frontends.mpd.exceptions import MpdAckError
from mopidy.frontends.mpd.protocol import request_handlers, handle_pattern
from mopidy.frontends.mpd.protocol import request_handlers, handle_request
from mopidy.mixers.dummy import DummyMixer
class MpdDispatcherTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.dispatcher = MpdDispatcher()
def tearDown(self):
self.b.stop().get()
self.backend.stop().get()
self.mixer.stop().get()
def test_register_same_pattern_twice_fails(self):
func = lambda: None
try:
handle_pattern('a pattern')(func)
handle_pattern('a pattern')(func)
handle_request('a pattern')(func)
handle_request('a pattern')(func)
self.fail('Registering a pattern twice shoulde raise ValueError')
except ValueError:
pass
def test_finding_handler_for_unknown_command_raises_exception(self):
try:
self.h.find_handler('an_unknown_command with args')
self.dispatcher._find_handler('an_unknown_command with args')
self.fail('Should raise exception')
except MpdAckError as e:
self.assertEqual(e.get_mpd_ack(),
@ -37,18 +37,18 @@ class MpdDispatcherTest(unittest.TestCase):
expected_handler = lambda x: None
request_handlers['known_command (?P<arg1>.+)'] = \
expected_handler
(handler, kwargs) = self.h.find_handler('known_command an_arg')
(handler, kwargs) = self.dispatcher._find_handler('known_command an_arg')
self.assertEqual(handler, expected_handler)
self.assert_('arg1' in kwargs)
self.assertEqual(kwargs['arg1'], 'an_arg')
def test_handling_unknown_request_yields_error(self):
result = self.h.handle_request('an unhandled request')
result = self.dispatcher.handle_request('an unhandled request')
self.assertEqual(result[0], u'ACK [5@0] {} unknown command "an"')
def test_handling_known_request(self):
expected = 'magic'
request_handlers['known request'] = lambda x: expected
result = self.h.handle_request('known request')
result = self.dispatcher.handle_request('known request')
self.assert_(u'OK' in result)
self.assert_(expected in result)

View File

@ -1,7 +1,7 @@
import unittest
from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdUnknownCommand,
MpdSystemError, MpdNotImplemented)
from mopidy.frontends.mpd.exceptions import (MpdAckError, MpdPermissionError,
MpdUnknownCommand, MpdSystemError, MpdNotImplemented)
class MpdExceptionsTest(unittest.TestCase):
def test_key_error_wrapped_in_mpd_ack_error(self):
@ -25,10 +25,9 @@ class MpdExceptionsTest(unittest.TestCase):
def test_get_mpd_ack_with_values(self):
try:
raise MpdAckError('A description', error_code=6, index=7,
command='foo')
raise MpdAckError('A description', index=7, command='foo')
except MpdAckError as e:
self.assertEqual(e.get_mpd_ack(), u'ACK [6@7] {foo} A description')
self.assertEqual(e.get_mpd_ack(), u'ACK [0@7] {foo} A description')
def test_mpd_unknown_command(self):
try:
@ -43,3 +42,10 @@ class MpdExceptionsTest(unittest.TestCase):
except MpdSystemError as e:
self.assertEqual(e.get_mpd_ack(),
u'ACK [52@0] {} foo')
def test_mpd_permission_error(self):
try:
raise MpdPermissionError(command='foo')
except MpdPermissionError as e:
self.assertEqual(e.get_mpd_ack(),
u'ACK [4@0] {foo} you don\'t have permission for "foo"')

View File

@ -1,390 +1,412 @@
import unittest
from mopidy.backends.dummy import DummyBackend
from mopidy.frontends.mpd import dispatcher
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
from mopidy.mixers.dummy import DummyMixer
class MusicDatabaseHandlerTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.dispatcher = MpdDispatcher()
def tearDown(self):
self.b.stop().get()
self.backend.stop().get()
self.mixer.stop().get()
def test_count(self):
result = self.h.handle_request(u'count "tag" "needle"')
result = self.dispatcher.handle_request(u'count "tag" "needle"')
self.assert_(u'songs: 0' in result)
self.assert_(u'playtime: 0' in result)
self.assert_(u'OK' in result)
def test_findadd(self):
result = self.h.handle_request(u'findadd "album" "what"')
result = self.dispatcher.handle_request(u'findadd "album" "what"')
self.assert_(u'OK' in result)
def test_listall(self):
result = self.h.handle_request(u'listall "file:///dev/urandom"')
result = self.dispatcher.handle_request(
u'listall "file:///dev/urandom"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_listallinfo(self):
result = self.h.handle_request(u'listallinfo "file:///dev/urandom"')
result = self.dispatcher.handle_request(
u'listallinfo "file:///dev/urandom"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_lsinfo_without_path_returns_same_as_listplaylists(self):
lsinfo_result = self.h.handle_request(u'lsinfo')
listplaylists_result = self.h.handle_request(u'listplaylists')
lsinfo_result = self.dispatcher.handle_request(u'lsinfo')
listplaylists_result = self.dispatcher.handle_request(u'listplaylists')
self.assertEqual(lsinfo_result, listplaylists_result)
def test_lsinfo_with_empty_path_returns_same_as_listplaylists(self):
lsinfo_result = self.h.handle_request(u'lsinfo ""')
listplaylists_result = self.h.handle_request(u'listplaylists')
lsinfo_result = self.dispatcher.handle_request(u'lsinfo ""')
listplaylists_result = self.dispatcher.handle_request(u'listplaylists')
self.assertEqual(lsinfo_result, listplaylists_result)
def test_lsinfo_for_root_returns_same_as_listplaylists(self):
lsinfo_result = self.h.handle_request(u'lsinfo "/"')
listplaylists_result = self.h.handle_request(u'listplaylists')
lsinfo_result = self.dispatcher.handle_request(u'lsinfo "/"')
listplaylists_result = self.dispatcher.handle_request(u'listplaylists')
self.assertEqual(lsinfo_result, listplaylists_result)
def test_update_without_uri(self):
result = self.h.handle_request(u'update')
result = self.dispatcher.handle_request(u'update')
self.assert_(u'OK' in result)
self.assert_(u'updating_db: 0' in result)
def test_update_with_uri(self):
result = self.h.handle_request(u'update "file:///dev/urandom"')
result = self.dispatcher.handle_request(u'update "file:///dev/urandom"')
self.assert_(u'OK' in result)
self.assert_(u'updating_db: 0' in result)
def test_rescan_without_uri(self):
result = self.h.handle_request(u'rescan')
result = self.dispatcher.handle_request(u'rescan')
self.assert_(u'OK' in result)
self.assert_(u'updating_db: 0' in result)
def test_rescan_with_uri(self):
result = self.h.handle_request(u'rescan "file:///dev/urandom"')
result = self.dispatcher.handle_request(u'rescan "file:///dev/urandom"')
self.assert_(u'OK' in result)
self.assert_(u'updating_db: 0' in result)
class MusicDatabaseFindTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.dispatcher = MpdDispatcher()
def tearDown(self):
self.b.stop().get()
self.backend.stop().get()
self.mixer.stop().get()
def test_find_album(self):
result = self.h.handle_request(u'find "album" "what"')
result = self.dispatcher.handle_request(u'find "album" "what"')
self.assert_(u'OK' in result)
def test_find_album_without_quotes(self):
result = self.h.handle_request(u'find album "what"')
result = self.dispatcher.handle_request(u'find album "what"')
self.assert_(u'OK' in result)
def test_find_artist(self):
result = self.h.handle_request(u'find "artist" "what"')
result = self.dispatcher.handle_request(u'find "artist" "what"')
self.assert_(u'OK' in result)
def test_find_artist_without_quotes(self):
result = self.h.handle_request(u'find artist "what"')
result = self.dispatcher.handle_request(u'find artist "what"')
self.assert_(u'OK' in result)
def test_find_title(self):
result = self.h.handle_request(u'find "title" "what"')
result = self.dispatcher.handle_request(u'find "title" "what"')
self.assert_(u'OK' in result)
def test_find_title_without_quotes(self):
result = self.h.handle_request(u'find title "what"')
result = self.dispatcher.handle_request(u'find title "what"')
self.assert_(u'OK' in result)
def test_find_date(self):
result = self.h.handle_request(u'find "date" "2002-01-01"')
result = self.dispatcher.handle_request(u'find "date" "2002-01-01"')
self.assert_(u'OK' in result)
def test_find_date_without_quotes(self):
result = self.h.handle_request(u'find date "2002-01-01"')
result = self.dispatcher.handle_request(u'find date "2002-01-01"')
self.assert_(u'OK' in result)
def test_find_date_with_capital_d_and_incomplete_date(self):
result = self.h.handle_request(u'find Date "2005"')
result = self.dispatcher.handle_request(u'find Date "2005"')
self.assert_(u'OK' in result)
def test_find_else_should_fail(self):
result = self.h.handle_request(u'find "somethingelse" "what"')
result = self.dispatcher.handle_request(u'find "somethingelse" "what"')
self.assertEqual(result[0], u'ACK [2@0] {find} incorrect arguments')
def test_find_album_and_artist(self):
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'find album "album_what" artist "artist_what"')
self.assert_(u'OK' in result)
class MusicDatabaseListTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.dispatcher = MpdDispatcher()
def tearDown(self):
self.b.stop().get()
self.backend.stop().get()
self.mixer.stop().get()
def test_list_foo_returns_ack(self):
result = self.h.handle_request(u'list "foo"')
result = self.dispatcher.handle_request(u'list "foo"')
self.assertEqual(result[0],
u'ACK [2@0] {list} incorrect arguments')
### Artist
def test_list_artist_with_quotes(self):
result = self.h.handle_request(u'list "artist"')
result = self.dispatcher.handle_request(u'list "artist"')
self.assert_(u'OK' in result)
def test_list_artist_without_quotes(self):
result = self.h.handle_request(u'list artist')
result = self.dispatcher.handle_request(u'list artist')
self.assert_(u'OK' in result)
def test_list_artist_without_quotes_and_capitalized(self):
result = self.h.handle_request(u'list Artist')
result = self.dispatcher.handle_request(u'list Artist')
self.assert_(u'OK' in result)
def test_list_artist_with_query_of_one_token(self):
result = self.h.handle_request(u'list "artist" "anartist"')
result = self.dispatcher.handle_request(u'list "artist" "anartist"')
self.assertEqual(result[0],
u'ACK [2@0] {list} should be "Album" for 3 arguments')
def test_list_artist_with_unknown_field_in_query_returns_ack(self):
result = self.h.handle_request(u'list "artist" "foo" "bar"')
result = self.dispatcher.handle_request(u'list "artist" "foo" "bar"')
self.assertEqual(result[0],
u'ACK [2@0] {list} not able to parse args')
def test_list_artist_by_artist(self):
result = self.h.handle_request(u'list "artist" "artist" "anartist"')
result = self.dispatcher.handle_request(
u'list "artist" "artist" "anartist"')
self.assert_(u'OK' in result)
def test_list_artist_by_album(self):
result = self.h.handle_request(u'list "artist" "album" "analbum"')
result = self.dispatcher.handle_request(
u'list "artist" "album" "analbum"')
self.assert_(u'OK' in result)
def test_list_artist_by_full_date(self):
result = self.h.handle_request(u'list "artist" "date" "2001-01-01"')
result = self.dispatcher.handle_request(
u'list "artist" "date" "2001-01-01"')
self.assert_(u'OK' in result)
def test_list_artist_by_year(self):
result = self.h.handle_request(u'list "artist" "date" "2001"')
result = self.dispatcher.handle_request(
u'list "artist" "date" "2001"')
self.assert_(u'OK' in result)
def test_list_artist_by_genre(self):
result = self.h.handle_request(u'list "artist" "genre" "agenre"')
result = self.dispatcher.handle_request(
u'list "artist" "genre" "agenre"')
self.assert_(u'OK' in result)
def test_list_artist_by_artist_and_album(self):
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'list "artist" "artist" "anartist" "album" "analbum"')
self.assert_(u'OK' in result)
### Album
def test_list_album_with_quotes(self):
result = self.h.handle_request(u'list "album"')
result = self.dispatcher.handle_request(u'list "album"')
self.assert_(u'OK' in result)
def test_list_album_without_quotes(self):
result = self.h.handle_request(u'list album')
result = self.dispatcher.handle_request(u'list album')
self.assert_(u'OK' in result)
def test_list_album_without_quotes_and_capitalized(self):
result = self.h.handle_request(u'list Album')
result = self.dispatcher.handle_request(u'list Album')
self.assert_(u'OK' in result)
def test_list_album_with_artist_name(self):
result = self.h.handle_request(u'list "album" "anartist"')
result = self.dispatcher.handle_request(u'list "album" "anartist"')
self.assert_(u'OK' in result)
def test_list_album_by_artist(self):
result = self.h.handle_request(u'list "album" "artist" "anartist"')
result = self.dispatcher.handle_request(
u'list "album" "artist" "anartist"')
self.assert_(u'OK' in result)
def test_list_album_by_album(self):
result = self.h.handle_request(u'list "album" "album" "analbum"')
result = self.dispatcher.handle_request(
u'list "album" "album" "analbum"')
self.assert_(u'OK' in result)
def test_list_album_by_full_date(self):
result = self.h.handle_request(u'list "album" "date" "2001-01-01"')
result = self.dispatcher.handle_request(
u'list "album" "date" "2001-01-01"')
self.assert_(u'OK' in result)
def test_list_album_by_year(self):
result = self.h.handle_request(u'list "album" "date" "2001"')
result = self.dispatcher.handle_request(
u'list "album" "date" "2001"')
self.assert_(u'OK' in result)
def test_list_album_by_genre(self):
result = self.h.handle_request(u'list "album" "genre" "agenre"')
result = self.dispatcher.handle_request(
u'list "album" "genre" "agenre"')
self.assert_(u'OK' in result)
def test_list_album_by_artist_and_album(self):
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'list "album" "artist" "anartist" "album" "analbum"')
self.assert_(u'OK' in result)
### Date
def test_list_date_with_quotes(self):
result = self.h.handle_request(u'list "date"')
result = self.dispatcher.handle_request(u'list "date"')
self.assert_(u'OK' in result)
def test_list_date_without_quotes(self):
result = self.h.handle_request(u'list date')
result = self.dispatcher.handle_request(u'list date')
self.assert_(u'OK' in result)
def test_list_date_without_quotes_and_capitalized(self):
result = self.h.handle_request(u'list Date')
result = self.dispatcher.handle_request(u'list Date')
self.assert_(u'OK' in result)
def test_list_date_with_query_of_one_token(self):
result = self.h.handle_request(u'list "date" "anartist"')
result = self.dispatcher.handle_request(u'list "date" "anartist"')
self.assertEqual(result[0],
u'ACK [2@0] {list} should be "Album" for 3 arguments')
def test_list_date_by_artist(self):
result = self.h.handle_request(u'list "date" "artist" "anartist"')
result = self.dispatcher.handle_request(
u'list "date" "artist" "anartist"')
self.assert_(u'OK' in result)
def test_list_date_by_album(self):
result = self.h.handle_request(u'list "date" "album" "analbum"')
result = self.dispatcher.handle_request(
u'list "date" "album" "analbum"')
self.assert_(u'OK' in result)
def test_list_date_by_full_date(self):
result = self.h.handle_request(u'list "date" "date" "2001-01-01"')
result = self.dispatcher.handle_request(
u'list "date" "date" "2001-01-01"')
self.assert_(u'OK' in result)
def test_list_date_by_year(self):
result = self.h.handle_request(u'list "date" "date" "2001"')
result = self.dispatcher.handle_request(u'list "date" "date" "2001"')
self.assert_(u'OK' in result)
def test_list_date_by_genre(self):
result = self.h.handle_request(u'list "date" "genre" "agenre"')
result = self.dispatcher.handle_request(u'list "date" "genre" "agenre"')
self.assert_(u'OK' in result)
def test_list_date_by_artist_and_album(self):
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'list "date" "artist" "anartist" "album" "analbum"')
self.assert_(u'OK' in result)
### Genre
def test_list_genre_with_quotes(self):
result = self.h.handle_request(u'list "genre"')
result = self.dispatcher.handle_request(u'list "genre"')
self.assert_(u'OK' in result)
def test_list_genre_without_quotes(self):
result = self.h.handle_request(u'list genre')
result = self.dispatcher.handle_request(u'list genre')
self.assert_(u'OK' in result)
def test_list_genre_without_quotes_and_capitalized(self):
result = self.h.handle_request(u'list Genre')
result = self.dispatcher.handle_request(u'list Genre')
self.assert_(u'OK' in result)
def test_list_genre_with_query_of_one_token(self):
result = self.h.handle_request(u'list "genre" "anartist"')
result = self.dispatcher.handle_request(u'list "genre" "anartist"')
self.assertEqual(result[0],
u'ACK [2@0] {list} should be "Album" for 3 arguments')
def test_list_genre_by_artist(self):
result = self.h.handle_request(u'list "genre" "artist" "anartist"')
result = self.dispatcher.handle_request(
u'list "genre" "artist" "anartist"')
self.assert_(u'OK' in result)
def test_list_genre_by_album(self):
result = self.h.handle_request(u'list "genre" "album" "analbum"')
result = self.dispatcher.handle_request(
u'list "genre" "album" "analbum"')
self.assert_(u'OK' in result)
def test_list_genre_by_full_date(self):
result = self.h.handle_request(u'list "genre" "date" "2001-01-01"')
result = self.dispatcher.handle_request(
u'list "genre" "date" "2001-01-01"')
self.assert_(u'OK' in result)
def test_list_genre_by_year(self):
result = self.h.handle_request(u'list "genre" "date" "2001"')
result = self.dispatcher.handle_request(
u'list "genre" "date" "2001"')
self.assert_(u'OK' in result)
def test_list_genre_by_genre(self):
result = self.h.handle_request(u'list "genre" "genre" "agenre"')
result = self.dispatcher.handle_request(
u'list "genre" "genre" "agenre"')
self.assert_(u'OK' in result)
def test_list_genre_by_artist_and_album(self):
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'list "genre" "artist" "anartist" "album" "analbum"')
self.assert_(u'OK' in result)
class MusicDatabaseSearchTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.dispatcher = MpdDispatcher()
def tearDown(self):
self.b.stop().get()
self.backend.stop().get()
self.mixer.stop().get()
def test_search_album(self):
result = self.h.handle_request(u'search "album" "analbum"')
result = self.dispatcher.handle_request(u'search "album" "analbum"')
self.assert_(u'OK' in result)
def test_search_album_without_quotes(self):
result = self.h.handle_request(u'search album "analbum"')
result = self.dispatcher.handle_request(u'search album "analbum"')
self.assert_(u'OK' in result)
def test_search_artist(self):
result = self.h.handle_request(u'search "artist" "anartist"')
result = self.dispatcher.handle_request(u'search "artist" "anartist"')
self.assert_(u'OK' in result)
def test_search_artist_without_quotes(self):
result = self.h.handle_request(u'search artist "anartist"')
result = self.dispatcher.handle_request(u'search artist "anartist"')
self.assert_(u'OK' in result)
def test_search_filename(self):
result = self.h.handle_request(u'search "filename" "afilename"')
result = self.dispatcher.handle_request(
u'search "filename" "afilename"')
self.assert_(u'OK' in result)
def test_search_filename_without_quotes(self):
result = self.h.handle_request(u'search filename "afilename"')
result = self.dispatcher.handle_request(u'search filename "afilename"')
self.assert_(u'OK' in result)
def test_search_title(self):
result = self.h.handle_request(u'search "title" "atitle"')
result = self.dispatcher.handle_request(u'search "title" "atitle"')
self.assert_(u'OK' in result)
def test_search_title_without_quotes(self):
result = self.h.handle_request(u'search title "atitle"')
result = self.dispatcher.handle_request(u'search title "atitle"')
self.assert_(u'OK' in result)
def test_search_any(self):
result = self.h.handle_request(u'search "any" "anything"')
result = self.dispatcher.handle_request(u'search "any" "anything"')
self.assert_(u'OK' in result)
def test_search_any_without_quotes(self):
result = self.h.handle_request(u'search any "anything"')
result = self.dispatcher.handle_request(u'search any "anything"')
self.assert_(u'OK' in result)
def test_search_date(self):
result = self.h.handle_request(u'search "date" "2002-01-01"')
result = self.dispatcher.handle_request(u'search "date" "2002-01-01"')
self.assert_(u'OK' in result)
def test_search_date_without_quotes(self):
result = self.h.handle_request(u'search date "2002-01-01"')
result = self.dispatcher.handle_request(u'search date "2002-01-01"')
self.assert_(u'OK' in result)
def test_search_date_with_capital_d_and_incomplete_date(self):
result = self.h.handle_request(u'search Date "2005"')
result = self.dispatcher.handle_request(u'search Date "2005"')
self.assert_(u'OK' in result)
def test_search_else_should_fail(self):
result = self.h.handle_request(u'search "sometype" "something"')
result = self.dispatcher.handle_request(
u'search "sometype" "something"')
self.assertEqual(result[0], u'ACK [2@0] {search} incorrect arguments')

View File

@ -2,7 +2,7 @@ import unittest
from mopidy.backends.base import PlaybackController
from mopidy.backends.dummy import DummyBackend
from mopidy.frontends.mpd import dispatcher
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
from mopidy.mixers.dummy import DummyMixer
from mopidy.models import Track
@ -14,393 +14,378 @@ STOPPED = PlaybackController.STOPPED
class PlaybackOptionsHandlerTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.dispatcher = MpdDispatcher()
def tearDown(self):
self.b.stop().get()
self.backend.stop().get()
self.mixer.stop().get()
def test_consume_off(self):
result = self.h.handle_request(u'consume "0"')
self.assertFalse(self.b.playback.consume.get())
result = self.dispatcher.handle_request(u'consume "0"')
self.assertFalse(self.backend.playback.consume.get())
self.assert_(u'OK' in result)
def test_consume_off_without_quotes(self):
result = self.h.handle_request(u'consume 0')
self.assertFalse(self.b.playback.consume.get())
result = self.dispatcher.handle_request(u'consume 0')
self.assertFalse(self.backend.playback.consume.get())
self.assert_(u'OK' in result)
def test_consume_on(self):
result = self.h.handle_request(u'consume "1"')
self.assertTrue(self.b.playback.consume.get())
result = self.dispatcher.handle_request(u'consume "1"')
self.assertTrue(self.backend.playback.consume.get())
self.assert_(u'OK' in result)
def test_consume_on_without_quotes(self):
result = self.h.handle_request(u'consume 1')
self.assertTrue(self.b.playback.consume.get())
result = self.dispatcher.handle_request(u'consume 1')
self.assertTrue(self.backend.playback.consume.get())
self.assert_(u'OK' in result)
def test_crossfade(self):
result = self.h.handle_request(u'crossfade "10"')
result = self.dispatcher.handle_request(u'crossfade "10"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_random_off(self):
result = self.h.handle_request(u'random "0"')
self.assertFalse(self.b.playback.random.get())
result = self.dispatcher.handle_request(u'random "0"')
self.assertFalse(self.backend.playback.random.get())
self.assert_(u'OK' in result)
def test_random_off_without_quotes(self):
result = self.h.handle_request(u'random 0')
self.assertFalse(self.b.playback.random.get())
result = self.dispatcher.handle_request(u'random 0')
self.assertFalse(self.backend.playback.random.get())
self.assert_(u'OK' in result)
def test_random_on(self):
result = self.h.handle_request(u'random "1"')
self.assertTrue(self.b.playback.random.get())
result = self.dispatcher.handle_request(u'random "1"')
self.assertTrue(self.backend.playback.random.get())
self.assert_(u'OK' in result)
def test_random_on_without_quotes(self):
result = self.h.handle_request(u'random 1')
self.assertTrue(self.b.playback.random.get())
result = self.dispatcher.handle_request(u'random 1')
self.assertTrue(self.backend.playback.random.get())
self.assert_(u'OK' in result)
def test_repeat_off(self):
result = self.h.handle_request(u'repeat "0"')
self.assertFalse(self.b.playback.repeat.get())
result = self.dispatcher.handle_request(u'repeat "0"')
self.assertFalse(self.backend.playback.repeat.get())
self.assert_(u'OK' in result)
def test_repeat_off_without_quotes(self):
result = self.h.handle_request(u'repeat 0')
self.assertFalse(self.b.playback.repeat.get())
result = self.dispatcher.handle_request(u'repeat 0')
self.assertFalse(self.backend.playback.repeat.get())
self.assert_(u'OK' in result)
def test_repeat_on(self):
result = self.h.handle_request(u'repeat "1"')
self.assertTrue(self.b.playback.repeat.get())
result = self.dispatcher.handle_request(u'repeat "1"')
self.assertTrue(self.backend.playback.repeat.get())
self.assert_(u'OK' in result)
def test_repeat_on_without_quotes(self):
result = self.h.handle_request(u'repeat 1')
self.assertTrue(self.b.playback.repeat.get())
result = self.dispatcher.handle_request(u'repeat 1')
self.assertTrue(self.backend.playback.repeat.get())
self.assert_(u'OK' in result)
def test_setvol_below_min(self):
result = self.h.handle_request(u'setvol "-10"')
result = self.dispatcher.handle_request(u'setvol "-10"')
self.assert_(u'OK' in result)
self.assertEqual(0, self.mixer.volume.get())
def test_setvol_min(self):
result = self.h.handle_request(u'setvol "0"')
result = self.dispatcher.handle_request(u'setvol "0"')
self.assert_(u'OK' in result)
self.assertEqual(0, self.mixer.volume.get())
def test_setvol_middle(self):
result = self.h.handle_request(u'setvol "50"')
result = self.dispatcher.handle_request(u'setvol "50"')
self.assert_(u'OK' in result)
self.assertEqual(50, self.mixer.volume.get())
def test_setvol_max(self):
result = self.h.handle_request(u'setvol "100"')
result = self.dispatcher.handle_request(u'setvol "100"')
self.assert_(u'OK' in result)
self.assertEqual(100, self.mixer.volume.get())
def test_setvol_above_max(self):
result = self.h.handle_request(u'setvol "110"')
result = self.dispatcher.handle_request(u'setvol "110"')
self.assert_(u'OK' in result)
self.assertEqual(100, self.mixer.volume.get())
def test_setvol_plus_is_ignored(self):
result = self.h.handle_request(u'setvol "+10"')
result = self.dispatcher.handle_request(u'setvol "+10"')
self.assert_(u'OK' in result)
self.assertEqual(10, self.mixer.volume.get())
def test_setvol_without_quotes(self):
result = self.h.handle_request(u'setvol 50')
result = self.dispatcher.handle_request(u'setvol 50')
self.assert_(u'OK' in result)
self.assertEqual(50, self.mixer.volume.get())
def test_single_off(self):
result = self.h.handle_request(u'single "0"')
self.assertFalse(self.b.playback.single.get())
result = self.dispatcher.handle_request(u'single "0"')
self.assertFalse(self.backend.playback.single.get())
self.assert_(u'OK' in result)
def test_single_off_without_quotes(self):
result = self.h.handle_request(u'single 0')
self.assertFalse(self.b.playback.single.get())
result = self.dispatcher.handle_request(u'single 0')
self.assertFalse(self.backend.playback.single.get())
self.assert_(u'OK' in result)
def test_single_on(self):
result = self.h.handle_request(u'single "1"')
self.assertTrue(self.b.playback.single.get())
result = self.dispatcher.handle_request(u'single "1"')
self.assertTrue(self.backend.playback.single.get())
self.assert_(u'OK' in result)
def test_single_on_without_quotes(self):
result = self.h.handle_request(u'single 1')
self.assertTrue(self.b.playback.single.get())
result = self.dispatcher.handle_request(u'single 1')
self.assertTrue(self.backend.playback.single.get())
self.assert_(u'OK' in result)
def test_replay_gain_mode_off(self):
result = self.h.handle_request(u'replay_gain_mode "off"')
result = self.dispatcher.handle_request(u'replay_gain_mode "off"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_replay_gain_mode_track(self):
result = self.h.handle_request(u'replay_gain_mode "track"')
result = self.dispatcher.handle_request(u'replay_gain_mode "track"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_replay_gain_mode_album(self):
result = self.h.handle_request(u'replay_gain_mode "album"')
result = self.dispatcher.handle_request(u'replay_gain_mode "album"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_replay_gain_status_default(self):
expected = u'off'
result = self.h.handle_request(u'replay_gain_status')
result = self.dispatcher.handle_request(u'replay_gain_status')
self.assert_(u'OK' in result)
self.assert_(expected in result)
def test_replay_gain_status_off(self):
raise SkipTest
expected = u'off'
self.h._replay_gain_mode(expected)
result = self.h.handle_request(u'replay_gain_status')
self.assert_(u'OK' in result)
self.assert_(expected in result)
raise SkipTest # TODO
def test_replay_gain_status_track(self):
raise SkipTest
expected = u'track'
self.h._replay_gain_mode(expected)
result = self.h.handle_request(u'replay_gain_status')
self.assert_(u'OK' in result)
self.assert_(expected in result)
raise SkipTest # TODO
def test_replay_gain_status_album(self):
raise SkipTest
expected = u'album'
self.h._replay_gain_mode(expected)
result = self.h.handle_request(u'replay_gain_status')
self.assert_(u'OK' in result)
self.assert_(expected in result)
raise SkipTest # TODO
class PlaybackControlHandlerTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.dispatcher = MpdDispatcher()
def tearDown(self):
self.b.stop().get()
self.backend.stop().get()
self.mixer.stop().get()
def test_next(self):
result = self.h.handle_request(u'next')
result = self.dispatcher.handle_request(u'next')
self.assert_(u'OK' in result)
def test_pause_off(self):
self.b.current_playlist.append([Track()])
self.h.handle_request(u'play "0"')
self.h.handle_request(u'pause "1"')
result = self.h.handle_request(u'pause "0"')
self.backend.current_playlist.append([Track()])
self.dispatcher.handle_request(u'play "0"')
self.dispatcher.handle_request(u'pause "1"')
result = self.dispatcher.handle_request(u'pause "0"')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
self.assertEqual(PLAYING, self.backend.playback.state.get())
def test_pause_on(self):
self.b.current_playlist.append([Track()])
self.h.handle_request(u'play "0"')
result = self.h.handle_request(u'pause "1"')
self.backend.current_playlist.append([Track()])
self.dispatcher.handle_request(u'play "0"')
result = self.dispatcher.handle_request(u'pause "1"')
self.assert_(u'OK' in result)
self.assertEqual(PAUSED, self.b.playback.state.get())
self.assertEqual(PAUSED, self.backend.playback.state.get())
def test_pause_toggle(self):
self.b.current_playlist.append([Track()])
result = self.h.handle_request(u'play "0"')
self.backend.current_playlist.append([Track()])
result = self.dispatcher.handle_request(u'play "0"')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
result = self.h.handle_request(u'pause')
self.assertEqual(PLAYING, self.backend.playback.state.get())
result = self.dispatcher.handle_request(u'pause')
self.assert_(u'OK' in result)
self.assertEqual(PAUSED, self.b.playback.state.get())
result = self.h.handle_request(u'pause')
self.assertEqual(PAUSED, self.backend.playback.state.get())
result = self.dispatcher.handle_request(u'pause')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
self.assertEqual(PLAYING, self.backend.playback.state.get())
def test_play_without_pos(self):
self.b.current_playlist.append([Track()])
self.b.playback.state = PAUSED
result = self.h.handle_request(u'play')
self.backend.current_playlist.append([Track()])
self.backend.playback.state = PAUSED
result = self.dispatcher.handle_request(u'play')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
self.assertEqual(PLAYING, self.backend.playback.state.get())
def test_play_with_pos(self):
self.b.current_playlist.append([Track()])
result = self.h.handle_request(u'play "0"')
self.backend.current_playlist.append([Track()])
result = self.dispatcher.handle_request(u'play "0"')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
self.assertEqual(PLAYING, self.backend.playback.state.get())
def test_play_with_pos_without_quotes(self):
self.b.current_playlist.append([Track()])
result = self.h.handle_request(u'play 0')
self.backend.current_playlist.append([Track()])
result = self.dispatcher.handle_request(u'play 0')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
self.assertEqual(PLAYING, self.backend.playback.state.get())
def test_play_with_pos_out_of_bounds(self):
self.b.current_playlist.append([])
result = self.h.handle_request(u'play "0"')
self.backend.current_playlist.append([])
result = self.dispatcher.handle_request(u'play "0"')
self.assertEqual(result[0], u'ACK [2@0] {play} Bad song index')
self.assertEqual(STOPPED, self.b.playback.state.get())
self.assertEqual(STOPPED, self.backend.playback.state.get())
def test_play_minus_one_plays_first_in_playlist_if_no_current_track(self):
self.assertEqual(self.b.playback.current_track.get(), None)
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
result = self.h.handle_request(u'play "-1"')
self.assertEqual(self.backend.playback.current_track.get(), None)
self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')])
result = self.dispatcher.handle_request(u'play "-1"')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
self.assertEqual(self.b.playback.current_track.get().uri, 'a')
self.assertEqual(PLAYING, self.backend.playback.state.get())
self.assertEqual(self.backend.playback.current_track.get().uri, 'a')
def test_play_minus_one_plays_current_track_if_current_track_is_set(self):
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
self.assertEqual(self.b.playback.current_track.get(), None)
self.b.playback.play()
self.b.playback.next()
self.b.playback.stop()
self.assertNotEqual(self.b.playback.current_track.get(), None)
result = self.h.handle_request(u'play "-1"')
self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')])
self.assertEqual(self.backend.playback.current_track.get(), None)
self.backend.playback.play()
self.backend.playback.next()
self.backend.playback.stop()
self.assertNotEqual(self.backend.playback.current_track.get(), None)
result = self.dispatcher.handle_request(u'play "-1"')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
self.assertEqual(self.b.playback.current_track.get().uri, 'b')
self.assertEqual(PLAYING, self.backend.playback.state.get())
self.assertEqual(self.backend.playback.current_track.get().uri, 'b')
def test_play_minus_one_on_empty_playlist_does_not_ack(self):
self.b.current_playlist.clear()
result = self.h.handle_request(u'play "-1"')
self.backend.current_playlist.clear()
result = self.dispatcher.handle_request(u'play "-1"')
self.assert_(u'OK' in result)
self.assertEqual(STOPPED, self.b.playback.state.get())
self.assertEqual(self.b.playback.current_track.get(), None)
self.assertEqual(STOPPED, self.backend.playback.state.get())
self.assertEqual(self.backend.playback.current_track.get(), None)
def test_play_minus_is_ignored_if_playing(self):
self.b.current_playlist.append([Track(length=40000)])
self.b.playback.seek(30000)
self.assert_(self.b.playback.time_position.get() >= 30000)
self.assertEquals(PLAYING, self.b.playback.state.get())
result = self.h.handle_request(u'play "-1"')
self.backend.current_playlist.append([Track(length=40000)])
self.backend.playback.seek(30000)
self.assert_(self.backend.playback.time_position.get() >= 30000)
self.assertEquals(PLAYING, self.backend.playback.state.get())
result = self.dispatcher.handle_request(u'play "-1"')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
self.assert_(self.b.playback.time_position.get() >= 30000)
self.assertEqual(PLAYING, self.backend.playback.state.get())
self.assert_(self.backend.playback.time_position.get() >= 30000)
def test_play_minus_one_resumes_if_paused(self):
self.b.current_playlist.append([Track(length=40000)])
self.b.playback.seek(30000)
self.assert_(self.b.playback.time_position.get() >= 30000)
self.assertEquals(PLAYING, self.b.playback.state.get())
self.b.playback.pause()
self.assertEquals(PAUSED, self.b.playback.state.get())
result = self.h.handle_request(u'play "-1"')
self.backend.current_playlist.append([Track(length=40000)])
self.backend.playback.seek(30000)
self.assert_(self.backend.playback.time_position.get() >= 30000)
self.assertEquals(PLAYING, self.backend.playback.state.get())
self.backend.playback.pause()
self.assertEquals(PAUSED, self.backend.playback.state.get())
result = self.dispatcher.handle_request(u'play "-1"')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
self.assert_(self.b.playback.time_position.get() >= 30000)
self.assertEqual(PLAYING, self.backend.playback.state.get())
self.assert_(self.backend.playback.time_position.get() >= 30000)
def test_playid(self):
self.b.current_playlist.append([Track()])
result = self.h.handle_request(u'playid "0"')
self.backend.current_playlist.append([Track()])
result = self.dispatcher.handle_request(u'playid "0"')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
self.assertEqual(PLAYING, self.backend.playback.state.get())
def test_playid_minus_one_plays_first_in_playlist_if_no_current_track(self):
self.assertEqual(self.b.playback.current_track.get(), None)
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
result = self.h.handle_request(u'playid "-1"')
self.assertEqual(self.backend.playback.current_track.get(), None)
self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')])
result = self.dispatcher.handle_request(u'playid "-1"')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
self.assertEqual(self.b.playback.current_track.get().uri, 'a')
self.assertEqual(PLAYING, self.backend.playback.state.get())
self.assertEqual(self.backend.playback.current_track.get().uri, 'a')
def test_playid_minus_one_plays_current_track_if_current_track_is_set(self):
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
self.assertEqual(self.b.playback.current_track.get(), None)
self.b.playback.play()
self.b.playback.next()
self.b.playback.stop()
self.assertNotEqual(self.b.playback.current_track.get(), None)
result = self.h.handle_request(u'playid "-1"')
self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')])
self.assertEqual(self.backend.playback.current_track.get(), None)
self.backend.playback.play()
self.backend.playback.next()
self.backend.playback.stop()
self.assertNotEqual(self.backend.playback.current_track.get(), None)
result = self.dispatcher.handle_request(u'playid "-1"')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
self.assertEqual(self.b.playback.current_track.get().uri, 'b')
self.assertEqual(PLAYING, self.backend.playback.state.get())
self.assertEqual(self.backend.playback.current_track.get().uri, 'b')
def test_playid_minus_one_on_empty_playlist_does_not_ack(self):
self.b.current_playlist.clear()
result = self.h.handle_request(u'playid "-1"')
self.backend.current_playlist.clear()
result = self.dispatcher.handle_request(u'playid "-1"')
self.assert_(u'OK' in result)
self.assertEqual(STOPPED, self.b.playback.state.get())
self.assertEqual(self.b.playback.current_track.get(), None)
self.assertEqual(STOPPED, self.backend.playback.state.get())
self.assertEqual(self.backend.playback.current_track.get(), None)
def test_playid_minus_is_ignored_if_playing(self):
self.b.current_playlist.append([Track(length=40000)])
self.b.playback.seek(30000)
self.assert_(self.b.playback.time_position.get() >= 30000)
self.assertEquals(PLAYING, self.b.playback.state.get())
result = self.h.handle_request(u'playid "-1"')
self.backend.current_playlist.append([Track(length=40000)])
self.backend.playback.seek(30000)
self.assert_(self.backend.playback.time_position.get() >= 30000)
self.assertEquals(PLAYING, self.backend.playback.state.get())
result = self.dispatcher.handle_request(u'playid "-1"')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
self.assert_(self.b.playback.time_position.get() >= 30000)
self.assertEqual(PLAYING, self.backend.playback.state.get())
self.assert_(self.backend.playback.time_position.get() >= 30000)
def test_playid_minus_one_resumes_if_paused(self):
self.b.current_playlist.append([Track(length=40000)])
self.b.playback.seek(30000)
self.assert_(self.b.playback.time_position.get() >= 30000)
self.assertEquals(PLAYING, self.b.playback.state.get())
self.b.playback.pause()
self.assertEquals(PAUSED, self.b.playback.state.get())
result = self.h.handle_request(u'playid "-1"')
self.backend.current_playlist.append([Track(length=40000)])
self.backend.playback.seek(30000)
self.assert_(self.backend.playback.time_position.get() >= 30000)
self.assertEquals(PLAYING, self.backend.playback.state.get())
self.backend.playback.pause()
self.assertEquals(PAUSED, self.backend.playback.state.get())
result = self.dispatcher.handle_request(u'playid "-1"')
self.assert_(u'OK' in result)
self.assertEqual(PLAYING, self.b.playback.state.get())
self.assert_(self.b.playback.time_position.get() >= 30000)
self.assertEqual(PLAYING, self.backend.playback.state.get())
self.assert_(self.backend.playback.time_position.get() >= 30000)
def test_playid_which_does_not_exist(self):
self.b.current_playlist.append([Track()])
result = self.h.handle_request(u'playid "12345"')
self.backend.current_playlist.append([Track()])
result = self.dispatcher.handle_request(u'playid "12345"')
self.assertEqual(result[0], u'ACK [50@0] {playid} No such song')
def test_previous(self):
result = self.h.handle_request(u'previous')
result = self.dispatcher.handle_request(u'previous')
self.assert_(u'OK' in result)
def test_seek(self):
self.b.current_playlist.append([Track(length=40000)])
self.h.handle_request(u'seek "0"')
result = self.h.handle_request(u'seek "0" "30"')
self.backend.current_playlist.append([Track(length=40000)])
self.dispatcher.handle_request(u'seek "0"')
result = self.dispatcher.handle_request(u'seek "0" "30"')
self.assert_(u'OK' in result)
self.assert_(self.b.playback.time_position >= 30000)
self.assert_(self.backend.playback.time_position >= 30000)
def test_seek_with_songpos(self):
seek_track = Track(uri='2', length=40000)
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(uri='1', length=40000), seek_track])
result = self.h.handle_request(u'seek "1" "30"')
result = self.dispatcher.handle_request(u'seek "1" "30"')
self.assert_(u'OK' in result)
self.assertEqual(self.b.playback.current_track.get(), seek_track)
self.assertEqual(self.backend.playback.current_track.get(), seek_track)
def test_seek_without_quotes(self):
self.b.current_playlist.append([Track(length=40000)])
self.h.handle_request(u'seek 0')
result = self.h.handle_request(u'seek 0 30')
self.backend.current_playlist.append([Track(length=40000)])
self.dispatcher.handle_request(u'seek 0')
result = self.dispatcher.handle_request(u'seek 0 30')
self.assert_(u'OK' in result)
self.assert_(self.b.playback.time_position.get() >= 30000)
self.assert_(self.backend.playback.time_position.get() >= 30000)
def test_seekid(self):
self.b.current_playlist.append([Track(length=40000)])
result = self.h.handle_request(u'seekid "0" "30"')
self.backend.current_playlist.append([Track(length=40000)])
result = self.dispatcher.handle_request(u'seekid "0" "30"')
self.assert_(u'OK' in result)
self.assert_(self.b.playback.time_position.get() >= 30000)
self.assert_(self.backend.playback.time_position.get() >= 30000)
def test_seekid_with_cpid(self):
seek_track = Track(uri='2', length=40000)
self.b.current_playlist.append(
self.backend.current_playlist.append(
[Track(length=40000), seek_track])
result = self.h.handle_request(u'seekid "1" "30"')
result = self.dispatcher.handle_request(u'seekid "1" "30"')
self.assert_(u'OK' in result)
self.assertEqual(self.b.playback.current_cpid.get(), 1)
self.assertEqual(self.b.playback.current_track.get(), seek_track)
self.assertEqual(self.backend.playback.current_cpid.get(), 1)
self.assertEqual(self.backend.playback.current_track.get(), seek_track)
def test_stop(self):
result = self.h.handle_request(u'stop')
result = self.dispatcher.handle_request(u'stop')
self.assert_(u'OK' in result)
self.assertEqual(STOPPED, self.b.playback.state.get())
self.assertEqual(STOPPED, self.backend.playback.state.get())

View File

@ -1,25 +1,29 @@
import unittest
from mopidy import settings
from mopidy.backends.dummy import DummyBackend
from mopidy.frontends.mpd import dispatcher
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
from mopidy.mixers.dummy import DummyMixer
class ReflectionHandlerTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.dispatcher = MpdDispatcher()
def tearDown(self):
self.b.stop().get()
settings.runtime.clear()
self.backend.stop().get()
self.mixer.stop().get()
def test_commands_returns_list_of_all_commands(self):
result = self.h.handle_request(u'commands')
result = self.dispatcher.handle_request(u'commands')
# Check if some random commands are included
self.assert_(u'command: commands' in result)
self.assert_(u'command: play' in result)
self.assert_(u'command: status' in result)
# Check if commands you do not have access to are not present
self.assert_(u'command: kill' not in result)
# Check if the blacklisted commands are not present
self.assert_(u'command: command_list_begin' not in result)
self.assert_(u'command: command_list_ok_begin' not in result)
@ -29,20 +33,47 @@ class ReflectionHandlerTest(unittest.TestCase):
self.assert_(u'command: sticker' not in result)
self.assert_(u'OK' in result)
def test_commands_show_less_if_auth_required_and_not_authed(self):
settings.MPD_SERVER_PASSWORD = u'secret'
result = self.dispatcher.handle_request(u'commands')
# Not requiring auth
self.assert_(u'command: close' in result, result)
self.assert_(u'command: commands' in result, result)
self.assert_(u'command: notcommands' in result, result)
self.assert_(u'command: password' in result, result)
self.assert_(u'command: ping' in result, result)
# Requiring auth
self.assert_(u'command: play' not in result, result)
self.assert_(u'command: status' not in result, result)
def test_decoders(self):
result = self.h.handle_request(u'decoders')
result = self.dispatcher.handle_request(u'decoders')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_notcommands_returns_only_ok(self):
result = self.h.handle_request(u'notcommands')
self.assertEqual(1, len(result))
def test_notcommands_returns_only_kill_and_ok(self):
result = self.dispatcher.handle_request(u'notcommands')
self.assertEqual(2, len(result))
self.assert_(u'command: kill' in result)
self.assert_(u'OK' in result)
def test_notcommands_returns_more_if_auth_required_and_not_authed(self):
settings.MPD_SERVER_PASSWORD = u'secret'
result = self.dispatcher.handle_request(u'notcommands')
# Not requiring auth
self.assert_(u'command: close' not in result, result)
self.assert_(u'command: commands' not in result, result)
self.assert_(u'command: notcommands' not in result, result)
self.assert_(u'command: password' not in result, result)
self.assert_(u'command: ping' not in result, result)
# Requiring auth
self.assert_(u'command: play' in result, result)
self.assert_(u'command: status' in result, result)
def test_tagtypes(self):
result = self.h.handle_request(u'tagtypes')
result = self.dispatcher.handle_request(u'tagtypes')
self.assert_(u'OK' in result)
def test_urlhandlers(self):
result = self.h.handle_request(u'urlhandlers')
result = self.dispatcher.handle_request(u'urlhandlers')
self.assert_(u'OK' in result)
self.assert_(u'handler: dummy:' in result)

View File

@ -44,52 +44,3 @@ class MpdSessionTest(unittest.TestCase):
self.session.input_buffer = ['\xff']
self.session.found_terminator()
self.assertEqual(len(self.session.input_buffer), 0)
def test_authentication_with_valid_password_is_accepted(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
authed, response = self.session.check_password(u'password "topsecret"')
self.assertTrue(authed)
self.assertEqual(u'OK', response)
def test_authentication_with_invalid_password_is_not_accepted(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
authed, response = self.session.check_password(u'password "secret"')
self.assertFalse(authed)
self.assertEqual(u'ACK [3@0] {password} incorrect password', response)
def test_authentication_with_anything_when_password_check_turned_off(self):
settings.MPD_SERVER_PASSWORD = None
authed, response = self.session.check_password(u'any request at all')
self.assertTrue(authed)
self.assertEqual(None, response)
def test_anything_when_not_authenticated_should_fail(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
authed, response = self.session.check_password(u'any request at all')
self.assertFalse(authed)
self.assertEqual(
u'ACK [4@0] {any} you don\'t have permission for "any"', response)
def test_close_is_allowed_without_authentication(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
authed, response = self.session.check_password(u'close')
self.assertFalse(authed)
self.assertEqual(None, response)
def test_commands_is_allowed_without_authentication(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
authed, response = self.session.check_password(u'commands')
self.assertFalse(authed)
self.assertEqual(None, response)
def test_notcommands_is_allowed_without_authentication(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
authed, response = self.session.check_password(u'notcommands')
self.assertFalse(authed)
self.assertEqual(None, response)
def test_ping_is_allowed_without_authentication(self):
settings.MPD_SERVER_PASSWORD = u'topsecret'
authed, response = self.session.check_password(u'ping')
self.assertFalse(authed)
self.assertEqual(None, response)

View File

@ -2,7 +2,8 @@ import unittest
from mopidy.backends.base import PlaybackController
from mopidy.backends.dummy import DummyBackend
from mopidy.frontends.mpd import dispatcher
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
from mopidy.frontends.mpd.protocol import status
from mopidy.mixers.dummy import DummyMixer
from mopidy.models import Track
@ -12,23 +13,24 @@ STOPPED = PlaybackController.STOPPED
class StatusHandlerTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.dispatcher = MpdDispatcher()
self.context = self.dispatcher.context
def tearDown(self):
self.b.stop().get()
self.backend.stop().get()
self.mixer.stop().get()
def test_clearerror(self):
result = self.h.handle_request(u'clearerror')
result = self.dispatcher.handle_request(u'clearerror')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_currentsong(self):
track = Track()
self.b.current_playlist.append([track])
self.b.playback.play()
result = self.h.handle_request(u'currentsong')
self.backend.current_playlist.append([track])
self.backend.playback.play()
result = self.dispatcher.handle_request(u'currentsong')
self.assert_(u'file: ' in result)
self.assert_(u'Time: 0' in result)
self.assert_(u'Artist: ' in result)
@ -41,27 +43,27 @@ class StatusHandlerTest(unittest.TestCase):
self.assert_(u'OK' in result)
def test_currentsong_without_song(self):
result = self.h.handle_request(u'currentsong')
result = self.dispatcher.handle_request(u'currentsong')
self.assert_(u'OK' in result)
def test_idle_without_subsystems(self):
result = self.h.handle_request(u'idle')
result = self.dispatcher.handle_request(u'idle')
self.assert_(u'OK' in result)
def test_idle_with_subsystems(self):
result = self.h.handle_request(u'idle database playlist')
result = self.dispatcher.handle_request(u'idle database playlist')
self.assert_(u'OK' in result)
def test_noidle(self):
result = self.h.handle_request(u'noidle')
result = self.dispatcher.handle_request(u'noidle')
self.assert_(u'OK' in result)
def test_stats_command(self):
result = self.h.handle_request(u'stats')
result = self.dispatcher.handle_request(u'stats')
self.assert_(u'OK' in result)
def test_stats_method(self):
result = dispatcher.status.stats(self.h)
result = status.stats(self.context)
self.assert_('artists' in result)
self.assert_(int(result['artists']) >= 0)
self.assert_('albums' in result)
@ -78,110 +80,110 @@ class StatusHandlerTest(unittest.TestCase):
self.assert_(int(result['playtime']) >= 0)
def test_status_command(self):
result = self.h.handle_request(u'status')
result = self.dispatcher.handle_request(u'status')
self.assert_(u'OK' in result)
def test_status_method_contains_volume_which_defaults_to_0(self):
result = dict(dispatcher.status.status(self.h))
result = dict(status.status(self.context))
self.assert_('volume' in result)
self.assertEqual(int(result['volume']), 0)
def test_status_method_contains_volume(self):
self.mixer.volume = 17
result = dict(dispatcher.status.status(self.h))
result = dict(status.status(self.context))
self.assert_('volume' in result)
self.assertEqual(int(result['volume']), 17)
def test_status_method_contains_repeat_is_0(self):
result = dict(dispatcher.status.status(self.h))
result = dict(status.status(self.context))
self.assert_('repeat' in result)
self.assertEqual(int(result['repeat']), 0)
def test_status_method_contains_repeat_is_1(self):
self.b.playback.repeat = 1
result = dict(dispatcher.status.status(self.h))
self.backend.playback.repeat = 1
result = dict(status.status(self.context))
self.assert_('repeat' in result)
self.assertEqual(int(result['repeat']), 1)
def test_status_method_contains_random_is_0(self):
result = dict(dispatcher.status.status(self.h))
result = dict(status.status(self.context))
self.assert_('random' in result)
self.assertEqual(int(result['random']), 0)
def test_status_method_contains_random_is_1(self):
self.b.playback.random = 1
result = dict(dispatcher.status.status(self.h))
self.backend.playback.random = 1
result = dict(status.status(self.context))
self.assert_('random' in result)
self.assertEqual(int(result['random']), 1)
def test_status_method_contains_single(self):
result = dict(dispatcher.status.status(self.h))
result = dict(status.status(self.context))
self.assert_('single' in result)
self.assert_(int(result['single']) in (0, 1))
def test_status_method_contains_consume_is_0(self):
result = dict(dispatcher.status.status(self.h))
result = dict(status.status(self.context))
self.assert_('consume' in result)
self.assertEqual(int(result['consume']), 0)
def test_status_method_contains_consume_is_1(self):
self.b.playback.consume = 1
result = dict(dispatcher.status.status(self.h))
self.backend.playback.consume = 1
result = dict(status.status(self.context))
self.assert_('consume' in result)
self.assertEqual(int(result['consume']), 1)
def test_status_method_contains_playlist(self):
result = dict(dispatcher.status.status(self.h))
result = dict(status.status(self.context))
self.assert_('playlist' in result)
self.assert_(int(result['playlist']) in xrange(0, 2**31 - 1))
def test_status_method_contains_playlistlength(self):
result = dict(dispatcher.status.status(self.h))
result = dict(status.status(self.context))
self.assert_('playlistlength' in result)
self.assert_(int(result['playlistlength']) >= 0)
def test_status_method_contains_xfade(self):
result = dict(dispatcher.status.status(self.h))
result = dict(status.status(self.context))
self.assert_('xfade' in result)
self.assert_(int(result['xfade']) >= 0)
def test_status_method_contains_state_is_play(self):
self.b.playback.state = PLAYING
result = dict(dispatcher.status.status(self.h))
self.backend.playback.state = PLAYING
result = dict(status.status(self.context))
self.assert_('state' in result)
self.assertEqual(result['state'], 'play')
def test_status_method_contains_state_is_stop(self):
self.b.playback.state = STOPPED
result = dict(dispatcher.status.status(self.h))
self.backend.playback.state = STOPPED
result = dict(status.status(self.context))
self.assert_('state' in result)
self.assertEqual(result['state'], 'stop')
def test_status_method_contains_state_is_pause(self):
self.b.playback.state = PLAYING
self.b.playback.state = PAUSED
result = dict(dispatcher.status.status(self.h))
self.backend.playback.state = PLAYING
self.backend.playback.state = PAUSED
result = dict(status.status(self.context))
self.assert_('state' in result)
self.assertEqual(result['state'], 'pause')
def test_status_method_when_playlist_loaded_contains_song(self):
self.b.current_playlist.append([Track()])
self.b.playback.play()
result = dict(dispatcher.status.status(self.h))
self.backend.current_playlist.append([Track()])
self.backend.playback.play()
result = dict(status.status(self.context))
self.assert_('song' in result)
self.assert_(int(result['song']) >= 0)
def test_status_method_when_playlist_loaded_contains_cpid_as_songid(self):
self.b.current_playlist.append([Track()])
self.b.playback.play()
result = dict(dispatcher.status.status(self.h))
self.backend.current_playlist.append([Track()])
self.backend.playback.play()
result = dict(status.status(self.context))
self.assert_('songid' in result)
self.assertEqual(int(result['songid']), 0)
def test_status_method_when_playing_contains_time_with_no_length(self):
self.b.current_playlist.append([Track(length=None)])
self.b.playback.play()
result = dict(dispatcher.status.status(self.h))
self.backend.current_playlist.append([Track(length=None)])
self.backend.playback.play()
result = dict(status.status(self.context))
self.assert_('time' in result)
(position, total) = result['time'].split(':')
position = int(position)
@ -189,9 +191,9 @@ class StatusHandlerTest(unittest.TestCase):
self.assert_(position <= total)
def test_status_method_when_playing_contains_time_with_length(self):
self.b.current_playlist.append([Track(length=10000)])
self.b.playback.play()
result = dict(dispatcher.status.status(self.h))
self.backend.current_playlist.append([Track(length=10000)])
self.backend.playback.play()
result = dict(status.status(self.context))
self.assert_('time' in result)
(position, total) = result['time'].split(':')
position = int(position)
@ -199,15 +201,15 @@ class StatusHandlerTest(unittest.TestCase):
self.assert_(position <= total)
def test_status_method_when_playing_contains_elapsed(self):
self.b.playback.state = PAUSED
self.b.playback.play_time_accumulated = 59123
result = dict(dispatcher.status.status(self.h))
self.backend.playback.state = PAUSED
self.backend.playback.play_time_accumulated = 59123
result = dict(status.status(self.context))
self.assert_('elapsed' in result)
self.assertEqual(int(result['elapsed']), 59123)
def test_status_method_when_playing_contains_bitrate(self):
self.b.current_playlist.append([Track(bitrate=320)])
self.b.playback.play()
result = dict(dispatcher.status.status(self.h))
self.backend.current_playlist.append([Track(bitrate=320)])
self.backend.playback.play()
result = dict(status.status(self.context))
self.assert_('bitrate' in result)
self.assertEqual(int(result['bitrate']), 320)

View File

@ -1,45 +1,45 @@
import unittest
from mopidy.backends.dummy import DummyBackend
from mopidy.frontends.mpd import dispatcher
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
from mopidy.mixers.dummy import DummyMixer
class StickersHandlerTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.dispatcher = MpdDispatcher()
def tearDown(self):
self.b.stop().get()
self.backend.stop().get()
self.mixer.stop().get()
def test_sticker_get(self):
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'sticker get "song" "file:///dev/urandom" "a_name"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_sticker_set(self):
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'sticker set "song" "file:///dev/urandom" "a_name" "a_value"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_sticker_delete_with_name(self):
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'sticker delete "song" "file:///dev/urandom" "a_name"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_sticker_delete_without_name(self):
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'sticker delete "song" "file:///dev/urandom"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_sticker_list(self):
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'sticker list "song" "file:///dev/urandom"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_sticker_find(self):
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'sticker find "song" "file:///dev/urandom" "a_name"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)

View File

@ -2,64 +2,64 @@ import datetime as dt
import unittest
from mopidy.backends.dummy import DummyBackend
from mopidy.frontends.mpd import dispatcher
from mopidy.frontends.mpd.dispatcher import MpdDispatcher
from mopidy.mixers.dummy import DummyMixer
from mopidy.models import Track, Playlist
class StoredPlaylistsHandlerTest(unittest.TestCase):
def setUp(self):
self.b = DummyBackend.start().proxy()
self.backend = DummyBackend.start().proxy()
self.mixer = DummyMixer.start().proxy()
self.h = dispatcher.MpdDispatcher()
self.dispatcher = MpdDispatcher()
def tearDown(self):
self.b.stop().get()
self.backend.stop().get()
self.mixer.stop().get()
def test_listplaylist(self):
self.b.stored_playlists.playlists = [
self.backend.stored_playlists.playlists = [
Playlist(name='name', tracks=[Track(uri='file:///dev/urandom')])]
result = self.h.handle_request(u'listplaylist "name"')
result = self.dispatcher.handle_request(u'listplaylist "name"')
self.assert_(u'file: file:///dev/urandom' in result)
self.assert_(u'OK' in result)
def test_listplaylist_fails_if_no_playlist_is_found(self):
result = self.h.handle_request(u'listplaylist "name"')
result = self.dispatcher.handle_request(u'listplaylist "name"')
self.assertEqual(result[0],
u'ACK [50@0] {listplaylist} No such playlist')
def test_listplaylistinfo(self):
self.b.stored_playlists.playlists = [
self.backend.stored_playlists.playlists = [
Playlist(name='name', tracks=[Track(uri='file:///dev/urandom')])]
result = self.h.handle_request(u'listplaylistinfo "name"')
result = self.dispatcher.handle_request(u'listplaylistinfo "name"')
self.assert_(u'file: file:///dev/urandom' in result)
self.assert_(u'Track: 0' in result)
self.assert_(u'Pos: 0' not in result)
self.assert_(u'OK' in result)
def test_listplaylistinfo_fails_if_no_playlist_is_found(self):
result = self.h.handle_request(u'listplaylistinfo "name"')
result = self.dispatcher.handle_request(u'listplaylistinfo "name"')
self.assertEqual(result[0],
u'ACK [50@0] {listplaylistinfo} No such playlist')
def test_listplaylists(self):
last_modified = dt.datetime(2001, 3, 17, 13, 41, 17, 12345)
self.b.stored_playlists.playlists = [Playlist(name='a',
self.backend.stored_playlists.playlists = [Playlist(name='a',
last_modified=last_modified)]
result = self.h.handle_request(u'listplaylists')
result = self.dispatcher.handle_request(u'listplaylists')
self.assert_(u'playlist: a' in result)
# Date without microseconds and with time zone information
self.assert_(u'Last-Modified: 2001-03-17T13:41:17Z' in result)
self.assert_(u'OK' in result)
def test_load_known_playlist_appends_to_current_playlist(self):
self.b.current_playlist.append([Track(uri='a'), Track(uri='b')])
self.assertEqual(len(self.b.current_playlist.tracks.get()), 2)
self.b.stored_playlists.playlists = [Playlist(name='A-list',
self.backend.current_playlist.append([Track(uri='a'), Track(uri='b')])
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 2)
self.backend.stored_playlists.playlists = [Playlist(name='A-list',
tracks=[Track(uri='c'), Track(uri='d'), Track(uri='e')])]
result = self.h.handle_request(u'load "A-list"')
result = self.dispatcher.handle_request(u'load "A-list"')
self.assert_(u'OK' in result)
tracks = self.b.current_playlist.tracks.get()
tracks = self.backend.current_playlist.tracks.get()
self.assertEqual(len(tracks), 5)
self.assertEqual(tracks[0].uri, 'a')
self.assertEqual(tracks[1].uri, 'b')
@ -68,35 +68,35 @@ class StoredPlaylistsHandlerTest(unittest.TestCase):
self.assertEqual(tracks[4].uri, 'e')
def test_load_unknown_playlist_acks(self):
result = self.h.handle_request(u'load "unknown playlist"')
result = self.dispatcher.handle_request(u'load "unknown playlist"')
self.assert_(u'ACK [50@0] {load} No such playlist' in result)
self.assertEqual(len(self.b.current_playlist.tracks.get()), 0)
self.assertEqual(len(self.backend.current_playlist.tracks.get()), 0)
def test_playlistadd(self):
result = self.h.handle_request(
result = self.dispatcher.handle_request(
u'playlistadd "name" "file:///dev/urandom"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_playlistclear(self):
result = self.h.handle_request(u'playlistclear "name"')
result = self.dispatcher.handle_request(u'playlistclear "name"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_playlistdelete(self):
result = self.h.handle_request(u'playlistdelete "name" "5"')
result = self.dispatcher.handle_request(u'playlistdelete "name" "5"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_playlistmove(self):
result = self.h.handle_request(u'playlistmove "name" "5" "10"')
result = self.dispatcher.handle_request(u'playlistmove "name" "5" "10"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_rename(self):
result = self.h.handle_request(u'rename "old_name" "new_name"')
result = self.dispatcher.handle_request(u'rename "old_name" "new_name"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_rm(self):
result = self.h.handle_request(u'rm "name"')
result = self.dispatcher.handle_request(u'rm "name"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)
def test_save(self):
result = self.h.handle_request(u'save "name"')
result = self.dispatcher.handle_request(u'save "name"')
self.assert_(u'ACK [0@0] {} Not implemented' in result)

View File

@ -1,7 +1,7 @@
import datetime as dt
import unittest
from mopidy.models import Artist, Album, Track, Playlist
from mopidy.models import Artist, Album, CpTrack, Track, Playlist
from tests import SkipTest
@ -274,6 +274,21 @@ class AlbumTest(unittest.TestCase):
self.assertNotEqual(hash(album1), hash(album2))
class CpTrackTest(unittest.TestCase):
def setUp(self):
self.cpid = 123
self.track = Track()
self.cp_track = CpTrack(self.cpid, self.track)
def test_cp_track_can_be_accessed_as_a_tuple(self):
self.assertEqual(self.cpid, self.cp_track[0])
self.assertEqual(self.track, self.cp_track[1])
def test_cp_track_can_be_accessed_by_attribute_names(self):
self.assertEqual(self.cpid, self.cp_track.cpid)
self.assertEqual(self.track, self.cp_track.track)
class TrackTest(unittest.TestCase):
def test_uri(self):
uri = u'an_uri'