mpd: implemented MPD commands for modifying stored playlists.

This commit is contained in:
kozec 2015-05-21 01:06:54 +02:00 committed by Stein Magnus Jodal
parent f3f2375560
commit 9e1de9989d
3 changed files with 140 additions and 10 deletions

View File

@ -66,10 +66,10 @@ class MpdFrontend(pykka.ThreadingActor, CoreListener):
def tracklist_changed(self):
self.send_idle('playlist')
def playlist_changed(self, playlist):
self.send_idle('stored_playlist')
def options_changed(self):
self.send_idle('options')

View File

@ -92,6 +92,27 @@ class MpdNotImplemented(MpdAckError):
self.message = 'Not implemented'
class MpdInvalidTrackForPlaylist(MpdAckError):
error_code = 0
def __init__(self, backend_scheme, track_scheme, *args, **kwargs):
super(MpdInvalidTrackForPlaylist, self).__init__(*args, **kwargs)
self.message = 'Playlist backend "%s" can\'t store ' \
'track scheme "%s"' % (backend_scheme, track_scheme)
class MpdFailedToSavePlaylist(MpdAckError):
error_code = 0
def __init__(self, backend_scheme, *args, **kwargs):
super(MpdFailedToSavePlaylist, self).__init__(*args, **kwargs)
if backend_scheme is None:
self.message = 'Failed to save playlist'
else:
self.message = 'Backend "%s" failed to save playlist' % (
backend_scheme, )
class MpdDisabled(MpdAckError):
# NOTE: This is a custom error for Mopidy that does not exist in MPD.
error_code = 0

View File

