X-Git-Url: http://git.kaliko.me/?a=blobdiff_plain;f=musicpd.py;h=a2637fb4c4aa139e11cd28cf7b1cd8e3ddc5de80;hb=refs%2Ftags%2Fv0.4.2;hp=864337e711baeb4ffa1cf05841df5c2acd87d3c0;hpb=e1bde448be82c3cedaadd6fa0f5447d250ecefb2;p=python-musicpd.git diff --git a/musicpd.py b/musicpd.py index 864337e..a2637fb 100644 --- a/musicpd.py +++ b/musicpd.py @@ -1,6 +1,6 @@ # python-musicpd: Python MPD client library # Copyright (C) 2008-2010 J. Alexander Treuman -# Copyright (C) 2012-2013 Kaliko Jack +# Copyright (C) 2012-2014 Kaliko Jack # # python-musicpd is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by @@ -15,14 +15,35 @@ # You should have received a copy of the GNU Lesser General Public License # along with python-musicpd. If not, see . +# pylint: disable=C0111 + import socket +from functools import wraps HELLO_PREFIX = "OK MPD " ERROR_PREFIX = "ACK " SUCCESS = "OK" NEXT = "list_OK" -VERSION = '0.3.1b' +VERSION = '0.4.2' + + +def iterator_wrapper(func): + """Decorator handling iterate option""" + @wraps(func) + def decorated_function(instance, *args, **kwargs): + generator = func(instance, *args, **kwargs) + if not instance.iterate: + return list(generator) + instance._iterating = True + def iterator(gen): + try: + for item in gen: + yield item + finally: + instance._iterating = False + return iterator(generator) + return decorated_function class MPDError(Exception): @@ -46,15 +67,41 @@ class PendingCommandError(MPDError): class IteratingError(MPDError): pass +class Range: + + def __init__(self, tpl): + self.tpl = tpl + self._check() + + def __str__(self): + if len(self.tpl) == 0: + return ':' + if len(self.tpl) == 1: + return '{0}:'.format(self.tpl[0]) + return '{0[0]}:{0[1]}'.format(self.tpl) -class _NotConnected(object): + def __repr__(self): + return 'Range({0})'.format(self.tpl) + + def _check(self): + if not isinstance(self.tpl, tuple): + raise CommandError('Wrong type, provide a tuple') + if len(self.tpl) not in [0, 1, 2]: + raise CommandError('length not in [0, 1, 2]') + for index in self.tpl: + try: + index = int(index) + except (TypeError, ValueError): + raise CommandError('Not a tuple of int') + +class _NotConnected: def __getattr__(self, attr): return self._dummy def _dummy(*args): raise ConnectionError("Not connected") -class MPDClient(object): +class MPDClient: def __init__(self): self.iterate = False self._reset() @@ -63,7 +110,7 @@ class MPDClient(object): "clearerror": self._fetch_nothing, "currentsong": self._fetch_object, "idle": self._fetch_list, - "noidle": None, + #"noidle": None, "status": self._fetch_object, "stats": self._fetch_object, # Playback Option Commands @@ -103,9 +150,14 @@ class MPDClient(object): "playlistsearch": self._fetch_songs, "plchanges": self._fetch_songs, "plchangesposid": self._fetch_changes, + "prio": self._fetch_nothing, + "prioid": self._fetch_nothing, + "rangeid": self._fetch_nothing, "shuffle": self._fetch_nothing, "swap": self._fetch_nothing, "swapid": self._fetch_nothing, + "addtagid": self._fetch_nothing, + "cleartagid": self._fetch_nothing, # Stored Playlist Commands "listplaylist": self._fetch_list, "listplaylistinfo": self._fetch_songs, @@ -131,6 +183,12 @@ class MPDClient(object): "searchaddpl": self._fetch_nothing, "update": self._fetch_item, "rescan": self._fetch_item, + "readcomments": self._fetch_object, + # Mounts and neighbors + "mount": self._fetch_nothing, + "unmount": self._fetch_nothing, + "listmounts": self._fetch_mounts, + "listneighbors": self._fetch_neighbors, # Sticker Commands "sticker get": self._fetch_item, "sticker set": self._fetch_nothing, @@ -145,8 +203,10 @@ class MPDClient(object): # Audio Output Commands "disableoutput": self._fetch_nothing, "enableoutput": self._fetch_nothing, + "toggleoutput": self._fetch_nothing, "outputs": self._fetch_outputs, # Reflection Commands + "config": self._fetch_object, "commands": self._fetch_list, "notcommands": self._fetch_list, "tagtypes": self._fetch_list, @@ -161,6 +221,8 @@ class MPDClient(object): } def __getattr__(self, attr): + if attr == 'send_noidle': # have send_noidle to cancel idle as well as noidle + return self.noidle() if attr.startswith("send_"): command = attr.replace("send_", "", 1) wrapper = self._send @@ -228,10 +290,15 @@ class MPDClient(object): self._wfile.write("%s\n" % line) self._wfile.flush() - def _write_command(self, command, args=[]): + def _write_command(self, command, args=None): + if args is None: + args = [] parts = [command] for arg in args: - parts.append('"%s"' % escape(str(arg))) + if isinstance(arg, tuple): + parts.append('{0!s}'.format(Range(arg))) + else: + parts.append('"%s"' % escape(str(arg))) self._write_line(" ".join(parts)) def _read_line(self): @@ -278,11 +345,13 @@ class MPDClient(object): yield value def _read_playlist(self): - for key, value in self._read_pairs(":"): + for _, value in self._read_pairs(":"): yield value - def _read_objects(self, delimiters=[]): + def _read_objects(self, delimiters=None): obj = {} + if delimiters is None: + delimiters = [] for key, value in self._read_pairs(): key = key.lower() if obj: @@ -307,19 +376,6 @@ class MPDClient(object): self._command_list = None self._fetch_nothing() - def _iterator_wrapper(self, iterator): - try: - for item in iterator: - yield item - finally: - self._iterating = False - - def _wrap_iterator(self, iterator): - if not self.iterate: - return list(iterator) - self._iterating = True - return self._iterator_wrapper(iterator) - def _fetch_nothing(self): line = self._read_line() if line is not None: @@ -331,11 +387,13 @@ class MPDClient(object): return return pairs[0][1] + @iterator_wrapper def _fetch_list(self): - return self._wrap_iterator(self._read_list()) + return self._read_list() + @iterator_wrapper def _fetch_playlist(self): - return self._wrap_iterator(self._read_playlist()) + return self._read_playlist() def _fetch_object(self): objs = list(self._read_objects()) @@ -343,8 +401,9 @@ class MPDClient(object): return {} return objs[0] + @iterator_wrapper def _fetch_objects(self, delimiters): - return self._wrap_iterator(self._read_objects(delimiters)) + return self._read_objects(delimiters) def _fetch_changes(self): return self._fetch_objects(["cpos"]) @@ -367,8 +426,15 @@ class MPDClient(object): def _fetch_messages(self): return self._fetch_objects(["channel"]) + def _fetch_mounts(self): + return self._fetch_objects(["mount"]) + + def _fetch_neighbors(self): + return self._fetch_objects(["neighbor"]) + + @iterator_wrapper def _fetch_command_list(self): - return self._wrap_iterator(self._read_command_list()) + return self._read_command_list() def _hello(self): line = self._rfile.readline() @@ -380,6 +446,7 @@ class MPDClient(object): self.mpd_version = line[len(HELLO_PREFIX):].strip() def _reset(self): + # pylint: disable=w0201 self.mpd_version = None self._iterating = False self._pending = [] @@ -405,7 +472,7 @@ class MPDClient(object): for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM, socket.IPPROTO_TCP, flags): - af, socktype, proto, canonname, sa = res + af, socktype, proto, _, sa = res sock = None try: sock = socket.socket(af, socktype, proto) @@ -420,6 +487,14 @@ class MPDClient(object): else: raise ConnectionError("getaddrinfo returns an empty list") + def noidle(self): + # noidle's special case + if not self._pending or self._pending[0] != 'idle': + raise CommandError('cannot send noidle if send_idle was not called') + del self._pending[0] + self._write_command("noidle") + return self._fetch_list() + def connect(self, host, port): if self._sock is not None: raise ConnectionError("Already connected") @@ -440,7 +515,7 @@ class MPDClient(object): self._rfile.close() if hasattr(self._wfile, 'close'): self._wfile.close() - if isinstance(self._sock, socket.socket): + if hasattr(self._sock, 'close'): self._sock.close() self._reset() @@ -472,5 +547,4 @@ class MPDClient(object): def escape(text): return text.replace("\\", "\\\\").replace('"', '\\"') - # vim: set expandtab shiftwidth=4 softtabstop=4 textwidth=79: