Merge pull request #277 from jodal/feature/mpd-0.17
Implement MPD protocol changes from MPD 0.17
This commit is contained in:
commit
e00dd958b9
@ -8,7 +8,24 @@ This change log is used to track all major changes to Mopidy.
|
||||
v0.11.0 (in development)
|
||||
========================
|
||||
|
||||
- No changes yet.
|
||||
**MPD frontend**
|
||||
|
||||
- Add support for the ``findadd`` command.
|
||||
|
||||
- Updated to match the MPD 0.17 protocol (Fixes: :issue:`228`):
|
||||
|
||||
- Add support for ``seekcur`` command.
|
||||
|
||||
- Add support for ``config`` command.
|
||||
|
||||
- Add support for loading a range of tracks from a playlist to the ``load``
|
||||
command.
|
||||
|
||||
- Add support for ``searchadd`` command.
|
||||
|
||||
- Add support for ``searchaddpl`` command.
|
||||
|
||||
- Add empty stubs for channel commands for client to client communication.
|
||||
|
||||
|
||||
v0.10.0 (2012-12-12)
|
||||
|
||||
@ -30,6 +30,14 @@ Audio output
|
||||
:members:
|
||||
|
||||
|
||||
Channels
|
||||
--------
|
||||
|
||||
.. automodule:: mopidy.frontends.mpd.protocol.channels
|
||||
:synopsis: MPD protocol: channels -- client to client communication
|
||||
:members:
|
||||
|
||||
|
||||
Command list
|
||||
------------
|
||||
|
||||
|
||||
@ -21,8 +21,8 @@ ENCODING = 'UTF-8'
|
||||
#: The MPD protocol uses ``\n`` as line terminator.
|
||||
LINE_TERMINATOR = '\n'
|
||||
|
||||
#: The MPD protocol version is 0.16.0.
|
||||
VERSION = '0.16.0'
|
||||
#: The MPD protocol version is 0.17.0.
|
||||
VERSION = '0.17.0'
|
||||
|
||||
MpdCommand = namedtuple('MpdCommand', ['name', 'auth_required'])
|
||||
|
||||
@ -74,6 +74,7 @@ def load_protocol_modules():
|
||||
"""
|
||||
# pylint: disable = W0612
|
||||
from . import ( # noqa
|
||||
audio_output, command_list, connection, current_playlist, empty,
|
||||
music_db, playback, reflection, status, stickers, stored_playlists)
|
||||
audio_output, channels, command_list, connection, current_playlist,
|
||||
empty, music_db, playback, reflection, status, stickers,
|
||||
stored_playlists)
|
||||
# pylint: enable = W0612
|
||||
|
||||
69
mopidy/frontends/mpd/protocol/channels.py
Normal file
69
mopidy/frontends/mpd/protocol/channels.py
Normal file
@ -0,0 +1,69 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from mopidy.frontends.mpd.protocol import handle_request
|
||||
from mopidy.frontends.mpd.exceptions import MpdNotImplemented
|
||||
|
||||
|
||||
@handle_request(r'^subscribe "(?P<channel>[A-Za-z0-9:._-]+)"$')
|
||||
def subscribe(context, channel):
|
||||
"""
|
||||
*musicpd.org, client to client section:*
|
||||
|
||||
``subscribe {NAME}``
|
||||
|
||||
Subscribe to a channel. The channel is created if it does not exist
|
||||
already. The name may consist of alphanumeric ASCII characters plus
|
||||
underscore, dash, dot and colon.
|
||||
"""
|
||||
raise MpdNotImplemented # TODO
|
||||
|
||||
|
||||
@handle_request(r'^unsubscribe "(?P<channel>[A-Za-z0-9:._-]+)"$')
|
||||
def unsubscribe(context, channel):
|
||||
"""
|
||||
*musicpd.org, client to client section:*
|
||||
|
||||
``unsubscribe {NAME}``
|
||||
|
||||
Unsubscribe from a channel.
|
||||
"""
|
||||
raise MpdNotImplemented # TODO
|
||||
|
||||
|
||||
@handle_request(r'^channels$')
|
||||
def channels(context):
|
||||
"""
|
||||
*musicpd.org, client to client section:*
|
||||
|
||||
``channels``
|
||||
|
||||
Obtain a list of all channels. The response is a list of "channel:"
|
||||
lines.
|
||||
"""
|
||||
raise MpdNotImplemented # TODO
|
||||
|
||||
|
||||
@handle_request(r'^readmessages$')
|
||||
def readmessages(context):
|
||||
"""
|
||||
*musicpd.org, client to client section:*
|
||||
|
||||
``readmessages``
|
||||
|
||||
Reads messages for this client. The response is a list of "channel:"
|
||||
and "message:" lines.
|
||||
"""
|
||||
raise MpdNotImplemented # TODO
|
||||
|
||||
|
||||
@handle_request(
|
||||
r'^sendmessage "(?P<channel>[A-Za-z0-9:._-]+)" "(?P<text>[^"]*)"$')
|
||||
def sendmessage(context, channel, text):
|
||||
"""
|
||||
*musicpd.org, client to client section:*
|
||||
|
||||
``sendmessage {CHANNEL} {TEXT}``
|
||||
|
||||
Send a message to the specified channel.
|
||||
"""
|
||||
raise MpdNotImplemented # TODO
|
||||
@ -8,6 +8,11 @@ from mopidy.frontends.mpd.protocol import handle_request, stored_playlists
|
||||
from mopidy.frontends.mpd.translator import tracks_to_mpd_format
|
||||
|
||||
|
||||
QUERY_RE = (
|
||||
r'(?P<mpd_query>("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ile|[Ff]ilename|'
|
||||
r'[Tt]itle|[Aa]ny)"? "[^"]*"\s?)+)$')
|
||||
|
||||
|
||||
def _build_query(mpd_query):
|
||||
"""
|
||||
Parses a MPD query string and converts it to the Mopidy query format.
|
||||
@ -50,17 +55,17 @@ def count(context, tag, needle):
|
||||
return [('songs', 0), ('playtime', 0)] # TODO
|
||||
|
||||
|
||||
@handle_request(
|
||||
r'^find (?P<mpd_query>("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ile[name]*|'
|
||||
r'[Tt]itle|[Aa]ny)"? "[^"]*"\s?)+)$')
|
||||
@handle_request(r'^find ' + QUERY_RE)
|
||||
def find(context, mpd_query):
|
||||
"""
|
||||
*musicpd.org, music database section:*
|
||||
|
||||
``find {TYPE} {WHAT}``
|
||||
|
||||
Finds songs in the db that are exactly ``WHAT``. ``TYPE`` should be
|
||||
``album``, ``artist``, or ``title``. ``WHAT`` is what to find.
|
||||
Finds songs in the db that are exactly ``WHAT``. ``TYPE`` can be any
|
||||
tag supported by MPD, or one of the two special parameters - ``file``
|
||||
to search by full path (relative to database root), and ``any`` to
|
||||
match against all available tags. ``WHAT`` is what to find.
|
||||
|
||||
*GMPC:*
|
||||
|
||||
@ -82,26 +87,26 @@ def find(context, mpd_query):
|
||||
query = _build_query(mpd_query)
|
||||
except ValueError:
|
||||
return
|
||||
return tracks_to_mpd_format(
|
||||
context.core.library.find_exact(**query).get())
|
||||
result = context.core.library.find_exact(**query).get()
|
||||
return tracks_to_mpd_format(result)
|
||||
|
||||
|
||||
@handle_request(
|
||||
r'^findadd '
|
||||
r'(?P<query>("?([Aa]lbum|[Aa]rtist|[Ff]ilename|[Tt]itle|[Aa]ny)"? '
|
||||
r'"[^"]+"\s?)+)$')
|
||||
def findadd(context, query):
|
||||
@handle_request(r'^findadd ' + QUERY_RE)
|
||||
def findadd(context, mpd_query):
|
||||
"""
|
||||
*musicpd.org, music database section:*
|
||||
|
||||
``findadd {TYPE} {WHAT}``
|
||||
|
||||
Finds songs in the db that are exactly ``WHAT`` and adds them to
|
||||
current playlist. ``TYPE`` can be any tag supported by MPD.
|
||||
``WHAT`` is what to find.
|
||||
current playlist. Parameters have the same meaning as for ``find``.
|
||||
"""
|
||||
# TODO Add result to current playlist
|
||||
#result = context.find(query)
|
||||
try:
|
||||
query = _build_query(mpd_query)
|
||||
except ValueError:
|
||||
return
|
||||
result = context.core.library.find_exact(**query).get()
|
||||
context.core.tracklist.add(result)
|
||||
|
||||
|
||||
@handle_request(
|
||||
@ -333,18 +338,15 @@ def rescan(context, uri=None):
|
||||
return update(context, uri, rescan_unmodified_files=True)
|
||||
|
||||
|
||||
@handle_request(
|
||||
r'^search (?P<mpd_query>("?([Aa]lbum|[Aa]rtist|[Dd]ate|[Ff]ile[name]*|'
|
||||
r'[Tt]itle|[Aa]ny)"? "[^"]*"\s?)+)$')
|
||||
@handle_request(r'^search ' + QUERY_RE)
|
||||
def search(context, mpd_query):
|
||||
"""
|
||||
*musicpd.org, music database section:*
|
||||
|
||||
``search {TYPE} {WHAT}``
|
||||
``search {TYPE} {WHAT} [...]``
|
||||
|
||||
Searches for any song that contains ``WHAT``. ``TYPE`` can be
|
||||
``title``, ``artist``, ``album`` or ``filename``. Search is not
|
||||
case sensitive.
|
||||
Searches for any song that contains ``WHAT``. Parameters have the same
|
||||
meaning as for ``find``, except that search is not case sensitive.
|
||||
|
||||
*GMPC:*
|
||||
|
||||
@ -368,8 +370,60 @@ def search(context, mpd_query):
|
||||
query = _build_query(mpd_query)
|
||||
except ValueError:
|
||||
return
|
||||
return tracks_to_mpd_format(
|
||||
context.core.library.search(**query).get())
|
||||
result = context.core.library.search(**query).get()
|
||||
return tracks_to_mpd_format(result)
|
||||
|
||||
|
||||
@handle_request(r'^searchadd ' + QUERY_RE)
|
||||
def searchadd(context, mpd_query):
|
||||
"""
|
||||
*musicpd.org, music database section:*
|
||||
|
||||
``searchadd {TYPE} {WHAT} [...]``
|
||||
|
||||
Searches for any song that contains ``WHAT`` in tag ``TYPE`` and adds
|
||||
them to current playlist.
|
||||
|
||||
Parameters have the same meaning as for ``find``, except that search is
|
||||
not case sensitive.
|
||||
"""
|
||||
try:
|
||||
query = _build_query(mpd_query)
|
||||
except ValueError:
|
||||
return
|
||||
result = context.core.library.search(**query).get()
|
||||
context.core.tracklist.add(result)
|
||||
|
||||
|
||||
@handle_request(r'^searchaddpl "(?P<playlist_name>[^"]+)" ' + QUERY_RE)
|
||||
def searchaddpl(context, playlist_name, mpd_query):
|
||||
"""
|
||||
*musicpd.org, music database section:*
|
||||
|
||||
``searchaddpl {NAME} {TYPE} {WHAT} [...]``
|
||||
|
||||
Searches for any song that contains ``WHAT`` in tag ``TYPE`` and adds
|
||||
them to the playlist named ``NAME``.
|
||||
|
||||
If a playlist by that name doesn't exist it is created.
|
||||
|
||||
Parameters have the same meaning as for ``find``, except that search is
|
||||
not case sensitive.
|
||||
"""
|
||||
try:
|
||||
query = _build_query(mpd_query)
|
||||
except ValueError:
|
||||
return
|
||||
result = context.core.library.search(**query).get()
|
||||
|
||||
playlists = context.core.playlists.filter(name=playlist_name).get()
|
||||
if playlists:
|
||||
playlist = playlists[0]
|
||||
else:
|
||||
playlist = context.core.playlists.create(playlist_name).get()
|
||||
tracks = list(playlist.tracks) + result
|
||||
playlist = playlist.copy(tracks=tracks)
|
||||
context.core.playlists.save(playlist)
|
||||
|
||||
|
||||
@handle_request(r'^update( "(?P<uri>[^"]+)")*$')
|
||||
|
||||
@ -349,6 +349,26 @@ def seekid(context, tlid, seconds):
|
||||
context.core.playback.seek(int(seconds) * 1000).get()
|
||||
|
||||
|
||||
@handle_request(r'^seekcur "(?P<position>\d+)"$')
|
||||
@handle_request(r'^seekcur "(?P<diff>[-+]\d+)"$')
|
||||
def seekcur(context, position=None, diff=None):
|
||||
"""
|
||||
*musicpd.org, playback section:*
|
||||
|
||||
``seekcur {TIME}``
|
||||
|
||||
Seeks to the position ``TIME`` within the current song. If prefixed by
|
||||
'+' or '-', then the time is relative to the current playing position.
|
||||
"""
|
||||
if position is not None:
|
||||
position = int(position) * 1000
|
||||
context.core.playback.seek(position).get()
|
||||
elif diff is not None:
|
||||
position = context.core.playback.time_position.get()
|
||||
position += int(diff) * 1000
|
||||
context.core.playback.seek(position).get()
|
||||
|
||||
|
||||
@handle_request(r'^setvol (?P<volume>[-+]*\d+)$')
|
||||
@handle_request(r'^setvol "(?P<volume>[-+]*\d+)"$')
|
||||
def setvol(context, volume):
|
||||
|
||||
@ -1,8 +1,23 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from mopidy.frontends.mpd.exceptions import MpdPermissionError
|
||||
from mopidy.frontends.mpd.protocol import handle_request, mpd_commands
|
||||
|
||||
|
||||
@handle_request(r'^config$', auth_required=False)
|
||||
def config(context):
|
||||
"""
|
||||
*musicpd.org, reflection section:*
|
||||
|
||||
``config``
|
||||
|
||||
Dumps configuration values that may be interesting for the client. This
|
||||
command is only permitted to "local" clients (connected via UNIX domain
|
||||
socket).
|
||||
"""
|
||||
raise MpdPermissionError(command='config')
|
||||
|
||||
|
||||
@handle_request(r'^commands$', auth_required=False)
|
||||
def commands(context):
|
||||
"""
|
||||
@ -19,10 +34,10 @@ def commands(context):
|
||||
command.name for command in mpd_commands
|
||||
if not command.auth_required])
|
||||
|
||||
# No one is permited to use kill, rest of commands are not listed by MPD,
|
||||
# so we shouldn't either.
|
||||
# No one is permited to use 'config' or 'kill', rest of commands are not
|
||||
# listed by MPD, so we shouldn't either.
|
||||
command_names = command_names - set([
|
||||
'kill', 'command_list_begin', 'command_list_ok_begin',
|
||||
'config', 'kill', 'command_list_begin', 'command_list_ok_begin',
|
||||
'command_list_ok_begin', 'command_list_end', 'idle', 'noidle',
|
||||
'sticker'])
|
||||
|
||||
@ -73,6 +88,7 @@ def notcommands(context):
|
||||
command.name for command in mpd_commands if command.auth_required]
|
||||
|
||||
# No permission to use
|
||||
command_names.append('config')
|
||||
command_names.append('kill')
|
||||
|
||||
return [
|
||||
|
||||
@ -92,23 +92,36 @@ def listplaylists(context):
|
||||
return result
|
||||
|
||||
|
||||
@handle_request(r'^load "(?P<name>[^"]+)"$')
|
||||
def load(context, name):
|
||||
@handle_request(r'^load "(?P<name>[^"]+)"( "(?P<start>\d+):(?P<end>\d+)*")*$')
|
||||
def load(context, name, start=None, end=None):
|
||||
"""
|
||||
*musicpd.org, stored playlists section:*
|
||||
|
||||
``load {NAME}``
|
||||
``load {NAME} [START:END]``
|
||||
|
||||
Loads the playlist ``NAME.m3u`` from the playlist directory.
|
||||
Loads the playlist into the current queue. Playlist plugins are
|
||||
supported. A range may be specified to load only a part of the
|
||||
playlist.
|
||||
|
||||
*Clarifications:*
|
||||
|
||||
- ``load`` appends the given playlist to the current playlist.
|
||||
|
||||
- MPD 0.17.1 does not support open-ended ranges, i.e. without end
|
||||
specified, for the ``load`` command, even though MPD's general range docs
|
||||
allows open-ended ranges.
|
||||
|
||||
- MPD 0.17.1 does not fail if the specified range is outside the playlist,
|
||||
in either or both ends.
|
||||
"""
|
||||
playlists = context.core.playlists.filter(name=name).get()
|
||||
if not playlists:
|
||||
raise MpdNoExistError('No such playlist', command='load')
|
||||
context.core.tracklist.add(playlists[0].tracks)
|
||||
if start is not None:
|
||||
start = int(start)
|
||||
if end is not None:
|
||||
end = int(end)
|
||||
context.core.tracklist.add(playlists[0].tracks[start:end])
|
||||
|
||||
|
||||
@handle_request(r'^playlistadd "(?P<name>[^"]+)" "(?P<uri>[^"]+)"$')
|
||||
|
||||
25
tests/frontends/mpd/protocol/channels_test.py
Normal file
25
tests/frontends/mpd/protocol/channels_test.py
Normal file
@ -0,0 +1,25 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from tests.frontends.mpd import protocol
|
||||
|
||||
|
||||
class ChannelsHandlerTest(protocol.BaseTestCase):
|
||||
def test_subscribe(self):
|
||||
self.sendRequest('subscribe "topic"')
|
||||
self.assertEqualResponse('ACK [0@0] {} Not implemented')
|
||||
|
||||
def test_unsubscribe(self):
|
||||
self.sendRequest('unsubscribe "topic"')
|
||||
self.assertEqualResponse('ACK [0@0] {} Not implemented')
|
||||
|
||||
def test_channels(self):
|
||||
self.sendRequest('channels')
|
||||
self.assertEqualResponse('ACK [0@0] {} Not implemented')
|
||||
|
||||
def test_readmessages(self):
|
||||
self.sendRequest('readmessages')
|
||||
self.assertEqualResponse('ACK [0@0] {} Not implemented')
|
||||
|
||||
def test_sendmessage(self):
|
||||
self.sendRequest('sendmessage "topic" "a message"')
|
||||
self.assertEqualResponse('ACK [0@0] {} Not implemented')
|
||||
@ -13,7 +13,65 @@ class MusicDatabaseHandlerTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_findadd(self):
|
||||
self.sendRequest('findadd "album" "what"')
|
||||
self.backend.library.dummy_find_exact_result = [
|
||||
Track(uri='dummy:a', name='A'),
|
||||
]
|
||||
self.assertEqual(self.core.tracklist.length.get(), 0)
|
||||
|
||||
self.sendRequest('findadd "title" "A"')
|
||||
|
||||
self.assertEqual(self.core.tracklist.length.get(), 1)
|
||||
self.assertEqual(self.core.tracklist.tracks.get()[0].uri, 'dummy:a')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_searchadd(self):
|
||||
self.backend.library.dummy_search_result = [
|
||||
Track(uri='dummy:a', name='A'),
|
||||
]
|
||||
self.assertEqual(self.core.tracklist.length.get(), 0)
|
||||
|
||||
self.sendRequest('searchadd "title" "a"')
|
||||
|
||||
self.assertEqual(self.core.tracklist.length.get(), 1)
|
||||
self.assertEqual(self.core.tracklist.tracks.get()[0].uri, 'dummy:a')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_searchaddpl_appends_to_existing_playlist(self):
|
||||
playlist = self.core.playlists.create('my favs').get()
|
||||
playlist = playlist.copy(tracks=[
|
||||
Track(uri='dummy:x', name='X'),
|
||||
Track(uri='dummy:y', name='y'),
|
||||
])
|
||||
self.core.playlists.save(playlist)
|
||||
self.backend.library.dummy_search_result = [
|
||||
Track(uri='dummy:a', name='A'),
|
||||
]
|
||||
playlists = self.core.playlists.filter(name='my favs').get()
|
||||
self.assertEqual(len(playlists), 1)
|
||||
self.assertEqual(len(playlists[0].tracks), 2)
|
||||
|
||||
self.sendRequest('searchaddpl "my favs" "title" "a"')
|
||||
|
||||
playlists = self.core.playlists.filter(name='my favs').get()
|
||||
self.assertEqual(len(playlists), 1)
|
||||
self.assertEqual(len(playlists[0].tracks), 3)
|
||||
self.assertEqual(playlists[0].tracks[0].uri, 'dummy:x')
|
||||
self.assertEqual(playlists[0].tracks[1].uri, 'dummy:y')
|
||||
self.assertEqual(playlists[0].tracks[2].uri, 'dummy:a')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_searchaddpl_creates_missing_playlist(self):
|
||||
self.backend.library.dummy_search_result = [
|
||||
Track(uri='dummy:a', name='A'),
|
||||
]
|
||||
self.assertEqual(
|
||||
len(self.core.playlists.filter(name='my favs').get()), 0)
|
||||
|
||||
self.sendRequest('searchaddpl "my favs" "title" "a"')
|
||||
|
||||
playlists = self.core.playlists.filter(name='my favs').get()
|
||||
self.assertEqual(len(playlists), 1)
|
||||
self.assertEqual(playlists[0].tracks[0].uri, 'dummy:a')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_listall(self):
|
||||
|
||||
@ -414,6 +414,37 @@ class PlaybackControlHandlerTest(protocol.BaseTestCase):
|
||||
self.assertEqual(seek_track, self.core.playback.current_track.get())
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_seekcur_absolute_value(self):
|
||||
self.core.tracklist.add([Track(uri='dummy:a', length=40000)])
|
||||
self.core.playback.play()
|
||||
|
||||
self.sendRequest('seekcur "30"')
|
||||
|
||||
self.assertGreaterEqual(self.core.playback.time_position.get(), 30000)
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_seekcur_positive_diff(self):
|
||||
self.core.tracklist.add([Track(uri='dummy:a', length=40000)])
|
||||
self.core.playback.play()
|
||||
self.core.playback.seek(10000)
|
||||
self.assertGreaterEqual(self.core.playback.time_position.get(), 10000)
|
||||
|
||||
self.sendRequest('seekcur "+20"')
|
||||
|
||||
self.assertGreaterEqual(self.core.playback.time_position.get(), 30000)
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_seekcur_negative_diff(self):
|
||||
self.core.tracklist.add([Track(uri='dummy:a', length=40000)])
|
||||
self.core.playback.play()
|
||||
self.core.playback.seek(30000)
|
||||
self.assertGreaterEqual(self.core.playback.time_position.get(), 30000)
|
||||
|
||||
self.sendRequest('seekcur "-20"')
|
||||
|
||||
self.assertLessEqual(self.core.playback.time_position.get(), 15000)
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_stop(self):
|
||||
self.sendRequest('stop')
|
||||
self.assertEqual(STOPPED, self.core.playback.state.get())
|
||||
|
||||
@ -6,6 +6,11 @@ from tests.frontends.mpd import protocol
|
||||
|
||||
|
||||
class ReflectionHandlerTest(protocol.BaseTestCase):
|
||||
def test_config_is_not_allowed_across_the_network(self):
|
||||
self.sendRequest('config')
|
||||
self.assertEqualResponse(
|
||||
'ACK [4@0] {config} you don\'t have permission for "config"')
|
||||
|
||||
def test_commands_returns_list_of_all_commands(self):
|
||||
self.sendRequest('commands')
|
||||
# Check if some random commands are included
|
||||
@ -13,6 +18,7 @@ class ReflectionHandlerTest(protocol.BaseTestCase):
|
||||
self.assertInResponse('command: play')
|
||||
self.assertInResponse('command: status')
|
||||
# Check if commands you do not have access to are not present
|
||||
self.assertNotInResponse('command: config')
|
||||
self.assertNotInResponse('command: kill')
|
||||
# Check if the blacklisted commands are not present
|
||||
self.assertNotInResponse('command: command_list_begin')
|
||||
@ -40,9 +46,10 @@ class ReflectionHandlerTest(protocol.BaseTestCase):
|
||||
self.sendRequest('decoders')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_notcommands_returns_only_kill_and_ok(self):
|
||||
def test_notcommands_returns_only_config_and_kill_and_ok(self):
|
||||
response = self.sendRequest('notcommands')
|
||||
self.assertEqual(2, len(response))
|
||||
self.assertEqual(3, len(response))
|
||||
self.assertInResponse('command: config')
|
||||
self.assertInResponse('command: kill')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ class PlaylistsHandlerTest(protocol.BaseTestCase):
|
||||
self.assertNotInResponse('playlist: ')
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_load_known_playlist_appends_to_tracklist(self):
|
||||
def test_load_appends_to_tracklist(self):
|
||||
self.core.tracklist.add([Track(uri='a'), Track(uri='b')])
|
||||
self.assertEqual(len(self.core.tracklist.tracks.get()), 2)
|
||||
self.backend.playlists.playlists = [
|
||||
@ -81,6 +81,7 @@ class PlaylistsHandlerTest(protocol.BaseTestCase):
|
||||
Track(uri='c'), Track(uri='d'), Track(uri='e')])]
|
||||
|
||||
self.sendRequest('load "A-list"')
|
||||
|
||||
tracks = self.core.tracklist.tracks.get()
|
||||
self.assertEqual(5, len(tracks))
|
||||
self.assertEqual('a', tracks[0].uri)
|
||||
@ -90,6 +91,39 @@ class PlaylistsHandlerTest(protocol.BaseTestCase):
|
||||
self.assertEqual('e', tracks[4].uri)
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_load_with_range_loads_part_of_playlist(self):
|
||||
self.core.tracklist.add([Track(uri='a'), Track(uri='b')])
|
||||
self.assertEqual(len(self.core.tracklist.tracks.get()), 2)
|
||||
self.backend.playlists.playlists = [
|
||||
Playlist(name='A-list', tracks=[
|
||||
Track(uri='c'), Track(uri='d'), Track(uri='e')])]
|
||||
|
||||
self.sendRequest('load "A-list" "1:2"')
|
||||
|
||||
tracks = self.core.tracklist.tracks.get()
|
||||
self.assertEqual(3, len(tracks))
|
||||
self.assertEqual('a', tracks[0].uri)
|
||||
self.assertEqual('b', tracks[1].uri)
|
||||
self.assertEqual('d', tracks[2].uri)
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_load_with_range_without_end_loads_rest_of_playlist(self):
|
||||
self.core.tracklist.add([Track(uri='a'), Track(uri='b')])
|
||||
self.assertEqual(len(self.core.tracklist.tracks.get()), 2)
|
||||
self.backend.playlists.playlists = [
|
||||
Playlist(name='A-list', tracks=[
|
||||
Track(uri='c'), Track(uri='d'), Track(uri='e')])]
|
||||
|
||||
self.sendRequest('load "A-list" "1:"')
|
||||
|
||||
tracks = self.core.tracklist.tracks.get()
|
||||
self.assertEqual(4, len(tracks))
|
||||
self.assertEqual('a', tracks[0].uri)
|
||||
self.assertEqual('b', tracks[1].uri)
|
||||
self.assertEqual('d', tracks[2].uri)
|
||||
self.assertEqual('e', tracks[3].uri)
|
||||
self.assertInResponse('OK')
|
||||
|
||||
def test_load_unknown_playlist_acks(self):
|
||||
self.sendRequest('load "unknown playlist"')
|
||||
self.assertEqual(0, len(self.core.tracklist.tracks.get()))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user