# -*- coding: utf-8 -*-
-# SPDX-FileCopyrightText: 2012-2023 kaliko <kaliko@azylum.org>
+# SPDX-FileCopyrightText: 2012-2024 kaliko <kaliko@azylum.org>
# SPDX-FileCopyrightText: 2021 Wonko der Verständige <wonko@hanstool.org>
# SPDX-FileCopyrightText: 2019 Naglis Jonaitis <naglis@mailbox.org>
# SPDX-FileCopyrightText: 2019 Bart Van Loon <bbb@bbbart.be>
"""Python Music Player Daemon client library"""
-import socket
+import logging
import os
+import socket
from functools import wraps
SUCCESS = "OK"
NEXT = "list_OK"
#: Module version
-VERSION = '0.9.0b0'
+VERSION = '0.9.0b2'
#: Seconds before a connection attempt times out
#: (overriden by :envvar:`MPD_TIMEOUT` env. var.)
CONNECTION_TIMEOUT = 30
-#: Socket timeout in second (Default is None for no timeout)
+#: Socket timeout in second > 0 (Default is :py:obj:`None` for no timeout)
SOCKET_TIMEOUT = None
+log = logging.getLogger(__name__)
+
def iterator_wrapper(func):
"""Decorator handling iterate option"""
mpd_host_env = _host.split('@', 1)
if mpd_host_env[0]:
# A password is actually set
+ log.debug('password detected in MPD_HOST, set client pwd attribute')
self.pwd = mpd_host_env[0]
if mpd_host_env[1]:
self.host = mpd_host_env[1]
+ log.debug('host detected in MPD_HOST: %s', self.host)
elif mpd_host_env[1]:
# No password set but leading @ is an abstract socket
self.host = '@'+mpd_host_env[1]
+ log.debug('host detected in MPD_HOST: %s (abstract socket)', self.host)
else:
# MPD_HOST is a plain host
self.host = _host
+ log.debug('host detected in MPD_HOST: @%s', self.host)
else:
# Is socket there
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
+ log.debug('host detected in ${XDG_RUNTIME_DIR}/run: %s (unix socket)', self.host)
_mpd_timeout = os.getenv('MPD_TIMEOUT', '')
if _mpd_timeout.isdigit():
self.mpd_timeout = int(_mpd_timeout)
+ log.debug('timeout detected in MPD_TIMEOUT: %d', self.mpd_timeout)
else: # Use CONNECTION_TIMEOUT as default even if MPD_TIMEOUT carries gargage
self.mpd_timeout = CONNECTION_TIMEOUT
parts = [command]
for arg in args:
if isinstance(arg, tuple):
- parts.append('{0!s}'.format(Range(arg)))
+ parts.append(f'{Range(arg)!s}')
else:
- parts.append('"%s"' % escape(str(arg)))
+ parts.append(f'"{escape(str(arg))}"')
if '\n' in ' '.join(parts):
raise CommandError('new line found in the command!')
self._write_line(" ".join(parts))
# 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)
+ try:
+ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ sock.settimeout(self.mpd_timeout)
+ sock.connect(path)
+ sock.settimeout(self.socket_timeout)
+ except socket.error as socket_err:
+ raise ConnectionError(socket_err) from socket_err
return sock
def _connect_tcp(self, host, port):
except AttributeError:
flags = 0
err = None
- for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
- socket.SOCK_STREAM, socket.IPPROTO_TCP,
- flags):
+ try:
+ gai = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
+ socket.SOCK_STREAM, socket.IPPROTO_TCP,
+ flags)
+ except socket.error as gaierr:
+ raise ConnectionError(gaierr) from gaierr
+ for res in gai:
af, socktype, proto, _, sa = res
sock = None
try:
+ log.debug('opening socket %s', sa)
sock = socket.socket(af, socktype, proto)
sock.settimeout(self.mpd_timeout)
sock.connect(sa)
sock.settimeout(self.socket_timeout)
return sock
except socket.error as socket_err:
+ log.debug('opening socket %s failed: %s', sa, socket_err)
err = socket_err
if sock is not None:
sock.close()
if err is not None:
- raise ConnectionError(str(err))
+ raise ConnectionError(err)
raise ConnectionError("getaddrinfo returns an empty list")
def noidle(self):
if self._sock is not None:
raise ConnectionError("Already connected")
if host[0] in ['/', '@']:
+ log.debug('Connecting unix socket %s', host)
self._sock = self._connect_unix(host)
else:
+ log.debug('Connecting tcp socket %s:%s (timeout: %ss)', host, port, self.mpd_timeout)
self._sock = self._connect_tcp(host, port)
self._rfile = self._sock.makefile("r", encoding='utf-8', errors='surrogateescape')
self._rbfile = self._sock.makefile("rb")
except:
self.disconnect()
raise
+ log.debug('Connected')
@property
def socket_timeout(self):
"""Socket timeout in second (defaults to :py:obj:`SOCKET_TIMEOUT`).
- Use None to disable socket timout."""
+ Use :py:obj:`None` to disable socket timout.
+
+ :setter: Set the socket timeout
+ :type: int or None (integer > 0)
+ """
return self._socket_timeout
@socket_timeout.setter
def socket_timeout(self, timeout):
- self._socket_timeout = timeout
+ if timeout is not None:
+ if int(timeout) <= 0:
+ raise ValueError('socket_timeout expects a non zero positive integer')
+ self._socket_timeout = int(timeout)
+ else:
+ 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