]> kaliko git repositories - python-musicpdaio.git/blob - mpdaio/client.py
Add module mpdaio
[python-musicpdaio.git] / mpdaio / client.py
1 # -*- coding: utf-8 -*-
2 # SPDX-FileCopyrightText: 2012-2024  kaliko <kaliko@azylum.org>
3 # SPDX-License-Identifier: LGPL-3.0-or-later
4
5 import logging
6 import os
7
8 from .connection import ConnectionPool, Connection
9 from .exceptions import MPDError, MPDConnectionError, MPDProtocolError
10
11 HELLO_PREFIX = 'OK MPD '
12 ERROR_PREFIX = 'ACK '
13 SUCCESS = 'OK\n'
14 NEXT = 'list_OK'
15 #: Module version
16 VERSION = '0.9.0b2'
17 #: Seconds before a connection attempt times out
18 #: (overriden by :envvar:`MPD_TIMEOUT` env. var.)
19 CONNECTION_TIMEOUT = 30
20 #: Socket timeout in second > 0 (Default is :py:obj:`None` for no timeout)
21 SOCKET_TIMEOUT = None
22 #: Maximum concurrent connections
23 CONNECTION_MAX = 10
24
25 logging.basicConfig(level=logging.DEBUG,
26                     format='%(levelname)-8s %(module)-10s %(message)s')
27 log = logging.getLogger(__name__)
28
29
30 class MPDClient:
31
32     def __init__(self,):
33         #: host used with the current connection (:py:obj:`str`)
34         self.host = None
35         #: password detected in :envvar:`MPD_HOST` environment variable (:py:obj:`str`)
36         self.pwd = None
37         #: port used with the current connection (:py:obj:`int`, :py:obj:`str`)
38         self.port = None
39         self._get_envvars()
40         self.pool = ConnectionPool(max_connections=CONNECTION_MAX)
41         log.info('logger : "%s"', __name__)
42
43     def _get_envvars(self):
44         """
45         Retrieve MPD env. var. to overrides default "localhost:6600"
46         """
47         # Set some defaults
48         self.host = 'localhost'
49         self.port = os.getenv('MPD_PORT', '6600')
50         _host = os.getenv('MPD_HOST', '')
51         if _host:
52             # If password is set: MPD_HOST=pass@host
53             if '@' in _host:
54                 mpd_host_env = _host.split('@', 1)
55                 if mpd_host_env[0]:
56                     # A password is actually set
57                     log.debug(
58                         'password detected in MPD_HOST, set client pwd attribute')
59                     self.pwd = mpd_host_env[0]
60                     if mpd_host_env[1]:
61                         self.host = mpd_host_env[1]
62                         log.debug('host detected in MPD_HOST: %s', self.host)
63                 elif mpd_host_env[1]:
64                     # No password set but leading @ is an abstract socket
65                     self.host = '@'+mpd_host_env[1]
66                     log.debug(
67                         'host detected in MPD_HOST: %s (abstract socket)', self.host)
68             else:
69                 # MPD_HOST is a plain host
70                 self.host = _host
71                 log.debug('host detected in MPD_HOST: @%s', self.host)
72         else:
73             # Is socket there
74             xdg_runtime_dir = os.getenv('XDG_RUNTIME_DIR', '/run')
75             rundir = os.path.join(xdg_runtime_dir, 'mpd/socket')
76             if os.path.exists(rundir):
77                 self.host = rundir
78                 log.debug(
79                     'host detected in ${XDG_RUNTIME_DIR}/run: %s (unix socket)', self.host)
80         _mpd_timeout = os.getenv('MPD_TIMEOUT', '')
81         if _mpd_timeout.isdigit():
82             self.mpd_timeout = int(_mpd_timeout)
83             log.debug('timeout detected in MPD_TIMEOUT: %d', self.mpd_timeout)
84         else:  # Use CONNECTION_TIMEOUT as default even if MPD_TIMEOUT carries gargage
85             self.mpd_timeout = CONNECTION_TIMEOUT
86
87     async def _hello(self, conn):
88         """Consume HELLO_PREFIX"""
89         data = await conn.readuntil(b'\n')
90         rcv = data.decode('utf-8')
91         if not rcv.startswith(HELLO_PREFIX):
92             raise MPDProtocolError(f'Got invalid MPD hello: "{rcv}"')
93         log.debug('consumed hello prefix: "%s"', rcv)
94         self.version = rcv.split('\n')[0][len(HELLO_PREFIX):]
95         log.info('protocol version: %s', self.version)
96
97     async def connect(self, server=None, port=None) -> Connection:
98         if not server:
99             server = self.host
100         if not port:
101             port = self.port
102         try:
103             conn = await self.pool.connect(server, port)
104         except (ValueError, OSError) as err:
105             raise MPDConnectionError(err) from err
106         async with conn:
107             await self._hello(conn)
108
109     async def close(self):
110         await self.pool.close()
111