]> kaliko git repositories - python-musicpdaio.git/blobdiff - mpdaio/client.py
Add module mpdaio
[python-musicpdaio.git] / mpdaio / client.py
diff --git a/mpdaio/client.py b/mpdaio/client.py
new file mode 100644 (file)
index 0000000..1d48da5
--- /dev/null
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+# SPDX-FileCopyrightText: 2012-2024  kaliko <kaliko@azylum.org>
+# SPDX-License-Identifier: LGPL-3.0-or-later
+
+import logging
+import os
+
+from .connection import ConnectionPool, Connection
+from .exceptions import MPDError, MPDConnectionError, MPDProtocolError
+
+HELLO_PREFIX = 'OK MPD '
+ERROR_PREFIX = 'ACK '
+SUCCESS = 'OK\n'
+NEXT = 'list_OK'
+#: Module version
+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 > 0 (Default is :py:obj:`None` for no timeout)
+SOCKET_TIMEOUT = None
+#: Maximum concurrent connections
+CONNECTION_MAX = 10
+
+logging.basicConfig(level=logging.DEBUG,
+                    format='%(levelname)-8s %(module)-10s %(message)s')
+log = logging.getLogger(__name__)
+
+
+class MPDClient:
+
+    def __init__(self,):
+        #: host used with the current connection (:py:obj:`str`)
+        self.host = None
+        #: password detected in :envvar:`MPD_HOST` environment variable (:py:obj:`str`)
+        self.pwd = None
+        #: port used with the current connection (:py:obj:`int`, :py:obj:`str`)
+        self.port = None
+        self._get_envvars()
+        self.pool = ConnectionPool(max_connections=CONNECTION_MAX)
+        log.info('logger : "%s"', __name__)
+
+    def _get_envvars(self):
+        """
+        Retrieve MPD env. var. to overrides default "localhost:6600"
+        """
+        # Set some defaults
+        self.host = 'localhost'
+        self.port = os.getenv('MPD_PORT', '6600')
+        _host = os.getenv('MPD_HOST', '')
+        if _host:
+            # If password is set: MPD_HOST=pass@host
+            if '@' in _host:
+                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
+
+    async def _hello(self, conn):
+        """Consume HELLO_PREFIX"""
+        data = await conn.readuntil(b'\n')
+        rcv = data.decode('utf-8')
+        if not rcv.startswith(HELLO_PREFIX):
+            raise MPDProtocolError(f'Got invalid MPD hello: "{rcv}"')
+        log.debug('consumed hello prefix: "%s"', rcv)
+        self.version = rcv.split('\n')[0][len(HELLO_PREFIX):]
+        log.info('protocol version: %s', self.version)
+
+    async def connect(self, server=None, port=None) -> Connection:
+        if not server:
+            server = self.host
+        if not port:
+            port = self.port
+        try:
+            conn = await self.pool.connect(server, port)
+        except (ValueError, OSError) as err:
+            raise MPDConnectionError(err) from err
+        async with conn:
+            await self._hello(conn)
+
+    async def close(self):
+        await self.pool.close()
+