MPD's load and listplaylistinfo lookup track metadata. Fixes #1511.

Includes tests and refactored all playlist lookups to use helper.
This commit is contained in:
nsteel 2017-01-17 23:10:13 +00:00 committed by Nick Steel
parent 53c8159bbc
commit 52a90a5a06
2 changed files with 67 additions and 44 deletions

View File

@ -3,7 +3,6 @@ from __future__ import absolute_import, division, unicode_literals
import datetime import datetime
import logging import logging
import re import re
import warnings
from mopidy.compat import urllib from mopidy.compat import urllib
from mopidy.mpd import exceptions, protocol, translator from mopidy.mpd import exceptions, protocol, translator
@ -16,6 +15,16 @@ def _check_playlist_name(name):
raise exceptions.MpdInvalidPlaylistName() raise exceptions.MpdInvalidPlaylistName()
def _get_playlist(context, name, must_exist=True):
playlist = None
uri = context.lookup_playlist_uri_from_name(name)
if uri:
playlist = context.core.playlists.lookup(uri).get()
if must_exist and not playlist:
raise exceptions.MpdNoExistError('No such playlist')
return playlist
@protocol.commands.add('listplaylist') @protocol.commands.add('listplaylist')
def listplaylist(context, name): def listplaylist(context, name):
""" """
@ -31,10 +40,7 @@ def listplaylist(context, name):
file: relative/path/to/file2.ogg file: relative/path/to/file2.ogg
file: relative/path/to/file3.mp3 file: relative/path/to/file3.mp3
""" """
uri = context.lookup_playlist_uri_from_name(name) playlist = _get_playlist(context, name)
playlist = uri is not None and context.core.playlists.lookup(uri).get()
if not playlist:
raise exceptions.MpdNoExistError('No such playlist')
return ['file: %s' % t.uri for t in playlist.tracks] return ['file: %s' % t.uri for t in playlist.tracks]
@ -52,10 +58,13 @@ def listplaylistinfo(context, name):
Standard track listing, with fields: file, Time, Title, Date, Standard track listing, with fields: file, Time, Title, Date,
Album, Artist, Track Album, Artist, Track
""" """
uri = context.lookup_playlist_uri_from_name(name) playlist = _get_playlist(context, name)
playlist = uri is not None and context.core.playlists.lookup(uri).get() track_uris = [track.uri for track in playlist.tracks]
if not playlist: tracks_map = context.core.library.lookup(uris=track_uris).get()
raise exceptions.MpdNoExistError('No such playlist') tracks = []
for uri in track_uris:
tracks.extend(tracks_map[uri])
playlist = playlist.replace(tracks=tracks)
return translator.playlist_to_mpd_format(playlist) return translator.playlist_to_mpd_format(playlist)
@ -134,14 +143,9 @@ def load(context, name, playlist_slice=slice(0, None)):
- MPD 0.17.1 does not fail if the specified range is outside the playlist, - MPD 0.17.1 does not fail if the specified range is outside the playlist,
in either or both ends. in either or both ends.
""" """
uri = context.lookup_playlist_uri_from_name(name) playlist = _get_playlist(context, name)
playlist = uri is not None and context.core.playlists.lookup(uri).get() track_uris = [track.uri for track in playlist.tracks[playlist_slice]]
if not playlist: context.core.tracklist.add(uris=track_uris).get()
raise exceptions.MpdNoExistError('No such playlist')
with warnings.catch_warnings():
warnings.filterwarnings('ignore', 'tracklist.add.*"tracks".*')
context.core.tracklist.add(playlist.tracks[playlist_slice]).get()
@protocol.commands.add('playlistadd') @protocol.commands.add('playlistadd')
@ -156,8 +160,7 @@ def playlistadd(context, name, track_uri):
``NAME.m3u`` will be created if it does not exist. ``NAME.m3u`` will be created if it does not exist.
""" """
_check_playlist_name(name) _check_playlist_name(name)
uri = context.lookup_playlist_uri_from_name(name) old_playlist = _get_playlist(context, name, must_exist=False)
old_playlist = uri is not None and context.core.playlists.lookup(uri).get()
if not old_playlist: if not old_playlist:
# Create new playlist with this single track # Create new playlist with this single track
lookup_res = context.core.library.lookup(uris=[track_uri]).get() lookup_res = context.core.library.lookup(uris=[track_uri]).get()
@ -249,10 +252,7 @@ def playlistdelete(context, name, songpos):
Deletes ``SONGPOS`` from the playlist ``NAME.m3u``. Deletes ``SONGPOS`` from the playlist ``NAME.m3u``.
""" """
_check_playlist_name(name) _check_playlist_name(name)
uri = context.lookup_playlist_uri_from_name(name) playlist = _get_playlist(context, name)
playlist = uri is not None and context.core.playlists.lookup(uri).get()
if not playlist:
raise exceptions.MpdNoExistError('No such playlist')
try: try:
# Convert tracks to list and remove requested # Convert tracks to list and remove requested
@ -290,10 +290,7 @@ def playlistmove(context, name, from_pos, to_pos):
return return
_check_playlist_name(name) _check_playlist_name(name)
uri = context.lookup_playlist_uri_from_name(name) playlist = _get_playlist(context, name)
playlist = uri is not None and context.core.playlists.lookup(uri).get()
if not playlist:
raise exceptions.MpdNoExistError('No such playlist')
if from_pos == to_pos: if from_pos == to_pos:
return # Nothing to do return # Nothing to do
@ -325,21 +322,14 @@ def rename(context, old_name, new_name):
_check_playlist_name(old_name) _check_playlist_name(old_name)
_check_playlist_name(new_name) _check_playlist_name(new_name)
old_uri = context.lookup_playlist_uri_from_name(old_name) old_playlist = _get_playlist(context, old_name)
if not old_uri:
raise exceptions.MpdNoExistError('No such playlist')
old_playlist = context.core.playlists.lookup(old_uri).get() if _get_playlist(context, new_name, must_exist=False):
if not old_playlist:
raise exceptions.MpdNoExistError('No such playlist')
new_uri = context.lookup_playlist_uri_from_name(new_name)
if new_uri and context.core.playlists.lookup(new_uri).get():
raise exceptions.MpdExistError('Playlist already exists') raise exceptions.MpdExistError('Playlist already exists')
# TODO: should we purge the mapping in an else? # TODO: should we purge the mapping in an else?
# Create copy of the playlist and remove original # Create copy of the playlist and remove original
uri_scheme = urllib.parse.urlparse(old_uri).scheme uri_scheme = urllib.parse.urlparse(old_playlist.uri).scheme
new_playlist = context.core.playlists.create(new_name, uri_scheme).get() new_playlist = context.core.playlists.create(new_name, uri_scheme).get()
new_playlist = new_playlist.replace(tracks=old_playlist.tracks) new_playlist = new_playlist.replace(tracks=old_playlist.tracks)
saved_playlist = context.core.playlists.save(new_playlist).get() saved_playlist = context.core.playlists.save(new_playlist).get()
@ -377,8 +367,7 @@ def save(context, name):
""" """
_check_playlist_name(name) _check_playlist_name(name)
tracks = context.core.tracklist.get_tracks().get() tracks = context.core.tracklist.get_tracks().get()
uri = context.lookup_playlist_uri_from_name(name) playlist = _get_playlist(context, name, must_exist=False)
playlist = uri is not None and context.core.playlists.lookup(uri).get()
if not playlist: if not playlist:
# Create new playlist # Create new playlist
_create_playlist(context, name, tracks) _create_playlist(context, name, tracks)
@ -388,4 +377,4 @@ def save(context, name):
saved_playlist = context.core.playlists.save(new_playlist).get() saved_playlist = context.core.playlists.save(new_playlist).get()
if saved_playlist is None: if saved_playlist is None:
raise exceptions.MpdFailedToSavePlaylist( raise exceptions.MpdFailedToSavePlaylist(
urllib.parse.urlparse(uri).scheme) urllib.parse.urlparse(playlist.uri).scheme)

