# -*- coding: utf-8 -*-
-# SPDX-FileCopyrightText: 2012-2024 kaliko <kaliko@azylum.org>
+# SPDX-FileCopyrightText: 2012-2025 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>
SUCCESS = "OK"
NEXT = "list_OK"
#: Module version
-VERSION = '0.9.0b1'
+VERSION = '0.9.2'
#: Seconds before a connection attempt times out
#: (overriden by :envvar:`MPD_TIMEOUT` env. var.)
CONNECTION_TIMEOUT = 30
:py:attr:`musicpd.MPDClient.port` keep track of current connection
host and port, :py:attr:`musicpd.MPDClient.pwd` is set once with
password extracted from environment variable.
- Calling :py:meth:`password` methode with a new password
+ Calling MPS's password method with a new password
won't update :py:attr:`musicpd.MPDClient.pwd` value.
Moreover, :py:attr:`musicpd.MPDClient.pwd` is only an helper attribute
exposing password extracted from :envvar:`MPD_HOST` environment variable, it
- will not be used as default value for the :py:meth:`password` method
+ will not be used as default value for the MPD's password command.
"""
def __init__(self):
.. note:: This is the version of the protocol spoken, not the real version of the daemon."""
self._reset()
self._commands = {
- # Status Commands
+ # Querying MPD’s status # querying-mpd-s-status
"clearerror": self._fetch_nothing,
"currentsong": self._fetch_object,
"idle": self._fetch_list,
#"noidle": None,
"status": self._fetch_object,
"stats": self._fetch_object,
- # Playback Option Commands
+ # Playback Option # playback-options
"consume": self._fetch_nothing,
"crossfade": self._fetch_nothing,
"mixrampdb": self._fetch_nothing,
"replay_gain_mode": self._fetch_nothing,
"replay_gain_status": self._fetch_item,
"volume": self._fetch_nothing,
- # Playback Control Commands
+ # Controlling playback # controlling-playback
"next": self._fetch_nothing,
"pause": self._fetch_nothing,
"play": self._fetch_nothing,
"seekid": self._fetch_nothing,
"seekcur": self._fetch_nothing,
"stop": self._fetch_nothing,
- # Queue Commands
+ # The Queue # the-queue
"add": self._fetch_nothing,
"addid": self._fetch_item,
"clear": self._fetch_nothing,
"swapid": self._fetch_nothing,
"addtagid": self._fetch_nothing,
"cleartagid": self._fetch_nothing,
- # Stored Playlist Commands
+ # Stored playlists # stored-playlists
"listplaylist": self._fetch_list,
"listplaylistinfo": self._fetch_songs,
+ "searchplaylist": self._fetch_songs,
"listplaylists": self._fetch_playlists,
"load": self._fetch_nothing,
"playlistadd": self._fetch_nothing,
"playlistclear": self._fetch_nothing,
"playlistdelete": self._fetch_nothing,
+ "playlistlength": self._fetch_object,
"playlistmove": self._fetch_nothing,
"rename": self._fetch_nothing,
"rm": self._fetch_nothing,
"save": self._fetch_nothing,
- # Database Commands
+ # The music database # the-music-database
"albumart": self._fetch_composite,
"count": self._fetch_object,
"getfingerprint": self._fetch_object,
"search": self._fetch_songs,
"searchadd": self._fetch_nothing,
"searchaddpl": self._fetch_nothing,
+ "searchcount": self._fetch_object,
"update": self._fetch_item,
"rescan": self._fetch_item,
- # Mounts and neighbors
+ # Mounts and neighbors # mounts-and-neighbors
"mount": self._fetch_nothing,
"unmount": self._fetch_nothing,
"listmounts": self._fetch_mounts,
"listneighbors": self._fetch_neighbors,
- # Sticker Commands
+ # Stickers # stickers
"sticker get": self._fetch_item,
"sticker set": self._fetch_nothing,
"sticker delete": self._fetch_nothing,
"sticker list": self._fetch_list,
"sticker find": self._fetch_songs,
- # Connection Commands
+ "stickernames": self._fetch_list,
+ "stickertypes": self._fetch_list,
+ "stickernamestypes": self._fetch_list,
+ # Connection settings # connection-settings
"close": None,
"kill": None,
"password": self._fetch_nothing,
"tagtypes enable": self._fetch_nothing,
"tagtypes clear": self._fetch_nothing,
"tagtypes all": self._fetch_nothing,
- # Partition Commands
+ "protocol": self._fetch_list,
+ "protocol disable": self._fetch_nothing,
+ "protocol enable": self._fetch_nothing,
+ "protocol clear": self._fetch_nothing,
+ "protocol all": self._fetch_nothing,
+ "protocol available": self._fetch_list,
+ # Partition Commands # partition-commands
"partition": self._fetch_nothing,
"listpartitions": self._fetch_list,
"newpartition": self._fetch_nothing,
"delpartition": self._fetch_nothing,
"moveoutput": self._fetch_nothing,
- # Audio Output Commands
+ # Audio output devices # audio-output-devices
"disableoutput": self._fetch_nothing,
"enableoutput": self._fetch_nothing,
"toggleoutput": self._fetch_nothing,
"outputs": self._fetch_outputs,
"outputset": self._fetch_nothing,
- # Reflection Commands
+ # Reflection # reflection
"config": self._fetch_object,
"commands": self._fetch_list,
"notcommands": self._fetch_list,
"urlhandlers": self._fetch_list,
"decoders": self._fetch_plugins,
- # Client to Client
+ # Client to Client # client-to-client
"subscribe": self._fetch_nothing,
"unsubscribe": self._fetch_nothing,
"channels": self._fetch_list,
else:
# MPD_HOST is a plain host
self.host = _host
- log.debug('host detected in MPD_HOST: @%s', self.host)
+ log.debug('host detected in MPD_HOST: %s', self.host)
else:
# Is socket there
xdg_runtime_dir = os.getenv('XDG_RUNTIME_DIR', '/run')
# 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):
"""Socket timeout in second (defaults to :py:obj:`SOCKET_TIMEOUT`).
Use :py:obj:`None` to disable socket timout.
- :setter: Set the socket timeout
- :type: int or None (integer > 0)
+ :setter: Set the socket timeout (integer > 0)
+ :type: int or None
"""
return self._socket_timeout