Source code for command_base

This module permits to define mpd commands. Each command inherits from
:class:`Command` which is a command base class. There are
also some specialized subclasses of :class:`Command`:

- :class:`CommandItems` can be used when respond is a list of items
- :class:`CommandSongs` can be used when respond is a list of songs
- :class:`CommandPlaylist` can be used when respond is a list of songs

MPD songs are represented by classes:

- :class:`MpdPlaylistSong` which contains a song position and a song id.
- :class:`MpdLibrarySong` 

Moreover, you can map your playlist to the mpd playlist by overriding
:class:`MpdPlaylist`. Then when you override this class, a lot of
commands are defined (for example :class:`Move`, :class:`MoveId`,
:class:`Delete`, etc.).

MPD protocol supports playlist diff (for example
command :class:`command_skel.PlChanges`). This feature is internally
management with :class:`PlaylistHistory`.

Note: 'command' and 'notcommand' commands seems to not be used by
gmpc. Then, we have to implement a lot of commands with dummy
respond. However, gmpc use 'command' command to allow user to play,
pause ...
import logging
import mpdserver

class CommandArgumentException(Exception):pass

[docs]class Command(): """ Command class is the base command class. You can define argument format by setting :attr:`formatArg`. Command argument can be accessed with :attr:`args` dictionnary. Each command has a playlist attribute which is given by MpdRequestHandler. This playlist must implement MpdPlaylist class and by default, this one is used. :class:`Command` contains command arguments definition via :attr:`Command.formatArg`. You can handle them with :func:`Command.handle_args`. An argument is : - an int - a string - an optionnal int (:class:`OptInt`) - an optionnal string (:class:`OptStr`) """ formatArg=[] """ To specify command arguments format. For example, :: formatArg=[("song",OptStr)] means command accept a optionnal string argument bound to `song` attribute name.""" args={} """ A dictionnary of received arguments from mpd client. They must be defined in :attr:`formatArg`.""" def __init__(self,args,playlist,frontend,player): self.args=self.__parseArg(args) self.playlist=playlist self.frontend=frontend self.player=player
[docs] def run(self): """To treat a command. This class handle_args method and toMpdMsg method.""" try: self.handle_args(**(self.args)) return self.toMpdMsg() except NotImplementedError as e: raise mpdserver.CommandNotImplemented(self.__class__,str(e))
[docs] def GetCommandName(cls): """ MPD command name. Command name is the lower class name. This string is used to parse a client request. You can override this classmethod to define particular commandname.""" return cls.__name__.lower()
def __parseArg(self,args): if len(args) > len(self.formatArg): raise CommandArgumentException("Too much arguments: %s command arguments should be %s instead of %s" % (self.__class__,self.formatArg,args)) try: d=dict() for i in range(0,len(self.formatArg)): if Opt in self.formatArg[i][1].__bases__ : try: d.update({self.formatArg[i][0] : self.formatArg[i][1](args[i])}) except IndexError : pass else: d.update({self.formatArg[i][0] : self.formatArg[i][1](args[i])}) except IndexError : raise CommandArgumentException("Not enough arguments: %s command arguments should be %s instead of %s" %(self.__class__,self.formatArg,args)) except ValueError as e: raise CommandArgumentException("Wrong argument type: %s command arguments should be %s instead of %s (%s)" %(self.__class__,self.formatArg,args,e)) return d
[docs] def handle_args(self,**kwargs): """ Override this method to treat commands arguments.""" logger.debug("Parsing arguments %s in %s" % (str(kwargs),str(self.__class__)))
[docs] def toMpdMsg(self): """ Override this method to send a specific respond to mpd client.""" logger.debug("Not implemented respond for command %s"%self.__class__) return ""
class CommandDummy(Command): def toMpdMsg(self):"Dummy respond sent for command %s"%self.__class__) return "ACK [error@command_listNum] {%s} Dummy respond for command '%s'\n" % (self.__class__,self.__class__)
[docs]class CommandItems(Command): """ This is a subclass of :class:`Command` class. CommandItems is used to send items respond such as :class:`command.Status` command."""
[docs] def items(self): """ Overwrite this method to send items to mpd client. This method must return a list a tuples ("key",value).""" return []
def toMpdMsg(self): items=self.items() acc="" for (i,v) in items: acc+="%s: %s\n"%(i,str(v)) return acc
[docs]class CommandSongs(Command): """ This is a subclass of :class:`Command` class. Respond songs informations for mpd clients."""
[docs] def songs(self): """ Override it to adapt this command. This must return a list of :class:`MpdPlaylistSong` """ return [] #self.helper_mkSong("/undefined/")
def toMpdMsg(self): return ''.join([MpdPlaylistSong.toMpdMsg(s) for s in self.songs()])
[docs]class CommandPlaylist(CommandSongs): """ This class of commands is used on mpd internal playlist."""
[docs] def songs(self): """ Overide it to specify a real playlist. Should return a list of dict song informations""" return [] # self.playlist.generateMpdPlaylist()
[docs]class MpdPlaylistSong(object): """ To create a mpd song which is in a playtlist. MPD use a song id which is unique for all song in playlist and stable over time. This field is automatically generated. """ def __init__(self,file,songId,playlistPosition=None,title=" ",time=0,album=" ",artist=" ",track=0): self.file=file self.title=title self.time=time self.album=album self.artist=artist self.track=track self.playlistPosition=playlistPosition self.songId=songId #self.generatePlaylistSongId(self) def generatePlaylistSongId(self,song): return id(song) def toMpdMsg(self): if self.playlistPosition == None : logger.warning("MpdPlaylistSong.playlistposition attribute is not set.") return ('file: '+self.file+"\n"+ 'Time: '+str(self.time)+"\n"+ 'Album: '+self.album+"\n"+ 'Artist: '+self.artist+"\n"+ 'Title: '+self.title+"\n"+ 'Track: '+str(self.track)+"\n"+ 'Pos: '+str(self.playlistPosition)+"\n"+ 'Id: '+ str(self.songId)+"\n")
[docs]class MpdLibrarySong(object): """ To create a mpd library song. This is actually not used.""" def __init__(self,filename): self.file=filename self.lastModDate="2011-12-17T22:47:58Z" self.time="0" self.artist= "" self.title=filename self.album="" self.track="" self.genre="" def toMpdMsg(self): return ("file: "+self.filename+"\n"+ "Last-Modified: "+self.lastModDate+"\n"+ "Time: "+self.time+"\n"+ "Artist: "+self.artist+"\n"+ "Title: "+self.title+"\n"+ "Album: "+self.album+"\n"+ "Track: "+self.track+"\n"+ "Genre :"+self.genre+"\n")
import types class Opt(object):pass
[docs]class OptInt(Opt,types.IntType): """ Represent optionnal integer command argument""" pass
[docs]class OptStr(Opt,types.StringType): """ Represent optionnal string command argument""" pass
[docs]class PlaylistHistory(object): """ Contains all playlist version to generate playlist diff (see plchanges* commands). This class is a singleton and it is used by MpdPlaylist. """ _instance = None playlistHistory=[] def __new__(cls, *args, **kwargs): if not cls._instance: cls._instance = super(PlaylistHistory, cls).__new__( cls, *args, **kwargs) return cls._instance
[docs] def addPlaylist(self,version,playlist): """ Add playlist version if not exist in history """ for (v,p) in self.playlistHistory: if v == version: return None self.playlistHistory.append((version,playlist))
[docs] def getPlaylist(self,version): """ Get playlist from version""" for (i,p) in self.playlistHistory: if i==version: return p return None
[docs] def diff(self,version): """ Return new songs in current playlist since version """ plOld=self.getPlaylist(version) plCur=self.playlistHistory[len(self.playlistHistory)-1][1] if plOld == None: return plCur diff=[] try : for i in range(0,len(plOld)): if plOld[i] != plCur[i]: diff.append(plCur[i]) for i in range(len(plOld),len(plCur)): diff.append(plCur[i]) except IndexError: pass return diff
def show(self): print "show playlistHistory" print "number of version: " + str(len(self.playlistHistory)) for i in self.playlistHistory: print i print "------" print "show playlistHistory end"
[docs]class MpdPlaylist(object): """ MpdPlaylist is a list of song. Use it to create a mapping between your player and the fictive mpd server. Some methods must be implemented, otherwise, NotImplementedError is raised. To bind a playlist to this class, use overide `handlePlaylist` method. """ def __init__(self): self.playlistHistory=PlaylistHistory()
[docs] def handlePlaylist(self): """ Implement this method to bind your playlist with mpd playlist. This method should return a list of :class:`MpdPlaylistSong`.""" raise NotImplementedError("you should implement MpdPlaylist.handlePlaylist method (or use MpdPlaylistDummy) ")
[docs] def generateMpdPlaylist(self): """ This is an internal method to automatically add playlistPosition to songs in playlist. """ p=self.handlePlaylist() for i in range(len(p)): p[i].playlistPosition=i self.playlistHistory.addPlaylist(self.version(),p) return p
def generateMpdPlaylistDiff(self,oldVersion): self.generateMpdPlaylist() return self.playlistHistory.diff(oldVersion)
[docs] def songIdToPosition(self,id): """ This method MUST be implemented. It permits to generate the position from a mpd song id.""" raise NotImplementedError("you should implement MpdPlaylist.songIdToPosition method")
def version(self): return 0 def length(self): return len(self.generateMpdPlaylist())
[docs] def move(self,fromPostion,toPosition): """ Implement it to support move* commands """ raise NotImplementedError("you should implement MpdPlaylist.move method")
def moveId(self,fromId,toPosition): self.move(self.songIdToPosition(fromId),toPosition)
[docs] def delete(self,position): """ Implement it to support delete* commands """ raise NotImplementedError("you should implement MpdPlaylist.delete method")
def deleteId(self,songId): self.delete(self.songIdToPosition(songId))
class MpdPlaylistDummy(MpdPlaylist): def handlePlaylist(self): logger.warning("Dummy implementation of handlePlaylist method") return []