Package screenlets :: Package plugins :: Module mpdclient2
[hide private]
[frames] | no frames]

Source Code for Module screenlets.plugins.mpdclient2

  1  #!/usr/bin/env python 
  2   
  3  # py-libmpdclient2 is written by Nick Welch <mack@incise.org>, 2005. 
  4  # 
  5  # This software is in the public domain 
  6  # and is provided AS IS, with NO WARRANTY. 
  7   
  8  import socket 
  9   
 10  # a line is either: 
 11  # 
 12  # key:val pair 
 13  # OK 
 14  # ACK 
 15   
16 -class socket_talker(object):
17
18 - def __init__(self, host, port):
19 self.host = host 20 self.port = port 21 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 22 self.sock.connect((host, port)) 23 self.file = self.sock.makefile("rb+") 24 self.current_line = '' 25 self.ack = '' 26 self.done = True
27 28 # this SUCKS 29
30 - def get_line(self):
31 if not self.current_line: 32 self.current_line = self.file.readline().rstrip("\n") 33 if not self.current_line: 34 raise EOFError 35 if self.current_line == "OK" or self.current_line.startswith("ACK"): 36 self.done = True 37 return self.current_line
38
39 - def putline(self, line):
40 self.file.write("%s\n" % line) 41 self.file.flush() 42 self.done = False
43
44 - def get_pair(self):
45 line = self.get_line() 46 self.ack = '' 47 48 if self.done: 49 if line.startswith("ACK"): 50 self.ack = line.split(None, 1)[1] 51 return () 52 53 pair = line.split(": ", 1) 54 if len(pair) != 2: 55 raise RuntimeError("bogus response: ``%s''" % line) 56 57 return pair
58 59 ZERO = 0 60 ONE = 1 61 MANY = 2 62 63 plitem_delim = ["file", "directory", "playlist"] 64 65 commands = { 66 # (name, nargs): (format string, nresults, results_type_name, delimiter_key) 67 68 # delimiter key is for commands that return multiple results. we use this 69 # string to detect the beginning of a new result object. 70 71 # if results_type_name is empty, the result object's .type will be set to 72 # the key of the first key/val pair in it; otherwise, it will be set to 73 # results_type_name. 74 75 ("kill", 0): ('%s', ZERO, '', []), 76 ("outputs", 0): ('%s', MANY, 'outputs', ['outputid']), 77 ("clear", 0): ('%s', ZERO, '', []), 78 ("currentsong", 0): ('%s', ONE, '', []), 79 ("shuffle", 0): ('%s', ZERO, '', []), 80 ("next", 0): ('%s', ZERO, '', []), 81 ("previous", 0): ('%s', ZERO, '', []), 82 ("stop", 0): ('%s', ZERO, '', []), 83 ("clearerror", 0): ('%s', ZERO, '', []), 84 ("close", 0): ('%s', ZERO, '', []), 85 ("commands", 0): ('%s', MANY, 'commands', ['command']), 86 ("notcommands", 0): ('%s', MANY, 'notcommands', ['command']), 87 ("ping", 0): ('%s', ZERO, '', []), 88 ("stats", 0): ('%s', ONE, 'stats', []), 89 ("status", 0): ('%s', ONE, 'status', []), 90 ("play", 0): ('%s', ZERO, '', []), 91 ("playlistinfo", 0): ('%s', MANY, '', plitem_delim), 92 ("playlistid", 0): ('%s', MANY, '', plitem_delim), 93 ("lsinfo", 0): ('%s', MANY, '', plitem_delim), 94 ("update", 0): ('%s', ZERO, '', []), 95 ("listall", 0): ('%s', MANY, '', plitem_delim), 96 ("listallinfo", 0): ('%s', MANY, '', plitem_delim), 97 98 ("disableoutput", 1): ("%s %d", ZERO, '', []), 99 ("enableoutput", 1): ("%s %d", ZERO, '', []), 100 ("delete", 1): ('%s %d', ZERO, '', []), # <int song> 101 ("deleteid", 1): ('%s %d', ZERO, '', []), # <int songid> 102 ("playlistinfo", 1): ('%s %d', MANY, '', plitem_delim), # <int song> 103 ("playlistid", 1): ('%s %d', MANY, '', plitem_delim), # <int songid> 104 ("crossfade", 1): ('%s %d', ZERO, '', []), # <int seconds> 105 ("play", 1): ('%s %d', ZERO, '', []), # <int song> 106 ("playid", 1): ('%s %d', ZERO, '', []), # <int songid> 107 ("random", 1): ('%s %d', ZERO, '', []), # <int state> 108 ("repeat", 1): ('%s %d', ZERO, '', []), # <int state> 109 ("setvol", 1): ('%s %d', ZERO, '', []), # <int vol> 110 ("plchanges", 1): ('%s %d', MANY, '', plitem_delim), # <playlist version> 111 ("pause", 1): ('%s %d', ZERO, '', []), # <bool pause> 112 113 ("update", 1): ('%s "%s"', ONE, 'update', []), # <string path> 114 ("listall", 1): ('%s "%s"', MANY, '', plitem_delim), # <string path> 115 ("listallinfo", 1): ('%s "%s"', MANY, '', plitem_delim), # <string path> 116 ("lsinfo", 1): ('%s "%s"', MANY, '', plitem_delim), # <string directory> 117 ("add", 1): ('%s "%s"', ZERO, '', []), # <string> 118 ("load", 1): ('%s "%s"', ZERO, '', []), # <string name> 119 ("rm", 1): ('%s "%s"', ZERO, '', []), # <string name> 120 ("save", 1): ('%s "%s"', ZERO, '', []), # <string playlist name> 121 ("password", 1): ('%s "%s"', ZERO, '', []), # <string password> 122 123 ("move", 2): ("%s %d %d", ZERO, '', []), # <int from> <int to> 124 ("moveid", 2): ("%s %d %d", ZERO, '', []), # <int songid from> <int to> 125 ("swap", 2): ("%s %d %d", ZERO, '', []), # <int song1> <int song2> 126 ("swapid", 2): ("%s %d %d", ZERO, '', []), # <int songid1> <int songid2> 127 ("seek", 2): ("%s %d %d", ZERO, '', []), # <int song> <int time> 128 ("seekid", 2): ("%s %d %d", ZERO, '', []), # <int songid> <int time> 129 130 # <string type> <string what> 131 ("find", 2): ('%s "%s" "%s"', MANY, '', plitem_delim), 132 133 # <string type> <string what> 134 ("search", 2): ('%s "%s" "%s"', MANY, '', plitem_delim), 135 136 # list <metadata arg1> [<metadata arg2> <search term>] 137 138 # <metadata arg1> 139 ("list", 1): ('%s "%s"', MANY, '', plitem_delim), 140 141 # <metadata arg1> <metadata arg2> <search term> 142 ("list", 3): ('%s "%s" "%s" "%s"', MANY, '', plitem_delim), 143 } 144
145 -def is_command(cmd):
146 return cmd in [ k[0] for k in commands.keys() ]
147
148 -def escape(text):
149 # join/split is faster than replace 150 text = '\\\\'.join(text.split('\\')) # \ -> \\ 151 text = '\\"'.join(text.split('"')) # " -> \" 152 return text
153
154 -def get_command(cmd, args):
155 try: 156 return commands[(cmd, len(args))] 157 except KeyError: 158 raise RuntimeError("no such command: %s (%d args)" % (cmd, len(args)))
159
160 -def send_command(talker, cmd, args):
161 args = list(args[:]) 162 for i, arg in enumerate(args): 163 if not isinstance(arg, int): 164 args[i] = escape(str(arg)) 165 format = get_command(cmd, args)[0] 166 talker.putline(format % tuple([cmd] + list(args)))
167
168 -class sender_n_fetcher(object):
169 - def __init__(self, sender, fetcher):
170 self.sender = sender 171 self.fetcher = fetcher 172 self.iterate = False
173
174 - def __getattr__(self, cmd):
175 return lambda *args: self.send_n_fetch(cmd, args)
176
177 - def send_n_fetch(self, cmd, args):
178 getattr(self.sender, cmd)(*args) 179 junk, howmany, type, keywords = get_command(cmd, args) 180 181 if howmany == ZERO: 182 self.fetcher.clear() 183 return 184 185 if howmany == ONE: 186 return self.fetcher.one_object(keywords, type) 187 188 assert howmany == MANY 189 result = self.fetcher.all_objects(keywords, type) 190 191 if not self.iterate: 192 result = list(result) 193 self.fetcher.clear() 194 return result 195 196 # stupid hack because you apparently can't return non-None and yield 197 # within the same function 198 def yield_then_clear(it): 199 for x in it: 200 yield x 201 self.fetcher.clear()
202 return yield_then_clear(result)
203 204
205 -class command_sender(object):
206 - def __init__(self, talker):
207 self.talker = talker
208 - def __getattr__(self, cmd):
209 return lambda *args: send_command(self.talker, cmd, args)
210
211 -class response_fetcher(object):
212 - def __init__(self, talker):
213 self.talker = talker 214 self.converters = {}
215
216 - def clear(self):
217 while not self.talker.done: 218 self.talker.current_line = '' 219 self.talker.get_line() 220 self.talker.current_line = ''
221
222 - def one_object(self, keywords, type):
223 # if type isn't empty, then the object's type is set to it. otherwise 224 # the type is set to the key of the first key/val pair. 225 226 # keywords lists the keys that indicate a new object -- like for the 227 # 'outputs' command, keywords would be ['outputid']. 228 229 entity = dictobj() 230 if type: 231 entity['type'] = type 232 233 while not self.talker.done: 234 self.talker.get_line() 235 pair = self.talker.get_pair() 236 237 if not pair: 238 self.talker.current_line = '' 239 return entity 240 241 key, val = pair 242 key = key.lower() 243 244 if key in keywords and key in entity.keys(): 245 return entity 246 247 if not type and 'type' not in entity.keys(): 248 entity['type'] = key 249 250 entity[key] = self.convert(entity['type'], key, val) 251 self.talker.current_line = '' 252 253 return entity
254
255 - def all_objects(self, keywords, type):
256 while 1: 257 obj = self.one_object(keywords, type) 258 if not obj: 259 raise StopIteration 260 yield obj 261 if self.talker.done: 262 raise StopIteration
263
264 - def convert(self, cmd, key, val):
265 # if there's a converter, convert it, otherwise return it the same 266 return self.converters.get(cmd, {}).get(key, lambda x: x)(val)
267
268 -class dictobj(dict):
269 - def __getattr__(self, attr):
270 try: 271 return self[attr] 272 except KeyError: 273 raise AttributeError
274 - def __repr__(self):
275 # <mpdclient2.dictobj at 0x12345678 .. 276 # { 277 # key: val, 278 # key2: val2 279 # }> 280 return (object.__repr__(self).rstrip('>') + ' ..\n' + 281 ' {\n ' + 282 ',\n '.join([ '%s: %s' % (k, v) for k, v in self.items() ]) + 283 '\n }>')
284
285 -class mpd_connection(object):
286 - def __init__(self, host, port):
287 self.talker = socket_talker(host, port) 288 self.send = command_sender(self.talker) 289 self.fetch = response_fetcher(self.talker) 290 self.do = sender_n_fetcher(self.send, self.fetch) 291 292 self._hello()
293
294 - def _hello(self):
295 line = self.talker.get_line() 296 if not line.startswith("OK MPD "): 297 raise RuntimeError("this ain't mpd") 298 self.mpd_version = line[len("OK MPD "):].strip() 299 self.talker.current_line = ''
300 301 # conn.foo() is equivalent to conn.do.foo(), but nicer
302 - def __getattr__(self, attr):
303 if is_command(attr): 304 return getattr(self.do, attr) 305 raise AttributeError(attr)
306
307 -def parse_host(host):
308 if '@' in host: 309 return host.split('@', 1) 310 return '', host
311
312 -def connect(**kw):
313 import os 314 315 port = int(os.environ.get('MPD_PORT', 6600)) 316 password, host = parse_host(os.environ.get('MPD_HOST', 'localhost')) 317 318 kw_port = kw.get('port', 0) 319 kw_password = kw.get('password', '') 320 kw_host = kw.get('host', '') 321 322 if kw_port: 323 port = kw_port 324 if kw_password: 325 password = kw_password 326 if kw_host: 327 host = kw_host 328 329 conn = mpd_connection(host, port) 330 if password: 331 conn.password(password) 332 return conn
333