+ async def close(self) -> None:
+ """:synopsis: Close connections in the pool"""
+ await self._pool.close()
+
+
+class CmdHandler:
+
+ def __init__(self, pool, server, port, password, timeout):
+ self._commands = {
+ # Status Commands
+ "clearerror": self._fetch_nothing,
+ "currentsong": self._fetch_object,
+ "idle": self._fetch_list,
+ "noidle": self._fetch_nothing,
+ "status": self._fetch_object,
+ "stats": self._fetch_object,
+ # Playback Option Commands
+ "consume": self._fetch_nothing,
+ "crossfade": self._fetch_nothing,
+ "mixrampdb": self._fetch_nothing,
+ "mixrampdelay": self._fetch_nothing,
+ "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,
+ "volume": self._fetch_nothing,
+ # Playback Control Commands
+ "next": self._fetch_nothing,
+ "pause": self._fetch_nothing,
+ "play": self._fetch_nothing,
+ "playid": self._fetch_nothing,
+ "previous": self._fetch_nothing,
+ "seek": self._fetch_nothing,
+ "seekid": self._fetch_nothing,
+ "seekcur": self._fetch_nothing,
+ "stop": self._fetch_nothing,
+ # Queue Commands
+ "add": self._fetch_nothing,
+ "addid": self._fetch_item,
+ "clear": self._fetch_nothing,
+ "delete": self._fetch_nothing,
+ "deleteid": self._fetch_nothing,
+ "move": self._fetch_nothing,
+ "moveid": self._fetch_nothing,
+ "playlist": self._fetch_playlist,
+ "playlistfind": self._fetch_songs,
+ "playlistid": self._fetch_songs,
+ "playlistinfo": self._fetch_songs,
+ "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,
+ "listplaylists": self._fetch_playlists,
+ "load": self._fetch_nothing,
+ "playlistadd": self._fetch_nothing,
+ "playlistclear": self._fetch_nothing,
+ "playlistdelete": self._fetch_nothing,
+ "playlistmove": self._fetch_nothing,
+ "rename": self._fetch_nothing,
+ "rm": self._fetch_nothing,
+ "save": self._fetch_nothing,
+ # Database Commands
+ "albumart": self._fetch_composite,
+ "count": self._fetch_object,
+ "getfingerprint": self._fetch_object,
+ "find": self._fetch_songs,
+ "findadd": self._fetch_nothing,
+ "list": self._fetch_list,
+ "listall": self._fetch_database,
+ "listallinfo": self._fetch_database,
+ "listfiles": self._fetch_database,
+ "lsinfo": self._fetch_database,
+ "readcomments": self._fetch_object,
+ "readpicture": self._fetch_composite,
+ "search": self._fetch_songs,
+ "searchadd": self._fetch_nothing,
+ "searchaddpl": self._fetch_nothing,
+ "update": self._fetch_item,
+ "rescan": self._fetch_item,
+ # 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,
+ "sticker delete": self._fetch_nothing,
+ "sticker list": self._fetch_list,
+ "sticker find": self._fetch_songs,
+ # Connection Commands
+ "close": None,
+ "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,
+ "tagtypes clear": self._fetch_nothing,
+ "tagtypes all": self._fetch_nothing,
+ # 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
+ "disableoutput": self._fetch_nothing,
+ "enableoutput": self._fetch_nothing,
+ "toggleoutput": self._fetch_nothing,
+ "outputs": self._fetch_outputs,
+ "outputset": self._fetch_nothing,
+ # Reflection Commands
+ "config": self._fetch_object,
+ "commands": self._fetch_list,
+ "notcommands": self._fetch_list,
+ "urlhandlers": self._fetch_list,
+ "decoders": self._fetch_plugins,
+ # Client to Client
+ "subscribe": self._fetch_nothing,
+ "unsubscribe": self._fetch_nothing,
+ "channels": self._fetch_list,
+ "readmessages": self._fetch_messages,
+ "sendmessage": self._fetch_nothing,
+ }
+ self.command = None
+ self._command_list: list | None = None
+ self.args = None
+ self.pool = pool
+ self.host = (server, port)
+ self.password = password
+ self.timeout = timeout
+ #: current connection
+ self.connection: [None, Connection] = None
+
+ def __repr__(self):
+ args = [str(_) for _ in self.args]
+ args = ','.join(args or [])
+ return f'{self.command}({args})'
+
+ async def __call__(self, command: str, args: list | None):
+ server, port = self.host
+ self.command = command
+ self.args = args or ''
+ self.connection = await self.pool.connect(server, port, self.timeout)
+ async with self.connection:
+ await self._init_connection()
+ retval = self._commands[command]
+ await self._write_command(command, args)
+ if callable(retval):
+ return await retval()
+ return retval
+
+ async def _init_connection(self):
+ """Init connection if needed
+
+ * Consumes the hello line and sets the protocol version
+ * Send password command if a password is provided
+ """
+ if not self.connection.version:
+ await self._hello()
+ if self.password and not self.connection.auth:
+ # Need to send password
+ await self._write_command('password', [self.password])
+ await self._fetch_nothing()
+ self.connection.auth = True
+
+ async def _hello(self) -> None: