screenlets.plugins.mpdclient2

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