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