3 # TODO: return {} if no object read (?)
4 # TODO: implement argument checking/parsing (?)
5 # TODO: check for EOF when reading and benchmark it
6 # TODO: command_list support
7 # TODO: converter support
8 # TODO: global for parsing MPD_HOST/MPD_PORT
9 # TODO: global for parsing MPD error messages
10 # TODO: IPv6 support (AF_INET6)
15 HELLO_PREFIX = "OK MPD "
20 class MPDError(Exception):
23 class ProtocolError(MPDError):
26 class CommandError(MPDError):
30 class MPDClient(object):
36 "disableoutput": self._getnone,
37 "enableoutput": self._getnone,
39 "update": self._getitem,
40 # Informational Commands
41 "status": self._getobject,
42 "stats": self._getobject,
43 "outputs": self._getoutputs,
44 "commands": self._getlist,
45 "notcommands": self._getlist,
46 "tagtypes": self._getlist,
47 "urlhandlers": self._getlist,
49 "find": self._getsongs,
50 "list": self._getlist,
51 "listall": self._getdatabase,
52 "listallinfo": self._getdatabase,
53 "lsinfo": self._getdatabase,
54 "search": self._getsongs,
55 "count": self._getobject,
58 "addid": self._getitem,
59 "clear": self._getnone,
60 "currentsong": self._getobject,
61 "delete": self._getnone,
62 "deleteid": self._getnone,
63 "load": self._getnone,
64 "rename": self._getnone,
65 "move": self._getnone,
66 "moveid": self._getnone,
67 "playlist": self._getplaylist,
68 "playlistinfo": self._getsongs,
69 "playlistid": self._getsongs,
70 "plchanges": self._getsongs,
71 "plchangesposid": self._getchanges,
73 "save": self._getnone,
74 "shuffle": self._getnone,
75 "swap": self._getnone,
76 "swapid": self._getnone,
77 "listplaylist": self._getlist,
78 "listplaylistinfo": self._getsongs,
79 "playlistadd": self._getnone,
80 "playlistclear": self._getnone,
81 "playlistdelete": self._getnone,
82 "playlistmove": self._getnone,
83 "playlistfind": self._getsongs,
84 "playlistsearch": self._getsongs,
86 "crossfade": self._getnone,
87 "next": self._getnone,
88 "pause": self._getnone,
89 "play": self._getnone,
90 "playid": self._getnone,
91 "previous": self._getnone,
92 "random": self._getnone,
93 "repeat": self._getnone,
94 "seek": self._getnone,
95 "seekid": self._getnone,
96 "setvol": self._getnone,
97 "stop": self._getnone,
98 "volume": self._getnone,
99 # Miscellaneous Commands
100 "clearerror": self._getnone,
102 "password": self._getnone,
103 "ping": self._getnone,
106 def __getattr__(self, attr):
108 retval = self._commands[attr]
110 raise AttributeError, "'%s' object has no attribute '%s'" % \
111 (self.__class__.__name__, attr)
112 return lambda *args: self._docommand(attr, args, retval)
114 def _docommand(self, command, args, retval):
115 self._writecommand(command, args)
120 def _writeline(self, line):
121 self._sockfile.write("%s\n" % line)
122 self._sockfile.flush()
124 def _writecommand(self, command, args):
127 parts.append('"%s"' % escape(str(arg)))
128 self._writeline(" ".join(parts))
131 line = self._sockfile.readline().rstrip("\n")
132 if line.startswith(ERROR_PREFIX):
133 error = line[len(ERROR_PREFIX):].strip()
134 raise CommandError, error
139 def _readitem(self, separator):
140 line = self._readline()
143 item = line.split(separator, 1)
145 raise ProtocolError, "Could not parse item: '%s'" % line
148 def _readitems(self, separator=": "):
149 item = self._readitem(separator)
152 item = self._readitem(separator)
157 for key, value in self._readitems():
160 raise ProtocolError, "Expected key '%s', got '%s'" % \
166 def _readplaylist(self):
167 for key, value in self._readitems(":"):
171 def _readobjects(self, delimiters=[]):
173 for key, value in self._readitems():
176 if key in delimiters:
179 elif obj.has_key(key):
180 if not isinstance(obj[key], list):
181 obj[key] = [obj[key], value]
183 obj[key].append(value)
190 def _wrapiterator(self, iterator):
192 return list(iterator)
196 line = self._readline()
198 raise ProtocolError, "Got unexpected return value: '%s'" % line
201 items = list(self._readitems())
203 raise ProtocolError, "Expected 1 item, got %i" % len(items)
207 return self._wrapiterator(self._readlist())
209 def _getplaylist(self):
210 return self._wrapiterator(self._readplaylist())
212 def _getobject(self):
213 objs = list(self._readobjects())
218 def _getobjects(self, delimiters):
219 return self._wrapiterator(self._readobjects(delimiters))
222 return self._getobjects(["file"])
224 def _getdatabase(self):
225 return self._getobjects(["file", "directory", "playlist"])
227 def _getoutputs(self):
228 return self._getobjects(["outputid"])
230 def _getchanges(self):
231 return self._getobjects(["cpos"])
234 line = self._sockfile.readline().rstrip("\n")
235 if not line.startswith(HELLO_PREFIX):
236 raise ProtocolError, "Got invalid MPD hello: '%s'" % line
237 self.mpd_version = line[len(HELLO_PREFIX):].strip()
240 self.mpd_version = None
241 self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
242 self._sockfile = self._sock.makefile("rb+")
244 def connect(self, host, port):
246 self._sock.connect((host, port))
249 def disconnect(self):
250 self._sockfile.close()
256 return text.replace("\\", "\\\\").replace('"', '\\"')
259 # vim: set expandtab shiftwidth=4 softtabstop=4 textwidth=79: