X-Git-Url: http://git.kaliko.me/?p=python-musicpd.git;a=blobdiff_plain;f=musicpd.py;h=619282d832f9ec63d77a658536b111042bbce41e;hp=6157c103b6b7b2192391c3ea37a2255b9ff601a6;hb=f7fb00d50c22fed77da15b66c7293d345bb5c8d3;hpb=e3e71de3b46e62a2bf6575c11dc8fe856e340bc4 diff --git a/musicpd.py b/musicpd.py index 6157c10..619282d 100644 --- a/musicpd.py +++ b/musicpd.py @@ -1,5 +1,5 @@ # python-musicpd: Python MPD client library -# Copyright (C) 2012-2020 kaliko +# Copyright (C) 2012-2021 kaliko # Copyright (C) 2019 Naglis Jonaitis # Copyright (C) 2019 Bart Van Loon # Copyright (C) 2008-2010 J. Alexander Treuman @@ -28,9 +28,12 @@ HELLO_PREFIX = "OK MPD " ERROR_PREFIX = "ACK " SUCCESS = "OK" NEXT = "list_OK" -VERSION = '0.4.5' -#: seconds before a tcp connection attempt times out -CONNECTION_TIMEOUT = 5 +VERSION = '0.7.0' +#: Seconds before a connection attempt times out +#: (overriden by MPD_TIMEOUT env. var.) +CONNECTION_TIMEOUT = 30 +#: Socket timeout in second (Default is None for no timeout) +SOCKET_TIMEOUT = None def iterator_wrapper(func): @@ -156,6 +159,11 @@ class MPDClient: def __init__(self): self.iterate = False + #: Socket timeout value in seconds + self._socket_timeout = SOCKET_TIMEOUT + #: Current connection timeout value, defaults to + #: :py:obj:`CONNECTION_TIMEOUT` or env. var. ``MPD_TIMEOUT`` if provided + self.mpd_timeout = None self._reset() self._commands = { # Status Commands @@ -173,6 +181,7 @@ class MPDClient: "random": self._fetch_nothing, "repeat": self._fetch_nothing, "setvol": self._fetch_nothing, + "getvol": self._fetch_object, "single": self._fetch_nothing, "replay_gain_mode": self._fetch_nothing, "replay_gain_status": self._fetch_item, @@ -256,6 +265,7 @@ class MPDClient: "kill": None, "password": self._fetch_nothing, "ping": self._fetch_nothing, + "binarylimit": self._fetch_nothing, "tagtypes": self._fetch_list, "tagtypes disable": self._fetch_nothing, "tagtypes enable": self._fetch_nothing, @@ -265,6 +275,8 @@ class MPDClient: "partition": self._fetch_nothing, "listpartitions": self._fetch_list, "newpartition": self._fetch_nothing, + "delpartition": self._fetch_nothing, + "moveoutput": self._fetch_nothing, # Audio Output Commands "disableoutput": self._fetch_nothing, "enableoutput": self._fetch_nothing, @@ -294,22 +306,33 @@ class MPDClient: """ self.host = 'localhost' self.pwd = None - self.port = os.environ.get('MPD_PORT', '6600') - mpd_host_env = os.environ.get('MPD_HOST') - if mpd_host_env: - # If password is set: - # mpd_host_env = ['pass', 'host'] because MPD_HOST=pass@host - mpd_host_env = mpd_host_env.split('@') - mpd_host_env.reverse() - self.host = mpd_host_env[0] - if len(mpd_host_env) > 1 and mpd_host_env[1]: - self.pwd = mpd_host_env[1] + self.port = os.getenv('MPD_PORT', '6600') + if os.getenv('MPD_HOST'): + # If password is set: MPD_HOST=pass@host + if '@' in os.getenv('MPD_HOST'): + mpd_host_env = os.getenv('MPD_HOST').split('@', 1) + if mpd_host_env[0]: + # A password is actually set + self.pwd = mpd_host_env[0] + if mpd_host_env[1]: + self.host = mpd_host_env[1] + elif mpd_host_env[1]: + # No password set but leading @ is an abstract socket + self.host = '@'+mpd_host_env[1] + else: + # MPD_HOST is a plain host + self.host = os.getenv('MPD_HOST') else: # Is socket there - xdg_runtime_dir = os.environ.get('XDG_RUNTIME_DIR', '/run') + xdg_runtime_dir = os.getenv('XDG_RUNTIME_DIR', '/run') rundir = os.path.join(xdg_runtime_dir, 'mpd/socket') if os.path.exists(rundir): self.host = rundir + self.mpd_timeout = os.getenv('MPD_TIMEOUT') + if self.mpd_timeout and self.mpd_timeout.isdigit(): + self.mpd_timeout = int(self.mpd_timeout) + else: # Use CONNECTION_TIMEOUT as default even if MPD_TIMEOUT carries gargage + self.mpd_timeout = CONNECTION_TIMEOUT def __getattr__(self, attr): if attr == 'send_noidle': # have send_noidle to cancel idle as well as noidle @@ -390,6 +413,8 @@ class MPDClient: parts.append('{0!s}'.format(Range(arg))) else: parts.append('"%s"' % escape(str(arg))) + if '\n' in ' '.join(parts): + raise CommandError('new line found in the command!') self._write_line(" ".join(parts)) def _read_binary(self, amount): @@ -553,7 +578,7 @@ class MPDClient: obj['data'] = self._read_binary(amount) except IOError as err: raise ConnectionError('Error reading binary content: %s' % err) - if len(obj['data']) != amount: + if len(obj['data']) != amount: # can we ever get there? raise ConnectionError('Error reading binary content: ' 'Expects %sB, got %s' % (amount, len(obj['data']))) # Fetches trailing new line @@ -589,8 +614,13 @@ class MPDClient: if not hasattr(socket, "AF_UNIX"): raise ConnectionError( "Unix domain sockets not supported on this platform") + # abstract socket + if path.startswith('@'): + path = '\0'+path[1:] sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.settimeout(self.mpd_timeout) sock.connect(path) + sock.settimeout(self.socket_timeout) return sock def _connect_tcp(self, host, port): @@ -606,9 +636,9 @@ class MPDClient: sock = None try: sock = socket.socket(af, socktype, proto) - sock.settimeout(CONNECTION_TIMEOUT) + sock.settimeout(self.mpd_timeout) sock.connect(sa) - sock.settimeout(None) + sock.settimeout(self.socket_timeout) return sock except socket.error as socket_err: err = socket_err @@ -637,11 +667,18 @@ class MPDClient: The connect method honors MPD_HOST/MPD_PORT environment variables. + The underlying socket also honors MPD_TIMEOUT environment variable + and defaults to :py:obj:`musicpd.CONNECTION_TIMEOUT` (connect command only). + + If you want to have a timeout for each command once you got connected, + set its value in :py:obj:`MPDClient.socket_timeout` (in second) or at + module level in :py:obj:`musicpd.SOCKET_TIMEOUT`. + .. note:: Default host/port If host evaluate to :py:obj:`False` * use ``MPD_HOST`` environment variable if set, extract password if present, - * else looks for a existing file in ``${XDG_RUNTIME_DIR:-/run/}/mpd/socket`` + * else looks for an existing file in ``${XDG_RUNTIME_DIR:-/run/}/mpd/socket`` * else set host to ``localhost`` If port evaluate to :py:obj:`False` @@ -658,7 +695,7 @@ class MPDClient: self.port = port if self._sock is not None: raise ConnectionError("Already connected") - if host.startswith("/"): + if host[0] in ['/', '@']: self._sock = self._connect_unix(host) else: self._sock = self._connect_tcp(host, port) @@ -671,6 +708,18 @@ class MPDClient: self.disconnect() raise + @property + def socket_timeout(self): + """Socket timeout in second (defaults to :py:obj:`SOCKET_TIMEOUT`). + Use None to disable socket timout.""" + return self._socket_timeout + + @socket_timeout.setter + def socket_timeout(self, timeout): + self._socket_timeout = timeout + if getattr(self._sock, 'settimeout', False): + self._sock.settimeout(self._socket_timeout) + def disconnect(self): """Closes the MPD connection. The client closes the actual socket, it does not use the @@ -687,6 +736,9 @@ class MPDClient: self._reset() def fileno(self): + """Return the socket’s file descriptor (a small integer). + This is useful with :py:obj:`select.select`. + """ if self._sock is None: raise ConnectionError("Not connected") return self._sock.fileno()