View File

@ -46,6 +46,10 @@ class PlaylistsHandlerTest(protocol.BaseTestCase):
self.assertInResponse('OK') self.assertInResponse('OK')
def test_listplaylistinfo(self): def test_listplaylistinfo(self):
tracks = [
Track(uri='dummy:a', name='Track A', length=5000),
]
self.backend.library.dummy_library = tracks
self.backend.playlists.set_dummy_playlists([ self.backend.playlists.set_dummy_playlists([
Playlist( Playlist(
name='name', uri='dummy:name', tracks=[Track(uri='dummy:a')])]) name='name', uri='dummy:name', tracks=[Track(uri='dummy:a')])])
@ -53,14 +57,20 @@ class PlaylistsHandlerTest(protocol.BaseTestCase):
self.send_request('listplaylistinfo "name"') self.send_request('listplaylistinfo "name"')
self.assertInResponse('file: dummy:a') self.assertInResponse('file: dummy:a')
self.assertInResponse('Title: Track A')
self.assertInResponse('Time: 5')
self.assertNotInResponse('Track: 0') self.assertNotInResponse('Track: 0')
self.assertNotInResponse('Pos: 0') self.assertNotInResponse('Pos: 0')
self.assertInResponse('OK') self.assertInResponse('OK')
def test_listplaylistinfo_without_quotes(self): def test_listplaylistinfo_without_quotes(self):
tracks = [
Track(uri='dummy:a'),
]
self.backend.library.dummy_library = tracks
self.backend.playlists.set_dummy_playlists([ self.backend.playlists.set_dummy_playlists([
Playlist( Playlist(
name='name', uri='dummy:name', tracks=[Track(uri='dummy:a')])]) name='name', uri='dummy:name', tracks=tracks)])
self.send_request('listplaylistinfo name') self.send_request('listplaylistinfo name')
@ -76,13 +86,18 @@ class PlaylistsHandlerTest(protocol.BaseTestCase):
'ACK [50@0] {listplaylistinfo} No such playlist') 'ACK [50@0] {listplaylistinfo} No such playlist')
def test_listplaylistinfo_duplicate(self): def test_listplaylistinfo_duplicate(self):
playlist1 = Playlist(name='a', uri='dummy:a1', tracks=[Track(uri='b')]) tracks = [
playlist2 = Playlist(name='a', uri='dummy:a2', tracks=[Track(uri='c')]) Track(uri='dummy:b'),
Track(uri='dummy:c'),
]
self.backend.library.dummy_library = tracks
playlist1 = Playlist(name='a', uri='dummy:a1', tracks=tracks[:1])
playlist2 = Playlist(name='a', uri='dummy:a2', tracks=tracks[1:])
self.backend.playlists.set_dummy_playlists([playlist1, playlist2]) self.backend.playlists.set_dummy_playlists([playlist1, playlist2])
self.send_request('listplaylistinfo "a [2]"') self.send_request('listplaylistinfo "a [2]"')
self.assertInResponse('file: c') self.assertInResponse('file: dummy:c')
self.assertNotInResponse('Track: 0') self.assertNotInResponse('Track: 0')
self.assertNotInResponse('Pos: 0') self.assertNotInResponse('Pos: 0')
self.assertInResponse('OK') self.assertInResponse('OK')
@ -236,6 +251,25 @@ class PlaylistsHandlerTest(protocol.BaseTestCase):
self.send_request('load "unknown/playlist"') self.send_request('load "unknown/playlist"')
self.assertEqualResponse('ACK [50@0] {load} No such playlist') self.assertEqualResponse('ACK [50@0] {load} No such playlist')
def test_load_full_track_metadata(self):
tracks = [
Track(uri='dummy:a', name='Track A', length=5000),
]
self.backend.library.dummy_library = tracks
self.backend.playlists.set_dummy_playlists([
Playlist(
name='A-list', uri='dummy:a1', tracks=[Track(uri='dummy:a')])])
self.send_request('load "A-list"')
tracks = self.core.tracklist.tracks.get()
self.assertEqual(1, len(tracks))
self.assertEqual('dummy:a', tracks[0].uri)
self.assertEqual('Track A', tracks[0].name)
self.assertEqual(5000, tracks[0].length)
self.assertInResponse('OK')
def test_playlistadd(self): def test_playlistadd(self):
tracks = [ tracks = [
Track(uri='dummy:a'), Track(uri='dummy:a'),