@ -1,10 +1,14 @@
from __future__ import absolute_import, division, unicode_literals
import datetime
import logging
import urlparse
import warnings
from mopidy.mpd import exceptions, protocol, translator
logger = logging.getLogger(__name__)
@protocol.commands.add('listplaylist')
def listplaylist(context, name):
@ -135,7 +139,7 @@ def load(context, name, playlist_slice=slice(0, None)):
@protocol.commands.add('playlistadd')
def playlistadd(context, name, uri):
def playlistadd(context, name, track_uri):
"""
*musicpd.org, stored playlists section:*
@ -145,7 +149,52 @@ def playlistadd(context, name, uri):
``NAME.m3u`` will be created if it does not exist.
"""
raise exceptions.MpdNotImplemented # TODO
uri = context.lookup_playlist_uri_from_name(name)
playlist = uri is not None and context.core.playlists.lookup(uri).get()
if not playlist:
# Create new playlist with this single track
lookup_res = context.core.library.lookup(uris=[track_uri]).get()
tracks = [a for sl in lookup_res.values() for a in sl]
_playlistcreate(context, name, tracks)
else:
# Add track to existing playlist
uri_scheme = urlparse.urlparse(track_uri).scheme
lookup_res = context.core.library.lookup(uris=[track_uri]).get()
to_add = [a for sl in lookup_res.values() for a in sl]
playlist = playlist.replace(tracks=list(playlist.tracks) + to_add)
if context.core.playlists.save(playlist).get() is None:
playlist_scheme = urlparse.urlparse(playlist.uri).scheme
raise exceptions.MpdInvalidTrackForPlaylist(
playlist_scheme, uri_scheme)
def _playlistcreate(context, name, tracks):
"""
Creates new playlist using backend aprropriate for passed list of tracks
"""
uri_schemes = set([urlparse.urlparse(t.uri).scheme for t in tracks])
for scheme in uri_schemes:
playlist = context.core.playlists.create(name, scheme).get()
if not playlist:
# Backend can't create playlists at all
logger.warning('%s backend can\'t create playlists', scheme)
continue
playlist = playlist.replace(tracks=tracks)
if context.core.playlists.save(playlist).get() is None:
# Falied to save using this backend
continue
# Created and saved
return
# Can't use backend aprropriate to passed uri schemes, use default one
playlist = context.core.playlists.create(name).get()
if not playlist:
# If even default backend can't save playlist, everything is lost
logger.warning('Default backend can\'t create playlists')
raise exceptions.MpdFailedToSavePlaylist(None)
playlist = playlist.replace(tracks=tracks)
if context.core.playlists.save(playlist).get() is None:
uri_scheme = urlparse.urlparse(playlist.uri).scheme
raise exceptions.MpdFailedToSavePlaylist(uri_scheme)
@protocol.commands.add('playlistclear')
@ -156,8 +205,18 @@ def playlistclear(context, name):
``playlistclear {NAME}``
Clears the playlist ``NAME.m3u``.
``NAME.m3u`` will be created if it does not exist.
"""
raise exceptions.MpdNotImplemented # TODO
uri = context.lookup_playlist_uri_from_name(name)
playlist = uri is not None and context.core.playlists.lookup(uri).get()
if not playlist:
playlist = context.core.playlists.create(name).get()
# Just replace tracks with empty list and save
playlist = playlist.replace(tracks=[])
if context.core.playlists.save(playlist).get() is None:
raise exceptions.MpdFailedToSavePlaylist(urlparse.urlparse(uri).scheme)
@protocol.commands.add('playlistdelete', songpos=protocol.UINT)
@ -169,7 +228,19 @@ def playlistdelete(context, name, songpos):
Deletes ``SONGPOS`` from the playlist ``NAME.m3u``.
"""
raise exceptions.MpdNotImplemented # TODO
uri = context.lookup_playlist_uri_from_name(name)
playlist = uri is not None and context.core.playlists.lookup(uri).get()
if not playlist:
raise exceptions.MpdNoExistError('No such playlist')
# Convert tracks to list and remove requested
tracks = list(playlist.tracks)
tracks.pop(songpos)
# Replace tracks and save playlist
playlist = playlist.replace(tracks=tracks)
if context.core.playlists.save(playlist).get() is None:
raise exceptions.MpdFailedToSavePlaylist(urlparse.urlparse(uri).scheme)
@protocol.commands.add(
@ -189,7 +260,22 @@ def playlistmove(context, name, from_pos, to_pos):
documentation, but just the ``SONGPOS`` to move *from*, i.e.
``playlistmove {NAME} {FROM_SONGPOS} {TO_SONGPOS}``.
"""
raise exceptions.MpdNotImplemented # TODO
uri = context.lookup_playlist_uri_from_name(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:
return # Nothing to do
# Convert tracks to list and perform move
tracks = list(playlist.tracks)
track = tracks.pop(from_pos)
tracks.insert(to_pos, track)
# Replace tracks and save playlist
playlist = playlist.replace(tracks=tracks)
if context.core.playlists.save(playlist).get() is None:
raise exceptions.MpdFailedToSavePlaylist(urlparse.urlparse(uri).scheme)
@protocol.commands.add('rename')
@ -201,7 +287,17 @@ def rename(context, old_name, new_name):
Renames the playlist ``NAME.m3u`` to ``NEW_NAME.m3u``.
"""
raise exceptions.MpdNotImplemented # TODO
uri = context.lookup_playlist_uri_from_name(old_name)
uri_scheme = urlparse.urlparse(uri).scheme
playlist = uri is not None and context.core.playlists.lookup(uri).get()
if not playlist:
raise exceptions.MpdNoExistError('No such playlist')
# Create copy of the playlist and remove original
copy = context.core.playlists.create(new_name, uri_scheme).get()
copy = copy.replace(tracks=playlist.tracks)
context.core.playlists.save(copy).get()
context.core.playlists.delete(uri).get()
@protocol.commands.add('rm')
@ -213,7 +309,8 @@ def rm(context, name):
Removes the playlist ``NAME.m3u`` from the playlist directory.
"""
raise exceptions.MpdNotImplemented # TODO
uri = context.lookup_playlist_uri_from_name(name)
context.core.playlists.delete(uri).get()
@protocol.commands.add('save')
@ -226,4 +323,16 @@ def save(context, name):
Saves the current playlist to ``NAME.m3u`` in the playlist
directory.
"""
raise exceptions.MpdNotImplemented # TODO
tl_tracks = context.core.tracklist.get_tl_tracks().get()
tracks = [t.track for t in tl_tracks]
uri = context.lookup_playlist_uri_from_name(name)
playlist = uri is not None and context.core.playlists.lookup(uri).get()
if not playlist:
# Create new playlist
_playlistcreate(context, name, tracks)
else:
# Overwrite existing playlist
playlist = playlist.replace(tracks=tracks)
if not context.core.playlists.save(playlist).get():
raise exceptions.MpdFailedToSavePlaylist(
urlparse.urlparse(uri).scheme)