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

Source Code for Module screenlets.plugins.mpdclient2

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