refactor: speed, stability and code quality
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Wed, 17 Feb 2021 00:05:05 +0000 (01:05 +0100)
committerMarcel van der Veldt <m.vanderveldt@outlook.com>
Wed, 17 Feb 2021 00:05:05 +0000 (01:05 +0100)
41 files changed:
music_assistant/__main__.py
music_assistant/constants.py
music_assistant/helpers/cache.py
music_assistant/helpers/encryption.py
music_assistant/helpers/images.py
music_assistant/helpers/migration.py
music_assistant/helpers/musicbrainz.py
music_assistant/helpers/process.py
music_assistant/helpers/typing.py
music_assistant/helpers/util.py
music_assistant/helpers/web.py
music_assistant/managers/config.py
music_assistant/managers/database.py
music_assistant/managers/library.py
music_assistant/managers/metadata.py
music_assistant/managers/music.py
music_assistant/managers/players.py
music_assistant/managers/streams.py
music_assistant/mass.py
music_assistant/models/media_types.py
music_assistant/models/player.py
music_assistant/models/player_queue.py
music_assistant/models/player_state.py [deleted file]
music_assistant/models/provider.py
music_assistant/providers/builtin_player/__init__.py
music_assistant/providers/chromecast/__init__.py
music_assistant/providers/chromecast/player.py
music_assistant/providers/fanarttv/__init__.py
music_assistant/providers/file/__init__.py
music_assistant/providers/qobuz/__init__.py
music_assistant/providers/sonos/__init__.py
music_assistant/providers/sonos/sonos.py
music_assistant/providers/spotify/__init__.py
music_assistant/providers/squeezebox/__init__.py
music_assistant/providers/squeezebox/socket_client.py
music_assistant/providers/tunein/__init__.py
music_assistant/providers/universal_group/__init__.py
music_assistant/web/json_rpc.py
music_assistant/web/server.py
music_assistant/web/streams.py
requirements.txt

index 4fe28a6db5523e4e29f31078a2b59750c78fa995..e6fbb2f97bbb3ef33200897716180c71df9912ce 100755 (executable)
@@ -44,7 +44,7 @@ def main():
     # setup logger
     logger = logging.getLogger()
     logformat = logging.Formatter(
-        "%(asctime)-15s %(levelname)-5s %(name)s.%(funcName)s  -- %(message)s"
+        "%(asctime)-15s %(levelname)-5s %(name)s  -- %(message)s"
     )
     consolehandler = logging.StreamHandler()
     consolehandler.setFormatter(logformat)
@@ -68,10 +68,10 @@ def main():
 
     def on_shutdown(loop):
         logger.info("shutdown requested!")
-        loop.run_until_complete(mass.async_stop())
+        loop.run_until_complete(mass.stop())
 
     run(
-        mass.async_start(),
+        mass.start(),
         use_uvloop=True,
         shutdown_callback=on_shutdown,
         executor_workers=64,
index 876fde6b3d48a4214232cdb34b8c92631cceae28..056d7d5dc631b6b37f0c5e055141ac1766f46f37 100755 (executable)
@@ -1,6 +1,6 @@
 """All constants for Music Assistant."""
 
-__version__ = "0.0.87"
+__version__ = "0.1.0"
 REQUIRED_PYTHON_VER = "3.7"
 
 # configuration keys/attributes
index e48aad8206f0a10805bbbc22e99a0aa0df2db09e..dca7611ad719107c78157c269b56b6d537102a0b 100644 (file)
@@ -25,7 +25,7 @@ class Cache:
         self._dbfile = os.path.join(mass.config.data_path, ".cache.db")
         self._mem_cache = {}
 
-    async def async_setup(self):
+    async def setup(self):
         """Async initialize of cache module."""
         async with aiosqlite.connect(self._dbfile, timeout=180) as db_conn:
             await db_conn.execute(
@@ -35,9 +35,9 @@ class Cache:
             await db_conn.commit()
             await db_conn.execute("VACUUM;")
             await db_conn.commit()
-        self.mass.add_job(self.async_auto_cleanup())
+        self.mass.add_job(self.auto_cleanup())
 
-    async def async_get(self, cache_key, checksum="", default=None):
+    async def get(self, cache_key, checksum="", default=None):
         """
         Get object from cache and return the results.
 
@@ -80,7 +80,7 @@ class Cache:
         LOGGER.debug("no cache data for %s", cache_key)
         return default
 
-    async def async_set(self, cache_key, data, checksum="", expiration=(86400 * 30)):
+    async def set(self, cache_key, data, checksum="", expiration=(86400 * 30)):
         """Set data in cache."""
         checksum = self._get_checksum(checksum)
         expires = int(time.time() + expiration)
@@ -94,7 +94,7 @@ class Cache:
             await db_conn.execute(sql_query, (cache_key, expires, data, checksum))
             await db_conn.commit()
 
-    async def async_delete(self, cache_key):
+    async def delete(self, cache_key):
         """Delete data from cache."""
         self._mem_cache.pop(cache_key, None)
         sql_query = "DELETE FROM simplecache WHERE id = ?"
@@ -103,7 +103,7 @@ class Cache:
             await db_conn.commit()
 
     @run_periodic(3600)
-    async def async_auto_cleanup(self):
+    async def auto_cleanup(self):
         """Sceduled auto cleanup task."""
         # for now we simply rest the memory cache
         self._mem_cache = {}
@@ -133,7 +133,7 @@ class Cache:
         return functools.reduce(lambda x, y: x + y, map(ord, stringinput))
 
 
-async def async_cached(
+async def cached(
     cache,
     cache_key: str,
     coro_func: Awaitable,
@@ -142,31 +142,31 @@ async def async_cached(
     checksum=None
 ):
     """Return helper method to store results of a coroutine in the cache."""
-    cache_result = await cache.async_get(cache_key, checksum)
+    cache_result = await cache.get(cache_key, checksum)
     if cache_result is not None:
         return cache_result
     result = await coro_func(*args)
-    asyncio.create_task(cache.async_set(cache_key, result, checksum, expires))
+    asyncio.create_task(cache.set(cache_key, result, checksum, expires))
     return result
 
 
-def async_use_cache(cache_days=14, cache_checksum=None):
+def use_cache(cache_days=14, cache_checksum=None):
     """Return decorator that can be used to cache a method's result."""
 
     def wrapper(func):
         @functools.wraps(func)
-        async def async_wrapped(*args, **kwargs):
+        async def wrapped(*args, **kwargs):
             method_class = args[0]
             method_class_name = method_class.__class__.__name__
             cache_str = "%s.%s" % (method_class_name, func.__name__)
             cache_str += __cache_id_from_args(*args, **kwargs)
             cache_str = cache_str.lower()
-            cachedata = await method_class.cache.async_get(cache_str)
+            cachedata = await method_class.cache.get(cache_str)
             if cachedata is not None:
                 return cachedata
             result = await func(*args, **kwargs)
             asyncio.create_task(
-                method_class.cache.async_set(
+                method_class.cache.set(
                     cache_str,
                     result,
                     checksum=cache_checksum,
@@ -175,7 +175,7 @@ def async_use_cache(cache_days=14, cache_checksum=None):
             )
             return result
 
-        return async_wrapped
+        return wrapped
 
     return wrapper
 
index a66390572d2f47ae83d728cd78eb052c69e460fd..eca69ba969e9a4973329c1820767eae3cb2774fc 100644 (file)
@@ -6,38 +6,38 @@ from cryptography.fernet import Fernet, InvalidToken
 from music_assistant.helpers.app_vars import get_app_var  # noqa # pylint: disable=all
 
 
-async def async_encrypt_string(str_value: str) -> str:
+async def encrypt_string(str_value: str) -> str:
     """Encrypt a string with Fernet."""
     return await asyncio.get_running_loop().run_in_executor(
-        None, encrypt_string, str_value
+        None, _encrypt_string, str_value
     )
 
 
-def encrypt_string(str_value: str) -> str:
+def _encrypt_string(str_value: str) -> str:
     """Encrypt a string with Fernet."""
     return Fernet(get_app_var(3)).encrypt(str_value.encode()).decode()
 
 
-async def async_encrypt_bytes(bytes_value: bytes) -> bytes:
+async def encrypt_bytes(bytes_value: bytes) -> bytes:
     """Encrypt bytes with Fernet."""
     return await asyncio.get_running_loop().run_in_executor(
-        None, encrypt_bytes, bytes_value
+        None, _encrypt_bytes, bytes_value
     )
 
 
-def encrypt_bytes(bytes_value: bytes) -> bytes:
+def _encrypt_bytes(bytes_value: bytes) -> bytes:
     """Encrypt bytes with Fernet."""
     return Fernet(get_app_var(3)).encrypt(bytes_value)
 
 
-async def async_decrypt_string(encrypted_str: str) -> str:
+async def decrypt_string(encrypted_str: str) -> str:
     """Decrypt a string with Fernet."""
     return await asyncio.get_running_loop().run_in_executor(
-        None, decrypt_string, encrypted_str
+        None, _decrypt_string, encrypted_str
     )
 
 
-def decrypt_string(encrypted_str: str) -> str:
+def _decrypt_string(encrypted_str: str) -> str:
     """Decrypt a string with Fernet."""
     try:
         return Fernet(get_app_var(3)).decrypt(encrypted_str.encode()).decode()
@@ -45,14 +45,14 @@ def decrypt_string(encrypted_str: str) -> str:
         return None
 
 
-async def async_decrypt_bytes(bytes_value: bytes) -> bytes:
+async def decrypt_bytes(bytes_value: bytes) -> bytes:
     """Decrypt bytes with Fernet."""
     return await asyncio.get_running_loop().run_in_executor(
-        None, decrypt_bytes, bytes_value
+        None, _decrypt_bytes, bytes_value
     )
 
 
-def decrypt_bytes(bytes_value):
+def _decrypt_bytes(bytes_value):
     """Decrypt bytes with Fernet."""
     try:
         return Fernet(get_app_var(3)).decrypt(bytes_value)
index 424e0aebcc2422a70de22967f47771b5337993fe..ebdea7aa2f3604dd5e077108b0ca49c9d319ec19 100644 (file)
@@ -3,16 +3,16 @@
 import os
 from io import BytesIO
 
-from music_assistant.helpers.typing import MusicAssistantType
+from music_assistant.helpers.typing import MusicAssistant
 from music_assistant.models.media_types import MediaType
 from PIL import Image
 
 
-async def async_get_thumb_file(mass: MusicAssistantType, url, size: int = 150):
+async def get_thumb_file(mass: MusicAssistant, url, size: int = 150):
     """Get path to (resized) thumbnail image for given image url."""
     assert url
     cache_folder = os.path.join(mass.config.data_path, ".thumbs")
-    cache_id = await mass.database.async_get_thumbnail_id(url, size)
+    cache_id = await mass.database.get_thumbnail_id(url, size)
     cache_file = os.path.join(cache_folder, f"{cache_id}.png")
     if os.path.isfile(cache_file):
         # return file from cache
@@ -39,11 +39,11 @@ async def async_get_thumb_file(mass: MusicAssistantType, url, size: int = 150):
     return cache_file
 
 
-async def async_get_image_url(
-    mass: MusicAssistantType, item_id: str, provider_id: str, media_type: MediaType
+async def get_image_url(
+    mass: MusicAssistant, item_id: str, provider_id: str, media_type: MediaType
 ):
     """Get url to image for given media item."""
-    item = await mass.music.async_get_item(item_id, provider_id, media_type)
+    item = await mass.music.get_item(item_id, provider_id, media_type)
     if not item:
         return None
     if item and item.metadata.get("image"):
@@ -66,12 +66,12 @@ async def async_get_image_url(
         return item.album.metadata["image"]
     if media_type == MediaType.Track and item.album:
         # try album instead for tracks
-        return await async_get_image_url(
+        return await get_image_url(
             mass, item.album.item_id, item.album.provider, MediaType.Album
         )
     if media_type == MediaType.Album and item.artist:
         # try artist instead for albums
-        return await async_get_image_url(
+        return await get_image_url(
             mass, item.artist.item_id, item.artist.provider, MediaType.Artist
         )
     return None
index 14b212e80531a9507eabb428e38bbf5c9e53b351..a82931ccd52b89d3c143478472f934221321af16 100644 (file)
@@ -9,11 +9,11 @@ from pkg_resources import packaging
 import aiosqlite
 from music_assistant.constants import __version__ as app_version
 from music_assistant.helpers.encryption import encrypt_string
-from music_assistant.helpers.typing import MusicAssistantType
+from music_assistant.helpers.typing import MusicAssistant
 from music_assistant.helpers.util import get_hostname
 
 
-async def check_migrations(mass: MusicAssistantType):
+async def check_migrations(mass: MusicAssistant):
     """Check for any migrations that need to be done."""
 
     is_fresh_setup = len(mass.config.stored_config.keys()) == 0
@@ -29,7 +29,7 @@ async def check_migrations(mass: MusicAssistantType):
     if "server_id" not in mass.config.stored_config:
         mass.config.stored_config["server_id"] = str(uuid.getnode())
     if "jwt_key" not in mass.config.stored_config:
-        mass.config.stored_config["jwt_key"] = encrypt_string(str(uuid.uuid4()))
+        mass.config.stored_config["jwt_key"] = await encrypt_string(str(uuid.uuid4()))
     if "initialized" not in mass.config.stored_config:
         mass.config.stored_config["initialized"] = False
     if "friendly_name" not in mass.config.stored_config:
@@ -37,10 +37,10 @@ async def check_migrations(mass: MusicAssistantType):
     mass.config.save()
 
     # create default db tables (if needed)
-    await async_create_db_tables(mass.database.db_file)
+    await create_db_tables(mass.database.db_file)
 
 
-async def run_migration_0070(mass: MusicAssistantType):
+async def run_migration_0070(mass: MusicAssistant):
     """Run migration for version 0.0.70."""
     # 0.0.70 introduced major changes to all data models and db structure
     # a full refresh of data is unavoidable
@@ -82,7 +82,7 @@ async def run_migration_0070(mass: MusicAssistantType):
             shutil.rmtree(dirname, True)
 
     # create default db tables (if needed)
-    await async_create_db_tables(mass.database.db_file)
+    await create_db_tables(mass.database.db_file)
 
     # restore loudness measurements
     if tracks_loudness:
@@ -94,7 +94,7 @@ async def run_migration_0070(mass: MusicAssistantType):
             await db_conn.commit()
 
 
-async def async_create_db_tables(db_file):
+async def create_db_tables(db_file):
     """Async initialization."""
     async with aiosqlite.connect(db_file, timeout=120) as db_conn:
 
index 5795fac432da7a32475db1a2f7ab1cf0c8e89a56..5660d414d77e65721920fa4e93f14bcc86bb537f 100644 (file)
@@ -7,7 +7,7 @@ from typing import Optional
 
 import aiohttp
 from asyncio_throttle import Throttler
-from music_assistant.helpers.cache import async_use_cache
+from music_assistant.helpers.cache import use_cache
 from music_assistant.helpers.compare import compare_strings, get_compare_string
 
 LUCENE_SPECIAL = r'([+\-&|!(){}\[\]\^"~*?:\\\/])'
@@ -24,7 +24,7 @@ class MusicBrainz:
         self.cache = mass.cache
         self.throttler = Throttler(rate_limit=1, period=1)
 
-    async def async_get_mb_artist_id(
+    async def get_mb_artist_id(
         self,
         artistname,
         albumname=None,
@@ -44,7 +44,7 @@ class MusicBrainz:
         )
         mb_artist_id = None
         if album_upc:
-            mb_artist_id = await self.async_search_artist_by_album(
+            mb_artist_id = await self.search_artist_by_album(
                 artistname, None, album_upc
             )
             if mb_artist_id:
@@ -55,7 +55,7 @@ class MusicBrainz:
                     mb_artist_id,
                 )
         if not mb_artist_id and track_isrc:
-            mb_artist_id = await self.async_search_artist_by_track(
+            mb_artist_id = await self.search_artist_by_track(
                 artistname, None, track_isrc
             )
             if mb_artist_id:
@@ -66,9 +66,7 @@ class MusicBrainz:
                     mb_artist_id,
                 )
         if not mb_artist_id and albumname:
-            mb_artist_id = await self.async_search_artist_by_album(
-                artistname, albumname
-            )
+            mb_artist_id = await self.search_artist_by_album(artistname, albumname)
             if mb_artist_id:
                 LOGGER.debug(
                     "Got MusicbrainzArtistId for %s after search on albumname %s --> %s",
@@ -77,9 +75,7 @@ class MusicBrainz:
                     mb_artist_id,
                 )
         if not mb_artist_id and trackname:
-            mb_artist_id = await self.async_search_artist_by_track(
-                artistname, trackname
-            )
+            mb_artist_id = await self.search_artist_by_track(artistname, trackname)
             if mb_artist_id:
                 LOGGER.debug(
                     "Got MusicbrainzArtistId for %s after search on trackname %s --> %s",
@@ -89,9 +85,7 @@ class MusicBrainz:
                 )
         return mb_artist_id
 
-    async def async_search_artist_by_album(
-        self, artistname, albumname=None, album_upc=None
-    ):
+    async def search_artist_by_album(self, artistname, albumname=None, album_upc=None):
         """Retrieve musicbrainz artist id by providing the artist name and albumname or upc."""
         for searchartist in [
             re.sub(LUCENE_SPECIAL, r"\\\1", artistname),
@@ -107,7 +101,7 @@ class MusicBrainz:
                     "query": 'artist:"%s" AND release:"%s"'
                     % (searchartist, searchalbum)
                 }
-            result = await self.async_get_data(endpoint, params)
+            result = await self.get_data(endpoint, params)
             if result and "releases" in result:
                 for strictness in [True, False]:
                     for item in result["releases"]:
@@ -126,9 +120,7 @@ class MusicBrainz:
                                         return artist["id"]
         return ""
 
-    async def async_search_artist_by_track(
-        self, artistname, trackname=None, track_isrc=None
-    ):
+    async def search_artist_by_track(self, artistname, trackname=None, track_isrc=None):
         """Retrieve artist id by providing the artist name and trackname or track isrc."""
         endpoint = "recording"
         searchartist = re.sub(LUCENE_SPECIAL, r"\\\1", artistname)
@@ -140,7 +132,7 @@ class MusicBrainz:
             searchtrack = re.sub(LUCENE_SPECIAL, r"\\\1", trackname)
             endpoint = "recording"
             params = {"query": '"%s" AND artist:"%s"' % (searchtrack, searchartist)}
-        result = await self.async_get_data(endpoint, params)
+        result = await self.get_data(endpoint, params)
         if result and "recordings" in result:
             for strictness in [True, False]:
                 for item in result["recordings"]:
@@ -159,8 +151,8 @@ class MusicBrainz:
                                     return artist["id"]
         return ""
 
-    @async_use_cache(2)
-    async def async_get_data(self, endpoint: str, params: Optional[dict] = None):
+    @use_cache(2)
+    async def get_data(self, endpoint: str, params: Optional[dict] = None):
         """Get data from api."""
         if params is None:
             params = {}
index 1bcfa27bfa6282b2498766bcc932bc641868121e..79a740f84ecb1d2c91b9394533e934b764efaa5d 100644 (file)
@@ -3,17 +3,10 @@ Implementation of a (truly) non blocking subprocess.
 
 The subprocess implementation in asyncio can (still) sometimes cause deadlocks,
 even when properly handling reading/writes from different tasks.
-Besides that, when using multiple asyncio subprocesses, together with uvloop
-things go very wrong: https://github.com/MagicStack/uvloop/issues/317
-
-As we rely a lot on moving chunks around through subprocesses (mainly sox),
-this custom implementation can be seen as a temporary solution until the main issue
-in uvloop is resolved.
 """
 
 import asyncio
 import logging
-import subprocess
 from typing import AsyncGenerator, List, Optional
 
 LOGGER = logging.getLogger("AsyncProcess")
@@ -24,129 +17,6 @@ DEFAULT_CHUNKSIZE = 512000
 class AsyncProcess:
     """Implementation of a (truly) non blocking subprocess."""
 
-    # workaround that is compatible with uvloop
-
-    def __init__(
-        self, process_args: List, enable_write: bool = False, enable_shell=False
-    ):
-        """Initialize."""
-        self._id = "".join(process_args)
-        self._proc = subprocess.Popen(
-            process_args,
-            shell=enable_shell,
-            stdout=subprocess.PIPE,
-            stdin=subprocess.PIPE if enable_write else None,
-            # bufsize needs to be very high for smooth playback
-            bufsize=4000000,
-        )
-        self.loop = asyncio.get_running_loop()
-        self._cancelled = False
-
-    async def __aenter__(self) -> "AsyncProcess":
-        """Enter context manager."""
-        return self
-
-    async def __aexit__(self, exc_type, exc_value, traceback) -> bool:
-        """Exit context manager."""
-        self._cancelled = True
-        if self._proc.poll() is None:
-            # process needs to be cleaned up..
-            await self.loop.run_in_executor(None, self.__close)
-
-        return exc_type
-
-    async def iterate_chunks(
-        self, chunksize: int = DEFAULT_CHUNKSIZE
-    ) -> AsyncGenerator[bytes, None]:
-        """Yield chunks from the process stdout. Generator."""
-        while True:
-            chunk = await self.read(chunksize)
-            yield chunk
-            if len(chunk) < chunksize:
-                # last chunk
-                break
-
-    async def read(self, chunksize: int = DEFAULT_CHUNKSIZE) -> bytes:
-        """Read x bytes from the process stdout."""
-        if self._cancelled:
-            raise asyncio.CancelledError()
-        return await self.loop.run_in_executor(None, self.__read, chunksize)
-
-    async def write(self, data: bytes) -> None:
-        """Write data to process stdin."""
-        if self._cancelled:
-            raise asyncio.CancelledError()
-        await self.loop.run_in_executor(None, self.__write, data)
-
-    async def write_eof(self) -> None:
-        """Write eof to process."""
-        if self._cancelled:
-            raise asyncio.CancelledError()
-        await self.loop.run_in_executor(None, self.__write_eof)
-
-    async def communicate(self, input_data: Optional[bytes] = None) -> bytes:
-        """Write bytes to process and read back results."""
-        if self._cancelled:
-            raise asyncio.CancelledError()
-        return await self.loop.run_in_executor(None, self._proc.communicate, input_data)
-
-    def __read(self, chunksize: int = DEFAULT_CHUNKSIZE):
-        """Try read chunk from process."""
-        try:
-            return self._proc.stdout.read(chunksize)
-        except (BrokenPipeError, ValueError, AttributeError):
-            # Process already exited
-            return b""
-
-    def __write(self, data: bytes):
-        """Write data to process stdin."""
-        try:
-            self._proc.stdin.write(data)
-            self._proc.stdin.flush()
-        except (BrokenPipeError, ValueError, AttributeError):
-            # Process already exited
-            pass
-
-    def __write_eof(self):
-        """Write eof to process stdin."""
-        try:
-            self._proc.stdin.close()
-        except (BrokenPipeError, ValueError, AttributeError):
-            # Process already exited
-            pass
-
-    def __close(self):
-        """Prevent subprocess deadlocking, make sure it closes."""
-        LOGGER.debug("Cleaning up process %s...", self._id)
-        # close stdout
-        if not self._proc.stdout.closed:
-            try:
-                self._proc.stdout.close()
-            except BrokenPipeError:
-                pass
-        # close stdin if needed
-        if self._proc.stdin and not self._proc.stdin.closed:
-            try:
-                self._proc.stdin.close()
-            except BrokenPipeError:
-                pass
-        # send terminate
-        self._proc.terminate()
-        # wait for exit
-        try:
-            self._proc.wait(5)
-            LOGGER.debug("Process %s exited with %s.", self._id, self._proc.returncode)
-        except subprocess.TimeoutExpired:
-            LOGGER.error("Process %s did not terminate in time.", self._id)
-            self._proc.kill()
-        LOGGER.debug("Process %s closed.", self._id)
-
-
-class AsyncProcessBroken:
-    """Implementation of a (truly) non blocking subprocess."""
-
-    # this version is not compatible with uvloop
-
     def __init__(self, process_args: List, enable_write: bool = False):
         """Initialize."""
         self._proc = None
@@ -160,14 +30,13 @@ class AsyncProcessBroken:
             *self._process_args,
             stdin=asyncio.subprocess.PIPE if self._enable_write else None,
             stdout=asyncio.subprocess.PIPE,
-            limit=64000000
+            limit=4000000
         )
         return self
 
     async def __aexit__(self, exc_type, exc_value, traceback) -> bool:
         """Exit context manager."""
         self._cancelled = True
-        LOGGER.debug("subprocess exit requested")
         if self._proc.returncode is None:
             # prevent subprocess deadlocking, send terminate and read remaining bytes
             if self._enable_write and self._proc.stdin.can_write_eof():
@@ -175,7 +44,6 @@ class AsyncProcessBroken:
             self._proc.terminate()
             await self._proc.stdout.read()
         del self._proc
-        LOGGER.debug("subprocess exited")
 
     async def iterate_chunks(
         self, chunk_size: int = DEFAULT_CHUNKSIZE
@@ -198,10 +66,15 @@ class AsyncProcessBroken:
 
     async def write(self, data: bytes) -> None:
         """Write data to process stdin."""
-        if self._cancelled:
+        if self._cancelled or not self._proc:
+            raise asyncio.CancelledError()
+        if self._proc.stdin.is_closing():
             raise asyncio.CancelledError()
         self._proc.stdin.write(data)
-        await self._proc.stdin.drain()
+        try:
+            await self._proc.stdin.drain()
+        except BrokenPipeError:
+            pass
 
     async def write_eof(self) -> None:
         """Write eof to process."""
index 29f1228929ed8095bca3440e4bc6aac3de243d24..2b567a7d4c82add95f64fc356a2872f5b85251cb 100644 (file)
@@ -1,27 +1,29 @@
 """Typing helper."""
 
-from typing import TYPE_CHECKING, List, Optional
+from typing import TYPE_CHECKING, Optional, Set
 
 # pylint: disable=invalid-name
 if TYPE_CHECKING:
-    from music_assistant.mass import MusicAssistant as MusicAssistantType
+    from music_assistant.mass import MusicAssistant
     from music_assistant.models.player_queue import (
-        QueueItem as QueueItemType,
-        PlayerQueue as PlayerQueueType,
+        QueueItem,
+        PlayerQueue,
     )
-    from music_assistant.models.streamdetails import StreamDetails as StreamDetailsType
-    from music_assistant.models.player import Player as PlayerType
+    from music_assistant.models.streamdetails import StreamDetails
+    from music_assistant.models.player import Player
+    from music_assistant.managers.config import ConfigSubItem
 
 else:
-    MusicAssistantType = "MusicAssistant"
-    QueueItemType = "QueueItem"
-    PlayerQueueType = "PlayerQueue"
-    StreamDetailsType = "StreamDetailsType"
-    PlayerType = "PlayerType"
+    MusicAssistant = "MusicAssistant"
+    QueueItem = "QueueItem"
+    PlayerQueue = "PlayerQueue"
+    StreamDetails = "StreamDetails"
+    Player = "Player"
+    ConfigSubItem = "ConfigSubItem"
 
 
-QueueItems = List[QueueItemType]
-Players = List[PlayerType]
+QueueItems = Set[QueueItem]
+Players = Set[Player]
 
 OptionalInt = Optional[int]
 OptionalStr = Optional[str]
index 331f8b155d89a63217723a19992b13ffba5b8f1f..8c5489f6346ae7ffcae3dac4e7be012ca6e5c6b9 100755 (executable)
@@ -8,7 +8,7 @@ import struct
 import tempfile
 import urllib.request
 from io import BytesIO
-from typing import Any, Callable, TypeVar
+from typing import Any, Callable, Dict, Optional, Set, TypeVar
 
 import memory_tempfile
 import ujson
@@ -36,12 +36,12 @@ def run_periodic(period):
     """Run a coroutine at interval."""
 
     def scheduler(fcn):
-        async def async_wrapper(*args, **kwargs):
+        async def wrapper(*args, **kwargs):
             while True:
                 asyncio.create_task(fcn(*args, **kwargs))
                 await asyncio.sleep(period)
 
-        return async_wrapper
+        return wrapper
 
     return scheduler
 
@@ -66,7 +66,7 @@ def run_background_task(corofn, *args, executor=None):
     return asyncio.get_event_loop().run_in_executor(executor, corofn, *args)
 
 
-def run_async_background_task(executor, corofn, *args):
+def run__background_task(executor, corofn, *args):
     """Run async task in background."""
 
     def run_task(corofn, *args):
@@ -88,7 +88,7 @@ def try_parse_int(possible_int):
         return 0
 
 
-async def async_iter_items(items):
+async def iter_items(items):
     """Fake async iterator for compatability reasons."""
     if not isinstance(items, list):
         yield items
@@ -240,23 +240,22 @@ def merge_dict(base_dict: dict, new_dict: dict, allow_overwite=False):
     return final_dict
 
 
-def merge_list(base_list: list, new_list: list):
+def merge_list(base_list: list, new_list: list) -> Set:
     """Merge 2 lists."""
-    final_list = []
-    final_list += base_list
+    final_list = set(base_list)
     for item in new_list:
         if hasattr(item, "item_id"):
             for prov_item in final_list:
                 if prov_item.item_id == item.item_id:
                     prov_item = item
         if item not in final_list:
-            final_list.append(item)
+            final_list.add(item)
     return final_list
 
 
 def unique_item_ids(objects):
     """Filter duplicate item id's from list of items."""
-    return list({object_.item_id: object_ for object_ in objects}.values())
+    return set({object_.item_id for object_ in objects})
 
 
 def try_load_json_file(jsonfile):
@@ -280,13 +279,30 @@ def create_tempfile():
     return tempfile.NamedTemporaryFile(buffering=0)
 
 
-async def async_yield_chunks(_obj, chunk_size):
+async def yield_chunks(_obj, chunk_size):
     """Yield successive n-sized chunks from list/str/bytes."""
     chunk_size = int(chunk_size)
     for i in range(0, len(_obj), chunk_size):
         yield _obj[i : i + chunk_size]
 
 
+def get_changed_keys(
+    dict1: Dict[str, Any], dict2: Dict[str, Any], ignore_keys: Optional[Set[str]] = None
+) -> Set[str]:
+    """Compare 2 dicts and return set of changed keys."""
+    if not dict2:
+        return set(dict1.keys())
+    changed_keys = set()
+    for key, value in dict2.items():
+        if ignore_keys and key in ignore_keys:
+            continue
+        if isinstance(value, dict):
+            changed_keys.update(get_changed_keys(dict1[key], value))
+        elif dict1[key] != value:
+            changed_keys.add(key)
+    return changed_keys
+
+
 def create_wave_header(samplerate=44100, channels=2, bitspersample=16, duration=3600):
     """Generate a wave header from given params."""
     file = BytesIO()
index e0c5bcd38626178641c1966d2f98d2473946fe2f..98c35a4e38b38f3287ef6b9413a74153e5f6abbd 100644 (file)
@@ -1,9 +1,7 @@
 """Various helpers for web requests."""
 
-import asyncio
 import inspect
 import ipaddress
-from datetime import datetime
 from functools import wraps
 from typing import Any, Callable, Union
 
@@ -47,10 +45,10 @@ def serialize_values(obj):
     def get_val(val):
         if hasattr(val, "to_dict"):
             return val.to_dict()
-        if isinstance(val, (list, set, filter, {}.values().__class__)):
+        if isinstance(val, (list, set, filter, tuple)):
+            return [get_val(x) for x in val]
+        if val.__class__ == "dict_valueiterator":
             return [get_val(x) for x in val]
-        if isinstance(val, datetime):
-            return val.isoformat()
         if isinstance(val, dict):
             return {key: get_val(value) for key, value in val.items()}
         return val
@@ -61,27 +59,16 @@ def serialize_values(obj):
 def json_serializer(obj):
     """Json serializer to recursively create serializable values for custom data types."""
     return ujson.dumps(serialize_values(obj))
+    # return ujson.dumps(obj)
 
 
 def json_response(data: Any, status: int = 200):
     """Return json in web request."""
-    # return web.json_response(data, dumps=json_serializer)
     return web.Response(
         body=json_serializer(data), status=200, content_type="application/json"
     )
 
 
-async def async_json_response(data: Any, status: int = 200):
-    """Return json in web request."""
-    if isinstance(data, list):
-        # we could potentially receive a large list of objects to serialize
-        # which is blocking IO so run it in executor to be safe
-        return await asyncio.get_running_loop().run_in_executor(
-            None, json_response, data
-        )
-    return json_response(data)
-
-
 def api_route(ws_cmd_path, ws_require_auth=True):
     """Decorate a function as websocket command."""
 
index 4da39d0a4081dbea46a032a411ef279dee2c80d3..d64d37d39c544cec577cd459c9f0c31fed3ef74f 100755 (executable)
@@ -32,7 +32,7 @@ from music_assistant.constants import (
     CONF_VOLUME_NORMALISATION,
     EVENT_CONFIG_CHANGED,
 )
-from music_assistant.helpers.encryption import decrypt_string, encrypt_string
+from music_assistant.helpers.encryption import _decrypt_string, _encrypt_string
 from music_assistant.helpers.util import merge_dict, try_load_json_file
 from music_assistant.helpers.web import api_route
 from music_assistant.models.config_entry import ConfigEntry, ConfigEntryType
@@ -155,9 +155,9 @@ class ConfigManager:
             raise FileNotFoundError(f"data directory {data_path} does not exist!")
         self.__load()
 
-    async def async_setup(self):
+    async def setup(self):
         """Async initialize of module."""
-        self._translations = await self.__async_fetch_translations()
+        self._translations = await self._fetch_translations()
 
     @api_route("config/:conf_base?/:conf_key?")
     def all_items(self, conf_base: str = "", conf_key: str = "") -> dict:
@@ -279,7 +279,7 @@ class ConfigManager:
         """Return item value by key."""
         return getattr(self, item_key)
 
-    async def async_close(self):
+    async def close(self):
         """Save config on exit."""
         self.save()
 
@@ -301,7 +301,7 @@ class ConfigManager:
         self.loading = False
 
     @staticmethod
-    async def __async_fetch_translations() -> dict:
+    async def _fetch_translations() -> dict:
         """Build a list of all translations."""
         base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
         # get base translations
@@ -349,7 +349,7 @@ class ConfigBaseItem:
 
     def all_keys(self):
         """Return all possible keys of this Config object."""
-        return {key for key in self.conf_mgr.stored_config.get(self.conf_key, {}).keys()}
+        return self.conf_mgr.stored_config.get(self.conf_key, {}).keys()
 
     def __getitem__(self, item_key: str):
         """Return ConfigSubItem for given key."""
@@ -397,7 +397,7 @@ class SecuritySettings(ConfigBaseItem):
 
     def all_keys(self):
         """Return all possible keys of this Config object."""
-        return [CONF_KEY_SECURITY_LOGIN, CONF_KEY_SECURITY_APP_TOKENS]
+        return DEFAULT_SECURITY_CONFIG_ENTRIES.keys()
 
     def add_app_token(self, token_info: dict):
         """Add token to config."""
@@ -484,19 +484,15 @@ class PlayerSettings(ConfigBaseItem):
 
     def all_keys(self):
         """Return all possible keys of this Config object."""
-        all_keys = super().all_keys()
-        for player_id in self.mass.players.players:
-            if player_id not in all_keys:
-                all_keys.add(player_id)
-        return all_keys
+        return {player.player_id for player in self.mass.players}
 
     def get_config_entries(self, child_key: str) -> List[ConfigEntry]:
         """Return all config entries for the given child entry."""
         entries = []
         entries += DEFAULT_PLAYER_CONFIG_ENTRIES
-        player_state = self.mass.players.get_player_state(child_key)
-        if player_state:
-            entries += player_state.player.config_entries
+        player = self.mass.players.get_player(child_key)
+        if player:
+            entries += player.config_entries
             # append power control config entries
             power_controls = self.mass.players.get_player_controls(
                 PlayerControlType.POWER
@@ -534,8 +530,8 @@ class PlayerSettings(ConfigBaseItem):
                     )
                 )
             # append special group player entries
-            for parent_id in player_state.group_parents:
-                parent_player = self.mass.players.get_player_state(parent_id)
+            for parent_id in player.group_parents:
+                parent_player = self.mass.players.get_player(parent_id)
                 if parent_player and parent_player.provider_id == "group_player":
                     entries.append(
                         ConfigEntry(
@@ -605,7 +601,7 @@ class ConfigSubItem:
         entry = self.get_entry(key)
         if entry.entry_type == ConfigEntryType.PASSWORD:
             # decrypted password is only returned if explicitly asked for this key
-            decrypted_value = decrypt_string(entry.value)
+            decrypted_value = _decrypt_string(entry.value)
             if decrypted_value:
                 return decrypted_value
         return entry.value
@@ -667,7 +663,7 @@ class ConfigSubItem:
                 if entry.store_hashed:
                     value = pbkdf2_sha256.hash(value)
                 if entry.entry_type == ConfigEntryType.PASSWORD:
-                    value = encrypt_string(value)
+                    value = _encrypt_string(value)
 
                 # write value to stored config
                 stored_conf = self.conf_mgr.stored_config
@@ -681,14 +677,12 @@ class ConfigSubItem:
                 # reload provider/plugin if value changed
                 if self.parent_conf_key in PROVIDER_TYPE_MAPPINGS:
                     self.conf_mgr.mass.add_job(
-                        self.conf_mgr.mass.async_reload_provider(self.conf_key)
+                        self.conf_mgr.mass.reload_provider(self.conf_key)
                     )
                 if self.parent_conf_key == CONF_KEY_PLAYER_SETTINGS:
                     # force update of player if it's config changed
                     self.conf_mgr.mass.add_job(
-                        self.conf_mgr.mass.players.async_trigger_player_update(
-                            self.conf_key
-                        )
+                        self.conf_mgr.mass.players.trigger_player_update(self.conf_key)
                     )
                 # signal config changed event
                 self.conf_mgr.mass.signal_event(
index d50179f1197a89a759cb5e7a124c70aa7a34fda5..62639894aa39b82bf80f090c0c38c51cb7918e57 100755 (executable)
@@ -3,7 +3,7 @@
 import logging
 import os
 from datetime import datetime
-from typing import List, Optional, Union
+from typing import List, Optional, Set, Union
 
 import aiosqlite
 from music_assistant.helpers.compare import compare_album, compare_track
@@ -41,7 +41,7 @@ class DatabaseManager:
         """Return location of database on disk."""
         return self._dbfile
 
-    async def async_get_item_by_prov_id(
+    async def get_item_by_prov_id(
         self,
         provider_id: str,
         prov_item_id: str,
@@ -49,96 +49,96 @@ class DatabaseManager:
     ) -> Optional[MediaItem]:
         """Get the database item for the given prov_id."""
         if media_type == MediaType.Artist:
-            return await self.async_get_artist_by_prov_id(provider_id, prov_item_id)
+            return await self.get_artist_by_prov_id(provider_id, prov_item_id)
         if media_type == MediaType.Album:
-            return await self.async_get_album_by_prov_id(provider_id, prov_item_id)
+            return await self.get_album_by_prov_id(provider_id, prov_item_id)
         if media_type == MediaType.Track:
-            return await self.async_get_track_by_prov_id(provider_id, prov_item_id)
+            return await self.get_track_by_prov_id(provider_id, prov_item_id)
         if media_type == MediaType.Playlist:
-            return await self.async_get_playlist_by_prov_id(provider_id, prov_item_id)
+            return await self.get_playlist_by_prov_id(provider_id, prov_item_id)
         if media_type == MediaType.Radio:
-            return await self.async_get_radio_by_prov_id(provider_id, prov_item_id)
+            return await self.get_radio_by_prov_id(provider_id, prov_item_id)
         return None
 
-    async def async_get_track_by_prov_id(
+    async def get_track_by_prov_id(
         self,
         provider_id: str,
         prov_item_id: str,
     ) -> Optional[FullTrack]:
         """Get the database track for the given prov_id."""
         if provider_id == "database":
-            return await self.async_get_track(prov_item_id)
+            return await self.get_track(prov_item_id)
         sql_query = f"""WHERE item_id in
             (SELECT item_id FROM provider_mappings
                 WHERE prov_item_id = '{prov_item_id}'
                 AND provider = '{provider_id}' AND media_type = 'track')"""
-        for item in await self.async_get_tracks(sql_query):
+        for item in await self.get_tracks(sql_query):
             return item
         return None
 
-    async def async_get_album_by_prov_id(
+    async def get_album_by_prov_id(
         self,
         provider_id: str,
         prov_item_id: str,
     ) -> Optional[FullAlbum]:
         """Get the database album for the given prov_id."""
         if provider_id == "database":
-            return await self.async_get_album(prov_item_id)
+            return await self.get_album(prov_item_id)
         sql_query = f"""WHERE item_id in
             (SELECT item_id FROM provider_mappings
                 WHERE prov_item_id = '{prov_item_id}'
                 AND provider = '{provider_id}' AND media_type = 'album')"""
-        for item in await self.async_get_albums(sql_query):
+        for item in await self.get_albums(sql_query):
             return item
         return None
 
-    async def async_get_artist_by_prov_id(
+    async def get_artist_by_prov_id(
         self,
         provider_id: str,
         prov_item_id: str,
     ) -> Optional[Artist]:
         """Get the database artist for the given prov_id."""
         if provider_id == "database":
-            return await self.async_get_artist(prov_item_id)
+            return await self.get_artist(prov_item_id)
         sql_query = f"""WHERE item_id in
             (SELECT item_id FROM provider_mappings
                 WHERE prov_item_id = '{prov_item_id}'
                 AND provider = '{provider_id}' AND media_type = 'artist')"""
-        for item in await self.async_get_artists(sql_query):
+        for item in await self.get_artists(sql_query):
             return item
         return None
 
-    async def async_get_playlist_by_prov_id(
+    async def get_playlist_by_prov_id(
         self, provider_id: str, prov_item_id: str
     ) -> Optional[Playlist]:
         """Get the database playlist for the given prov_id."""
         if provider_id == "database":
-            return await self.async_get_playlist(prov_item_id)
+            return await self.get_playlist(prov_item_id)
         sql_query = f"""WHERE item_id in
             (SELECT item_id FROM provider_mappings
                 WHERE prov_item_id = '{prov_item_id}'
                 AND provider = '{provider_id}' AND media_type = 'playlist')"""
-        for item in await self.async_get_playlists(sql_query):
+        for item in await self.get_playlists(sql_query):
             return item
         return None
 
-    async def async_get_radio_by_prov_id(
+    async def get_radio_by_prov_id(
         self,
         provider_id: str,
         prov_item_id: str,
     ) -> Optional[Radio]:
         """Get the database radio for the given prov_id."""
         if provider_id == "database":
-            return await self.async_get_radio(prov_item_id)
+            return await self.get_radio(prov_item_id)
         sql_query = f"""WHERE item_id in
             (SELECT item_id FROM provider_mappings
                 WHERE prov_item_id = '{prov_item_id}'
                 AND provider = '{provider_id}' AND media_type = 'radio')"""
-        for item in await self.async_get_radios(sql_query):
+        for item in await self.get_radios(sql_query):
             return item
         return None
 
-    async def async_search(
+    async def search(
         self, searchquery: str, media_types: List[MediaType]
     ) -> SearchResult:
         """Search library for the given searchphrase."""
@@ -146,51 +146,49 @@ class DatabaseManager:
         searchquery = "%" + searchquery + "%"
         if media_types is None or MediaType.Artist in media_types:
             sql_query = ' WHERE name LIKE "%s"' % searchquery
-            result.artists = await self.async_get_artists(sql_query)
+            result.artists = await self.get_artists(sql_query)
         if media_types is None or MediaType.Album in media_types:
             sql_query = ' WHERE name LIKE "%s"' % searchquery
-            result.albums = await self.async_get_albums(sql_query)
+            result.albums = await self.get_albums(sql_query)
         if media_types is None or MediaType.Track in media_types:
             sql_query = ' WHERE name LIKE "%s"' % searchquery
-            result.tracks = await self.async_get_tracks(sql_query)
+            result.tracks = await self.get_tracks(sql_query)
         if media_types is None or MediaType.Playlist in media_types:
             sql_query = ' WHERE name LIKE "%s"' % searchquery
-            result.playlists = await self.async_get_playlists(sql_query)
+            result.playlists = await self.get_playlists(sql_query)
         if media_types is None or MediaType.Radio in media_types:
             sql_query = ' WHERE name LIKE "%s"' % searchquery
-            result.radios = await self.async_get_radios(sql_query)
+            result.radios = await self.get_radios(sql_query)
         return result
 
-    async def async_get_library_artists(self, orderby: str = "name") -> List[Artist]:
+    async def get_library_artists(self, orderby: str = "name") -> List[Artist]:
         """Get all library artists."""
         sql_query = "WHERE in_library = 1"
-        return await self.async_get_artists(sql_query, orderby=orderby)
+        return await self.get_artists(sql_query, orderby=orderby)
 
-    async def async_get_library_albums(self, orderby: str = "name") -> List[Album]:
+    async def get_library_albums(self, orderby: str = "name") -> List[Album]:
         """Get all library albums."""
         sql_query = "WHERE in_library = 1"
-        return await self.async_get_albums(sql_query, orderby=orderby)
+        return await self.get_albums(sql_query, orderby=orderby)
 
-    async def async_get_library_tracks(self, orderby: str = "name") -> List[Track]:
+    async def get_library_tracks(self, orderby: str = "name") -> List[Track]:
         """Get all library tracks."""
         sql_query = "WHERE in_library = 1"
-        return await self.async_get_tracks(sql_query, orderby=orderby)
+        return await self.get_tracks(sql_query, orderby=orderby)
 
-    async def async_get_library_playlists(
-        self, orderby: str = "name"
-    ) -> List[Playlist]:
+    async def get_library_playlists(self, orderby: str = "name") -> List[Playlist]:
         """Fetch all playlist records from table."""
         sql_query = "WHERE in_library = 1"
-        return await self.async_get_playlists(sql_query, orderby=orderby)
+        return await self.get_playlists(sql_query, orderby=orderby)
 
-    async def async_get_library_radios(
+    async def get_library_radios(
         self, provider_id: str = None, orderby: str = "name"
     ) -> List[Radio]:
         """Fetch all radio records from table."""
         sql_query = "WHERE in_library = 1"
-        return await self.async_get_radios(sql_query, orderby=orderby)
+        return await self.get_radios(sql_query, orderby=orderby)
 
-    async def async_get_playlists(
+    async def get_playlists(
         self,
         filter_query: str = None,
         orderby: str = "name",
@@ -207,14 +205,14 @@ class DatabaseManager:
                 for db_row in await db_conn.execute_fetchall(sql_query, ())
             ]
 
-    async def async_get_playlist(self, item_id: int) -> Playlist:
+    async def get_playlist(self, item_id: int) -> Playlist:
         """Get playlist record by id."""
         item_id = try_parse_int(item_id)
-        for item in await self.async_get_playlists(f"WHERE item_id = {item_id}"):
+        for item in await self.get_playlists(f"WHERE item_id = {item_id}"):
             return item
         return None
 
-    async def async_get_radios(
+    async def get_radios(
         self,
         filter_query: str = None,
         orderby: str = "name",
@@ -232,14 +230,14 @@ class DatabaseManager:
                 for db_row in await db_conn.execute_fetchall(sql_query, ())
             ]
 
-    async def async_get_radio(self, item_id: int) -> Playlist:
+    async def get_radio(self, item_id: int) -> Playlist:
         """Get radio record by id."""
         item_id = try_parse_int(item_id)
-        for item in await self.async_get_radios(f"WHERE item_id = {item_id}"):
+        for item in await self.get_radios(f"WHERE item_id = {item_id}"):
             return item
         return None
 
-    async def async_add_playlist(self, playlist: Playlist):
+    async def add_playlist(self, playlist: Playlist):
         """Add a new playlist record to the database."""
         assert playlist.name
         async with aiosqlite.connect(self._dbfile, timeout=120) as db_conn:
@@ -252,7 +250,7 @@ class DatabaseManager:
 
             if cur_item:
                 # update existing
-                return await self.async_update_playlist(cur_item[0], playlist)
+                return await self.update_playlist(cur_item[0], playlist)
             # insert playlist
             sql_query = """INSERT INTO playlists
                 (name, sort_name, owner, is_editable, checksum, metadata, provider_ids)
@@ -275,15 +273,15 @@ class DatabaseManager:
                     (last_row_id,),
                     db_conn,
                 )
-            await self.__async_add_prov_ids(
+            await self._add_prov_ids(
                 new_item[0], MediaType.Playlist, playlist.provider_ids, db_conn=db_conn
             )
             await db_conn.commit()
         LOGGER.debug("added playlist %s to database", playlist.name)
         # return created object
-        return await self.async_get_playlist(new_item[0])
+        return await self.get_playlist(new_item[0])
 
-    async def async_update_playlist(self, item_id: int, playlist: Playlist):
+    async def update_playlist(self, item_id: int, playlist: Playlist):
         """Update a playlist record in the database."""
         async with aiosqlite.connect(self._dbfile, timeout=120) as db_conn:
             db_conn.row_factory = aiosqlite.Row
@@ -316,15 +314,15 @@ class DatabaseManager:
                     item_id,
                 ),
             )
-            await self.__async_add_prov_ids(
+            await self._add_prov_ids(
                 item_id, MediaType.Playlist, playlist.provider_ids, db_conn=db_conn
             )
             LOGGER.debug("updated playlist %s in database: %s", playlist.name, item_id)
             await db_conn.commit()
         # return updated object
-        return await self.async_get_playlist(item_id)
+        return await self.get_playlist(item_id)
 
-    async def async_add_radio(self, radio: Radio):
+    async def add_radio(self, radio: Radio):
         """Add a new radio record to the database."""
         assert radio.name
         async with aiosqlite.connect(self._dbfile, timeout=120) as db_conn:
@@ -334,7 +332,7 @@ class DatabaseManager:
             )
             if cur_item:
                 # update existing
-                return await self.async_update_radio(cur_item[0], radio)
+                return await self.update_radio(cur_item[0], radio)
             # insert radio
             sql_query = """INSERT INTO radios (name, sort_name, metadata, provider_ids)
                 VALUES(?,?,?,?);"""
@@ -353,15 +351,15 @@ class DatabaseManager:
                     (last_row_id,),
                     db_conn,
                 )
-            await self.__async_add_prov_ids(
+            await self._add_prov_ids(
                 new_item[0], MediaType.Radio, radio.provider_ids, db_conn=db_conn
             )
             await db_conn.commit()
         LOGGER.debug("added radio %s to database", radio.name)
         # return created object
-        return await self.async_get_radio(new_item[0])
+        return await self.get_radio(new_item[0])
 
-    async def async_update_radio(self, item_id: int, radio: Radio):
+    async def update_radio(self, item_id: int, radio: Radio):
         """Update a radio record in the database."""
         async with aiosqlite.connect(self._dbfile, timeout=120) as db_conn:
             db_conn.row_factory = aiosqlite.Row
@@ -388,17 +386,15 @@ class DatabaseManager:
                     item_id,
                 ),
             )
-            await self.__async_add_prov_ids(
+            await self._add_prov_ids(
                 item_id, MediaType.Radio, radio.provider_ids, db_conn=db_conn
             )
             LOGGER.debug("updated radio %s in database: %s", radio.name, item_id)
             await db_conn.commit()
         # return updated object
-        return await self.async_get_radio(item_id)
+        return await self.get_radio(item_id)
 
-    async def async_add_to_library(
-        self, item_id: int, media_type: MediaType, provider: str
-    ):
+    async def add_to_library(self, item_id: int, media_type: MediaType, provider: str):
         """Add an item to the library (item must already be present in the db!)."""
         async with aiosqlite.connect(self._dbfile, timeout=120) as db_conn:
             item_id = try_parse_int(item_id)
@@ -407,7 +403,7 @@ class DatabaseManager:
             await db_conn.execute(sql_query, (item_id,))
             await db_conn.commit()
 
-    async def async_remove_from_library(
+    async def remove_from_library(
         self, item_id: int, media_type: MediaType, provider: str
     ):
         """Remove item from the library."""
@@ -418,7 +414,7 @@ class DatabaseManager:
             await db_conn.execute(sql_query, (item_id,))
             await db_conn.commit()
 
-    async def async_get_artists(
+    async def get_artists(
         self,
         filter_query: str = None,
         orderby: str = "name",
@@ -436,14 +432,14 @@ class DatabaseManager:
                 for db_row in await db_conn.execute_fetchall(sql_query, ())
             ]
 
-    async def async_get_artist(self, item_id: int) -> Artist:
+    async def get_artist(self, item_id: int) -> Artist:
         """Get artist record by id."""
         item_id = try_parse_int(item_id)
-        for item in await self.async_get_artists("WHERE item_id = %d" % item_id):
+        for item in await self.get_artists("WHERE item_id = %d" % item_id):
             return item
         return None
 
-    async def async_add_artist(self, artist: Artist):
+    async def add_artist(self, artist: Artist):
         """Add a new artist record to the database."""
         async with aiosqlite.connect(self._dbfile, timeout=120) as db_conn:
             db_conn.row_factory = aiosqlite.Row
@@ -454,7 +450,7 @@ class DatabaseManager:
             )
             if cur_item:
                 # update existing
-                return await self.async_update_artist(cur_item[0], artist)
+                return await self.update_artist(cur_item[0], artist)
             # insert artist
             sql_query = """INSERT INTO artists
                 (name, sort_name, musicbrainz_id, metadata, provider_ids)
@@ -475,15 +471,15 @@ class DatabaseManager:
                     (last_row_id,),
                     db_conn,
                 )
-            await self.__async_add_prov_ids(
+            await self._add_prov_ids(
                 new_item[0], MediaType.Artist, artist.provider_ids, db_conn=db_conn
             )
             await db_conn.commit()
             LOGGER.debug("added artist %s to database", artist.name)
             # return created object
-            return await self.async_get_artist(new_item[0])
+            return await self.get_artist(new_item[0])
 
-    async def async_update_artist(self, item_id: int, artist: Artist):
+    async def update_artist(self, item_id: int, artist: Artist):
         """Update a artist record in the database."""
         async with aiosqlite.connect(self._dbfile, timeout=120) as db_conn:
             db_conn.row_factory = aiosqlite.Row
@@ -507,15 +503,15 @@ class DatabaseManager:
                     item_id,
                 ),
             )
-            await self.__async_add_prov_ids(
+            await self._add_prov_ids(
                 item_id, MediaType.Artist, artist.provider_ids, db_conn=db_conn
             )
             LOGGER.debug("updated artist %s in database: %s", artist.name, item_id)
             await db_conn.commit()
             # return updated object
-            return await self.async_get_artist(item_id)
+            return await self.get_artist(item_id)
 
-    async def async_get_albums(
+    async def get_albums(
         self,
         filter_query: str = None,
         orderby: str = "name",
@@ -533,13 +529,13 @@ class DatabaseManager:
                 for db_row in await db_conn.execute_fetchall(sql_query, ())
             ]
 
-    async def async_get_album(self, item_id: int) -> FullAlbum:
+    async def get_album(self, item_id: int) -> FullAlbum:
         """Get album record by id."""
         item_id = try_parse_int(item_id)
         # get from db
-        for item in await self.async_get_albums("WHERE item_id = %d" % item_id):
+        for item in await self.get_albums("WHERE item_id = %d" % item_id):
             item.artist = (
-                await self.async_get_artist_by_prov_id(
+                await self.get_artist_by_prov_id(
                     item.artist.provider, item.artist.item_id
                 )
                 or item.artist
@@ -547,7 +543,7 @@ class DatabaseManager:
             return item
         return None
 
-    async def async_get_albums_from_provider_ids(
+    async def get_albums_from_provider_ids(
         self, provider_id: Union[str, List[str]], prov_item_ids: List[str]
     ) -> dict:
         """Get album records for the given prov_ids."""
@@ -559,16 +555,16 @@ class DatabaseManager:
                 WHERE provider in ({prov_id_str}) AND media_type = 'album'
                 AND prov_item_id in ({prov_item_id_str})
             )"""
-        return await self.async_get_albums(sql_query)
+        return await self.get_albums(sql_query)
 
-    async def async_add_album(self, album: Album):
+    async def add_album(self, album: Album):
         """Add a new album record to the database."""
         async with aiosqlite.connect(self._dbfile, timeout=120) as db_conn:
             db_conn.row_factory = aiosqlite.Row
             cur_item = None
             # always try to grab existing item by external_id
             if album.upc:
-                for item in await self.async_get_albums(f"WHERE upc='{album.upc}'"):
+                for item in await self.get_albums(f"WHERE upc='{album.upc}'"):
                     cur_item = item
             # fallback to matching
             if not cur_item:
@@ -576,18 +572,18 @@ class DatabaseManager:
                 for db_row in await db_conn.execute_fetchall(
                     sql_query, (album.sort_name,)
                 ):
-                    item = await self.async_get_album(db_row["item_id"])
+                    item = await self.get_album(db_row["item_id"])
                     if compare_album(item, album):
                         cur_item = item
                         break
             if cur_item:
                 # update existing
-                return await self.async_update_album(cur_item.item_id, album)
+                return await self.update_album(cur_item.item_id, album)
 
             # insert album
             assert album.artist
             album_artist = ItemMapping.from_item(
-                await self.async_get_artist_by_prov_id(
+                await self.get_artist_by_prov_id(
                     album.artist.provider, album.artist.item_id
                 )
                 or album.artist
@@ -615,24 +611,24 @@ class DatabaseManager:
                     (last_row_id,),
                     db_conn,
                 )
-            await self.__async_add_prov_ids(
+            await self._add_prov_ids(
                 new_item[0], MediaType.Album, album.provider_ids, db_conn=db_conn
             )
             await db_conn.commit()
             LOGGER.debug("added album %s to database", album.name)
             # return created object
-            return await self.async_get_album(new_item[0])
+            return await self.get_album(new_item[0])
 
-    async def async_update_album(self, item_id: int, album: Album):
+    async def update_album(self, item_id: int, album: Album):
         """Update a album record in the database."""
         async with aiosqlite.connect(self._dbfile, timeout=120) as db_conn:
             db_conn.row_factory = aiosqlite.Row
-            cur_item = await self.async_get_album(item_id)
+            cur_item = await self.get_album(item_id)
             album_artist = ItemMapping.from_item(
-                await self.async_get_artist_by_prov_id(
+                await self.get_artist_by_prov_id(
                     cur_item.artist.provider, cur_item.artist.item_id
                 )
-                or await self.async_get_artist_by_prov_id(
+                or await self.get_artist_by_prov_id(
                     album.artist.provider, album.artist.item_id
                 )
                 or cur_item.artist
@@ -661,15 +657,15 @@ class DatabaseManager:
                     item_id,
                 ),
             )
-            await self.__async_add_prov_ids(
+            await self._add_prov_ids(
                 item_id, MediaType.Album, album.provider_ids, db_conn=db_conn
             )
             LOGGER.debug("updated album %s in database: %s", album.name, item_id)
             await db_conn.commit()
             # return updated object
-            return await self.async_get_album(item_id)
+            return await self.get_album(item_id)
 
-    async def async_get_tracks(
+    async def get_tracks(
         self,
         filter_query: str = None,
         orderby: str = "name",
@@ -687,7 +683,7 @@ class DatabaseManager:
                 for db_row in await db_conn.execute_fetchall(sql_query, ())
             ]
 
-    async def async_get_tracks_from_provider_ids(
+    async def get_tracks_from_provider_ids(
         self, provider_id: Union[str, List[str]], prov_item_ids: List[str]
     ) -> dict:
         """Get track records for the given prov_ids."""
@@ -699,35 +695,33 @@ class DatabaseManager:
                 WHERE provider in ({prov_id_str}) AND media_type = 'track'
                 AND prov_item_id in ({prov_item_id_str})
             )"""
-        return await self.async_get_tracks(sql_query)
+        return await self.get_tracks(sql_query)
 
-    async def async_get_track(self, item_id: int) -> FullTrack:
+    async def get_track(self, item_id: int) -> FullTrack:
         """Get full track record by id."""
         item_id = try_parse_int(item_id)
-        for item in await self.async_get_tracks("WHERE item_id = %d" % item_id):
+        for item in await self.get_tracks("WHERE item_id = %d" % item_id):
             # include full album info
-            item.albums = list(
+            item.albums = set(
                 filter(
                     None,
                     [
-                        await self.async_get_album_by_prov_id(
-                            album.provider, album.item_id
-                        )
+                        await self.get_album_by_prov_id(album.provider, album.item_id)
                         for album in item.albums
                     ],
                 )
             )
-            item.album = item.albums[0]
+            item.album = next(iter(item.albums))
             # include full artist info
-            item.artists = [
-                await self.async_get_artist_by_prov_id(artist.provider, artist.item_id)
+            item.artists = {
+                await self.get_artist_by_prov_id(artist.provider, artist.item_id)
                 or artist
                 for artist in item.artists
-            ]
+            }
             return item
         return None
 
-    async def async_add_track(self, track: Track):
+    async def add_track(self, track: Track):
         """Add a new track record to the database."""
         assert track.album, "Track is missing album"
         assert track.artists, "Track is missing artist(s)"
@@ -736,7 +730,7 @@ class DatabaseManager:
             cur_item = None
             # always try to grab existing item by matching
             if track.isrc:
-                for item in await self.async_get_tracks(f"WHERE isrc='{track.isrc}'"):
+                for item in await self.get_tracks(f"WHERE isrc='{track.isrc}'"):
                     cur_item = item
             # fallback to matching
             if not cur_item:
@@ -744,20 +738,20 @@ class DatabaseManager:
                 for db_row in await db_conn.execute_fetchall(
                     sql_query, (track.sort_name,)
                 ):
-                    item = await self.async_get_track(db_row["item_id"])
+                    item = await self.get_track(db_row["item_id"])
                     if compare_track(item, track):
                         cur_item = item
                         break
             if cur_item:
                 # update existing
-                return await self.async_update_track(cur_item.item_id, track)
+                return await self.update_track(cur_item.item_id, track)
             # Item does not yet exist: Insert track
             sql_query = """INSERT INTO tracks
                 (name, sort_name, albums, artists, duration, version, isrc, metadata, provider_ids)
                 VALUES(?,?,?,?,?,?,?,?,?);"""
             # we store a mapping to artists and albums on the track for easier access/listings
-            track_artists = await self.__async_get_track_artists(track)
-            track_albums = await self.__async_get_track_albums(track)
+            track_artists = await self._get_track_artists(track)
+            track_albums = await self._get_track_albums(track)
 
             async with db_conn.execute(
                 sql_query,
@@ -779,25 +773,23 @@ class DatabaseManager:
                     (last_row_id,),
                     db_conn,
                 )
-            await self.__async_add_prov_ids(
+            await self._add_prov_ids(
                 new_item[0], MediaType.Track, track.provider_ids, db_conn=db_conn
             )
             await db_conn.commit()
             LOGGER.debug("added track %s to database", track.name)
             # return created object
-            return await self.async_get_track(new_item[0])
+            return await self.get_track(new_item[0])
 
-    async def async_update_track(self, item_id: int, track: Track):
+    async def update_track(self, item_id: int, track: Track):
         """Update a track record in the database."""
         async with aiosqlite.connect(self._dbfile, timeout=120) as db_conn:
             db_conn.row_factory = aiosqlite.Row
-            cur_item = await self.async_get_track(item_id)
+            cur_item = await self.get_track(item_id)
 
             # we store a mapping to artists and albums on the track for easier access/listings
-            track_artists = await self.__async_get_track_artists(
-                track, cur_item.artists
-            )
-            track_albums = await self.__async_get_track_albums(track, cur_item.albums)
+            track_artists = await self._get_track_artists(track, cur_item.artists)
+            track_albums = await self._get_track_albums(track, cur_item.albums)
             # merge metadata and provider id's
             metadata = merge_dict(cur_item.metadata, track.metadata)
             provider_ids = merge_list(cur_item.provider_ids, track.provider_ids)
@@ -819,17 +811,15 @@ class DatabaseManager:
                     item_id,
                 ),
             )
-            await self.__async_add_prov_ids(
+            await self._add_prov_ids(
                 item_id, MediaType.Track, track.provider_ids, db_conn=db_conn
             )
             LOGGER.debug("updated track %s in database: %s", track.name, item_id)
             await db_conn.commit()
             # return updated object
-            return await self.async_get_track(item_id)
+            return await self.get_track(item_id)
 
-    async def async_set_track_loudness(
-        self, item_id: str, provider: str, loudness: int
-    ):
+    async def set_track_loudness(self, item_id: str, provider: str, loudness: int):
         """Set integrated loudness for a track in db."""
         async with aiosqlite.connect(self._dbfile, timeout=120) as db_conn:
             sql_query = """INSERT or REPLACE INTO track_loudness
@@ -837,7 +827,7 @@ class DatabaseManager:
             await db_conn.execute(sql_query, (item_id, provider, loudness))
             await db_conn.commit()
 
-    async def async_get_track_loudness(self, provider_item_id, provider):
+    async def get_track_loudness(self, provider_item_id, provider):
         """Get integrated loudness for a track in db."""
         async with aiosqlite.connect(self._dbfile, timeout=120) as db_conn:
             sql_query = """SELECT loudness FROM track_loudness WHERE
@@ -850,7 +840,7 @@ class DatabaseManager:
                 return result[0]
         return None
 
-    async def async_mark_item_played(self, item_id: str, provider: str):
+    async def mark_item_played(self, item_id: str, provider: str):
         """Mark item as played in playlog."""
         timestamp = datetime.utcnow().timestamp()
         async with aiosqlite.connect(self._dbfile, timeout=120) as db_conn:
@@ -859,7 +849,7 @@ class DatabaseManager:
             await db_conn.execute(sql_query, (item_id, provider, timestamp))
             await db_conn.commit()
 
-    async def async_get_thumbnail_id(self, url, size):
+    async def get_thumbnail_id(self, url, size):
         """Get/create id for thumbnail."""
         async with aiosqlite.connect(self._dbfile, timeout=120) as db_conn:
             sql_query = """SELECT id FROM thumbs WHERE
@@ -882,11 +872,11 @@ class DatabaseManager:
             await db_conn.commit()
             return new_item[0]
 
-    async def __async_add_prov_ids(
+    async def _add_prov_ids(
         self,
         item_id: int,
         media_type: MediaType,
-        provider_ids: List[MediaItemProviderId],
+        provider_ids: Set[MediaItemProviderId],
         db_conn: aiosqlite.Connection,
     ):
         """Add provider ids for media item to database."""
@@ -915,44 +905,43 @@ class DatabaseManager:
             return item
         return None
 
-    async def __async_get_track_albums(
-        self, track: Track, cur_albums: Optional[List[ItemMapping]] = None
-    ) -> List[ItemMapping]:
+    async def _get_track_albums(
+        self, track: Track, cur_albums: Optional[Set[ItemMapping]] = None
+    ) -> Set[ItemMapping]:
         """Extract all (unique) albums of track as ItemMapping."""
         if not track.albums:
-            track.albums.append(track.album)
+            track.albums.add(track.album)
         if cur_albums is None:
-            cur_albums = []
-        track_albums = []
-        for album in track.albums + cur_albums:
-            cur_ids = [x.item_id for x in track_albums]
+            cur_albums = set()
+        cur_albums.update(track.albums)
+        track_albums = set()
+        for album in cur_albums:
+            cur_ids = {x.item_id for x in track_albums}
             if isinstance(album, ItemMapping):
-                track_album = await self.async_get_album_by_prov_id(
-                    album.provider_id, album
-                )
+                track_album = await self.get_album_by_prov_id(album.provider_id, album)
             else:
-                track_album = await self.async_add_album(album)
+                track_album = await self.add_album(album)
             if track_album.item_id not in cur_ids:
-                track_albums.append(ItemMapping.from_item(album))
+                track_albums.add(ItemMapping.from_item(album))
         return track_albums
 
-    async def __async_get_track_artists(
-        self, track: Track, cur_artists: Optional[List[ItemMapping]] = None
-    ) -> List[ItemMapping]:
+    async def _get_track_artists(
+        self, track: Track, cur_artists: Optional[Set[ItemMapping]] = None
+    ) -> Set[ItemMapping]:
         """Extract all (unique) artists of track as ItemMapping."""
         if cur_artists is None:
-            cur_artists = []
-        track_artists = []
-        for item in cur_artists + track.artists:
-            cur_names = [x.name for x in track_artists]
-            cur_ids = [x.item_id for x in track_artists]
+            cur_artists = set()
+        cur_artists.update(track.artists)
+        track_artists = set()
+        for item in cur_artists:
+            cur_names = {x.name for x in track_artists}
+            cur_ids = {x.item_id for x in track_artists}
             track_artist = (
-                await self.async_get_artist_by_prov_id(item.provider, item.item_id)
-                or item
+                await self.get_artist_by_prov_id(item.provider, item.item_id) or item
             )
             if (
                 track_artist.name not in cur_names
                 and track_artist.item_id not in cur_ids
             ):
-                track_artists.append(ItemMapping.from_item(track_artist))
+                track_artists.add(ItemMapping.from_item(track_artist))
         return track_artists
index 505ed0cd864269fce32313e82b2ba2826990626c..f1b0c189673f6d597f8fc0071e6123d9c232b30f 100755 (executable)
@@ -27,7 +27,7 @@ def sync_task(desc):
 
     def wrapper(func):
         @functools.wraps(func)
-        async def async_wrapped(*args):
+        async def wrapped(*args):
             method_class = args[0]
             prov_id = args[1]
             # check if this sync task is not already running
@@ -39,7 +39,7 @@ def sync_task(desc):
                     return
             LOGGER.debug("Start syncjob %s for provider %s.", desc, prov_id)
             sync_job = (prov_id, desc)
-            method_class.running_sync_jobs.append(sync_job)
+            method_class.running_sync_jobs.add(sync_job)
             method_class.mass.signal_event(
                 EVENT_MUSIC_SYNC_STATUS, method_class.running_sync_jobs
             )
@@ -50,7 +50,7 @@ def sync_task(desc):
                 EVENT_MUSIC_SYNC_STATUS, method_class.running_sync_jobs
             )
 
-        return async_wrapped
+        return wrapped
 
     return wrapper
 
@@ -60,15 +60,15 @@ class LibraryManager:
 
     def __init__(self, mass):
         """Initialize class."""
-        self.running_sync_jobs = []
+        self.running_sync_jobs = set()
         self.mass = mass
         self.cache = mass.cache
-        self.mass.add_event_listener(self.mass_event, [EVENT_PROVIDER_REGISTERED])
+        self.mass.add_event_listener(self.mass_event, EVENT_PROVIDER_REGISTERED)
 
-    async def async_setup(self):
+    async def setup(self):
         """Async initialize of module."""
         # schedule sync task
-        self.mass.add_job(self.__async_music_providers_sync())
+        self.mass.add_job(self._music_providers_sync())
 
     @callback
     def mass_event(self, msg: str, msg_details: Any):
@@ -77,53 +77,51 @@ class LibraryManager:
             # schedule a sync task when a new provider registers
             provider = self.mass.get_provider(msg_details)
             if provider.type == ProviderType.MUSIC_PROVIDER:
-                self.mass.add_job(self.async_music_provider_sync(msg_details))
+                self.mass.add_job(self.music_provider_sync(msg_details))
 
     ################ GET MediaItems that are added in the library ################
 
     @api_route("library/artists")
-    async def async_get_library_artists(self, orderby: str = "name") -> List[Artist]:
+    async def get_library_artists(self, orderby: str = "name") -> List[Artist]:
         """Return all library artists, optionally filtered by provider."""
-        return await self.mass.database.async_get_library_artists(orderby=orderby)
+        return await self.mass.database.get_library_artists(orderby=orderby)
 
     @api_route("library/albums")
-    async def async_get_library_albums(self, orderby: str = "name") -> List[Album]:
+    async def get_library_albums(self, orderby: str = "name") -> List[Album]:
         """Return all library albums, optionally filtered by provider."""
-        return await self.mass.database.async_get_library_albums(orderby=orderby)
+        return await self.mass.database.get_library_albums(orderby=orderby)
 
     @api_route("library/tracks")
-    async def async_get_library_tracks(self, orderby: str = "name") -> List[Track]:
+    async def get_library_tracks(self, orderby: str = "name") -> List[Track]:
         """Return all library tracks, optionally filtered by provider."""
-        return await self.mass.database.async_get_library_tracks(orderby=orderby)
+        return await self.mass.database.get_library_tracks(orderby=orderby)
 
     @api_route("library/playlists")
-    async def async_get_library_playlists(
-        self, orderby: str = "name"
-    ) -> List[Playlist]:
+    async def get_library_playlists(self, orderby: str = "name") -> List[Playlist]:
         """Return all library playlists, optionally filtered by provider."""
-        return await self.mass.database.async_get_library_playlists(orderby=orderby)
+        return await self.mass.database.get_library_playlists(orderby=orderby)
 
     @api_route("library/radios")
-    async def async_get_library_radios(self, orderby: str = "name") -> List[Playlist]:
+    async def get_library_radios(self, orderby: str = "name") -> List[Playlist]:
         """Return all library radios, optionally filtered by provider."""
-        return await self.mass.database.async_get_library_radios(orderby=orderby)
+        return await self.mass.database.get_library_radios(orderby=orderby)
 
-    async def async_get_library_playlist_by_name(self, name: str) -> Playlist:
+    async def get_library_playlist_by_name(self, name: str) -> Playlist:
         """Get in-library playlist by name."""
-        for playlist in await self.mass.music.async_get_library_playlists():
+        for playlist in await self.mass.music.get_library_playlists():
             if playlist.name == name:
                 return playlist
         return None
 
-    async def async_get_radio_by_name(self, name: str) -> Radio:
+    async def get_radio_by_name(self, name: str) -> Radio:
         """Get in-library radio by name."""
-        for radio in await self.mass.music.async_get_library_radios():
+        for radio in await self.mass.music.get_library_radios():
             if radio.name == name:
                 return radio
         return None
 
     @api_route("library/add")
-    async def async_library_add(self, items: List[MediaItem]):
+    async def library_add(self, items: List[MediaItem]):
         """Add media item(s) to the library."""
         result = False
         for media_item in items:
@@ -131,18 +129,18 @@ class LibraryManager:
             for prov in media_item.provider_ids:
                 provider = self.mass.get_provider(prov.provider)
                 if provider:
-                    result = await provider.async_library_add(
+                    result = await provider.library_add(
                         prov.item_id, media_item.media_type
                     )
             # mark as library item in internal db
             if media_item.provider == "database":
-                await self.mass.database.async_add_to_library(
+                await self.mass.database.add_to_library(
                     media_item.item_id, media_item.media_type, media_item.provider
                 )
         return result
 
     @api_route("library/remove")
-    async def async_library_remove(self, items: List[MediaItem]):
+    async def library_remove(self, items: List[MediaItem]):
         """Remove media item(s) from the library."""
         result = False
         for media_item in items:
@@ -150,33 +148,33 @@ class LibraryManager:
             for prov in media_item.provider_ids:
                 provider = self.mass.get_provider(prov.provider)
                 if provider:
-                    result = await provider.async_library_remove(
+                    result = await provider.library_remove(
                         prov.item_id, media_item.media_type
                     )
             # mark as library item in internal db
             if media_item.provider == "database":
-                await self.mass.database.async_remove_from_library(
+                await self.mass.database.remove_from_library(
                     media_item.item_id, media_item.media_type, media_item.provider
                 )
         return result
 
     @api_route("library/playlists/:db_playlist_id/tracks/add")
-    async def async_add_playlist_tracks(self, db_playlist_id: int, tracks: List[Track]):
+    async def add_playlist_tracks(self, db_playlist_id: int, tracks: List[Track]):
         """Add tracks to playlist - make sure we dont add duplicates."""
         # we can only edit playlists that are in the database (marked as editable)
-        playlist = await self.mass.music.async_get_playlist(db_playlist_id, "database")
+        playlist = await self.mass.music.get_playlist(db_playlist_id, "database")
         if not playlist or not playlist.is_editable:
             return False
         # playlist can only have one provider (for now)
-        playlist_prov = playlist.provider_ids[0]
+        playlist_prov = next(iter(playlist.provider_ids))
         # grab all existing track ids in the playlist so we can check for duplicates
-        cur_playlist_track_ids = []
-        for item in await self.mass.music.async_get_playlist_tracks(
+        cur_playlist_track_ids = set()
+        for item in await self.mass.music.get_playlist_tracks(
             playlist_prov.item_id, playlist_prov.provider
         ):
-            cur_playlist_track_ids.append(item.item_id)
-            cur_playlist_track_ids += [i.item_id for i in item.provider_ids]
-        track_ids_to_add = []
+            cur_playlist_track_ids.add(item.item_id)
+            cur_playlist_track_ids.update({i.item_id for i in item.provider_ids})
+        track_ids_to_add = set()
         for track in tracks:
             # check for duplicates
             already_exists = track.item_id in cur_playlist_track_ids
@@ -193,58 +191,58 @@ class LibraryManager:
                 track.provider_ids, key=lambda x: x.quality, reverse=True
             ):
                 if track_version.provider == playlist_prov.provider:
-                    track_ids_to_add.append(track_version.item_id)
+                    track_ids_to_add.add(track_version.item_id)
                     break
                 if playlist_prov.provider == "file":
                     # the file provider can handle uri's from all providers so simply add the uri
                     uri = f"{track_version.provider}://{track_version.item_id}"
-                    track_ids_to_add.append(uri)
+                    track_ids_to_add.add(uri)
                     break
         # actually add the tracks to the playlist on the provider
         if track_ids_to_add:
             # invalidate cache
             playlist.checksum = str(time.time())
-            await self.mass.database.async_update_playlist(playlist.item_id, playlist)
+            await self.mass.database.update_playlist(playlist.item_id, playlist)
             # return result of the action on the provider
             provider = self.mass.get_provider(playlist_prov.provider)
-            return await provider.async_add_playlist_tracks(
+            return await provider.add_playlist_tracks(
                 playlist_prov.item_id, track_ids_to_add
             )
         return False
 
     @api_route("library/playlists/:db_playlist_id/tracks/remove")
-    async def async_remove_playlist_tracks(self, db_playlist_id, tracks: List[Track]):
+    async def remove_playlist_tracks(self, db_playlist_id, tracks: List[Track]):
         """Remove tracks from playlist."""
         # we can only edit playlists that are in the database (marked as editable)
-        playlist = await self.mass.music.async_get_playlist(db_playlist_id, "database")
+        playlist = await self.mass.music.get_playlist(db_playlist_id, "database")
         if not playlist or not playlist.is_editable:
             return False
         # playlist can only have one provider (for now)
-        prov_playlist = playlist.provider_ids[0]
-        track_ids_to_remove = []
+        prov_playlist = next(iter(playlist.provider_ids))
+        track_ids_to_remove = set()
         for track in tracks:
             # a track can contain multiple versions on the same provider, remove all
             for track_provider in track.provider_ids:
                 if track_provider.provider == prov_playlist.provider:
-                    track_ids_to_remove.append(track_provider.item_id)
+                    track_ids_to_remove.add(track_provider.item_id)
         # actually remove the tracks from the playlist on the provider
         if track_ids_to_remove:
             # invalidate cache
             playlist.checksum = str(time.time())
-            await self.mass.database.async_update_playlist(playlist.item_id, playlist)
+            await self.mass.database.update_playlist(playlist.item_id, playlist)
             provider = self.mass.get_provider(prov_playlist.provider)
-            return await provider.async_remove_playlist_tracks(
+            return await provider.remove_playlist_tracks(
                 prov_playlist.item_id, track_ids_to_remove
             )
 
     @run_periodic(3600 * 3)
-    async def __async_music_providers_sync(self):
+    async def _music_providers_sync(self):
         """Periodic sync of all music providers."""
         await asyncio.sleep(10)
         for prov in self.mass.get_providers(ProviderType.MUSIC_PROVIDER):
-            await self.async_music_provider_sync(prov.id)
+            await self.music_provider_sync(prov.id)
 
-    async def async_music_provider_sync(self, prov_id: str):
+    async def music_provider_sync(self, prov_id: str):
         """
         Sync a music provider.
 
@@ -254,155 +252,155 @@ class LibraryManager:
         if not provider:
             return
         if MediaType.Album in provider.supported_mediatypes:
-            await self.async_library_albums_sync(prov_id)
+            await self.library_albums_sync(prov_id)
         if MediaType.Track in provider.supported_mediatypes:
-            await self.async_library_tracks_sync(prov_id)
+            await self.library_tracks_sync(prov_id)
         if MediaType.Artist in provider.supported_mediatypes:
-            await self.async_library_artists_sync(prov_id)
+            await self.library_artists_sync(prov_id)
         if MediaType.Playlist in provider.supported_mediatypes:
-            await self.async_library_playlists_sync(prov_id)
+            await self.library_playlists_sync(prov_id)
         if MediaType.Radio in provider.supported_mediatypes:
-            await self.async_library_radios_sync(prov_id)
+            await self.library_radios_sync(prov_id)
 
     @sync_task("artists")
-    async def async_library_artists_sync(self, provider_id: str):
+    async def library_artists_sync(self, provider_id: str):
         """Sync library artists for given provider."""
         music_provider = self.mass.get_provider(provider_id)
         cache_key = f"library_artists_{provider_id}"
-        prev_db_ids = await self.mass.cache.async_get(cache_key, default=[])
-        cur_db_ids = []
-        for item in await music_provider.async_get_library_artists():
-            db_item = await self.mass.music.async_get_artist(
+        prev_db_ids = await self.mass.cache.get(cache_key, default=[])
+        cur_db_ids = set()
+        for item in await music_provider.get_library_artists():
+            db_item = await self.mass.music.get_artist(
                 item.item_id, provider_id, lazy=False
             )
-            cur_db_ids.append(db_item.item_id)
+            cur_db_ids.add(db_item.item_id)
             if not db_item.in_library:
-                await self.mass.database.async_add_to_library(
+                await self.mass.database.add_to_library(
                     db_item.item_id, MediaType.Artist, provider_id
                 )
         # process deletions
         for db_id in prev_db_ids:
             if db_id not in cur_db_ids:
-                await self.mass.database.async_remove_from_library(
+                await self.mass.database.remove_from_library(
                     db_id, MediaType.Artist, provider_id
                 )
         # store ids in cache for next sync
-        await self.mass.cache.async_set(cache_key, cur_db_ids)
+        await self.mass.cache.set(cache_key, cur_db_ids)
 
     @sync_task("albums")
-    async def async_library_albums_sync(self, provider_id: str):
+    async def library_albums_sync(self, provider_id: str):
         """Sync library albums for given provider."""
         music_provider = self.mass.get_provider(provider_id)
         cache_key = f"library_albums_{provider_id}"
-        prev_db_ids = await self.mass.cache.async_get(cache_key, default=[])
-        cur_db_ids = []
-        for item in await music_provider.async_get_library_albums():
-            db_album = await self.mass.music.async_get_album(
+        prev_db_ids = await self.mass.cache.get(cache_key, default=[])
+        cur_db_ids = set()
+        for item in await music_provider.get_library_albums():
+            db_album = await self.mass.music.get_album(
                 item.item_id, provider_id, lazy=False
             )
             if not db_album.available and not item.available:
                 # album availability changed, sort this out with auto matching magic
-                db_album = await self.mass.music.async_match_album(db_album)
-            cur_db_ids.append(db_album.item_id)
+                db_album = await self.mass.music.match_album(db_album)
+            cur_db_ids.add(db_album.item_id)
             if not db_album.in_library:
-                await self.mass.database.async_add_to_library(
+                await self.mass.database.add_to_library(
                     db_album.item_id, MediaType.Album, provider_id
                 )
             # precache album tracks
-            await self.mass.music.async_get_album_tracks(item.item_id, provider_id)
+            await self.mass.music.get_album_tracks(item.item_id, provider_id)
         # process album deletions
         for db_id in prev_db_ids:
             if db_id not in cur_db_ids:
-                await self.mass.database.async_remove_from_library(
+                await self.mass.database.remove_from_library(
                     db_id, MediaType.Album, provider_id
                 )
         # store ids in cache for next sync
-        await self.mass.cache.async_set(cache_key, cur_db_ids)
+        await self.mass.cache.set(cache_key, cur_db_ids)
 
     @sync_task("tracks")
-    async def async_library_tracks_sync(self, provider_id: str):
+    async def library_tracks_sync(self, provider_id: str):
         """Sync library tracks for given provider."""
         music_provider = self.mass.get_provider(provider_id)
         cache_key = f"library_tracks_{provider_id}"
-        prev_db_ids = await self.mass.cache.async_get(cache_key, default=[])
-        cur_db_ids = []
-        for item in await music_provider.async_get_library_tracks():
-            db_item = await self.mass.music.async_get_track(
+        prev_db_ids = await self.mass.cache.get(cache_key, default=[])
+        cur_db_ids = set()
+        for item in await music_provider.get_library_tracks():
+            db_item = await self.mass.music.get_track(
                 item.item_id, provider_id, track_details=item, lazy=False
             )
             if not db_item.available and not item.available:
                 # track availability changed, sort this out with auto matching magic
-                db_item = await self.mass.music.async_add_track(item)
-            cur_db_ids.append(db_item.item_id)
+                db_item = await self.mass.music.add_track(item)
+            cur_db_ids.add(db_item.item_id)
             if not db_item.in_library:
-                await self.mass.database.async_add_to_library(
+                await self.mass.database.add_to_library(
                     db_item.item_id, MediaType.Track, provider_id
                 )
         # process deletions
         for db_id in prev_db_ids:
             if db_id not in cur_db_ids:
-                await self.mass.database.async_remove_from_library(
+                await self.mass.database.remove_from_library(
                     db_id, MediaType.Track, provider_id
                 )
         # store ids in cache for next sync
-        await self.mass.cache.async_set(cache_key, cur_db_ids)
+        await self.mass.cache.set(cache_key, cur_db_ids)
 
     @sync_task("playlists")
-    async def async_library_playlists_sync(self, provider_id: str):
+    async def library_playlists_sync(self, provider_id: str):
         """Sync library playlists for given provider."""
         music_provider = self.mass.get_provider(provider_id)
         cache_key = f"library_playlists_{provider_id}"
-        prev_db_ids = await self.mass.cache.async_get(cache_key, default=[])
-        cur_db_ids = []
-        for playlist in await music_provider.async_get_library_playlists():
-            db_item = await self.mass.music.async_get_playlist(
+        prev_db_ids = await self.mass.cache.get(cache_key, default=[])
+        cur_db_ids = set()
+        for playlist in await music_provider.get_library_playlists():
+            db_item = await self.mass.music.get_playlist(
                 playlist.item_id, provider_id, lazy=False
             )
             if db_item.checksum != playlist.checksum:
-                db_item = await self.mass.database.async_add_playlist(playlist)
-            cur_db_ids.append(db_item.item_id)
-            await self.mass.database.async_add_to_library(
+                db_item = await self.mass.database.add_playlist(playlist)
+            cur_db_ids.add(db_item.item_id)
+            await self.mass.database.add_to_library(
                 db_item.item_id, MediaType.Playlist, playlist.provider
             )
             # precache playlist tracks
-            for playlist_track in await self.mass.music.async_get_playlist_tracks(
+            for playlist_track in await self.mass.music.get_playlist_tracks(
                 playlist.item_id, provider_id
             ):
                 # try to find substitutes for unavailable tracks with matching technique
                 if not db_item.available and not playlist_track.available:
                     if playlist_track.provider == "database":
-                        await self.mass.music.async_match_track(playlist_track)
+                        await self.mass.music.match_track(playlist_track)
                     else:
-                        await self.mass.music.async_add_track(playlist_track)
+                        await self.mass.music.add_track(playlist_track)
         # process playlist deletions
         for db_id in prev_db_ids:
             if db_id not in cur_db_ids:
-                await self.mass.database.async_remove_from_library(
+                await self.mass.database.remove_from_library(
                     db_id, MediaType.Playlist, provider_id
                 )
         # store ids in cache for next sync
-        await self.mass.cache.async_set(cache_key, cur_db_ids)
+        await self.mass.cache.set(cache_key, cur_db_ids)
 
     @sync_task("radios")
-    async def async_library_radios_sync(self, provider_id: str):
+    async def library_radios_sync(self, provider_id: str):
         """Sync library radios for given provider."""
         music_provider = self.mass.get_provider(provider_id)
         cache_key = f"library_radios_{provider_id}"
-        prev_db_ids = await self.mass.cache.async_get(cache_key, default=[])
-        cur_db_ids = []
-        for item in await music_provider.async_get_library_radios():
-            db_radio = await self.mass.music.async_get_radio(
+        prev_db_ids = await self.mass.cache.get(cache_key, default=[])
+        cur_db_ids = set()
+        for item in await music_provider.get_library_radios():
+            db_radio = await self.mass.music.get_radio(
                 item.item_id, provider_id, lazy=False
             )
-            cur_db_ids.append(db_radio.item_id)
-            await self.mass.database.async_add_to_library(
+            cur_db_ids.add(db_radio.item_id)
+            await self.mass.database.add_to_library(
                 db_radio.item_id, MediaType.Radio, provider_id
             )
         # process deletions
         for db_id in prev_db_ids:
             if db_id not in cur_db_ids:
-                await self.mass.database.async_remove_from_library(
+                await self.mass.database.remove_from_library(
                     db_id, MediaType.Radio, provider_id
                 )
         # store ids in cache for next sync
-        await self.mass.cache.async_set(cache_key, cur_db_ids)
+        await self.mass.cache.set(cache_key, cur_db_ids)
index dac230f55c8562dd34a55bd599bb3b4f1330a42e..9533f24d7efb31ec3dc3016d138d9a70a5be96ba 100755 (executable)
@@ -3,8 +3,8 @@
 import logging
 from typing import Dict, List
 
-from music_assistant.helpers.cache import async_cached
-from music_assistant.helpers.typing import MusicAssistantType
+from music_assistant.helpers.cache import cached
+from music_assistant.helpers.typing import MusicAssistant
 from music_assistant.helpers.util import merge_dict
 from music_assistant.models.provider import MetadataProvider, ProviderType
 
@@ -15,7 +15,7 @@ class MetaDataManager:
     """Several helpers to search and store metadata for mediaitems using metadata providers."""
 
     # TODO: create periodic task to search for missing metadata
-    def __init__(self, mass: MusicAssistantType) -> None:
+    def __init__(self, mass: MusicAssistant) -> None:
         """Initialize class."""
         self.mass = mass
         self.cache = mass.cache
@@ -25,9 +25,7 @@ class MetaDataManager:
         """Return all providers of type MetadataProvider."""
         return self.mass.get_providers(ProviderType.METADATA_PROVIDER)
 
-    async def async_get_artist_metadata(
-        self, mb_artist_id: str, cur_metadata: Dict
-    ) -> Dict:
+    async def get_artist_metadata(self, mb_artist_id: str, cur_metadata: Dict) -> Dict:
         """Get/update rich metadata for an artist by providing the musicbrainz artist id."""
         metadata = cur_metadata
         for provider in self.providers:
@@ -35,8 +33,8 @@ class MetaDataManager:
                 # no need to query (other) metadata providers if we already have a result
                 break
             cache_key = f"{provider.id}.artist_metadata.{mb_artist_id}"
-            res = await async_cached(
-                self.cache, cache_key, provider.async_get_artist_images, mb_artist_id
+            res = await cached(
+                self.cache, cache_key, provider.get_artist_images, mb_artist_id
             )
             if res:
                 metadata = merge_dict(metadata, res)
index 8bb05a6068076975569cac6b4fdf05bfa5b6bee2..c823b9633ae2cde785d5181de9b5b374af3e13df 100755 (executable)
@@ -11,7 +11,7 @@ from music_assistant.constants import (
     EVENT_RADIO_ADDED,
     EVENT_TRACK_ADDED,
 )
-from music_assistant.helpers.cache import async_cached
+from music_assistant.helpers.cache import cached
 from music_assistant.helpers.compare import (
     compare_album,
     compare_artists,
@@ -49,7 +49,7 @@ class MusicManager:
         self.cache = mass.cache
         self.musicbrainz = MusicBrainz(mass)
 
-    async def async_setup(self):
+    async def setup(self):
         """Async initialize of module."""
 
     @property
@@ -60,7 +60,7 @@ class MusicManager:
     ################ GET MediaItem(s) by id and provider #################
 
     @api_route("items/:media_type/:provider_id/:item_id")
-    async def async_get_item(
+    async def get_item(
         self,
         item_id: str,
         provider_id: str,
@@ -70,58 +70,52 @@ class MusicManager:
     ):
         """Get single music item by id and media type."""
         if media_type == MediaType.Artist:
-            return await self.async_get_artist(
+            return await self.get_artist(
                 item_id, provider_id, refresh=refresh, lazy=lazy
             )
         if media_type == MediaType.Album:
-            return await self.async_get_album(
+            return await self.get_album(
                 item_id, provider_id, refresh=refresh, lazy=lazy
             )
         if media_type == MediaType.Track:
-            return await self.async_get_track(
+            return await self.get_track(
                 item_id, provider_id, refresh=refresh, lazy=lazy
             )
         if media_type == MediaType.Playlist:
-            return await self.async_get_playlist(
+            return await self.get_playlist(
                 item_id, provider_id, refresh=refresh, lazy=lazy
             )
         if media_type == MediaType.Radio:
-            return await self.async_get_radio(
+            return await self.get_radio(
                 item_id, provider_id, refresh=refresh, lazy=lazy
             )
         return None
 
     @api_route("artists/:provider_id/:item_id")
-    async def async_get_artist(
+    async def get_artist(
         self, item_id: str, provider_id: str, refresh: bool = False, lazy: bool = True
     ) -> Artist:
         """Return artist details for the given provider artist id."""
         if provider_id == "database" and not refresh:
-            return await self.mass.database.async_get_artist(item_id)
-        db_item = await self.mass.database.async_get_artist_by_prov_id(
-            provider_id, item_id
-        )
+            return await self.mass.database.get_artist(item_id)
+        db_item = await self.mass.database.get_artist_by_prov_id(provider_id, item_id)
         if db_item and refresh:
             provider_id, item_id = await self.__get_provider_id(db_item)
         elif db_item:
             return db_item
-        artist = await self.__async_get_provider_artist(item_id, provider_id)
+        artist = await self._get_provider_artist(item_id, provider_id)
         if not lazy:
-            return await self.async_add_artist(artist)
-        self.mass.add_background_task(self.async_add_artist(artist))
+            return await self.add_artist(artist)
+        self.mass.add_background_task(self.add_artist(artist))
         return db_item if db_item else artist
 
-    async def __async_get_provider_artist(
-        self, item_id: str, provider_id: str
-    ) -> Artist:
+    async def _get_provider_artist(self, item_id: str, provider_id: str) -> Artist:
         """Return artist details for the given provider artist id."""
         provider = self.mass.get_provider(provider_id)
         if not provider or not provider.available:
             raise Exception("Provider %s is not available!" % provider_id)
         cache_key = f"{provider_id}.get_artist.{item_id}"
-        artist = await async_cached(
-            self.cache, cache_key, provider.async_get_artist, item_id
-        )
+        artist = await cached(self.cache, cache_key, provider.get_artist, item_id)
         if not artist:
             raise Exception(
                 "Artist %s not found on provider %s" % (item_id, provider_id)
@@ -129,34 +123,30 @@ class MusicManager:
         return artist
 
     @api_route("albums/:provider_id/:item_id")
-    async def async_get_album(
+    async def get_album(
         self, item_id: str, provider_id: str, refresh: bool = False, lazy: bool = True
     ) -> Album:
         """Return album details for the given provider album id."""
         if provider_id == "database" and not refresh:
-            return await self.mass.database.async_get_album(item_id)
-        db_item = await self.mass.database.async_get_album_by_prov_id(
-            provider_id, item_id
-        )
+            return await self.mass.database.get_album(item_id)
+        db_item = await self.mass.database.get_album_by_prov_id(provider_id, item_id)
         if db_item and refresh:
             provider_id, item_id = await self.__get_provider_id(db_item)
         elif db_item:
             return db_item
-        album = await self.__async_get_provider_album(item_id, provider_id)
+        album = await self._get_provider_album(item_id, provider_id)
         if not lazy:
-            return await self.async_add_album(album)
-        self.mass.add_background_task(self.async_add_album(album))
+            return await self.add_album(album)
+        self.mass.add_background_task(self.add_album(album))
         return db_item if db_item else album
 
-    async def __async_get_provider_album(self, item_id: str, provider_id: str) -> Album:
+    async def _get_provider_album(self, item_id: str, provider_id: str) -> Album:
         """Return album details for the given provider album id."""
         provider = self.mass.get_provider(provider_id)
         if not provider or not provider.available:
             raise Exception("Provider %s is not available!" % provider_id)
         cache_key = f"{provider_id}.get_album.{item_id}"
-        album = await async_cached(
-            self.cache, cache_key, provider.async_get_album, item_id
-        )
+        album = await cached(self.cache, cache_key, provider.get_album, item_id)
         if not album:
             raise Exception(
                 "Album %s not found on provider %s" % (item_id, provider_id)
@@ -164,7 +154,7 @@ class MusicManager:
         return album
 
     @api_route("tracks/:provider_id/:item_id")
-    async def async_get_track(
+    async def get_track(
         self,
         item_id: str,
         provider_id: str,
@@ -175,32 +165,28 @@ class MusicManager:
     ) -> Track:
         """Return track details for the given provider track id."""
         if provider_id == "database" and not refresh:
-            return await self.mass.database.async_get_track(item_id)
-        db_item = await self.mass.database.async_get_track_by_prov_id(
-            provider_id, item_id
-        )
+            return await self.mass.database.get_track(item_id)
+        db_item = await self.mass.database.get_track_by_prov_id(provider_id, item_id)
         if db_item and refresh:
             provider_id, item_id = await self.__get_provider_id(db_item)
         elif db_item:
             return db_item
         if not track_details:
-            track_details = await self.__async_get_provider_track(item_id, provider_id)
+            track_details = await self._get_provider_track(item_id, provider_id)
         if album_details:
             track_details.album = album_details
         if not lazy:
-            return await self.async_add_track(track_details)
-        self.mass.add_background_task(self.async_add_track(track_details))
+            return await self.add_track(track_details)
+        self.mass.add_background_task(self.add_track(track_details))
         return db_item if db_item else track_details
 
-    async def __async_get_provider_track(self, item_id: str, provider_id: str) -> Track:
+    async def _get_provider_track(self, item_id: str, provider_id: str) -> Track:
         """Return track details for the given provider track id."""
         provider = self.mass.get_provider(provider_id)
         if not provider or not provider.available:
             raise Exception("Provider %s is not available!" % provider_id)
         cache_key = f"{provider_id}.get_track.{item_id}"
-        track = await async_cached(
-            self.cache, cache_key, provider.async_get_track, item_id
-        )
+        track = await cached(self.cache, cache_key, provider.get_track, item_id)
         if not track:
             raise Exception(
                 "Track %s not found on provider %s" % (item_id, provider_id)
@@ -208,36 +194,32 @@ class MusicManager:
         return track
 
     @api_route("playlists/:provider_id/:item_id")
-    async def async_get_playlist(
+    async def get_playlist(
         self, item_id: str, provider_id: str, refresh: bool = False, lazy: bool = True
     ) -> Playlist:
         """Return playlist details for the given provider playlist id."""
         assert item_id and provider_id
-        db_item = await self.mass.database.async_get_playlist_by_prov_id(
-            provider_id, item_id
-        )
+        db_item = await self.mass.database.get_playlist_by_prov_id(provider_id, item_id)
         if db_item and refresh:
             provider_id, item_id = await self.__get_provider_id(db_item)
         elif db_item:
             return db_item
-        playlist = await self.__async_get_provider_playlist(item_id, provider_id)
+        playlist = await self._get_provider_playlist(item_id, provider_id)
         if not lazy:
-            return await self.async_add_playlist(playlist)
-        self.mass.add_background_task(self.async_add_playlist(playlist))
+            return await self.add_playlist(playlist)
+        self.mass.add_background_task(self.add_playlist(playlist))
         return db_item if db_item else playlist
 
-    async def __async_get_provider_playlist(
-        self, item_id: str, provider_id: str
-    ) -> Playlist:
+    async def _get_provider_playlist(self, item_id: str, provider_id: str) -> Playlist:
         """Return playlist details for the given provider playlist id."""
         provider = self.mass.get_provider(provider_id)
         if not provider or not provider.available:
             raise Exception("Provider %s is not available!" % provider_id)
         cache_key = f"{provider_id}.get_playlist.{item_id}"
-        playlist = await async_cached(
+        playlist = await cached(
             self.cache,
             cache_key,
-            provider.async_get_playlist,
+            provider.get_playlist,
             item_id,
             expires=86400 * 2,
         )
@@ -248,33 +230,29 @@ class MusicManager:
         return playlist
 
     @api_route("radios/:provider_id/:item_id")
-    async def async_get_radio(
+    async def get_radio(
         self, item_id: str, provider_id: str, refresh: bool = False, lazy: bool = True
     ) -> Radio:
         """Return radio details for the given provider radio id."""
         assert item_id and provider_id
-        db_item = await self.mass.database.async_get_radio_by_prov_id(
-            provider_id, item_id
-        )
+        db_item = await self.mass.database.get_radio_by_prov_id(provider_id, item_id)
         if db_item and refresh:
             provider_id, item_id = await self.__get_provider_id(db_item)
         elif db_item:
             return db_item
-        radio = await self.__async_get_provider_radio(item_id, provider_id)
+        radio = await self._get_provider_radio(item_id, provider_id)
         if not lazy:
-            return await self.async_add_radio(radio)
-        self.mass.add_background_task(self.async_add_radio(radio))
+            return await self.add_radio(radio)
+        self.mass.add_background_task(self.add_radio(radio))
         return db_item if db_item else radio
 
-    async def __async_get_provider_radio(self, item_id: str, provider_id: str) -> Radio:
+    async def _get_provider_radio(self, item_id: str, provider_id: str) -> Radio:
         """Return radio details for the given provider playlist id."""
         provider = self.mass.get_provider(provider_id)
         if not provider or not provider.available:
             raise Exception("Provider %s is not available!" % provider_id)
         cache_key = f"{provider_id}.get_radio.{item_id}"
-        radio = await async_cached(
-            self.cache, cache_key, provider.async_get_radio, item_id
-        )
+        radio = await cached(self.cache, cache_key, provider.get_radio, item_id)
         if not radio:
             raise Exception(
                 "Radio %s not found on provider %s" % (item_id, provider_id)
@@ -282,23 +260,22 @@ class MusicManager:
         return radio
 
     @api_route("albums/:provider_id/:item_id/tracks")
-    async def async_get_album_tracks(
-        self, item_id: str, provider_id: str
-    ) -> List[Track]:
+    async def get_album_tracks(self, item_id: str, provider_id: str) -> List[Track]:
         """Return album tracks for the given provider album id."""
         assert item_id and provider_id
-        album = await self.async_get_album(item_id, provider_id)
+        album = await self.get_album(item_id, provider_id)
         if album.provider == "database":
             # album tracks are not stored in db, we always fetch them (cached) from the provider.
-            provider_id = album.provider_ids[0].provider
-            item_id = album.provider_ids[0].item_id
+            prov_id = next(iter(album.provider_ids))
+            provider_id = prov_id.provider
+            item_id = prov_id.item_id
         provider = self.mass.get_provider(provider_id)
         cache_key = f"{provider_id}.album_tracks.{item_id}"
-        all_prov_tracks = await async_cached(
-            self.cache, cache_key, provider.async_get_album_tracks, item_id
+        all_prov_tracks = await cached(
+            self.cache, cache_key, provider.get_album_tracks, item_id
         )
         # retrieve list of db items
-        db_tracks = await self.mass.database.async_get_tracks_from_provider_ids(
+        db_tracks = await self.mass.database.get_tracks_from_provider_ids(
             [x.provider for x in album.provider_ids],
             [x.item_id for x in all_prov_tracks],
         )
@@ -315,11 +292,9 @@ class MusicManager:
         ]
 
     @api_route("albums/:provider_id/:item_id/versions")
-    async def async_get_album_versions(
-        self, item_id: str, provider_id: str
-    ) -> List[Album]:
+    async def get_album_versions(self, item_id: str, provider_id: str) -> List[Album]:
         """Return all versions of an album we can find on all providers."""
-        album = await self.async_get_album(item_id, provider_id)
+        album = await self.get_album(item_id, provider_id)
         provider_ids = [
             item.id for item in self.mass.get_providers(ProviderType.MUSIC_PROVIDER)
         ]
@@ -328,9 +303,7 @@ class MusicManager:
             prov_item
             for prov_items in await asyncio.gather(
                 *[
-                    self.async_search_provider(
-                        search_query, prov_id, [MediaType.Album], 25
-                    )
+                    self.search_provider(search_query, prov_id, [MediaType.Album], 25)
                     for prov_id in provider_ids
                 ]
             )
@@ -339,11 +312,9 @@ class MusicManager:
         ]
 
     @api_route("tracks/:provider_id/:item_id/versions")
-    async def async_get_track_versions(
-        self, item_id: str, provider_id: str
-    ) -> List[Track]:
+    async def get_track_versions(self, item_id: str, provider_id: str) -> List[Track]:
         """Return all versions of a track we can find on all providers."""
-        track = await self.async_get_track(item_id, provider_id)
+        track = await self.get_track(item_id, provider_id)
         provider_ids = [
             item.id for item in self.mass.get_providers(ProviderType.MUSIC_PROVIDER)
         ]
@@ -352,9 +323,7 @@ class MusicManager:
             prov_item
             for prov_items in await asyncio.gather(
                 *[
-                    self.async_search_provider(
-                        search_query, prov_id, [MediaType.Track], 25
-                    )
+                    self.search_provider(search_query, prov_id, [MediaType.Track], 25)
                     for prov_id in provider_ids
                 ]
             )
@@ -363,30 +332,29 @@ class MusicManager:
         ]
 
     @api_route("playlists/:provider_id/:item_id/tracks")
-    async def async_get_playlist_tracks(
-        self, item_id: str, provider_id: str
-    ) -> List[Track]:
+    async def get_playlist_tracks(self, item_id: str, provider_id: str) -> List[Track]:
         """Return playlist tracks for the given provider playlist id."""
         assert item_id and provider_id
         if provider_id == "database":
             # playlist tracks are not stored in db, we always fetch them (cached) from the provider.
-            playlist = await self.mass.database.async_get_playlist(item_id)
-            provider_id = playlist.provider_ids[0].provider
-            item_id = playlist.provider_ids[0].item_id
+            playlist = await self.mass.database.get_playlist(item_id)
+            prov_id = next(iter(playlist.provider_ids))
+            provider_id = prov_id.provider
+            item_id = prov_id.item_id
             provider = self.mass.get_provider(provider_id)
         else:
             provider = self.mass.get_provider(provider_id)
-            playlist = await provider.async_get_playlist(item_id)
+            playlist = await provider.get_playlist(item_id)
         cache_checksum = playlist.checksum
         cache_key = f"{provider_id}.playlist_tracks.{item_id}"
-        playlist_tracks = await async_cached(
+        playlist_tracks = await cached(
             self.cache,
             cache_key,
-            provider.async_get_playlist_tracks,
+            provider.get_playlist_tracks,
             item_id,
             checksum=cache_checksum,
         )
-        db_tracks = await self.mass.database.async_get_tracks_from_provider_ids(
+        db_tracks = await self.mass.database.get_tracks_from_provider_ids(
             provider_id, [x.item_id for x in playlist_tracks]
         )
         # combine provider tracks with db tracks
@@ -418,40 +386,37 @@ class MusicManager:
         if track_number is not None:
             item.track_number = track_number
         # make sure artists are unique
-        if hasattr(item, "artists"):
-            item.artists = unique_item_ids(item.artists)
+        if hasattr(item, "artists"):
+            item.artists = unique_item_ids(item.artists)
         return item
 
     @api_route("artists/:provider_id/:item_id/tracks")
-    async def async_get_artist_toptracks(
-        self, item_id: str, provider_id: str
-    ) -> List[Track]:
+    async def get_artist_toptracks(self, item_id: str, provider_id: str) -> List[Track]:
         """Return top tracks for an artist."""
-        artist = await self.async_get_artist(item_id, provider_id)
+        artist = await self.get_artist(item_id, provider_id)
         # get results from all providers
         all_prov_tracks = [
             track
             for prov_tracks in await asyncio.gather(
                 *[
-                    self.__async_get_provider_artist_toptracks(
-                        item.item_id, item.provider
-                    )
+                    self._get_provider_artist_toptracks(item.item_id, item.provider)
                     for item in artist.provider_ids
                 ]
             )
             for track in prov_tracks
         ]
         # retrieve list of db items
-        db_tracks = await self.mass.database.async_get_tracks_from_provider_ids(
+        db_tracks = await self.mass.database.get_tracks_from_provider_ids(
             [x.provider for x in artist.provider_ids],
             [x.item_id for x in all_prov_tracks],
         )
         # combine provider tracks with db tracks and filter duplicate itemid's
-        return unique_item_ids(
-            [await self.__process_item(item, db_tracks) for item in all_prov_tracks]
-        )
+        return {await self.__process_item(item, db_tracks) for item in all_prov_tracks}
+        # return unique_item_ids(
+        #     [await self.__process_item(item, db_tracks) for item in all_prov_tracks]
+        # )
 
-    async def __async_get_provider_artist_toptracks(
+    async def _get_provider_artist_toptracks(
         self, item_id: str, provider_id: str
     ) -> List[Track]:
         """Return top tracks for an artist on given provider."""
@@ -460,32 +425,30 @@ class MusicManager:
             LOGGER.error("Provider %s is not available", provider_id)
             return []
         cache_key = f"{provider_id}.artist_toptracks.{item_id}"
-        return await async_cached(
+        return await cached(
             self.cache,
             cache_key,
-            provider.async_get_artist_toptracks,
+            provider.get_artist_toptracks,
             item_id,
         )
 
     @api_route("artists/:provider_id/:item_id/albums")
-    async def async_get_artist_albums(
-        self, item_id: str, provider_id: str
-    ) -> List[Album]:
+    async def get_artist_albums(self, item_id: str, provider_id: str) -> List[Album]:
         """Return (all) albums for an artist."""
-        artist = await self.async_get_artist(item_id, provider_id)
+        artist = await self.get_artist(item_id, provider_id)
         # get results from all providers
         all_prov_albums = [
             album
             for prov_albums in await asyncio.gather(
                 *[
-                    self.__async_get_provider_artist_albums(item.item_id, item.provider)
+                    self._get_provider_artist_albums(item.item_id, item.provider)
                     for item in artist.provider_ids
                 ]
             )
             for album in prov_albums
         ]
         # retrieve list of db items
-        db_tracks = await self.mass.database.async_get_albums_from_provider_ids(
+        db_tracks = await self.mass.database.get_albums_from_provider_ids(
             [x.provider for x in artist.provider_ids],
             [x.item_id for x in all_prov_albums],
         )
@@ -494,7 +457,7 @@ class MusicManager:
             [await self.__process_item(item, db_tracks) for item in all_prov_albums]
         )
 
-    async def __async_get_provider_artist_albums(
+    async def _get_provider_artist_albums(
         self, item_id: str, provider_id: str
     ) -> List[Album]:
         """Return albums for an artist on given provider."""
@@ -503,15 +466,15 @@ class MusicManager:
             LOGGER.error("Provider %s is not available", provider_id)
             return []
         cache_key = f"{provider_id}.artist_albums.{item_id}"
-        return await async_cached(
+        return await cached(
             self.cache,
             cache_key,
-            provider.async_get_artist_albums,
+            provider.get_artist_albums,
             item_id,
         )
 
     @api_route("search/:provider_id")
-    async def async_search_provider(
+    async def search_provider(
         self,
         search_query: str,
         provider_id: str,
@@ -528,20 +491,20 @@ class MusicManager:
         """
         if provider_id == "database":
             # get results from database
-            return await self.mass.database.async_search(search_query, media_types)
+            return await self.mass.database.search(search_query, media_types)
         provider = self.mass.get_provider(provider_id)
         cache_key = f"{provider_id}.search.{search_query}.{media_types}.{limit}"
-        return await async_cached(
+        return await cached(
             self.cache,
             cache_key,
-            provider.async_search,
+            provider.search,
             search_query,
             media_types,
             limit,
         )
 
     @api_route("search")
-    async def async_global_search(
+    async def global_search(
         self, search_query, media_types: List[MediaType], limit: int = 10
     ) -> SearchResult:
         """
@@ -557,7 +520,7 @@ class MusicManager:
             item.id for item in self.mass.get_providers(ProviderType.MUSIC_PROVIDER)
         ]
         for provider_id in provider_ids:
-            provider_result = await self.async_search_provider(
+            provider_result = await self.search_provider(
                 search_query, provider_id, media_types, limit
             )
             result.artists += provider_result.artists
@@ -568,7 +531,7 @@ class MusicManager:
             # TODO: sort by name and filter out duplicates ?
         return result
 
-    async def async_get_stream_details(
+    async def get_stream_details(
         self, media_item: MediaItem, player_id: str = ""
     ) -> StreamDetails:
         """
@@ -597,7 +560,7 @@ class MusicManager:
                 full_track = media_item
             else:
                 full_track = (
-                    await self.async_get_track(media_item.item_id, media_item.provider)
+                    await self.get_track(media_item.item_id, media_item.provider)
                     or media_item
                 )
             # sort by quality and check track availability
@@ -611,8 +574,8 @@ class MusicManager:
                 if not music_prov or not music_prov.available:
                     continue  # provider temporary unavailable ?
 
-                streamdetails: StreamDetails = (
-                    await music_prov.async_get_stream_details(prov_media.item_id)
+                streamdetails: StreamDetails = await music_prov.get_stream_details(
+                    prov_media.item_id
                 )
                 if streamdetails:
                     try:
@@ -635,65 +598,65 @@ class MusicManager:
 
     ################ ADD MediaItem(s) to database helpers ################
 
-    async def async_add_artist(self, artist: Artist) -> Artist:
+    async def add_artist(self, artist: Artist) -> Artist:
         """Add artist to local db and return the database item."""
         if not artist.musicbrainz_id:
-            artist.musicbrainz_id = await self.__async_get_artist_musicbrainz_id(artist)
+            artist.musicbrainz_id = await self._get_artist_musicbrainz_id(artist)
         # grab additional metadata
-        artist.metadata = await self.mass.metadata.async_get_artist_metadata(
+        artist.metadata = await self.mass.metadata.get_artist_metadata(
             artist.musicbrainz_id, artist.metadata
         )
-        db_item = await self.mass.database.async_add_artist(artist)
+        db_item = await self.mass.database.add_artist(artist)
         # also fetch same artist on all providers
-        await self.async_match_artist(db_item)
+        await self.match_artist(db_item)
         self.mass.signal_event(EVENT_ARTIST_ADDED, db_item)
         return db_item
 
-    async def async_add_album(self, album: Album) -> Album:
+    async def add_album(self, album: Album) -> Album:
         """Add album to local db and return the database item."""
         # make sure we have an artist
         assert album.artist
-        db_item = await self.mass.database.async_add_album(album)
+        db_item = await self.mass.database.add_album(album)
         # also fetch same album on all providers
-        await self.async_match_album(db_item)
+        await self.match_album(db_item)
         self.mass.signal_event(EVENT_ALBUM_ADDED, db_item)
         return db_item
 
-    async def async_add_track(self, track: Track) -> Track:
+    async def add_track(self, track: Track) -> Track:
         """Add track to local db and return the new database item."""
         # make sure we have artists
         assert track.artists
         # make sure we have an album
         assert track.album or track.albums
-        db_item = await self.mass.database.async_add_track(track)
+        db_item = await self.mass.database.add_track(track)
         # also fetch same track on all providers (will also get other quality versions)
-        await self.async_match_track(db_item)
+        await self.match_track(db_item)
         self.mass.signal_event(EVENT_TRACK_ADDED, db_item)
         return db_item
 
-    async def async_add_playlist(self, playlist: Playlist) -> Playlist:
+    async def add_playlist(self, playlist: Playlist) -> Playlist:
         """Add playlist to local db and return the new database item."""
-        db_item = await self.mass.database.async_add_playlist(playlist)
+        db_item = await self.mass.database.add_playlist(playlist)
         self.mass.signal_event(EVENT_PLAYLIST_ADDED, db_item)
         return db_item
 
-    async def async_add_radio(self, radio: Radio) -> Radio:
+    async def add_radio(self, radio: Radio) -> Radio:
         """Add radio to local db and return the new database item."""
-        db_item = await self.mass.database.async_add_radio(radio)
+        db_item = await self.mass.database.add_radio(radio)
         self.mass.signal_event(EVENT_RADIO_ADDED, db_item)
         return db_item
 
-    async def __async_get_artist_musicbrainz_id(self, artist: Artist):
+    async def _get_artist_musicbrainz_id(self, artist: Artist):
         """Fetch musicbrainz id by performing search using the artist name, albums and tracks."""
         # try with album first
-        for lookup_album in await self.__async_get_provider_artist_albums(
+        for lookup_album in await self._get_provider_artist_albums(
             artist.item_id, artist.provider
         ):
             if not lookup_album:
                 continue
             if artist.name != lookup_album.artist.name:
                 continue
-            musicbrainz_id = await self.musicbrainz.async_get_mb_artist_id(
+            musicbrainz_id = await self.musicbrainz.get_mb_artist_id(
                 artist.name,
                 albumname=lookup_album.name,
                 album_upc=lookup_album.upc,
@@ -701,12 +664,12 @@ class MusicManager:
             if musicbrainz_id:
                 return musicbrainz_id
         # fallback to track
-        for lookup_track in await self.__async_get_provider_artist_toptracks(
+        for lookup_track in await self._get_provider_artist_toptracks(
             artist.item_id, artist.provider
         ):
             if not lookup_track:
                 continue
-            musicbrainz_id = await self.musicbrainz.async_get_mb_artist_id(
+            musicbrainz_id = await self.musicbrainz.get_mb_artist_id(
                 artist.name,
                 trackname=lookup_track.name,
                 track_isrc=lookup_track.isrc,
@@ -717,7 +680,7 @@ class MusicManager:
         LOGGER.warning("Unable to get musicbrainz ID for artist %s !", artist.name)
         return artist.name
 
-    async def async_match_artist(self, db_artist: Artist):
+    async def match_artist(self, db_artist: Artist):
         """
         Try to find matching artists on all providers for the provided (database) item_id.
 
@@ -732,31 +695,27 @@ class MusicManager:
                 continue
             if MediaType.Artist not in provider.supported_mediatypes:
                 continue
-            if not await self.__async_match_prov_artist(db_artist, provider):
+            if not await self._match_prov_artist(db_artist, provider):
                 LOGGER.debug(
                     "Could not find match for Artist %s on provider %s",
                     db_artist.name,
                     provider.name,
                 )
 
-    async def __async_match_prov_artist(
-        self, db_artist: Artist, provider: MusicProvider
-    ):
+    async def _match_prov_artist(self, db_artist: Artist, provider: MusicProvider):
         """Try to find matching artists on given provider for the provided (database) artist."""
         LOGGER.debug(
             "Trying to match artist %s on provider %s", db_artist.name, provider.name
         )
         # try to get a match with some reference tracks of this artist
-        for ref_track in await self.async_get_artist_toptracks(
+        for ref_track in await self.get_artist_toptracks(
             db_artist.item_id, db_artist.provider
         ):
             # make sure we have a full track
             if isinstance(ref_track.album, ItemMapping):
-                ref_track = await self.async_get_track(
-                    ref_track.item_id, ref_track.provider
-                )
+                ref_track = await self.get_track(ref_track.item_id, ref_track.provider)
             searchstr = "%s %s" % (db_artist.name, ref_track.name)
-            search_results = await self.async_search_provider(
+            search_results = await self.search_provider(
                 searchstr, provider.id, [MediaType.Track], limit=25
             )
             for search_result_item in search_results.tracks:
@@ -766,22 +725,22 @@ class MusicManager:
                         if compare_strings(db_artist.name, search_item_artist.name):
                             # 100% album match
                             # get full artist details so we have all metadata
-                            prov_artist = await self.__async_get_provider_artist(
+                            prov_artist = await self._get_provider_artist(
                                 search_item_artist.item_id, search_item_artist.provider
                             )
-                            await self.mass.database.async_update_artist(
+                            await self.mass.database.update_artist(
                                 db_artist.item_id, prov_artist
                             )
                             return True
         # try to get a match with some reference albums of this artist
-        artist_albums = await self.async_get_artist_albums(
+        artist_albums = await self.get_artist_albums(
             db_artist.item_id, db_artist.provider
         )
         for ref_album in artist_albums[:50]:
             if ref_album.album_type == AlbumType.Compilation:
                 continue
             searchstr = "%s %s" % (db_artist.name, ref_album.name)
-            search_result = await self.async_search_provider(
+            search_result = await self.search_provider(
                 searchstr, provider.id, [MediaType.Album], limit=25
             )
             for search_result_item in search_result.albums:
@@ -791,17 +750,17 @@ class MusicManager:
                 if compare_album(search_result_item, ref_album):
                     # 100% album match
                     # get full artist details so we have all metadata
-                    prov_artist = await self.__async_get_provider_artist(
+                    prov_artist = await self._get_provider_artist(
                         search_result_item.artist.item_id,
                         search_result_item.artist.provider,
                     )
-                    await self.mass.database.async_update_artist(
+                    await self.mass.database.update_artist(
                         db_artist.item_id, prov_artist
                     )
                     return True
         return False
 
-    async def async_match_album(self, db_album: Album):
+    async def match_album(self, db_album: Album):
         """
         Try to find matching album on all providers for the provided (database) album_id.
 
@@ -812,7 +771,7 @@ class MusicManager:
         ), "Matching only supported for database items!"
         if not isinstance(db_album, FullAlbum):
             # matching only works if we have a full album object
-            db_album = await self.mass.database.async_get_album(db_album.item_id)
+            db_album = await self.mass.database.get_album(db_album.item_id)
 
         async def find_prov_match(provider):
             LOGGER.debug(
@@ -822,7 +781,7 @@ class MusicManager:
             searchstr = "%s %s" % (db_album.artist.name, db_album.name)
             if db_album.version:
                 searchstr += " " + db_album.version
-            search_result = await self.async_search_provider(
+            search_result = await self.search_provider(
                 searchstr, provider.id, [MediaType.Album], limit=25
             )
             for search_result_item in search_result.albums:
@@ -831,21 +790,19 @@ class MusicManager:
                 if not compare_album(search_result_item, db_album):
                     continue
                 # we must fetch the full album version, search results are simplified objects
-                prov_album = await self.__async_get_provider_album(
+                prov_album = await self._get_provider_album(
                     search_result_item.item_id, search_result_item.provider
                 )
                 if compare_album(prov_album, db_album):
                     # 100% match, we can simply update the db with additional provider ids
-                    await self.mass.database.async_update_album(
-                        db_album.item_id, prov_album
-                    )
+                    await self.mass.database.update_album(db_album.item_id, prov_album)
                     match_found = True
                     # while we're here, also match the artist
                     if db_album.artist.provider == "database":
-                        prov_artist = await self.__async_get_provider_artist(
+                        prov_artist = await self._get_provider_artist(
                             prov_album.artist.item_id, prov_album.artist.provider
                         )
-                        await self.mass.database.async_update_artist(
+                        await self.mass.database.update_artist(
                             db_album.artist.item_id, prov_artist
                         )
 
@@ -863,7 +820,7 @@ class MusicManager:
             if MediaType.Album in provider.supported_mediatypes:
                 await find_prov_match(provider)
 
-    async def async_match_track(self, db_track: Track):
+    async def match_track(self, db_track: Track):
         """
         Try to find matching track on all providers for the provided (database) track_id.
 
@@ -874,7 +831,7 @@ class MusicManager:
         ), "Matching only supported for database items!"
         if isinstance(db_track.album, ItemMapping):
             # matching only works if we have a full track object
-            db_track = await self.mass.database.async_get_track(db_track.item_id)
+            db_track = await self.mass.database.get_track(db_track.item_id)
         for provider in self.mass.get_providers(ProviderType.MUSIC_PROVIDER):
             if MediaType.Track not in provider.supported_mediatypes:
                 continue
@@ -888,7 +845,7 @@ class MusicManager:
                 searchstr = "%s %s" % (db_track_artist.name, db_track.name)
                 if db_track.version:
                     searchstr += " " + db_track.version
-                search_result = await self.async_search_provider(
+                search_result = await self.search_provider(
                     searchstr, provider.id, [MediaType.Track], limit=25
                 )
                 for search_result_item in search_result.tracks:
@@ -897,7 +854,7 @@ class MusicManager:
                     if compare_track(search_result_item, db_track):
                         # 100% match, we can simply update the db with additional provider ids
                         match_found = True
-                        await self.mass.database.async_update_track(
+                        await self.mass.database.update_track(
                             db_track.item_id, search_result_item
                         )
                         # while we're here, also match the artist
@@ -907,10 +864,10 @@ class MusicManager:
                                     db_track_artist.name, artist.name
                                 ):
                                     continue
-                                prov_artist = await self.__async_get_provider_artist(
+                                prov_artist = await self._get_provider_artist(
                                     artist.item_id, artist.provider
                                 )
-                                await self.mass.database.async_update_artist(
+                                await self.mass.database.update_artist(
                                     db_track_artist.item_id, prov_artist
                                 )
 
@@ -924,7 +881,7 @@ class MusicManager:
     async def __get_provider_id(self, media_item: MediaItem) -> tuple:
         """Return provider and item id."""
         if media_item.provider == "database":
-            media_item = await self.mass.database.async_get_item_by_prov_id(
+            media_item = await self.mass.database.get_item_by_prov_id(
                 "database", media_item.item_id, media_item.media_type
             )
             for prov in media_item.provider_ids:
index a8ba716499ca421616758da84e50f64306c8b49a..a8198a7836798b4a1b82c2d4196de38c62f22c39 100755 (executable)
@@ -1,16 +1,15 @@
 """PlayerManager: Orchestrates all players from player providers."""
 
 import logging
-from typing import List, Optional, Union
+from typing import Dict, List, Optional, Set, Tuple, Union
 
 from music_assistant.constants import (
-    CONF_ENABLED,
     CONF_POWER_CONTROL,
     CONF_VOLUME_CONTROL,
     EVENT_PLAYER_ADDED,
     EVENT_PLAYER_REMOVED,
 )
-from music_assistant.helpers.typing import MusicAssistantType
+from music_assistant.helpers.typing import MusicAssistant
 from music_assistant.helpers.util import callback, run_periodic, try_parse_int
 from music_assistant.helpers.web import api_route
 from music_assistant.models.media_types import MediaItem, MediaType
@@ -21,7 +20,6 @@ from music_assistant.models.player import (
     PlayerControlType,
 )
 from music_assistant.models.player_queue import PlayerQueue, QueueItem, QueueOption
-from music_assistant.models.player_state import PlayerState
 from music_assistant.models.provider import PlayerProvider, ProviderType
 
 POLL_INTERVAL = 30
@@ -32,75 +30,77 @@ LOGGER = logging.getLogger("player_manager")
 class PlayerManager:
     """Several helpers to handle playback through player providers."""
 
-    def __init__(self, mass: MusicAssistantType):
+    def __init__(self, mass: MusicAssistant) -> None:
         """Initialize class."""
         self.mass = mass
-        self._player_states = {}
+        self._players = {}
         self._providers = {}
         self._player_queues = {}
         self._poll_ticks = 0
         self._controls = {}
 
-    async def async_setup(self):
+    async def setup(self) -> None:
         """Async initialize of module."""
         self.mass.add_job(self.poll_task())
-        self.mass.web.register_api_route("players", self._player_states.values)
-        self.mass.web.register_api_route("players/queues", self._player_queues.values)
 
-    async def async_close(self):
+    async def close(self) -> None:
         """Handle stop/shutdown."""
-        for player_queue in list(self._player_queues.values()):
-            await player_queue.async_close()
-        for player in self.players:
-            await player.async_on_remove()
+        for player_queue in self._player_queues.values():
+            await player_queue.close()
+        for player in self:
+            await player.on_remove()
 
     @run_periodic(1)
     async def poll_task(self):
         """Check for updates on players that need to be polled."""
-        for player in self.players:
+        for player in self:
+            if not player.player_state.available:
+                continue
             if player.should_poll and (
                 self._poll_ticks >= POLL_INTERVAL
                 or player.state == PlaybackState.Playing
             ):
-                await player.async_on_update()
+                await player.on_poll()
         if self._poll_ticks >= POLL_INTERVAL:
             self._poll_ticks = 0
         else:
             self._poll_ticks += 1
 
     @property
-    def player_states(self) -> List[PlayerState]:
-        """Return PlayerState of all registered players."""
-        return list(self._player_states.values())
+    def players(self) -> Dict[str, Player]:
+        """Return dict of all registered players."""
+        return self._players
 
     @property
-    def players(self) -> List[Player]:
-        """Return all registered players."""
-        return [player_state.player for player_state in self._player_states.values()]
+    def player_queues(self) -> Dict[str, PlayerQueue]:
+        """Return dict of all player queues."""
+        return self._player_queues
 
     @property
-    def player_queues(self) -> List[PlayerQueue]:
-        """Return all player queues."""
-        return list(self._player_queues.values())
-
-    @property
-    def providers(self) -> List[PlayerProvider]:
-        """Return all loaded player providers."""
+    def providers(self) -> Tuple[PlayerProvider]:
+        """Return tuple with all loaded player providers."""
         return self.mass.get_providers(ProviderType.PLAYER_PROVIDER)
 
+    def __iter__(self):
+        """Iterate over players."""
+        return iter(self._players.values())
+
+    @callback
+    @api_route("players")
+    def get_players(self) -> Tuple[Player]:
+        """Return all players in a tuple."""
+        return tuple(self._players.values())
+
     @callback
-    @api_route("players/:player_id")
-    def get_player_state(self, player_id: str) -> PlayerState:
-        """Return PlayerState by player_id or None if player does not exist."""
-        return self._player_states.get(player_id)
+    @api_route("players/queues")
+    def get_player_queues(self) -> Tuple[PlayerQueue]:
+        """Return all player queues in a tuple."""
+        return tuple(self._player_queues.values())
 
     @callback
     def get_player(self, player_id: str) -> Player:
         """Return Player by player_id or None if player does not exist."""
-        player_state = self._player_states.get(player_id)
-        if player_state:
-            return player_state.player
-        return None
+        return self._players.get(player_id)
 
     @callback
     def get_player_provider(self, player_id: str) -> PlayerProvider:
@@ -112,17 +112,18 @@ class PlayerManager:
     @api_route("players/:player_id/queue")
     def get_player_queue(self, player_id: str) -> PlayerQueue:
         """Return player's queue by player_id or None if player does not exist."""
-        player_state = self.get_player_state(player_id)
-        if not player_state:
+        player = self.get_player(player_id)
+        if not player:
             LOGGER.warning("Player(queue) %s is not available!", player_id)
             return None
-        return self._player_queues.get(player_state.active_queue)
+        return self._player_queues.get(player.active_queue)
 
     @callback
     @api_route("players/:queue_id/queue/items")
-    def get_player_queue_items(self, queue_id: str) -> List[QueueItem]:
-        """Return player's queueitems by player_id or None if player does not exist."""
-        return self.get_player_queue(queue_id).items
+    def get_player_queue_items(self, queue_id: str) -> Set[QueueItem]:
+        """Return player's queueitems by player_id."""
+        player_queue = self.get_player_queue(queue_id)
+        return player_queue.items if player_queue else {}
 
     @callback
     @api_route("players/controls/:control_id")
@@ -137,73 +138,68 @@ class PlayerManager:
     @api_route("players/controls")
     def get_player_controls(
         self, filter_type: Optional[PlayerControlType] = None
-    ) -> List[PlayerControl]:
+    ) -> Set[PlayerControl]:
         """Return all PlayerControls, optionally filtered by type."""
-        return [
+        return {
             item
             for item in self._controls.values()
             if (filter_type is None or item.type == filter_type)
-        ]
+        }
 
     # ADD/REMOVE/UPDATE HELPERS
 
-    async def async_add_player(self, player: Player) -> None:
+    async def add_player(self, player: Player) -> None:
         """Register a new player or update an existing one."""
         # guard for invalid data or exit in progress
         if not player or self.mass.exit:
             return
-        # redirect to update if player already exists
-        if player.player_id in self._player_states:
-            return await self.async_update_player(player)
-        # do not add the player to states if it's disabled/unavailable
-        if not self.mass.config.get_player_config(player.player_id)[CONF_ENABLED]:
-            return
-        # set the mass object on the player and call on_add function
+        # redirect to update if player is already added
+        if player.added_to_mass:
+            return await self.trigger_player_update(player.player_id)
+        # make sure that the mass instance is set on the player
         player.mass = self.mass
-        await player.async_on_add()
-        # create playerstate and queue object
-        player_state = PlayerState(self.mass, player)
-        self._player_states[player.player_id] = player_state
-
-        self._player_queues[player.player_id] = PlayerQueue(self.mass, player.player_id)
-        # TODO: turn on player if it was previously turned on ?
-        LOGGER.info(
-            "New player added: %s/%s",
-            player.provider_id,
-            self._player_states[player.player_id].name,
-        )
-        self.mass.signal_event(
-            EVENT_PLAYER_ADDED, self._player_states[player.player_id]
-        )
+        self._players[player.player_id] = player
+        # make sure that the player state is created/updated
+        player.player_state.update(player.create_state())
+        # Fully initialize only if player is enabled
+        if player.enabled:
+            await player.on_add()
+            player.added_to_mass = True
+            # create playerqueue instance
+            self._player_queues[player.player_id] = PlayerQueue(
+                self.mass, player.player_id
+            )
+            LOGGER.info(
+                "Player added: %s/%s",
+                player.provider_id,
+                player.name,
+            )
+            self.mass.signal_event(EVENT_PLAYER_ADDED, player)
+        else:
+            LOGGER.debug(
+                "Ignoring player: %s/%s because it's disabled",
+                player.provider_id,
+                player.name,
+            )
 
-    async def async_remove_player(self, player_id: str):
+    async def remove_player(self, player_id: str):
         """Remove a player from the registry."""
-        player_state = self._player_states.pop(player_id, None)
-        if player_state:
-            await player_state.player.async_on_remove()
         self._player_queues.pop(player_id, None)
-        LOGGER.info("Player removed: %s", player_id)
+        player = self._players.pop(player_id, None)
+        if player:
+            await player.on_remove()
+        player_name = player.name if player else player_id
+        LOGGER.info("Player removed: %s", player_name)
         self.mass.signal_event(EVENT_PLAYER_REMOVED, {"player_id": player_id})
 
-    async def async_update_player(self, player: Player):
-        """Update an existing player (or register as new if non existing)."""
-        if self.mass.exit:
-            return
-        if player.player_id not in self._player_states:
-            return await self.async_add_player(player)
-        await self._player_states[player.player_id].async_update(player)
-
-    async def async_trigger_player_update(self, player_id: str):
+    async def trigger_player_update(self, player_id: str):
         """Trigger update of an existing player.."""
         player = self.get_player(player_id)
-        player_state = self.get_player_state(player_id)
-        if player and player_state:
-            await player_state.async_update(player)
+        if player:
+            await player.on_poll()
 
     @api_route("players/controls/:control_id/register")
-    async def async_register_player_control(
-        self, control_id: str, control: PlayerControl
-    ):
+    async def register_player_control(self, control_id: str, control: PlayerControl):
         """Register a playercontrol with the player manager."""
         control.mass = self.mass
         control.type = PlayerControlType(control.type)
@@ -215,23 +211,19 @@ class PlayerManager:
             control.name,
         )
         # update all players using this playercontrol
-        for player_state in self.player_states:
-            conf = self.mass.config.player_settings[player_state.player_id]
+        for player in self:
+            conf = self.mass.config.player_settings[player.player_id]
             if control_id in [
                 conf.get(CONF_POWER_CONTROL),
                 conf.get(CONF_VOLUME_CONTROL),
             ]:
-                self.mass.add_job(
-                    self.async_trigger_player_update(player_state.player_id)
-                )
+                self.mass.add_job(self.trigger_player_update(player.player_id))
 
     @api_route("players/controls/:control_id/update")
-    async def async_update_player_control(
-        self, control_id: str, control: PlayerControl
-    ):
+    async def update_player_control(self, control_id: str, control: PlayerControl):
         """Update a playercontrol's state on the player manager."""
         if control_id not in self._controls:
-            return await self.async_register_player_control(control_id, control)
+            return await self.register_player_control(control_id, control)
         new_state = control.state
         if self._controls[control_id].state == new_state:
             return
@@ -243,20 +235,18 @@ class PlayerManager:
             new_state,
         )
         # update all players using this playercontrol
-        for player_state in self.player_states:
-            conf = self.mass.config.player_settings[player_state.player_id]
+        for player in self:
+            conf = self.mass.config.player_settings[player.player_id]
             if control_id in [
                 conf.get(CONF_POWER_CONTROL),
                 conf.get(CONF_VOLUME_CONTROL),
             ]:
-                self.mass.add_job(
-                    self.async_trigger_player_update(player_state.player_id)
-                )
+                self.mass.add_job(self.trigger_player_update(player.player_id))
 
     # SERVICE CALLS / PLAYER COMMANDS
 
     @api_route("players/:player_id/play_media")
-    async def async_play_media(
+    async def play_media(
         self,
         player_id: str,
         items: Union[MediaItem, List[MediaItem]],
@@ -280,28 +270,28 @@ class PlayerManager:
         for media_item in items:
             # collect tracks to play
             if media_item.media_type == MediaType.Artist:
-                tracks = await self.mass.music.async_get_artist_toptracks(
+                tracks = await self.mass.music.get_artist_toptracks(
                     media_item.item_id, provider_id=media_item.provider
                 )
             elif media_item.media_type == MediaType.Album:
-                tracks = await self.mass.music.async_get_album_tracks(
+                tracks = await self.mass.music.get_album_tracks(
                     media_item.item_id, provider_id=media_item.provider
                 )
             elif media_item.media_type == MediaType.Playlist:
-                tracks = await self.mass.music.async_get_playlist_tracks(
+                tracks = await self.mass.music.get_playlist_tracks(
                     media_item.item_id, provider_id=media_item.provider
                 )
             elif media_item.media_type == MediaType.Radio:
                 # single radio
                 tracks = [
-                    await self.mass.music.async_get_radio(
+                    await self.mass.music.get_radio(
                         media_item.item_id, provider_id=media_item.provider
                     )
                 ]
             else:
                 # single track
                 tracks = [
-                    await self.mass.music.async_get_track(
+                    await self.mass.music.get_track(
                         media_item.item_id, provider_id=media_item.provider
                     )
                 ]
@@ -317,22 +307,22 @@ class PlayerManager:
                 )
                 queue_items.append(queue_item)
         # turn on player
-        await self.async_cmd_power_on(player_id)
+        await self.cmd_power_on(player_id)
         # load items into the queue
         player_queue = self.get_player_queue(player_id)
         if queue_opt == QueueOption.Replace or (
             len(queue_items) > 10 and queue_opt in [QueueOption.Play, QueueOption.Next]
         ):
-            return await player_queue.async_load(queue_items)
+            return await player_queue.load(queue_items)
         if queue_opt == QueueOption.Next:
-            return await player_queue.async_insert(queue_items, 1)
+            return await player_queue.insert(queue_items, 1)
         if queue_opt == QueueOption.Play:
-            return await player_queue.async_insert(queue_items, 0)
+            return await player_queue.insert(queue_items, 0)
         if queue_opt == QueueOption.Add:
-            return await player_queue.async_append(queue_items)
+            return await player_queue.append(queue_items)
 
     @api_route("players/:player_id/play_uri")
-    async def async_cmd_play_uri(self, player_id: str, uri: str):
+    async def cmd_play_uri(self, player_id: str, uri: str):
         """
         Play the specified uri/url on the given player.
 
@@ -349,190 +339,190 @@ class PlayerManager:
             queue_item.queue_item_id,
         )
         # turn on player
-        await self.async_cmd_power_on(player_id)
+        await self.cmd_power_on(player_id)
         # load item into the queue
         player_queue = self.get_player_queue(player_id)
-        return await player_queue.async_insert([queue_item], 0)
+        return await player_queue.insert([queue_item], 0)
 
     @api_route("players/:player_id/cmd/stop")
-    async def async_cmd_stop(self, player_id: str) -> None:
+    async def cmd_stop(self, player_id: str) -> None:
         """
         Send STOP command to given player.
 
             :param player_id: player_id of the player to handle the command.
         """
-        player_state = self.get_player_state(player_id)
-        if not player_state:
+        player = self.get_player(player_id)
+        if not player:
             return
-        queue_id = player_state.active_queue
+        queue_id = player.active_queue
         queue_player = self.get_player(queue_id)
-        return await queue_player.async_cmd_stop()
+        return await queue_player.cmd_stop()
 
     @api_route("players/:player_id/cmd/play")
-    async def async_cmd_play(self, player_id: str) -> None:
+    async def cmd_play(self, player_id: str) -> None:
         """
         Send PLAY command to given player.
 
             :param player_id: player_id of the player to handle the command.
         """
-        player_state = self.get_player_state(player_id)
-        if not player_state:
+        player = self.get_player(player_id)
+        if not player:
             return
-        queue_id = player_state.active_queue
+        queue_id = player.active_queue
         queue_player = self.get_player(queue_id)
         # unpause if paused else resume queue
         if queue_player.state == PlaybackState.Paused:
-            return await queue_player.async_cmd_play()
+            return await queue_player.cmd_play()
         # power on at play request
-        await self.async_cmd_power_on(player_id)
-        return await self._player_queues[queue_id].async_resume()
+        await self.cmd_power_on(player_id)
+        return await self._player_queues[queue_id].resume()
 
     @api_route("players/:player_id/cmd/pause")
-    async def async_cmd_pause(self, player_id: str):
+    async def cmd_pause(self, player_id: str):
         """
         Send PAUSE command to given player.
 
             :param player_id: player_id of the player to handle the command.
         """
-        player_state = self.get_player_state(player_id)
-        if not player_state:
+        player = self.get_player(player_id)
+        if not player:
             return
-        queue_id = player_state.active_queue
+        queue_id = player.active_queue
         queue_player = self.get_player(queue_id)
-        return await queue_player.async_cmd_pause()
+        return await queue_player.cmd_pause()
 
     @api_route("players/:player_id/cmd/play_pause")
-    async def async_cmd_play_pause(self, player_id: str):
+    async def cmd_play_pause(self, player_id: str):
         """
         Toggle play/pause on given player.
 
             :param player_id: player_id of the player to handle the command.
         """
-        player_state = self.get_player_state(player_id)
-        if not player_state:
+        player = self.get_player(player_id)
+        if not player:
             return
-        if player_state.state == PlaybackState.Playing:
-            return await self.async_cmd_pause(player_id)
-        return await self.async_cmd_play(player_id)
+        if player.state == PlaybackState.Playing:
+            return await self.cmd_pause(player_id)
+        return await self.cmd_play(player_id)
 
     @api_route("players/:player_id/cmd/next")
-    async def async_cmd_next(self, player_id: str):
+    async def cmd_next(self, player_id: str):
         """
         Send NEXT TRACK command to given player.
 
             :param player_id: player_id of the player to handle the command.
         """
-        player_state = self.get_player_state(player_id)
-        if not player_state:
+        player = self.get_player(player_id)
+        if not player:
             return
-        queue_id = player_state.active_queue
-        return await self.get_player_queue(queue_id).async_next()
+        queue_id = player.active_queue
+        return await self.get_player_queue(queue_id).next()
 
     @api_route("players/:player_id/cmd/previous")
-    async def async_cmd_previous(self, player_id: str):
+    async def cmd_previous(self, player_id: str):
         """
         Send PREVIOUS TRACK command to given player.
 
             :param player_id: player_id of the player to handle the command.
         """
-        player_state = self.get_player_state(player_id)
-        if not player_state:
+        player = self.get_player(player_id)
+        if not player:
             return
-        queue_id = player_state.active_queue
-        return await self.get_player_queue(queue_id).async_previous()
+        queue_id = player.active_queue
+        return await self.get_player_queue(queue_id).previous()
 
     @api_route("players/:player_id/cmd/power_on")
-    async def async_cmd_power_on(self, player_id: str) -> None:
+    async def cmd_power_on(self, player_id: str) -> None:
         """
         Send POWER ON command to given player.
 
             :param player_id: player_id of the player to handle the command.
         """
-        player_state = self.get_player_state(player_id)
-        if not player_state:
+        player = self.get_player(player_id)
+        if not player:
             return
-        player_config = self.mass.config.player_settings[player_state.player_id]
+        player_config = self.mass.config.player_settings[player.player_id]
         # turn on player
-        await player_state.player.async_cmd_power_on()
+        await player.cmd_power_on()
         # player control support
         if player_config.get(CONF_POWER_CONTROL):
             control = self.get_player_control(player_config[CONF_POWER_CONTROL])
             if control:
-                await control.async_set_state(True)
+                await control.set_state(True)
 
     @api_route("players/:player_id/cmd/power_off")
-    async def async_cmd_power_off(self, player_id: str) -> None:
+    async def cmd_power_off(self, player_id: str) -> None:
         """
         Send POWER OFF command to given player.
 
             :param player_id: player_id of the player to handle the command.
         """
-        player_state = self.get_player_state(player_id)
-        if not player_state:
+        player = self.get_player(player_id)
+        if not player:
             return
         # send stop if player is playing
-        if player_state.active_queue == player_id and player_state.state in [
+        if player.active_queue == player_id and player.state in [
             PlaybackState.Playing,
             PlaybackState.Paused,
         ]:
-            await self.async_cmd_stop(player_id)
-        player_config = self.mass.config.player_settings[player_state.player_id]
+            await self.cmd_stop(player_id)
+        player_config = self.mass.config.player_settings[player.player_id]
         # turn off player
-        await player_state.player.async_cmd_power_off()
+        await player.cmd_power_off()
         # player control support
         if player_config.get(CONF_POWER_CONTROL):
             control = self.get_player_control(player_config[CONF_POWER_CONTROL])
             if control:
-                await control.async_set_state(False)
+                await control.set_state(False)
         # handle group power
-        if player_state.is_group_player:
+        if player.is_group_player:
             # player is group, turn off all childs
-            for child_player_id in player_state.group_childs:
+            for child_player_id in player.group_childs:
                 child_player = self.get_player(child_player_id)
-                if child_player and child_player.powered:
-                    self.mass.add_job(self.async_cmd_power_off(child_player_id))
+                if child_player and child_player.player_state.powered:
+                    self.mass.add_job(self.cmd_power_off(child_player_id))
         else:
             # if this was the last powered player in the group, turn off group
-            for parent_player_id in player_state.group_parents:
-                parent_player = self.get_player_state(parent_player_id)
-                if not parent_player or not parent_player.powered:
+            for parent_player_id in player.group_parents:
+                parent_player = self.get_player(parent_player_id)
+                if not parent_player or not parent_player.player_state.powered:
                     continue
                 has_powered_players = False
                 for child_player_id in parent_player.group_childs:
                     if child_player_id == player_id:
                         continue
-                    child_player = self.get_player_state(child_player_id)
-                    if child_player and child_player.powered:
+                    child_player = self.get_player(child_player_id)
+                    if child_player and child_player.player_state.powered:
                         has_powered_players = True
                 if not has_powered_players:
-                    self.mass.add_job(self.async_cmd_power_off(parent_player_id))
+                    self.mass.add_job(self.cmd_power_off(parent_player_id))
 
     @api_route("players/:player_id/cmd/power_toggle")
-    async def async_cmd_power_toggle(self, player_id: str):
+    async def cmd_power_toggle(self, player_id: str):
         """
         Send POWER TOGGLE command to given player.
 
             :param player_id: player_id of the player to handle the command.
         """
-        player_state = self.get_player_state(player_id)
-        if not player_state:
+        player = self.get_player(player_id)
+        if not player:
             return
-        if player_state.powered:
-            return await self.async_cmd_power_off(player_id)
-        return await self.async_cmd_power_on(player_id)
+        if player.player_state.powered:
+            return await self.cmd_power_off(player_id)
+        return await self.cmd_power_on(player_id)
 
     @api_route("players/:player_id/cmd/volume_set/:volume_level?")
-    async def async_cmd_volume_set(self, player_id: str, volume_level: int) -> None:
+    async def cmd_volume_set(self, player_id: str, volume_level: int) -> None:
         """
         Send volume level command to given player.
 
             :param player_id: player_id of the player to handle the command.
             :param volume_level: volume level to set (0..100).
         """
-        player_state = self.get_player_state(player_id)
-        if not player_state:
+        player = self.get_player(player_id)
+        if not player:
             return
-        player_config = self.mass.config.player_settings[player_state.player_id]
+        player_config = self.mass.config.player_settings[player.player_id]
         volume_level = try_parse_int(volume_level)
         if volume_level < 0:
             volume_level = 0
@@ -542,86 +532,90 @@ class PlayerManager:
         if player_config.get(CONF_VOLUME_CONTROL):
             control = self.get_player_control(player_config[CONF_VOLUME_CONTROL])
             if control:
-                await control.async_set_state(volume_level)
+                await control.set_state(volume_level)
                 # just force full volume on actual player if volume is outsourced to volumecontrol
-                await player_state.player.async_cmd_volume_set(100)
+                await player.cmd_volume_set(100)
         # handle group volume
-        elif player_state.is_group_player:
-            cur_volume = player_state.volume_level
+        elif player.is_group_player:
+            cur_volume = player.volume_level
             new_volume = volume_level
             volume_dif = new_volume - cur_volume
             if cur_volume == 0:
                 volume_dif_percent = 1 + (new_volume / 100)
             else:
                 volume_dif_percent = volume_dif / cur_volume
-            for child_player_id in player_state.group_childs:
+            for child_player_id in player.group_childs:
                 if child_player_id == player_id:
                     continue
-                child_player = self.get_player_state(child_player_id)
-                if child_player and child_player.available and child_player.powered:
+                child_player = self.get_player(child_player_id)
+                if (
+                    child_player
+                    and child_player.available
+                    and child_player.player_state.powered
+                ):
                     cur_child_volume = child_player.volume_level
                     new_child_volume = cur_child_volume + (
                         cur_child_volume * volume_dif_percent
                     )
-                    await self.async_cmd_volume_set(child_player_id, new_child_volume)
+                    await self.cmd_volume_set(child_player_id, new_child_volume)
         # regular volume command
         else:
-            await player_state.player.async_cmd_volume_set(volume_level)
+            await player.cmd_volume_set(volume_level)
 
     @api_route("players/:player_id/cmd/volume_up")
-    async def async_cmd_volume_up(self, player_id: str):
+    async def cmd_volume_up(self, player_id: str):
         """
         Send volume UP command to given player.
 
             :param player_id: player_id of the player to handle the command.
         """
-        player_state = self.get_player_state(player_id)
-        if not player_state:
+        player = self.get_player(player_id)
+        if not player:
             return
-        if player_state.volume_level <= 10 or player_state.volume_level >= 90:
+        if player.volume_level <= 10 or player.volume_level >= 90:
             step_size = 2
         else:
             step_size = 5
-        new_level = player_state.volume_level + step_size
+        new_level = player.volume_level + step_size
         if new_level > 100:
             new_level = 100
-        return await self.async_cmd_volume_set(player_id, new_level)
+        return await self.cmd_volume_set(player_id, new_level)
 
     @api_route("players/:player_id/cmd/volume_down")
-    async def async_cmd_volume_down(self, player_id: str):
+    async def cmd_volume_down(self, player_id: str):
         """
         Send volume DOWN command to given player.
 
             :param player_id: player_id of the player to handle the command.
         """
-        player_state = self.get_player_state(player_id)
-        if not player_state:
+        player = self.get_player(player_id)
+        if not player:
             return
-        if player_state.volume_level <= 10 or player_state.volume_level >= 90:
+        if player.volume_level <= 10 or player.volume_level >= 90:
             step_size = 2
         else:
             step_size = 5
-        new_level = player_state.volume_level - step_size
+        new_level = player.volume_level - step_size
         if new_level < 0:
             new_level = 0
-        return await self.async_cmd_volume_set(player_id, new_level)
+        return await self.cmd_volume_set(player_id, new_level)
 
     @api_route("players/:player_id/cmd/volume_mute/:is_muted")
-    async def async_cmd_volume_mute(self, player_id: str, is_muted: bool = False):
+    async def cmd_volume_mute(self, player_id: str, is_muted: bool = False):
         """
         Send MUTE command to given player.
 
             :param player_id: player_id of the player to handle the command.
             :param is_muted: bool with the new mute state.
         """
-        player_state = self.get_player_state(player_id)
-        if not player_state:
+        player = self.get_player(player_id)
+        if not player:
             return
         # TODO: handle mute on volumecontrol?
-        return await player_state.player.async_cmd_volume_mute(is_muted)
+        return await player.cmd_volume_mute(is_muted)
 
     @api_route("players/:queue_id/queue/cmd/shuffle_enabled/:enable_shuffle?")
-    async def async_player_queue_cmd_set_shuffle(
+    async def player_queue_cmd_set_shuffle(
         self, queue_id: str, enable_shuffle: bool = False
     ):
         """
@@ -633,10 +627,10 @@ class PlayerManager:
         player_queue = self.get_player_queue(queue_id)
         if not player_queue:
             return
-        return await player_queue.async_set_shuffle_enabled(enable_shuffle)
+        return await player_queue.set_shuffle_enabled(enable_shuffle)
 
     @api_route("players/:queue_id/queue/cmd/repeat_enabled/:enable_repeat?")
-    async def async_player_queue_cmd_set_repeat(
+    async def player_queue_cmd_set_repeat(
         self, queue_id: str, enable_repeat: bool = False
     ):
         """
@@ -648,10 +642,10 @@ class PlayerManager:
         player_queue = self.get_player_queue(queue_id)
         if not player_queue:
             return
-        return await player_queue.async_set_repeat_enabled(enable_repeat)
+        return await player_queue.set_repeat_enabled(enable_repeat)
 
     @api_route("players/:queue_id/queue/cmd/next")
-    async def async_player_queue_cmd_next(self, queue_id: str):
+    async def player_queue_cmd_next(self, queue_id: str):
         """
         Send next track command to given playerqueue.
 
@@ -660,10 +654,10 @@ class PlayerManager:
         player_queue = self.get_player_queue(queue_id)
         if not player_queue:
             return
-        return await player_queue.async_next()
+        return await player_queue.next()
 
     @api_route("players/:queue_id/queue/cmd/previous")
-    async def async_player_queue_cmd_previous(self, queue_id: str):
+    async def player_queue_cmd_previous(self, queue_id: str):
         """
         Send previous track command to given playerqueue.
 
@@ -672,10 +666,10 @@ class PlayerManager:
         player_queue = self.get_player_queue(queue_id)
         if not player_queue:
             return
-        return await player_queue.async_previous()
+        return await player_queue.previous()
 
     @api_route("players/:queue_id/queue/cmd/move/:queue_item_id?/:pos_shift?")
-    async def async_player_queue_cmd_move_item(
+    async def player_queue_cmd_move_item(
         self, queue_id: str, queue_item_id: str, pos_shift: int = 1
     ):
         """
@@ -688,20 +682,18 @@ class PlayerManager:
         player_queue = self.get_player_queue(queue_id)
         if not player_queue:
             return
-        return await player_queue.async_move_item(queue_item_id, pos_shift)
+        return await player_queue.move_item(queue_item_id, pos_shift)
 
     @api_route("players/:queue_id/queue/cmd/play_index/:index?")
-    async def async_play_index(self, queue_id: str, index: Union[int, str]) -> None:
+    async def play_index(self, queue_id: str, index: Union[int, str]) -> None:
         """Play item at index (or item_id) X in queue."""
         player_queue = self.get_player_queue(queue_id)
         if not player_queue:
             return
-        return await player_queue.async_play_index(index)
+        return await player_queue.play_index(index)
 
     @api_route("players/:queue_id/queue/cmd/clear")
-    async def async_player_queue_cmd_clear(
-        self, queue_id: str, enable_repeat: bool = False
-    ):
+    async def player_queue_cmd_clear(self, queue_id: str, enable_repeat: bool = False):
         """
         Clear all items in player's queue.
 
@@ -710,20 +702,18 @@ class PlayerManager:
         player_queue = self.get_player_queue(queue_id)
         if not player_queue:
             return
-        return await player_queue.async_clear()
+        return await player_queue.clear()
 
     # OTHER/HELPER FUNCTIONS
 
-    async def async_get_gain_correct(
-        self, player_id: str, item_id: str, provider_id: str
-    ):
+    async def get_gain_correct(self, player_id: str, item_id: str, provider_id: str):
         """Get gain correction for given player / track combination."""
         player_conf = self.mass.config.get_player_config(player_id)
         if not player_conf["volume_normalisation"]:
             return 0
         target_gain = int(player_conf["target_volume"])
         fallback_gain = int(player_conf["fallback_gain_correct"])
-        track_loudness = await self.mass.database.async_get_track_loudness(
+        track_loudness = await self.mass.database.get_track_loudness(
             item_id, provider_id
         )
         if track_loudness is None:
index 0ca98e6d308e4fc95896edcdfb8d41121e2555a8..4900bb84228ba65b1f89b14468d5ed9e17b8cfd5 100755 (executable)
@@ -21,7 +21,7 @@ from music_assistant.constants import (
     EVENT_STREAM_STARTED,
 )
 from music_assistant.helpers.process import AsyncProcess
-from music_assistant.helpers.typing import MusicAssistantType
+from music_assistant.helpers.typing import MusicAssistant
 from music_assistant.helpers.util import create_tempfile, get_ip
 from music_assistant.models.streamdetails import ContentType, StreamDetails, StreamType
 
@@ -42,13 +42,13 @@ class SoxOutputFormat(Enum):
 class StreamManager:
     """Built-in streamer utilizing SoX."""
 
-    def __init__(self, mass: MusicAssistantType) -> None:
+    def __init__(self, mass: MusicAssistant) -> None:
         """Initialize class."""
         self.mass = mass
         self.local_ip = get_ip()
         self.analyze_jobs = {}
 
-    async def async_get_sox_stream(
+    async def get_sox_stream(
         self,
         streamdetails: StreamDetails,
         output_format: SoxOutputFormat = SoxOutputFormat.FLAC,
@@ -86,7 +86,7 @@ class StreamManager:
             async def fill_buffer():
                 """Forward audio chunks to sox stdin."""
                 # feed audio data into sox stdin for processing
-                async for chunk in self.async_get_media_stream(streamdetails):
+                async for chunk in self.get_media_stream(streamdetails):
                     await sox_proc.write(chunk)
                 await sox_proc.write_eof()
 
@@ -109,7 +109,7 @@ class StreamManager:
                 streamdetails.item_id,
             )
 
-    async def async_queue_stream_flac(self, player_id) -> AsyncGenerator[bytes, None]:
+    async def queue_stream_flac(self, player_id) -> AsyncGenerator[bytes, None]:
         """Stream the PlayerQueue's tracks as constant feed in flac format."""
         player_conf = self.mass.config.get_player_config(player_id)
         sample_rate = player_conf.get(CONF_MAX_SAMPLE_RATE, 96000)
@@ -132,9 +132,7 @@ class StreamManager:
             # feed stdin with pcm samples
             async def fill_buffer():
                 """Feed audio data into sox stdin for processing."""
-                async for chunk in self.async_queue_stream_pcm(
-                    player_id, sample_rate, 32
-                ):
+                async for chunk in self.queue_stream_pcm(player_id, sample_rate, 32):
                     await sox_proc.write(chunk)
 
             fill_buffer_task = self.mass.loop.create_task(fill_buffer())
@@ -144,7 +142,7 @@ class StreamManager:
                 yield chunk
             await asyncio.wait([fill_buffer_task])
 
-    async def async_queue_stream_pcm(
+    async def queue_stream_pcm(
         self, player_id, sample_rate=96000, bit_depth=32
     ) -> AsyncGenerator[bytes, None]:
         """Stream the PlayerQueue's tracks as constant feed in PCM raw audio."""
@@ -159,9 +157,9 @@ class StreamManager:
             # get the (next) track in queue
             if queue_index is None:
                 # report start of queue playback so we can calculate current track/duration etc.
-                queue_index = await player_queue.async_queue_stream_start()
+                queue_index = await player_queue.queue_stream_start()
             else:
-                queue_index = await player_queue.async_queue_stream_next(queue_index)
+                queue_index = await player_queue.queue_stream_next(queue_index)
             queue_track = player_queue.get_item(queue_index)
             if not queue_track:
                 LOGGER.info("no (more) tracks left in queue")
@@ -174,11 +172,11 @@ class StreamManager:
             buffer_size = sample_size * fade_length if fade_length else sample_size * 10
 
             # get streamdetails
-            streamdetails = await self.mass.music.async_get_stream_details(
+            streamdetails = await self.mass.music.get_stream_details(
                 queue_track, player_id
             )
             # get gain correct / replaygain
-            gain_correct = await self.mass.players.async_get_gain_correct(
+            gain_correct = await self.mass.players.get_gain_correct(
                 player_id, streamdetails.item_id, streamdetails.provider
             )
             streamdetails.gain_correct = gain_correct
@@ -194,7 +192,7 @@ class StreamManager:
             prev_chunk = None
             bytes_written = 0
             # handle incoming audio chunks
-            async for is_last_chunk, chunk in self.mass.streams.async_get_sox_stream(
+            async for is_last_chunk, chunk in self.mass.streams.get_sox_stream(
                 streamdetails,
                 SoxOutputFormat.S32,
                 resample=sample_rate,
@@ -207,7 +205,7 @@ class StreamManager:
                 if not chunk and bytes_written == 0:
                     # stream error: got empy first chunk
                     # prevent player queue get stuck by sending next track command
-                    self.mass.add_job(player_queue.async_next())
+                    self.mass.add_job(player_queue.next())
                     LOGGER.error("Stream error on track %s", queue_track.item_id)
                     return
                 if cur_chunk <= 2 and not last_fadeout_data:
@@ -221,7 +219,7 @@ class StreamManager:
                 # HANDLE CROSSFADE OF PREVIOUS TRACK FADE_OUT AND THIS TRACK FADE_IN
                 elif cur_chunk == 2 and last_fadeout_data:
                     # combine the first 2 chunks and strip off silence
-                    first_part = await async_strip_silence(prev_chunk + chunk, pcm_args)
+                    first_part = await strip_silence(prev_chunk + chunk, pcm_args)
                     if len(first_part) < buffer_size:
                         # part is too short after the strip action?!
                         # so we just use the full first part
@@ -230,7 +228,7 @@ class StreamManager:
                     remaining_bytes = first_part[buffer_size:]
                     del first_part
                     # do crossfade
-                    crossfade_part = await async_crossfade_pcm_parts(
+                    crossfade_part = await crossfade_pcm_parts(
                         fade_in_part, last_fadeout_data, pcm_args, fade_length
                     )
                     # send crossfade_part
@@ -250,9 +248,7 @@ class StreamManager:
                     # last chunk received so create the last_part
                     # with the previous chunk and this chunk
                     # and strip off silence
-                    last_part = await async_strip_silence(
-                        prev_chunk + chunk, pcm_args, True
-                    )
+                    last_part = await strip_silence(prev_chunk + chunk, pcm_args, True)
                     if len(last_part) < buffer_size:
                         # part is too short after the strip action
                         # so we just use the entire original data
@@ -313,7 +309,7 @@ class StreamManager:
         self.mass.add_job(gc.collect)
         LOGGER.info("streaming of queue for player %s completed", player_id)
 
-    async def async_stream_queue_item(
+    async def stream_queue_item(
         self, player_id: str, queue_item_id: str
     ) -> AsyncGenerator[bytes, None]:
         """Stream a single Queue item."""
@@ -324,32 +320,30 @@ class StreamManager:
         queue_item = player_queue.by_item_id(queue_item_id)
         if not queue_item:
             raise FileNotFoundError("invalid queue_item_id")
-        streamdetails = await self.mass.music.async_get_stream_details(
-            queue_item, player_id
-        )
+        streamdetails = await self.mass.music.get_stream_details(queue_item, player_id)
 
         # get gain correct / replaygain
-        gain_correct = await self.mass.players.async_get_gain_correct(
+        gain_correct = await self.mass.players.get_gain_correct(
             player_id, streamdetails.item_id, streamdetails.provider
         )
         streamdetails.gain_correct = gain_correct
 
         # start streaming
         LOGGER.debug("Start streaming %s (%s)", queue_item_id, queue_item.name)
-        async for _, audio_chunk in self.async_get_sox_stream(
+        async for _, audio_chunk in self.get_sox_stream(
             streamdetails, gain_db_adjust=gain_correct, chunk_size=4000000
         ):
             yield audio_chunk
         LOGGER.debug("Finished streaming %s (%s)", queue_item_id, queue_item.name)
 
-    async def async_get_media_stream(
+    async def get_media_stream(
         self, streamdetails: StreamDetails
     ) -> AsyncGenerator[bytes, None]:
         """Get the (original/untouched) audio data for the given streamdetails. Generator."""
         stream_path = streamdetails.path
         stream_type = StreamType(streamdetails.type)
         audio_data = b""
-        track_loudness = await self.mass.database.async_get_track_loudness(
+        track_loudness = await self.mass.database.get_track_loudness(
             streamdetails.item_id, streamdetails.provider
         )
         needs_analyze = track_loudness is None
@@ -397,7 +391,7 @@ class StreamManager:
             streamdetails.provider,
             streamdetails.item_id,
         )
-        await self.mass.database.async_mark_item_played(
+        await self.mass.database.mark_item_played(
             streamdetails.item_id, streamdetails.provider
         )
 
@@ -416,7 +410,7 @@ class StreamManager:
 
         # get track loudness
         track_loudness = self.mass.add_job(
-            self.mass.database.async_get_track_loudness(
+            self.mass.database.get_track_loudness(
                 streamdetails.item_id, streamdetails.provider
             )
         ).result()
@@ -433,7 +427,7 @@ class StreamManager:
             )
             loudness = float(value.decode().strip())
             self.mass.add_job(
-                self.mass.database.async_set_track_loudness(
+                self.mass.database.set_track_loudness(
                     streamdetails.item_id, streamdetails.provider, loudness
                 )
             )
@@ -442,7 +436,7 @@ class StreamManager:
         self.analyze_jobs.pop(item_key, None)
 
 
-async def async_crossfade_pcm_parts(
+async def crossfade_pcm_parts(
     fade_in_part: bytes, fade_out_part: bytes, pcm_args: List[str], fade_length: int
 ) -> bytes:
     """Crossfade two chunks of pcm/raw audio using sox."""
@@ -471,9 +465,7 @@ async def async_crossfade_pcm_parts(
     return crossfade_part
 
 
-async def async_strip_silence(
-    audio_data: bytes, pcm_args: List[str], reverse=False
-) -> bytes:
+async def strip_silence(audio_data: bytes, pcm_args: List[str], reverse=False) -> bytes:
     """Strip silence from (a chunk of) pcm audio."""
     args = ["sox", "--ignore-length", "-t"] + pcm_args + ["-", "-t"] + pcm_args + ["-"]
     if reverse:
index 0823fc6d393c0402977c68cf259b3aa240e06de8..7fe71d7031c10daa2c09a9f04bac6765d6074feb 100644 (file)
@@ -6,7 +6,7 @@ import importlib
 import logging
 import os
 import threading
-from typing import Any, Awaitable, Callable, Coroutine, Dict, List, Optional, Union
+from typing import Any, Awaitable, Callable, Coroutine, Dict, Optional, Tuple, Union
 
 import aiohttp
 from music_assistant.constants import (
@@ -37,13 +37,16 @@ def global_exception_handler(loop: asyncio.AbstractEventLoop, context: Dict) ->
     LOGGER.exception(
         "Caught exception: %s", context.get("exception", context["message"])
     )
+    if "Broken pipe" in str(context.get("exception")):
+        # fix for the spamming subprocess
+        return
     loop.default_exception_handler(context)
 
 
 class MusicAssistant:
     """Main MusicAssistant object."""
 
-    def __init__(self, datapath: str, debug: bool = False, port: int = 8095):
+    def __init__(self, datapath: str, debug: bool = False, port: int = 8095) -> None:
         """
         Create an instance of MusicAssistant.
 
@@ -71,7 +74,7 @@ class MusicAssistant:
         # shared zeroconf instance
         self.zeroconf = Zeroconf(interfaces=InterfaceChoice.All)
 
-    async def async_start(self):
+    async def start(self) -> None:
         """Start running the music assistant server."""
         # initialize loop
         self._loop = asyncio.get_event_loop()
@@ -84,26 +87,26 @@ class MusicAssistant:
         )
         # run migrations if needed
         await check_migrations(self)
-        await self._config.async_setup()
-        await self._cache.async_setup()
-        await self._music.async_setup()
-        await self._players.async_setup()
-        await self.__async_preload_providers()
-        await self.async_setup_discovery()
-        await self._web.async_setup()
-        await self._library.async_setup()
+        await self._config.setup()
+        await self._cache.setup()
+        await self._music.setup()
+        await self._players.setup()
+        await self._preload_providers()
+        await self.setup_discovery()
+        await self._web.setup()
+        await self._library.setup()
         self.loop.create_task(self.__process_background_tasks())
 
-    async def async_stop(self):
+    async def stop(self) -> None:
         """Stop running the music assistant server."""
         self._exit = True
         LOGGER.info("Application shutdown")
         self.signal_event(EVENT_SHUTDOWN)
-        await self.config.async_close()
-        await self._web.async_stop()
+        await self.config.close()
+        await self._web.stop()
         for prov in self._providers.values():
-            await prov.async_on_stop()
-        await self._players.async_close()
+            await prov.on_stop()
+        await self._players.close()
         await self._http_session.connector.close()
         self._http_session.detach()
 
@@ -167,7 +170,7 @@ class MusicAssistant:
         """Return the default http session."""
         return self._http_session
 
-    async def async_register_provider(self, provider: Provider) -> None:
+    async def register_provider(self, provider: Provider) -> None:
         """Register a new Provider/Plugin."""
         assert provider.id and provider.name
         if provider.id in self._providers:
@@ -177,7 +180,7 @@ class MusicAssistant:
         provider.available = False
         self._providers[provider.id] = provider
         if self.config.get_provider_config(provider.id, provider.type)[CONF_ENABLED]:
-            if await provider.async_on_start() is not False:
+            if await provider.on_start() is not False:
                 provider.available = True
                 LOGGER.debug("Provider registered: %s", provider.name)
                 self.signal_event(EVENT_PROVIDER_REGISTERED, provider.id)
@@ -188,24 +191,24 @@ class MusicAssistant:
         else:
             LOGGER.debug("Not loading provider %s as it is disabled", provider.name)
 
-    async def async_unregister_provider(self, provider_id: str) -> None:
+    async def unregister_provider(self, provider_id: str) -> None:
         """Unregister an existing Provider/Plugin."""
         if provider_id in self._providers:
             # unload it if it's loaded
-            await self._providers[provider_id].async_on_stop()
+            await self._providers[provider_id].on_stop()
             LOGGER.debug("Provider unregistered: %s", provider_id)
             self.signal_event(EVENT_PROVIDER_UNREGISTERED, provider_id)
         return self._providers.pop(provider_id, None)
 
-    async def async_reload_provider(self, provider_id: str) -> None:
+    async def reload_provider(self, provider_id: str) -> None:
         """Reload an existing Provider/Plugin."""
-        provider = await self.async_unregister_provider(provider_id)
+        provider = await self.unregister_provider(provider_id)
         if provider is not None:
             # simply re-register the same provider again
-            await self.async_register_provider(provider)
+            await self.register_provider(provider)
         else:
             # try preloading all providers
-            self.add_job(self.__async_preload_providers())
+            self.add_job(self._preload_providers())
 
     @callback
     def get_provider(self, provider_id: str) -> Provider:
@@ -220,14 +223,14 @@ class MusicAssistant:
         self,
         filter_type: Optional[ProviderType] = None,
         include_unavailable: bool = False,
-    ) -> List[Provider]:
+    ) -> Tuple[Provider]:
         """Return all providers, optionally filtered by type."""
-        return [
+        return (
             item
             for item in self._providers.values()
             if (filter_type is None or item.type == filter_type)
             and (include_unavailable or item.available)
-        ]
+        )
 
     @callback
     def signal_event(self, event_msg: str, event_details: Any = None) -> None:
@@ -245,7 +248,7 @@ class MusicAssistant:
     def add_event_listener(
         self,
         cb_func: Callable[..., Union[None, Awaitable]],
-        event_filter: Union[None, str, List] = None,
+        event_filter: Union[None, str, Tuple] = None,
     ) -> Callable:
         """
         Add callback to event listeners.
@@ -321,10 +324,10 @@ class MusicAssistant:
             await task
             await asyncio.sleep(1)
 
-    async def async_setup_discovery(self) -> None:
+    async def setup_discovery(self) -> None:
         """Make this Music Assistant instance discoverable on the network."""
 
-        def setup_discovery():
+        def _setup_discovery():
             zeroconf_type = "_music-assistant._tcp.local."
 
             info = ServiceInfo(
@@ -348,9 +351,9 @@ class MusicAssistant:
                     "Music Assistant instance with identical name present in the local network!"
                 )
 
-        self.add_job(setup_discovery)
+        self.add_job(_setup_discovery)
 
-    async def __async_preload_providers(self):
+    async def _preload_providers(self) -> None:
         """Dynamically load all providermodules."""
         base_dir = os.path.dirname(os.path.abspath(__file__))
         modules_path = os.path.join(base_dir, "providers")
@@ -374,7 +377,7 @@ class MusicAssistant:
                     prov_mod = importlib.import_module(
                         f".{module_name}", "music_assistant.providers"
                     )
-                    await prov_mod.async_setup(self)
+                    await prov_mod.setup(self)
                 # pylint: disable=broad-except
                 except Exception as exc:
                     LOGGER.exception("Error preloading module %s: %s", module_name, exc)
index 9add64232049763e8a48610daa233cc1e523032c..b505ca9c8b8772a13c4938e3c7616bc8bcaaed64 100755 (executable)
@@ -2,7 +2,7 @@
 
 from dataclasses import dataclass, field
 from enum import Enum, IntEnum
-from typing import Any, List, Mapping
+from typing import Any, Dict, List, Mapping, Set
 
 import ujson
 from mashumaro import DataClassDictMixin
@@ -59,6 +59,10 @@ class MediaItemProviderId(DataClassDictMixin):
     details: str = None
     available: bool = True
 
+    def __hash__(self):
+        """Return custom hash."""
+        return hash((self.provider, self.item_id, self.quality))
+
 
 @dataclass
 class MediaItem(DataClassDictMixin):
@@ -67,8 +71,8 @@ class MediaItem(DataClassDictMixin):
     item_id: str = ""
     provider: str = ""
     name: str = ""
-    metadata: Any = field(default_factory=dict)
-    provider_ids: List[MediaItemProviderId] = field(default_factory=list)
+    metadata: Dict[str, Any] = field(default_factory=dict)
+    provider_ids: Set[MediaItemProviderId] = field(default_factory=set)
     in_library: bool = False
     media_type: MediaType = MediaType.Track
 
@@ -115,18 +119,24 @@ class MediaItem(DataClassDictMixin):
     @property
     def available(self):
         """Return (calculated) availability."""
-        for item in self.provider_ids:
-            if item.available:
-                return True
+        return any(x.available for x in self.provider_ids)
+
+    def __hash__(self):
+        """Return custom hash."""
+        return hash((self.media_type, self.provider, self.item_id))
 
 
 @dataclass
-class Artist(MediaItem):
+class Artist(MediaItem, DataClassDictMixin):
     """Model for an artist."""
 
     media_type: MediaType = MediaType.Artist
     musicbrainz_id: str = ""
 
+    def __hash__(self):
+        """Return custom hash."""
+        return hash((self.media_type, self.provider, self.item_id))
+
 
 @dataclass
 class ItemMapping(DataClassDictMixin):
@@ -142,9 +152,13 @@ class ItemMapping(DataClassDictMixin):
         """Create ItemMapping object from regular item."""
         return cls.from_dict(item.to_dict())
 
+    def __hash__(self):
+        """Return custom hash."""
+        return hash((self.media_type, self.provider, self.item_id))
+
 
 @dataclass
-class Album(MediaItem):
+class Album(MediaItem, DataClassDictMixin):
     """Model for an album."""
 
     media_type: MediaType = MediaType.Album
@@ -154,6 +168,10 @@ class Album(MediaItem):
     album_type: AlbumType = AlbumType.Unknown
     upc: str = ""
 
+    def __hash__(self):
+        """Return custom hash."""
+        return hash((self.media_type, self.provider, self.item_id))
+
 
 @dataclass
 class FullAlbum(Album):
@@ -161,6 +179,10 @@ class FullAlbum(Album):
 
     artist: Artist = None
 
+    def __hash__(self):
+        """Return custom hash."""
+        return hash((self.media_type, self.provider, self.item_id))
+
 
 @dataclass
 class Track(MediaItem):
@@ -170,8 +192,8 @@ class Track(MediaItem):
     duration: int = 0
     version: str = ""
     isrc: str = ""
-    artists: List[ItemMapping] = field(default_factory=list)
-    albums: List[ItemMapping] = field(default_factory=list)
+    artists: Set[ItemMapping] = field(default_factory=set)
+    albums: Set[ItemMapping] = field(default_factory=set)
     # album track only
     album: ItemMapping = None
     disc_number: int = 0
@@ -179,15 +201,23 @@ class Track(MediaItem):
     # playlist track only
     position: int = 0
 
+    def __hash__(self):
+        """Return custom hash."""
+        return hash((self.media_type, self.provider, self.item_id))
+
 
 @dataclass
 class FullTrack(Track):
     """Model for an album with full details."""
 
-    artists: List[Artist] = field(default_factory=list)
-    albums: List[Album] = field(default_factory=list)
+    artists: Set[Artist] = field(default_factory=set)
+    albums: Set[Album] = field(default_factory=set)
     album: Album = None
 
+    def __hash__(self):
+        """Return custom hash."""
+        return hash((self.media_type, self.provider, self.item_id))
+
 
 @dataclass
 class Playlist(MediaItem):
@@ -198,6 +228,10 @@ class Playlist(MediaItem):
     checksum: str = ""  # some value to detect playlist track changes
     is_editable: bool = False
 
+    def __hash__(self):
+        """Return custom hash."""
+        return hash((self.media_type, self.provider, self.item_id))
+
 
 @dataclass
 class Radio(MediaItem):
@@ -206,6 +240,10 @@ class Radio(MediaItem):
     media_type: MediaType = MediaType.Radio
     duration: int = 86400
 
+    def __hash__(self):
+        """Return custom hash."""
+        return hash((self.media_type, self.provider, self.item_id))
+
 
 @dataclass
 class SearchResult(DataClassDictMixin):
index 56c5c5e880b94e27d47caf0530ae9a0006e7fe25..c2cdb3cbcbd24b79920cd8017688d1855b8fd7b2 100755 (executable)
@@ -1,12 +1,20 @@
 """Models and helpers for a player."""
 
 from abc import abstractmethod
-from dataclasses import dataclass
+from dataclasses import dataclass, field
+from datetime import datetime
 from enum import Enum, IntEnum
-from typing import Any, List, Optional
+from typing import Any, Optional, Set
 
 from mashumaro import DataClassDictMixin
-from music_assistant.helpers.typing import MusicAssistantType, QueueItems
+from music_assistant.constants import (
+    CONF_ENABLED,
+    CONF_NAME,
+    CONF_POWER_CONTROL,
+    CONF_VOLUME_CONTROL,
+    EVENT_PLAYER_CHANGED,
+)
+from music_assistant.helpers.typing import ConfigSubItem, MusicAssistant, QueueItems
 from music_assistant.helpers.util import callback
 from music_assistant.models.config_entry import ConfigEntry
 
@@ -37,11 +45,77 @@ class PlayerFeature(IntEnum):
     CROSSFADE = 2
 
 
+class PlayerControlType(Enum):
+    """Enum with different player control types."""
+
+    POWER = 0
+    VOLUME = 1
+    UNKNOWN = 99
+
+
+@dataclass
+class PlayerControl(DataClassDictMixin):
+    """
+    Model for a player control.
+
+    Allows for a plugin-like
+    structure to override common player commands.
+    """
+
+    # pylint: disable=no-member
+
+    type: PlayerControlType = PlayerControlType.UNKNOWN
+    control_id: str = ""
+    provider: str = ""
+    name: str = ""
+    state: Any = None
+
+    async def set_state(self, new_state: Any) -> None:
+        """Handle command to set the state for a player control."""
+        # by default we just signal an event on the eventbus
+        # pickup this event (e.g. from the websocket api)
+        # or override this method with your own implementation.
+
+        self.mass.signal_event(f"players/controls/{self.control_id}/state", new_state)
+
+
+@dataclass
+class PlayerState(DataClassDictMixin):
+    """Model for a (calculated) player state."""
+
+    player_id: str = None
+    provider_id: str = None
+    name: str = None
+    powered: bool = False
+    state: PlaybackState = PlaybackState.Off
+    available: bool = False
+    volume_level: int = 0
+    elapsed_time: int = 0
+    muted: bool = False
+    is_group_player: bool = False
+    group_childs: Set[str] = field(default_factory=set)
+    device_info: DeviceInfo = field(default_factory=DeviceInfo)
+    updated_at: datetime = None
+    group_parents: Set[str] = field(default_factory=set)
+    features: Set[PlayerFeature] = field(default_factory=set)
+    active_queue: str = None
+
+    def update(self, new_obj: "PlayerState") -> Set[str]:
+        """Update state from other PlayerState instance and return changed keys."""
+        changed_keys = set()
+        # pylint: disable=no-member
+        for key in self.__dataclass_fields__.keys():
+            new_val = getattr(new_obj, key)
+            if getattr(self, key) != new_val:
+                setattr(self, key, new_val)
+                if key != "updated_at":
+                    changed_keys.add(key)
+        return changed_keys
+
+
 class Player:
     """Model for a music player."""
 
-    mass: MusicAssistantType = None  # will be set by player manager
-
     # Public properties: should be overriden with provider specific implementation
 
     @property
@@ -120,9 +194,9 @@ class Player:
         return False
 
     @property
-    def group_childs(self) -> List[str]:
+    def group_childs(self) -> Set[str]:
         """Return list of child player id's if player is a group player."""
-        return []
+        return {}
 
     @property
     def device_info(self) -> DeviceInfo:
@@ -135,28 +209,28 @@ class Player:
         return False
 
     @property
-    def features(self) -> List[PlayerFeature]:
+    def features(self) -> Set[PlayerFeature]:
         """Return list of features this player supports."""
-        return []
+        return {}
 
     @property
-    def config_entries(self) -> List[ConfigEntry]:
+    def config_entries(self) -> Set[ConfigEntry]:
         """Return player specific config entries (if any)."""
-        return []
+        return {}
 
     # Public methods / player commands: should be overriden with provider specific implementation
 
-    async def async_on_update(self) -> None:
+    async def on_poll(self) -> None:
         """Call when player is periodically polled by the player manager (should_poll=True)."""
         self.update_state()
 
-    async def async_on_add(self) -> None:
+    async def on_add(self) -> None:
         """Call when player is added to the player manager."""
 
-    async def async_on_remove(self) -> None:
+    async def on_remove(self) -> None:
         """Call when player is removed from the player manager."""
 
-    async def async_cmd_play_uri(self, uri: str) -> None:
+    async def cmd_play_uri(self, uri: str) -> None:
         """
         Play the specified uri/url on the player.
 
@@ -164,35 +238,35 @@ class Player:
         """
         raise NotImplementedError
 
-    async def async_cmd_stop(self) -> None:
+    async def cmd_stop(self) -> None:
         """Send STOP command to player."""
         raise NotImplementedError
 
-    async def async_cmd_play(self) -> None:
+    async def cmd_play(self) -> None:
         """Send PLAY command to player."""
         raise NotImplementedError
 
-    async def async_cmd_pause(self) -> None:
+    async def cmd_pause(self) -> None:
         """Send PAUSE command to player."""
         raise NotImplementedError
 
-    async def async_cmd_next(self) -> None:
+    async def cmd_next(self) -> None:
         """Send NEXT TRACK command to player."""
         raise NotImplementedError
 
-    async def async_cmd_previous(self) -> None:
+    async def cmd_previous(self) -> None:
         """Send PREVIOUS TRACK command to player."""
         raise NotImplementedError
 
-    async def async_cmd_power_on(self) -> None:
+    async def cmd_power_on(self) -> None:
         """Send POWER ON command to player."""
         raise NotImplementedError
 
-    async def async_cmd_power_off(self) -> None:
+    async def cmd_power_off(self) -> None:
         """Send POWER OFF command to player."""
         raise NotImplementedError
 
-    async def async_cmd_volume_set(self, volume_level: int) -> None:
+    async def cmd_volume_set(self, volume_level: int) -> None:
         """
         Send volume level command to player.
 
@@ -200,7 +274,7 @@ class Player:
         """
         raise NotImplementedError
 
-    async def async_cmd_volume_mute(self, is_muted: bool = False) -> None:
+    async def cmd_volume_mute(self, is_muted: bool = False) -> None:
         """
         Send volume MUTE command to given player.
 
@@ -210,7 +284,7 @@ class Player:
 
     # OPTIONAL: QUEUE SERVICE CALLS/COMMANDS - OVERRIDE ONLY IF SUPPORTED BY PROVIDER
 
-    async def async_cmd_queue_play_index(self, index: int) -> None:
+    async def cmd_queue_play_index(self, index: int) -> None:
         """
         Play item at index X on player's queue.
 
@@ -219,7 +293,7 @@ class Player:
         if PlayerFeature.QUEUE in self.features:
             raise NotImplementedError
 
-    async def async_cmd_queue_load(self, queue_items: QueueItems) -> None:
+    async def cmd_queue_load(self, queue_items: QueueItems) -> None:
         """
         Load/overwrite given items in the player's queue implementation.
 
@@ -228,7 +302,7 @@ class Player:
         if PlayerFeature.QUEUE in self.features:
             raise NotImplementedError
 
-    async def async_cmd_queue_insert(
+    async def cmd_queue_insert(
         self, queue_items: QueueItems, insert_at_index: int
     ) -> None:
         """
@@ -241,7 +315,7 @@ class Player:
         if PlayerFeature.QUEUE in self.features:
             raise NotImplementedError
 
-    async def async_cmd_queue_append(self, queue_items: QueueItems) -> None:
+    async def cmd_queue_append(self, queue_items: QueueItems) -> None:
         """
         Append new items at the end of the queue.
 
@@ -250,7 +324,7 @@ class Player:
         if PlayerFeature.QUEUE in self.features:
             raise NotImplementedError
 
-    async def async_cmd_queue_update(self, queue_items: QueueItems) -> None:
+    async def cmd_queue_update(self, queue_items: QueueItems) -> None:
         """
         Overwrite the existing items in the queue, used for reordering.
 
@@ -259,48 +333,189 @@ class Player:
         if PlayerFeature.QUEUE in self.features:
             raise NotImplementedError
 
-    async def async_cmd_queue_clear(self) -> None:
+    async def cmd_queue_clear(self) -> None:
         """Clear the player's queue."""
         if PlayerFeature.QUEUE in self.features:
             raise NotImplementedError
 
-    # Do not override below this point
+    # Private properties and methods
+    # Do not override below this point!
 
-    @callback
-    def update_state(self) -> None:
-        """Call to store current player state in the player manager."""
-        self.mass.add_job(self.mass.players.async_update_player(self))
+    @property
+    def active_queue(self) -> str:
+        """Return the active parent player/queue for a player."""
+        return self._cur_state.active_queue or self.player_id
 
+    @property
+    def group_parents(self) -> Set[str]:
+        """Return all groups this player belongs to."""
+        return self._cur_state.group_parents
 
-class PlayerControlType(Enum):
-    """Enum with different player control types."""
+    @property
+    def config(self) -> ConfigSubItem:
+        """Return this player's configuration."""
+        return self.mass.config.get_player_config(self.player_id)
 
-    POWER = 0
-    VOLUME = 1
-    UNKNOWN = 99
+    @property
+    def enabled(self):
+        """Return True if this player is enabled."""
+        return self.config[CONF_ENABLED]
 
+    @property
+    def power_control(self) -> Optional[PlayerControl]:
+        """Return this player's Power Control."""
+        player_control_conf = self.config.get(CONF_POWER_CONTROL)
+        if player_control_conf:
+            return self.mass.players.get_player_control(player_control_conf)
+        return None
 
-@dataclass
-class PlayerControl(DataClassDictMixin):
-    """
-    Model for a player control.
+    @property
+    def volume_control(self) -> Optional[PlayerControl]:
+        """Return this player's Volume Control."""
+        player_control_conf = self.config.get(CONF_VOLUME_CONTROL)
+        if player_control_conf:
+            return self.mass.players.get_player_control(player_control_conf)
+        return None
 
-    Allows for a plugin-like
-    structure to override common player commands.
-    """
+    @property
+    def player_state(self) -> PlayerState:
+        """Return calculated/final state for this player."""
+        return self._cur_state
 
-    # pylint: disable=no-member
+    @callback
+    def update_state(self) -> None:
+        """Call to update current player state in the player manager."""
+        if not self.added_to_mass:
+            if self.enabled:
+                # player is now enabled and can be added
+                self.mass.add_job(self.mass.players.add_player(self))
+            return
+        new_state = self.create_state()
+        changed_keys = self._cur_state.update(new_state)
+        # basic throttle: do not send state changed events if player did not change
+        if not changed_keys:
+            return
+        self._cur_state = new_state
+        # always update the player queue
+        player_queue = self.mass.players.get_player_queue(self.active_queue)
+        if player_queue:
+            self.mass.add_job(player_queue.update_state)
+        if len(changed_keys) == 1 and "elapsed_time" in changed_keys:
+            # no need to send player update if only the elapsed time changes
+            # this is already handled by the queue manager
+            return
+        self.mass.signal_event(EVENT_PLAYER_CHANGED, new_state)
+        # update group player childs when parent updates
+        for child_player_id in self.group_childs:
+            self.mass.add_job(self.mass.players.trigger_player_update(child_player_id))
+        # update group player when child updates
+        for group_player_id in self._cur_state.group_parents:
+            self.mass.add_job(self.mass.players.trigger_player_update(group_player_id))
 
-    type: PlayerControlType = PlayerControlType.UNKNOWN
-    control_id: str = ""
-    provider: str = ""
-    name: str = ""
-    state: Any = None
+    @callback
+    def _get_name(self) -> str:
+        """Return final/calculated player name."""
+        conf_name = self.config.get(CONF_NAME)
+        return conf_name if conf_name else self.name
 
-    async def async_set_state(self, new_state: Any) -> None:
-        """Handle command to set the state for a player control."""
-        # by default we just signal an event on the eventbus
-        # pickup this event (e.g. from the websocket api)
-        # or override this method with your own implementation.
+    @callback
+    def _get_powered(self) -> bool:
+        """Return final/calculated player's power state."""
+        if not self.available or not self.enabled:
+            return False
+        power_control = self.power_control
+        if power_control:
+            return power_control.state
+        return self.powered
 
-        self.mass.signal_event(f"players/controls/{self.control_id}/state", new_state)
+    @callback
+    def _get_state(self) -> PlaybackState:
+        """Return final/calculated player's playback state."""
+        if self.powered and self.active_queue != self.player_id:
+            # use group state
+            return self.mass.players.get_player(self.active_queue).state
+        if self.state == PlaybackState.Stopped and not self.powered:
+            return PlaybackState.Off
+        return self.state
+
+    @callback
+    def _get_available(self) -> bool:
+        """Return current availablity of player."""
+        return False if not self.enabled else self.available
+
+    @callback
+    def _get_volume_level(self) -> int:
+        """Return final/calculated player's volume_level."""
+        if not self.available or not self.enabled:
+            return 0
+        # handle volume control
+        volume_control = self.volume_control
+        if volume_control:
+            return volume_control.state
+        # handle group volume
+        if self.is_group_player:
+            group_volume = 0
+            active_players = 0
+            for child_player_id in self.group_childs:
+                child_player = self.mass.players.get_player(child_player_id)
+                if child_player:
+                    group_volume += child_player.player_state.volume_level
+                    active_players += 1
+            if active_players:
+                group_volume = group_volume / active_players
+            return int(group_volume)
+        return int(self.volume_level)
+
+    @callback
+    def _get_group_parents(self) -> Set[str]:
+        """Return all group players this player belongs to."""
+        if self.is_group_player:
+            return {}
+        return {
+            player.player_id
+            for player in self.mass.players
+            if player.is_group_player and self.player_id in player.group_childs
+        }
+
+    @callback
+    def _get_active_queue(self) -> str:
+        """Return the active parent player/queue for a player."""
+        # if a group is powered on, all of it's childs will have/use
+        # the parent's player's queue.
+        for group_player_id in self.group_parents:
+            group_player = self.mass.players.get_player(group_player_id)
+            if group_player and group_player.powered:
+                return group_player_id
+        return self.player_id
+
+    @callback
+    def create_state(self) -> PlayerState:
+        """Create PlayerState."""
+        return PlayerState(
+            player_id=self.player_id,
+            provider_id=self.provider_id,
+            name=self._get_name(),
+            powered=self._get_powered(),
+            state=self.state,
+            available=self._get_available(),
+            volume_level=self._get_volume_level(),
+            elapsed_time=self.elapsed_time,
+            muted=self.muted,
+            is_group_player=self.is_group_player,
+            group_childs=self.group_childs,
+            device_info=self.device_info,
+            group_parents=self._get_group_parents(),
+            features=self.features,
+            active_queue=self._get_active_queue(),
+            updated_at=datetime.now(),
+        )
+
+    def to_dict(self) -> dict:
+        """Return playerstate for compatability with json serializer."""
+        return self._cur_state.to_dict()
+
+    def __init__(self, *args, **kwargs) -> None:
+        """Initialize a Player instance."""
+        self.mass: Optional[MusicAssistant] = None
+        self.added_to_mass = False
+        self._cur_state = PlayerState()
index 7e8db0a16460eeb4529b867466b75fc2bad703bd..8f6fd502c7c81df4d8d613b5266ab538507f5364 100755 (executable)
@@ -6,7 +6,7 @@ import time
 import uuid
 from dataclasses import dataclass
 from enum import Enum
-from typing import List, Optional, Tuple, Union
+from typing import Any, Dict, List, Optional, Tuple, Union
 
 from music_assistant.constants import (
     CONF_CROSSFADE_DURATION,
@@ -15,10 +15,10 @@ from music_assistant.constants import (
     EVENT_QUEUE_UPDATED,
 )
 from music_assistant.helpers.typing import (
-    MusicAssistantType,
+    MusicAssistant,
     OptionalInt,
     OptionalStr,
-    PlayerType,
+    Player,
 )
 from music_assistant.helpers.util import callback
 from music_assistant.models.media_types import Radio, Track
@@ -62,7 +62,7 @@ class QueueItem(Track):
 class PlayerQueue:
     """Class that holds the queue items for a player."""
 
-    def __init__(self, mass: MusicAssistantType, player_id: str) -> None:
+    def __init__(self, mass: MusicAssistant, player_id: str) -> None:
         """Initialize class."""
         self.mass = mass
         self._queue_id = player_id
@@ -74,25 +74,20 @@ class PlayerQueue:
         self._last_item = None
         self._queue_stream_start_index = 0
         self._queue_stream_next_index = 0
-        self._last_player_state = PlaybackState.Stopped
+        self._last_player = PlaybackState.Stopped
         # load previous queue settings from disk
-        self.mass.add_job(self.__async_restore_saved_state())
+        self.mass.add_job(self._restore_saved_state())
 
-    async def async_close(self) -> None:
+    async def close(self) -> None:
         """Handle shutdown/close."""
         # pylint: disable=unused-argument
-        await self.__async_save_state()
+        await self._save_state()
 
     @property
-    def player(self) -> PlayerType:
+    def player(self) -> Player:
         """Return handle to (master) player of this queue."""
         return self.mass.players.get_player(self._queue_id)
 
-    @property
-    def player_state(self) -> PlayerType:
-        """Return handle to player state."""
-        return self.mass.players.get_player_state(self._queue_id)
-
     @property
     def queue_id(self) -> str:
         """Return the Queue's id."""
@@ -110,7 +105,7 @@ class PlayerQueue:
         """Return shuffle enabled property."""
         return self._shuffle_enabled
 
-    async def async_set_shuffle_enabled(self, enable_shuffle: bool) -> None:
+    async def set_shuffle_enabled(self, enable_shuffle: bool) -> None:
         """Set shuffle."""
         if not self._shuffle_enabled and enable_shuffle:
             # shuffle requested
@@ -119,7 +114,7 @@ class PlayerQueue:
                 played_items = self.items[: self.cur_index]
                 next_items = self.__shuffle_items(self.items[self.cur_index + 1 :])
                 items = played_items + [self.cur_item] + next_items
-                self.mass.add_job(self.async_update(items))
+                self.mass.add_job(self.update(items))
         elif self._shuffle_enabled and not enable_shuffle:
             # unshuffle
             self._shuffle_enabled = False
@@ -128,22 +123,22 @@ class PlayerQueue:
                 next_items = self.items[self.cur_index + 1 :]
                 next_items.sort(key=lambda x: x.sort_index, reverse=False)
                 items = played_items + [self.cur_item] + next_items
-                self.mass.add_job(self.async_update(items))
-        self.mass.add_job(self.async_update_state())
-        self.mass.signal_event(EVENT_QUEUE_UPDATED, self.to_dict())
+                self.mass.add_job(self.update(items))
+        self.update_state()
+        self.mass.signal_event(EVENT_QUEUE_UPDATED, self)
 
     @property
     def repeat_enabled(self) -> bool:
         """Return if crossfade is enabled for this player."""
         return self._repeat_enabled
 
-    async def async_set_repeat_enabled(self, enable_repeat: bool) -> None:
+    async def set_repeat_enabled(self, enable_repeat: bool) -> None:
         """Set the repeat mode for this queue."""
         if self._repeat_enabled != enable_repeat:
             self._repeat_enabled = enable_repeat
-            self.mass.add_job(self.async_update_state())
-            self.mass.add_job(self.__async_save_state())
-            self.mass.signal_event(EVENT_QUEUE_UPDATED, self.to_dict())
+            self.update_state()
+            self.mass.add_job(self._save_state())
+            self.mass.signal_event(EVENT_QUEUE_UPDATED, self)
 
     @property
     def cur_index(self) -> OptionalInt:
@@ -277,39 +272,39 @@ class PlayerQueue:
                 return item
         return None
 
-    async def async_next(self) -> None:
+    async def next(self) -> None:
         """Play the next track in the queue."""
         if self.cur_index is None:
             return
         if self.use_queue_stream:
-            return await self.async_play_index(self.cur_index + 1)
-        return await self.player.async_cmd_next()
+            return await self.play_index(self.cur_index + 1)
+        return await self.player.cmd_next()
 
-    async def async_previous(self) -> None:
+    async def previous(self) -> None:
         """Play the previous track in the queue."""
         if self.cur_index is None:
             return
         if self.use_queue_stream:
-            return await self.async_play_index(self.cur_index - 1)
-        return await self.player.async_cmd_previous()
+            return await self.play_index(self.cur_index - 1)
+        return await self.player.cmd_previous()
 
-    async def async_resume(self) -> None:
+    async def resume(self) -> None:
         """Resume previous queue."""
         if self.items:
             prev_index = self.cur_index
             if self.use_queue_stream or not self.supports_queue:
-                await self.async_play_index(prev_index)
+                await self.play_index(prev_index)
             else:
                 # at this point we don't know if the queue is synced with the player
                 # so just to be safe we send the queue_items to the player
                 self._items = self._items[prev_index:]
-                return await self.player.async_cmd_queue_load(self._items)
+                return await self.player.cmd_queue_load(self._items)
         else:
             LOGGER.warning(
                 "resume queue requested for %s but queue is empty", self.queue_id
             )
 
-    async def async_play_index(self, index: Union[int, str]) -> None:
+    async def play_index(self, index: Union[int, str]) -> None:
         """Play item at index (or item_id) X in queue."""
         if not isinstance(index, int):
             index = self.__index_by_id(index)
@@ -319,21 +314,21 @@ class PlayerQueue:
         self._queue_stream_next_index = index
         if self.use_queue_stream:
             queue_stream_uri = self.get_stream_url()
-            return await self.player.async_cmd_play_uri(queue_stream_uri)
+            return await self.player.cmd_play_uri(queue_stream_uri)
         if self.supports_queue:
             try:
-                return await self.player.async_cmd_queue_play_index(index)
+                return await self.player.cmd_queue_play_index(index)
             except NotImplementedError:
                 # not supported by player, use load queue instead
                 LOGGER.debug(
                     "cmd_queue_insert not supported by player, fallback to cmd_queue_load "
                 )
                 self._items = self._items[index:]
-                return await self.player.async_cmd_queue_load(self._items)
+                return await self.player.cmd_queue_load(self._items)
         else:
-            return await self.player.async_cmd_play_uri(self._items[index].uri)
+            return await self.player.cmd_play_uri(self._items[index].uri)
 
-    async def async_move_item(self, queue_item_id: str, pos_shift: int = 1) -> None:
+    async def move_item(self, queue_item_id: str, pos_shift: int = 1) -> None:
         """
         Move queue item x up/down the queue.
 
@@ -353,11 +348,9 @@ class PlayerQueue:
             return
         # move the item in the list
         items.insert(new_index, items.pop(item_index))
-        await self.async_update(items)
-        if pos_shift == 0:
-            await self.async_play_index(new_index)
+        await self.update(items)
 
-    async def async_load(self, queue_items: List[QueueItem]) -> None:
+    async def load(self, queue_items: List[QueueItem]) -> None:
         """Load (overwrite) queue with new items."""
         for index, item in enumerate(queue_items):
             item.sort_index = index
@@ -365,13 +358,13 @@ class PlayerQueue:
             queue_items = self.__shuffle_items(queue_items)
         self._items = queue_items
         if self.use_queue_stream or not self.supports_queue:
-            await self.async_play_index(0)
+            await self.play_index(0)
         else:
-            await self.player.async_cmd_queue_load(queue_items)
-        self.mass.signal_event(EVENT_QUEUE_ITEMS_UPDATED, self.to_dict())
-        self.mass.add_job(self.__async_save_state())
+            await self.player.cmd_queue_load(queue_items)
+        self.mass.signal_event(EVENT_QUEUE_ITEMS_UPDATED, self)
+        self.mass.add_job(self._save_state())
 
-    async def async_insert(self, queue_items: List[QueueItem], offset: int = 0) -> None:
+    async def insert(self, queue_items: List[QueueItem], offset: int = 0) -> None:
         """
         Insert new items at offset x from current position.
 
@@ -387,7 +380,7 @@ class PlayerQueue:
             or self.cur_index == 0
             or (self.cur_index + offset > len(self.items))
         ):
-            return await self.async_load(queue_items)
+            return await self.load(queue_items)
         insert_at_index = self.cur_index + offset
         for index, item in enumerate(queue_items):
             item.sort_index = insert_at_index + index
@@ -408,22 +401,22 @@ class PlayerQueue:
             )
         if self.use_queue_stream:
             if offset == 0:
-                await self.async_play_index(insert_at_index)
+                await self.play_index(insert_at_index)
         else:
             # send queue to player's own implementation
             try:
-                await self.player.async_cmd_queue_insert(queue_items, insert_at_index)
+                await self.player.cmd_queue_insert(queue_items, insert_at_index)
             except NotImplementedError:
                 # not supported by player, use load queue instead
                 LOGGER.debug(
                     "cmd_queue_insert not supported by player, fallback to cmd_queue_load "
                 )
                 self._items = self._items[self.cur_index :]
-                return await self.player.async_cmd_queue_load(self._items)
-        self.mass.signal_event(EVENT_QUEUE_ITEMS_UPDATED, self.to_dict())
-        self.mass.add_job(self.__async_save_state())
+                return await self.player.cmd_queue_load(self._items)
+        self.mass.signal_event(EVENT_QUEUE_ITEMS_UPDATED, self)
+        self.mass.add_job(self._save_state())
 
-    async def async_append(self, queue_items: List[QueueItem]) -> None:
+    async def append(self, queue_items: List[QueueItem]) -> None:
         """Append new items at the end of the queue."""
         for index, item in enumerate(queue_items):
             item.sort_index = len(self.items) + index
@@ -432,57 +425,58 @@ class PlayerQueue:
             next_items = self.items[self.cur_index + 1 :] + queue_items
             next_items = self.__shuffle_items(next_items)
             items = played_items + [self.cur_item] + next_items
-            return await self.async_update(items)
+            return await self.update(items)
         self._items = self._items + queue_items
         if self.supports_queue and not self.use_queue_stream:
             # send queue to player's own implementation
             try:
-                await self.player.async_cmd_queue_append(queue_items)
+                await self.player.cmd_queue_append(queue_items)
             except NotImplementedError:
                 # not supported by player, use load queue instead
                 LOGGER.debug(
                     "cmd_queue_append not supported by player, fallback to cmd_queue_load "
                 )
                 self._items = self._items[self.cur_index :]
-                return await self.player.async_cmd_queue_load(self._items)
-        self.mass.signal_event(EVENT_QUEUE_ITEMS_UPDATED, self.to_dict())
-        self.mass.add_job(self.__async_save_state())
+                return await self.player.cmd_queue_load(self._items)
+        self.mass.signal_event(EVENT_QUEUE_ITEMS_UPDATED, self)
+        self.mass.add_job(self._save_state())
 
-    async def async_update(self, queue_items: List[QueueItem]) -> None:
+    async def update(self, queue_items: List[QueueItem]) -> None:
         """Update the existing queue items, mostly caused by reordering."""
         self._items = queue_items
         if self.supports_queue and not self.use_queue_stream:
             # send queue to player's own implementation
             try:
-                await self.player.async_cmd_queue_update(queue_items)
+                await self.player.cmd_queue_update(queue_items)
             except NotImplementedError:
                 # not supported by player, use load queue instead
                 LOGGER.debug(
                     "cmd_queue_update not supported by player, fallback to cmd_queue_load "
                 )
                 self._items = self._items[self.cur_index :]
-                await self.player.async_cmd_queue_load(self._items)
-        self.mass.signal_event(EVENT_QUEUE_ITEMS_UPDATED, self.to_dict())
-        self.mass.add_job(self.__async_save_state())
+                await self.player.cmd_queue_load(self._items)
+        self.mass.signal_event(EVENT_QUEUE_ITEMS_UPDATED, self)
+        self.mass.add_job(self._save_state())
 
-    async def async_clear(self) -> None:
+    async def clear(self) -> None:
         """Clear all items in the queue."""
-        await self.mass.players.async_cmd_stop(self.queue_id)
+        await self.mass.players.cmd_stop(self.queue_id)
         self._items = []
         if self.supports_queue:
             # send queue cmd to player's own implementation
             try:
-                await self.player.async_cmd_queue_clear()
+                await self.player.cmd_queue_clear()
             except NotImplementedError:
                 # not supported by player, try update instead
                 try:
-                    await self.player.async_cmd_queue_update([])
+                    await self.player.cmd_queue_update([])
                 except NotImplementedError:
                     # not supported by player, ignore
                     pass
-        self.mass.signal_event(EVENT_QUEUE_ITEMS_UPDATED, self.to_dict())
+        self.mass.signal_event(EVENT_QUEUE_ITEMS_UPDATED, self)
 
-    async def async_update_state(self) -> None:
+    @callback
+    def update_state(self) -> None:
         """Update queue details, called when player updates."""
         new_index = self._cur_index
         track_time = self._cur_item_time
@@ -511,7 +505,7 @@ class PlayerQueue:
             and self.cur_item.streamdetails
         ):
             # new active item in queue
-            self.mass.signal_event(EVENT_QUEUE_UPDATED, self.to_dict())
+            self.mass.signal_event(EVENT_QUEUE_UPDATED, self)
             # invalidate previous streamdetails
             if self._last_item:
                 self._last_item.streamdetails = None
@@ -524,7 +518,7 @@ class PlayerQueue:
                 {"queue_id": self.queue_id, "cur_item_time": track_time},
             )
 
-    async def async_queue_stream_start(self) -> None:
+    async def queue_stream_start(self) -> None:
         """Call when queue_streamer starts playing the queue stream."""
         self._cur_item_time = 0
         self._cur_index = self._queue_stream_next_index
@@ -532,7 +526,7 @@ class PlayerQueue:
         self._queue_stream_start_index = self._cur_index
         return self._cur_index
 
-    async def async_queue_stream_next(self, cur_index: int) -> None:
+    async def queue_stream_next(self, cur_index: int) -> None:
         """Call when queue_streamer loads next track in buffer."""
         next_index = 0
         if len(self.items) > (next_index):
@@ -543,7 +537,7 @@ class PlayerQueue:
         self._queue_stream_next_index = next_index + 1
         return next_index
 
-    def to_dict(self) -> dict:
+    def to_dict(self) -> Dict[str, Any]:
         """Instance attributes as dict so it can be serialized to json."""
         return {
             "queue_id": self.player.player_id,
@@ -598,10 +592,10 @@ class PlayerQueue:
                 item_index = index
         return item_index
 
-    async def __async_restore_saved_state(self) -> None:
+    async def _restore_saved_state(self) -> None:
         """Try to load the saved queue for this player from cache file."""
         cache_str = "queue_state_%s" % self.queue_id
-        cache_data = await self.mass.cache.async_get(cache_str)
+        cache_data = await self.mass.cache.get(cache_str)
         if cache_data:
             self._shuffle_enabled = cache_data["shuffle_enabled"]
             self._repeat_enabled = cache_data["repeat_enabled"]
@@ -611,7 +605,7 @@ class PlayerQueue:
 
     # pylint: enable=unused-argument
 
-    async def __async_save_state(self) -> None:
+    async def _save_state(self) -> None:
         """Save current queue settings to file."""
         cache_str = "queue_state_%s" % self.queue_id
         cache_data = {
@@ -620,5 +614,5 @@ class PlayerQueue:
             "items": self._items,
             "cur_index": self._cur_index,
         }
-        await self.mass.cache.async_set(cache_str, cache_data)
+        await self.mass.cache.set(cache_str, cache_data)
         LOGGER.info("queue state saved to file for player %s", self.queue_id)
diff --git a/music_assistant/models/player_state.py b/music_assistant/models/player_state.py
deleted file mode 100755 (executable)
index 2ffb0b6..0000000
+++ /dev/null
@@ -1,407 +0,0 @@
-"""
-Models and helpers for the calculated state of a player.
-
-PlayerProviders send Player objects to us with the raw/untouched player state.
-Due to configuration settings and other influences this playerstate needs alteration,
-that's why we store the final player state (we present to outside world)
-into a PlayerState object.
-"""
-
-import logging
-from datetime import datetime
-from typing import List, Optional
-
-from music_assistant.constants import (
-    ATTR_ACTIVE_QUEUE,
-    ATTR_AVAILABLE,
-    ATTR_CURRENT_URI,
-    ATTR_DEVICE_INFO,
-    ATTR_ELAPSED_TIME,
-    ATTR_FEATURES,
-    ATTR_GROUP_CHILDS,
-    ATTR_GROUP_PARENTS,
-    ATTR_IS_GROUP_PLAYER,
-    ATTR_MUTED,
-    ATTR_NAME,
-    ATTR_PLAYER_ID,
-    ATTR_POWERED,
-    ATTR_PROVIDER_ID,
-    ATTR_SHOULD_POLL,
-    ATTR_STATE,
-    ATTR_UPDATED_AT,
-    ATTR_VOLUME_LEVEL,
-    CONF_ENABLED,
-    CONF_GROUP_DELAY,
-    CONF_NAME,
-    CONF_POWER_CONTROL,
-    CONF_VOLUME_CONTROL,
-    EVENT_PLAYER_CHANGED,
-)
-from music_assistant.helpers.typing import MusicAssistantType
-from music_assistant.models.player import (
-    DeviceInfo,
-    PlaybackState,
-    Player,
-    PlayerFeature,
-)
-
-LOGGER = logging.getLogger("player_state")
-
-# List of all player_state attributes
-PLAYER_ATTRIBUTES = [
-    ATTR_ACTIVE_QUEUE,
-    ATTR_AVAILABLE,
-    ATTR_CURRENT_URI,
-    ATTR_DEVICE_INFO,
-    ATTR_ELAPSED_TIME,
-    ATTR_FEATURES,
-    ATTR_GROUP_CHILDS,
-    ATTR_GROUP_PARENTS,
-    ATTR_IS_GROUP_PLAYER,
-    ATTR_MUTED,
-    ATTR_NAME,
-    ATTR_PLAYER_ID,
-    ATTR_POWERED,
-    ATTR_PROVIDER_ID,
-    ATTR_SHOULD_POLL,
-    ATTR_STATE,
-    ATTR_VOLUME_LEVEL,
-]
-
-# list of Player attributes that can/will cause a player changed event
-UPDATE_ATTRIBUTES = [
-    ATTR_NAME,
-    ATTR_POWERED,
-    ATTR_STATE,
-    ATTR_AVAILABLE,
-    ATTR_CURRENT_URI,
-    ATTR_VOLUME_LEVEL,
-    ATTR_MUTED,
-    ATTR_IS_GROUP_PLAYER,
-    ATTR_GROUP_CHILDS,
-    ATTR_SHOULD_POLL,
-]
-
-
-class PlayerState:
-    """
-    Model for the calculated state of a player.
-
-    PlayerProviders send Player objects to us with the raw/untouched player state.
-    Due to configuration settings and other influences this playerstate needs alteration,
-    that's why we store the final player state (we present to outside world)
-    into this PlayerState object.
-    """
-
-    def __init__(self, mass: MusicAssistantType, player: Player):
-        """Initialize a PlayerState from a Player object."""
-        self.mass = mass
-        # make sure the MusicAssistant obj is present on the player
-        player.mass = mass
-        self._player = player
-        self._player_id = player.player_id
-        self._provider_id = player.provider_id
-        self._features = player.features
-        self._muted = player.muted
-        self._is_group_player = player.is_group_player
-        self._group_childs = player.group_childs
-        self._device_info = player.device_info
-        self._elapsed_time = player.elapsed_time
-        self._current_uri = player.current_uri
-        self._available = player.available
-        self._name = player.name
-        self._powered = player.powered
-        self._state = player.state
-        self._volume_level = player.volume_level
-        self._updated_at = datetime.utcnow()
-        self._group_parents = self.get_group_parents()
-        self._active_queue = self.get_active_queue()
-        self._group_delay = self.get_group_delay()
-        # schedule update to set the transforms
-        self.mass.add_job(self.async_update(player))
-
-    @property
-    def player(self):
-        """Return the underlying player object."""
-        return self._player
-
-    @property
-    def player_id(self) -> str:
-        """Return player id of this player."""
-        return self._player_id
-
-    @property
-    def provider_id(self) -> str:
-        """Return provider id of this player."""
-        return self._provider_id
-
-    @property
-    def name(self) -> str:
-        """Return name of the player."""
-        return self._name
-
-    @property
-    def powered(self) -> bool:
-        """Return current power state of player."""
-        return self._powered
-
-    @property
-    def elapsed_time(self) -> int:
-        """Return elapsed time of current playing media in seconds."""
-        return self._elapsed_time
-
-    @property
-    def elapsed_milliseconds(self) -> Optional[int]:
-        """
-        Return elapsed time of current playing media in milliseconds.
-
-        This is an optional property.
-        If provided, the property must return the REALTIME value while playing.
-        Used for synced playback in player groups.
-        """
-        # always realtime returned from player
-        if self.player.elapsed_milliseconds is not None:
-            return self.player.elapsed_milliseconds - self.group_delay
-        return None
-
-    @property
-    def state(self) -> PlaybackState:
-        """Return current PlaybackState of player."""
-        return self._state
-
-    @property
-    def available(self) -> bool:
-        """Return current availablity of player."""
-        return self._available
-
-    @property
-    def current_uri(self) -> Optional[str]:
-        """Return currently loaded uri of player (if any)."""
-        return self._current_uri
-
-    @property
-    def volume_level(self) -> int:
-        """Return current volume level of player (scale 0..100)."""
-        return self._volume_level
-
-    @property
-    def muted(self) -> bool:
-        """Return current mute state of player."""
-        return self._muted
-
-    @property
-    def is_group_player(self) -> bool:
-        """Return True if this player is a group player."""
-        return self._is_group_player
-
-    @property
-    def group_childs(self) -> List[str]:
-        """Return list of child player id's if player is a group player."""
-        return self._group_childs
-
-    @property
-    def device_info(self) -> DeviceInfo:
-        """Return the device info for this player."""
-        return self._device_info
-
-    @property
-    def should_poll(self) -> bool:
-        """Return True if this player should be polled for state updates."""
-        return self._player.should_poll  # always realtime returned from player
-
-    @property
-    def features(self) -> List[PlayerFeature]:
-        """Return list of features this player supports."""
-        return self._features
-
-    @property
-    def group_delay(self) -> int:
-        """Return group delay of this player in milliseconds (if configured)."""
-        return self._group_delay
-
-    async def async_update(self, player: Player):
-        """Run update player state task in executor."""
-        self.mass.add_job(self.update, player)
-
-    def update(self, player: Player):
-        """Update attributes from player object."""
-        new_available = self.get_available(player.available)
-        if self.available == new_available and not new_available:
-            return  # ignore players that are unavailable
-
-        # detect state changes
-        changed_keys = set()
-        for attr in PLAYER_ATTRIBUTES:
-
-            new_value = getattr(self._player, attr, None)
-
-            # handle transformations
-            if attr == ATTR_NAME:
-                new_value = self.get_name(new_value)
-            elif attr == ATTR_POWERED:
-                new_value = self.get_power(new_value)
-            elif attr == ATTR_STATE:
-                new_value = self.get_state(new_value)
-            elif attr == ATTR_AVAILABLE:
-                new_value = self.get_available(new_value)
-            elif attr == ATTR_VOLUME_LEVEL:
-                new_value = self.get_volume_level(new_value)
-            elif attr == ATTR_GROUP_PARENTS:
-                new_value = self.get_group_parents()
-            elif attr == ATTR_ACTIVE_QUEUE:
-                new_value = self.get_active_queue()
-
-            current_value = getattr(self, attr)
-
-            if current_value != new_value:
-                # value changed
-                setattr(self, "_" + attr, new_value)
-                changed_keys.add(attr)
-
-        if changed_keys:
-            self._updated_at = datetime.utcnow()
-
-            if changed_keys.intersection(set(UPDATE_ATTRIBUTES)):
-                self.mass.signal_event(EVENT_PLAYER_CHANGED, self)
-                # update group player childs when parent updates
-                for child_player_id in self.group_childs:
-                    self.mass.add_job(
-                        self.mass.players.async_trigger_player_update(child_player_id)
-                    )
-                # update group player when child updates
-                for group_player_id in self.group_parents:
-                    self.mass.add_job(
-                        self.mass.players.async_trigger_player_update(group_player_id)
-                    )
-
-            # always update the player queue
-            player_queue = self.mass.players.get_player_queue(self.active_queue)
-            if player_queue:
-                self.mass.add_job(player_queue.async_update_state())
-            self._group_delay = self.get_group_delay()
-
-    def get_name(self, name: str) -> str:
-        """Return final/calculated player name."""
-        conf_name = self.mass.config.get_player_config(self.player_id)[CONF_NAME]
-        return conf_name if conf_name else name
-
-    def get_power(self, power: bool) -> bool:
-        """Return final/calculated player's power state."""
-        if not self.available:
-            return False
-        player_config = self.mass.config.player_settings[self.player_id]
-        if player_config.get(CONF_POWER_CONTROL):
-            control = self.mass.players.get_player_control(
-                player_config[CONF_POWER_CONTROL]
-            )
-            if control:
-                return control.state
-        return power
-
-    def get_state(self, state: PlaybackState) -> PlaybackState:
-        """Return final/calculated player's playback state."""
-        if self.powered and self.active_queue != self.player_id:
-            # use group state
-            return self.mass.players.get_player_state(self.active_queue).state
-        if state == PlaybackState.Stopped and not self.powered:
-            return PlaybackState.Off
-        return state
-
-    def get_available(self, available: bool) -> bool:
-        """Return current availablity of player."""
-        player_enabled = self.mass.config.get_player_config(self.player_id)[
-            CONF_ENABLED
-        ]
-        return False if not player_enabled else available
-
-    def get_volume_level(self, volume_level: int) -> int:
-        """Return final/calculated player's volume_level."""
-        if not self.available:
-            return 0
-        player_config = self.mass.config.player_settings[self.player_id]
-        if player_config.get(CONF_VOLUME_CONTROL):
-            control = self.mass.players.get_player_control(
-                player_config[CONF_VOLUME_CONTROL]
-            )
-            if control:
-                return control.state
-        # handle group volume
-        if self.is_group_player:
-            group_volume = 0
-            active_players = 0
-            for child_player_id in self.group_childs:
-                child_player = self.mass.players.get_player_state(child_player_id)
-                if child_player and child_player.available and child_player.powered:
-                    group_volume += child_player.volume_level
-                    active_players += 1
-            if active_players:
-                group_volume = group_volume / active_players
-            return group_volume
-        return volume_level
-
-    @property
-    def group_parents(self) -> List[str]:
-        """Return all group players this player belongs to."""
-        return self._group_parents
-
-    def get_group_parents(self) -> List[str]:
-        """Return all group players this player belongs to."""
-        if self.is_group_player:
-            return []
-        result = []
-        for player in self.mass.players.player_states:
-            if not player.is_group_player:
-                continue
-            if self.player_id not in player.group_childs:
-                continue
-            result.append(player.player_id)
-        return result
-
-    @property
-    def active_queue(self) -> str:
-        """Return the active parent player/queue for a player."""
-        return self._active_queue
-
-    def get_active_queue(self) -> str:
-        """Return the active parent player/queue for a player."""
-        # if a group is powered on, all of it's childs will have/use
-        # the parent's player's queue.
-        for group_player_id in self.group_parents:
-            group_player = self.mass.players.get_player_state(group_player_id)
-            if group_player and group_player.powered:
-                return group_player_id
-        return self.player_id
-
-    @property
-    def updated_at(self) -> datetime:
-        """Return the datetime (UTC) that the player state was last updated."""
-        return self._updated_at
-
-    def get_group_delay(self):
-        """Get group delay for a player."""
-        player_settings = self.mass.config.get_player_config(self.player_id)
-        if player_settings:
-            return player_settings.get(CONF_GROUP_DELAY, 0)
-        return 0
-
-    def to_dict(self):
-        """Instance attributes as dict so it can be serialized to json."""
-        return {
-            ATTR_PLAYER_ID: self.player_id,
-            ATTR_PROVIDER_ID: self.provider_id,
-            ATTR_NAME: self.name,
-            ATTR_POWERED: self.powered,
-            ATTR_ELAPSED_TIME: int(self.elapsed_time),
-            ATTR_STATE: self.state.value,
-            ATTR_AVAILABLE: self.available,
-            ATTR_CURRENT_URI: self.current_uri,
-            ATTR_VOLUME_LEVEL: int(self.volume_level),
-            ATTR_MUTED: self.muted,
-            ATTR_IS_GROUP_PLAYER: self.is_group_player,
-            ATTR_GROUP_CHILDS: self.group_childs,
-            ATTR_DEVICE_INFO: self.device_info.to_dict(),
-            ATTR_UPDATED_AT: self.updated_at.isoformat(),
-            ATTR_GROUP_PARENTS: self.group_parents,
-            ATTR_FEATURES: self.features,
-            ATTR_ACTIVE_QUEUE: self.active_queue,
-        }
index 95e29e3b58043a701bc15b63c76295be340def9c..77d731c316ba6caba90504410e6d066d83a32416 100644 (file)
@@ -4,7 +4,7 @@ from abc import abstractmethod
 from enum import Enum
 from typing import Dict, List, Optional
 
-from music_assistant.helpers.typing import MusicAssistantType, Players
+from music_assistant.helpers.typing import MusicAssistant, Players
 from music_assistant.models.config_entry import ConfigEntry
 from music_assistant.models.media_types import (
     Album,
@@ -30,9 +30,7 @@ class ProviderType(Enum):
 class Provider:
     """Base model for a provider/plugin."""
 
-    mass: MusicAssistantType = (
-        None  # will be set automagically while loading the provider
-    )
+    mass: MusicAssistant = None  # will be set automagically while loading the provider
     available: bool = False  # will be set automagically while loading the provider
 
     @property
@@ -56,7 +54,7 @@ class Provider:
         """Return Config Entries for this provider."""
 
     @abstractmethod
-    async def async_on_start(self) -> bool:
+    async def on_start(self) -> bool:
         """
         Handle initialization of the provider based on config.
 
@@ -65,7 +63,7 @@ class Provider:
         raise NotImplementedError
 
     @abstractmethod
-    async def async_on_stop(self) -> None:
+    async def on_stop(self) -> None:
         """Handle correct close/cleanup of the provider on exit. Called on shutdown/reload."""
 
 
@@ -98,11 +96,7 @@ class PlayerProvider(Provider):
     def players(self) -> Players:
         """Return all players belonging to this provider."""
         # pylint: disable=no-member
-        return [
-            player
-            for player in self.mass.players.players
-            if player.provider_id == self.id
-        ]
+        return [player for player in self.mass.players if player.provider_id == self.id]
 
 
 class MetadataProvider(Provider):
@@ -117,11 +111,11 @@ class MetadataProvider(Provider):
         """Return ProviderType."""
         return ProviderType.METADATA_PROVIDER
 
-    async def async_get_artist_images(self, mb_artist_id: str) -> Dict:
+    async def get_artist_images(self, mb_artist_id: str) -> Dict:
         """Retrieve artist metadata as dict by musicbrainz artist id."""
         raise NotImplementedError
 
-    async def async_get_album_images(self, mb_album_id: str) -> Dict:
+    async def get_album_images(self, mb_album_id: str) -> Dict:
         """Retrieve album metadata as dict by musicbrainz album id."""
         raise NotImplementedError
 
@@ -149,7 +143,7 @@ class MusicProvider(Provider):
             MediaType.Track,
         ]
 
-    async def async_search(
+    async def search(
         self, search_query: str, media_types=Optional[List[MediaType]], limit: int = 5
     ) -> SearchResult:
         """
@@ -161,100 +155,98 @@ class MusicProvider(Provider):
         """
         raise NotImplementedError
 
-    async def async_get_library_artists(self) -> List[Artist]:
+    async def get_library_artists(self) -> List[Artist]:
         """Retrieve library artists from the provider."""
         if MediaType.Artist in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_get_library_albums(self) -> List[Album]:
+    async def get_library_albums(self) -> List[Album]:
         """Retrieve library albums from the provider."""
         if MediaType.Album in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_get_library_tracks(self) -> List[Track]:
+    async def get_library_tracks(self) -> List[Track]:
         """Retrieve library tracks from the provider."""
         if MediaType.Track in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_get_library_playlists(self) -> List[Playlist]:
+    async def get_library_playlists(self) -> List[Playlist]:
         """Retrieve library/subscribed playlists from the provider."""
         if MediaType.Playlist in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_get_radios(self) -> List[Radio]:
+    async def get_radios(self) -> List[Radio]:
         """Retrieve library/subscribed radio stations from the provider."""
         if MediaType.Radio in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_get_artist(self, prov_artist_id: str) -> Artist:
+    async def get_artist(self, prov_artist_id: str) -> Artist:
         """Get full artist details by id."""
         if MediaType.Artist in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_get_artist_albums(self, prov_artist_id: str) -> List[Album]:
+    async def get_artist_albums(self, prov_artist_id: str) -> List[Album]:
         """Get a list of all albums for the given artist."""
         if MediaType.Album in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_get_artist_toptracks(self, prov_artist_id: str) -> List[Track]:
+    async def get_artist_toptracks(self, prov_artist_id: str) -> List[Track]:
         """Get a list of most popular tracks for the given artist."""
         if MediaType.Track in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_get_album(self, prov_album_id: str) -> Album:
+    async def get_album(self, prov_album_id: str) -> Album:
         """Get full album details by id."""
         if MediaType.Album in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_get_track(self, prov_track_id: str) -> Track:
+    async def get_track(self, prov_track_id: str) -> Track:
         """Get full track details by id."""
         if MediaType.Track in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_get_playlist(self, prov_playlist_id: str) -> Playlist:
+    async def get_playlist(self, prov_playlist_id: str) -> Playlist:
         """Get full playlist details by id."""
         if MediaType.Playlist in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_get_radio(self, prov_radio_id: str) -> Radio:
+    async def get_radio(self, prov_radio_id: str) -> Radio:
         """Get full radio details by id."""
         if MediaType.Radio in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_get_album_tracks(self, prov_album_id: str) -> List[Track]:
+    async def get_album_tracks(self, prov_album_id: str) -> List[Track]:
         """Get album tracks for given album id."""
         if MediaType.Album in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_get_playlist_tracks(self, prov_playlist_id: str) -> List[Track]:
+    async def get_playlist_tracks(self, prov_playlist_id: str) -> List[Track]:
         """Get all playlist tracks for given playlist id."""
         if MediaType.Playlist in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_library_add(self, prov_item_id: str, media_type: MediaType) -> bool:
+    async def library_add(self, prov_item_id: str, media_type: MediaType) -> bool:
         """Add item to provider's library. Return true on succes."""
         raise NotImplementedError
 
-    async def async_library_remove(
-        self, prov_item_id: str, media_type: MediaType
-    ) -> bool:
+    async def library_remove(self, prov_item_id: str, media_type: MediaType) -> bool:
         """Remove item from provider's library. Return true on succes."""
         raise NotImplementedError
 
-    async def async_add_playlist_tracks(
+    async def add_playlist_tracks(
         self, prov_playlist_id: str, prov_track_ids: List[str]
     ) -> bool:
         """Add track(s) to playlist. Return true on succes."""
         if MediaType.Playlist in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_remove_playlist_tracks(
+    async def remove_playlist_tracks(
         self, prov_playlist_id: str, prov_track_ids: List[str]
     ) -> bool:
         """Remove track(s) from playlist. Return true on succes."""
         if MediaType.Playlist in self.supported_mediatypes:
             raise NotImplementedError
 
-    async def async_get_stream_details(self, item_id: str) -> StreamDetails:
+    async def get_stream_details(self, item_id: str) -> StreamDetails:
         """Get streamdetails for a track/radio."""
         raise NotImplementedError
index acfa36821e5bb4d89a6ab0a8851ca8589be82a6a..f78b33fdd53fad2f0259eebc14645b63c238fe52 100644 (file)
@@ -3,7 +3,7 @@ import logging
 import time
 from typing import List
 
-from music_assistant.helpers.typing import MusicAssistantType
+from music_assistant.helpers.typing import MusicAssistant
 from music_assistant.helpers.util import run_periodic
 from music_assistant.models.config_entry import ConfigEntry
 from music_assistant.models.player import (
@@ -23,14 +23,14 @@ PLAYER_CONFIG_ENTRIES = []
 PLAYER_FEATURES = []
 
 WS_COMMAND_WSPLAYER_CMD = "wsplayer command"
-WS_COMMAND_WSPLAYER_STATE = "wsplayer state"
+WS_COMMAND_WSplayer = "wsplayer state"
 WS_COMMAND_WSPLAYER_REGISTER = "wsplayer register"
 
 
-async def async_setup(mass):
+async def setup(mass):
     """Perform async setup of this Plugin/Provider."""
     prov = MassPlayerProvider()
-    await mass.async_register_provider(prov)
+    await mass.register_provider(prov)
 
 
 class MassPlayerProvider(PlayerProvider):
@@ -55,44 +55,42 @@ class MassPlayerProvider(PlayerProvider):
         """Return Config Entries for this provider."""
         return []
 
-    async def async_on_start(self) -> bool:
+    async def on_start(self) -> bool:
         """Handle initialization of the provider based on config."""
         # listen for websockets commands to dynamically create players
-        self.mass.add_job(self.async_check_players())
+        self.mass.add_job(self.check_players())
         self.mass.web.register_api_route(
-            WS_COMMAND_WSPLAYER_REGISTER, self.async_handle_ws_player_state
-        )
-        self.mass.web.register_api_route(
-            WS_COMMAND_WSPLAYER_STATE, self.async_handle_ws_player_state
+            WS_COMMAND_WSPLAYER_REGISTER, self.handle_ws_player
         )
+        self.mass.web.register_api_route(WS_COMMAND_WSplayer, self.handle_ws_player)
         return True
 
-    async def async_on_stop(self):
+    async def on_stop(self):
         """Handle correct close/cleanup of the provider on exit."""
         for player in self.players:
-            await player.async_cmd_stop()
+            await player.cmd_stop()
 
-    async def async_handle_ws_player_state(self, player_id: str, details: dict):
+    async def handle_ws_player(self, player_id: str, details: dict):
         """Handle state message from ws player."""
         player = self.mass.players.get_player(player_id)
         if not player:
             # register new player
             player = WebsocketsPlayer(self.mass, player_id, details["name"])
-            await self.mass.players.async_add_player(player)
-        await player.handle_player_state(details)
+            await self.mass.players.add_player(player)
+        await player.handle_player(details)
 
     @run_periodic(30)
-    async def async_check_players(self) -> None:
+    async def check_players(self) -> None:
         """Invalidate players that did not send a heartbeat message in a while."""
         cur_time = time.time()
-        offline_players = []
+        offline_players = set()
         for player in self.players:
             if not isinstance(player, WebsocketsPlayer):
                 continue
             if cur_time - player.last_message > 30:
-                offline_players.append(player.player_id)
+                offline_players.add(player.player_id)
         for player_id in offline_players:
-            await self.mass.players.async_remove_player(player_id)
+            await self.mass.players.remove_player(player_id)
 
 
 class WebsocketsPlayer(Player):
@@ -104,7 +102,7 @@ class WebsocketsPlayer(Player):
     and our internal event bus.
     """
 
-    def __init__(self, mass: MusicAssistantType, player_id: str, player_name: str):
+    def __init__(self, mass: MusicAssistant, player_id: str, player_name: str):
         """Initialize the wsplayer."""
         self._player_id = player_id
         self._player_name = player_name
@@ -117,7 +115,7 @@ class WebsocketsPlayer(Player):
         self._device_info = DeviceInfo()
         self.last_message = time.time()
 
-    async def handle_player_state(self, data: dict):
+    async def handle_player(self, data: dict):
         """Handle state event from player."""
         if "volume_level" in data:
             self._volume_level = data["volume_level"]
@@ -202,7 +200,7 @@ class WebsocketsPlayer(Player):
         """Return player specific config entries (if any)."""
         return PLAYER_CONFIG_ENTRIES
 
-    async def async_cmd_play_uri(self, uri: str) -> None:
+    async def cmd_play_uri(self, uri: str) -> None:
         """
         Play the specified uri/url on the player.
 
@@ -211,32 +209,32 @@ class WebsocketsPlayer(Player):
         data = {"player_id": self.player_id, "cmd": "play_uri", "uri": uri}
         self.mass.signal_event(WS_COMMAND_WSPLAYER_CMD, data)
 
-    async def async_cmd_stop(self) -> None:
+    async def cmd_stop(self) -> None:
         """Send STOP command to player."""
         data = {"player_id": self.player_id, "cmd": "stop"}
         self.mass.signal_event(WS_COMMAND_WSPLAYER_CMD, data)
 
-    async def async_cmd_play(self) -> None:
+    async def cmd_play(self) -> None:
         """Send PLAY command to player."""
         data = {"player_id": self.player_id, "cmd": "play"}
         self.mass.signal_event(WS_COMMAND_WSPLAYER_CMD, data)
 
-    async def async_cmd_pause(self) -> None:
+    async def cmd_pause(self) -> None:
         """Send PAUSE command to player."""
         data = {"player_id": self.player_id, "cmd": "pause"}
         self.mass.signal_event(WS_COMMAND_WSPLAYER_CMD, data)
 
-    async def async_cmd_power_on(self) -> None:
+    async def cmd_power_on(self) -> None:
         """Send POWER ON command to player."""
         self._powered = True
         self.update_state()
 
-    async def async_cmd_power_off(self) -> None:
+    async def cmd_power_off(self) -> None:
         """Send POWER OFF command to player."""
         self._powered = False
         self.update_state()
 
-    async def async_cmd_volume_set(self, volume_level: int) -> None:
+    async def cmd_volume_set(self, volume_level: int) -> None:
         """
         Send volume level command to player.
 
@@ -249,7 +247,7 @@ class WebsocketsPlayer(Player):
         }
         self.mass.signal_event(WS_COMMAND_WSPLAYER_CMD, data)
 
-    async def async_cmd_volume_mute(self, is_muted: bool = False) -> None:
+    async def cmd_volume_mute(self, is_muted: bool = False) -> None:
         """
         Send volume MUTE command to given player.
 
index 12ae3ab55e2515f7290c5664733ad51b1a173720..49c9ec876c3e905119ed4941bc18773d9167aca5 100644 (file)
@@ -15,11 +15,11 @@ from .player import ChromecastPlayer
 LOGGER = logging.getLogger(PROV_ID)
 
 
-async def async_setup(mass):
+async def setup(mass):
     """Perform async setup of this Plugin/Provider."""
     logging.getLogger("pychromecast").setLevel(logging.WARNING)
     prov = ChromecastProvider()
-    await mass.async_register_provider(prov)
+    await mass.register_provider(prov)
 
 
 class ChromecastProvider(PlayerProvider):
@@ -47,7 +47,7 @@ class ChromecastProvider(PlayerProvider):
         """Return Config Entries for this provider."""
         return PROVIDER_CONFIG_ENTRIES
 
-    async def async_on_start(self) -> bool:
+    async def on_start(self) -> bool:
         """Handle initialization of the provider based on config."""
         self._listener = pychromecast.CastListener(
             self.__chromecast_add_update_callback,
@@ -63,7 +63,7 @@ class ChromecastProvider(PlayerProvider):
         self.mass.add_job(start_discovery)
         return True
 
-    async def async_on_stop(self):
+    async def on_stop(self):
         """Handle correct close/cleanup of the provider on exit."""
         if not self._browser:
             return
@@ -94,7 +94,7 @@ class ChromecastProvider(PlayerProvider):
             player = ChromecastPlayer(self.mass, cast_info)
         # if player was already added, the player will take care of reconnects itself.
         player.set_cast_info(cast_info)
-        self.mass.add_job(self.mass.players.async_add_player(player))
+        self.mass.add_job(self.mass.players.add_player(player))
 
     @staticmethod
     def __chromecast_remove_callback(cast_uuid, cast_service_name, cast_service):
index 84d47d6d2fa8fc19b177f839cef71a8b80f6bed6..0e29e42c98dccdcb7337bce03436cfa2cb96b265 100644 (file)
@@ -7,8 +7,8 @@ from typing import List, Optional
 import pychromecast
 from asyncio_throttle import Throttler
 from music_assistant.helpers.compare import compare_strings
-from music_assistant.helpers.typing import MusicAssistantType
-from music_assistant.helpers.util import async_yield_chunks
+from music_assistant.helpers.typing import MusicAssistant
+from music_assistant.helpers.util import yield_chunks
 from music_assistant.models.config_entry import ConfigEntry
 from music_assistant.models.player import (
     DeviceInfo,
@@ -38,8 +38,9 @@ class ChromecastPlayer(Player):
     "elected leader" itself.
     """
 
-    def __init__(self, mass: MusicAssistantType, cast_info: ChromecastInfo) -> None:
+    def __init__(self, mass: MusicAssistant, cast_info: ChromecastInfo) -> None:
         """Initialize the cast device."""
+        super().__init__()
         self.mass = mass
         self._cast_info = cast_info
         self._player_id = cast_info.uuid
@@ -192,7 +193,7 @@ class ChromecastPlayer(Player):
         """Return player specific config entries (if any)."""
         return PLAYER_CONFIG_ENTRIES
 
-    async def async_on_add(self) -> None:
+    async def on_add(self) -> None:
         """Call when player is added to the player manager."""
         chromecast = await self.mass.loop.run_in_executor(
             None,
@@ -223,7 +224,7 @@ class ChromecastPlayer(Player):
         """Set (or update) the cast discovery info."""
         self._cast_info = cast_info
 
-    async def async_disconnect(self):
+    async def disconnect(self):
         """Disconnect Chromecast object if it is set."""
         if self._chromecast is None:
             # Can't disconnect if not connected.
@@ -252,9 +253,9 @@ class ChromecastPlayer(Player):
             self._status_listener.invalidate()
             self._status_listener = None
 
-    async def async_on_remove(self) -> None:
+    async def on_remove(self) -> None:
         """Call when player is removed from the player manager."""
-        await self.async_disconnect()
+        await self.disconnect()
 
     # ========== Callbacks ==========
 
@@ -299,79 +300,69 @@ class ChromecastPlayer(Player):
             if self._cast_info.is_audio_group and new_available:
                 self.mass.add_job(self._chromecast.mz_controller.update_members)
 
-    async def async_on_update(self) -> None:
+    async def on_poll(self) -> None:
         """Call when player is periodically polled by the player manager (should_poll=True)."""
-        if self.mass.players.get_player_state(self.player_id).active_queue.startswith(
-            "group_player"
-        ):
+        if self.active_queue.startswith("group_player"):
             # the group player wants very accurate elapsed_time state so we request it very often
-            await self.async_chromecast_command(
+            await self.chromecast_command(
                 self._chromecast.media_controller.update_status
             )
         self.update_state()
 
     # ========== Service Calls ==========
 
-    async def async_cmd_stop(self) -> None:
+    async def cmd_stop(self) -> None:
         """Send stop command to player."""
         if self._chromecast and self._chromecast.media_controller:
-            await self.async_chromecast_command(self._chromecast.quit_app)
+            await self.chromecast_command(self._chromecast.quit_app)
 
-    async def async_cmd_play(self) -> None:
+    async def cmd_play(self) -> None:
         """Send play command to player."""
         if self._chromecast.media_controller:
-            await self.async_chromecast_command(self._chromecast.media_controller.play)
+            await self.chromecast_command(self._chromecast.media_controller.play)
 
-    async def async_cmd_pause(self) -> None:
+    async def cmd_pause(self) -> None:
         """Send pause command to player."""
         if self._chromecast.media_controller:
-            await self.async_chromecast_command(self._chromecast.media_controller.pause)
+            await self.chromecast_command(self._chromecast.media_controller.pause)
 
-    async def async_cmd_next(self) -> None:
+    async def cmd_next(self) -> None:
         """Send next track command to player."""
         if self._chromecast.media_controller:
-            await self.async_chromecast_command(
-                self._chromecast.media_controller.queue_next
-            )
+            await self.chromecast_command(self._chromecast.media_controller.queue_next)
 
-    async def async_cmd_previous(self) -> None:
+    async def cmd_previous(self) -> None:
         """Send previous track command to player."""
         if self._chromecast.media_controller:
-            await self.async_chromecast_command(
-                self._chromecast.media_controller.queue_prev
-            )
+            await self.chromecast_command(self._chromecast.media_controller.queue_prev)
 
-    async def async_cmd_power_on(self) -> None:
+    async def cmd_power_on(self) -> None:
         """Send power ON command to player."""
-        await self.async_chromecast_command(self._chromecast.set_volume_muted, False)
+        await self.chromecast_command(self._chromecast.set_volume_muted, False)
 
-    async def async_cmd_power_off(self) -> None:
+    async def cmd_power_off(self) -> None:
         """Send power OFF command to player."""
         # chromecast has no real poweroff so we send mute instead
-        await self.async_chromecast_command(self._chromecast.set_volume_muted, True)
+        await self.chromecast_command(self._chromecast.set_volume_muted, True)
 
-    async def async_cmd_volume_set(self, volume_level: int) -> None:
+    async def cmd_volume_set(self, volume_level: int) -> None:
         """Send new volume level command to player."""
-        await self.async_chromecast_command(
-            self._chromecast.set_volume, volume_level / 100
-        )
+        await self.chromecast_command(self._chromecast.set_volume, volume_level / 100)
 
-    async def async_cmd_volume_mute(self, is_muted: bool = False) -> None:
+    async def cmd_volume_mute(self, is_muted: bool = False) -> None:
         """Send mute command to player."""
-        await self.async_chromecast_command(self._chromecast.set_volume_muted, is_muted)
+        await self.chromecast_command(self._chromecast.set_volume_muted, is_muted)
 
-    async def async_cmd_play_uri(self, uri: str) -> None:
+    async def cmd_play_uri(self, uri: str) -> None:
         """Play single uri on player."""
         player_queue = self.mass.players.get_player_queue(self.player_id)
         if player_queue.use_queue_stream:
             # create CC queue so that skip and previous will work
             queue_item = QueueItem(name="Music Assistant", uri=uri)
-            return await self.async_cmd_queue_load([queue_item, queue_item])
-        await self.async_chromecast_command(
-            self._chromecast.play_media, uri, "audio/flac"
-        )
+            return await self.cmd_queue_load([queue_item, queue_item])
+        await self.chromecast_command(self._chromecast.play_media, uri, "audio/flac")
 
-    async def async_cmd_queue_load(self, queue_items: List[QueueItem]) -> None:
+    async def cmd_queue_load(self, queue_items: List[QueueItem]) -> None:
         """Load (overwrite) queue with new items."""
         player_queue = self.mass.players.get_player_queue(self.player_id)
         cc_queue_items = self.__create_queue_items(queue_items[:50])
@@ -384,20 +375,20 @@ class ChromecastPlayer(Player):
             "startIndex": 0,  # Item index to play after this request or keep same item if undefined
             "items": cc_queue_items,  # only load 50 tracks at once or the socket will crash
         }
-        await self.async_chromecast_command(self.__send_player_queue, queuedata)
+        await self.chromecast_command(self.__send_player_queue, queuedata)
         if len(queue_items) > 50:
-            await self.async_cmd_queue_append(queue_items[51:])
+            await self.cmd_queue_append(queue_items[51:])
 
-    async def async_cmd_queue_append(self, queue_items: List[QueueItem]) -> None:
+    async def cmd_queue_append(self, queue_items: List[QueueItem]) -> None:
         """Append new items at the end of the queue."""
         cc_queue_items = self.__create_queue_items(queue_items)
-        async for chunk in async_yield_chunks(cc_queue_items, 50):
+        async for chunk in yield_chunks(cc_queue_items, 50):
             queuedata = {
                 "type": "QUEUE_INSERT",
                 "insertBefore": None,
                 "items": chunk,
             }
-            await self.async_chromecast_command(self.__send_player_queue, queuedata)
+            await self.chromecast_command(self.__send_player_queue, queuedata)
 
     def __create_queue_items(self, tracks) -> None:
         """Create list of CC queue items from tracks."""
@@ -424,7 +415,7 @@ class ChromecastPlayer(Player):
                 "streamType": "LIVE" if player_queue.use_queue_stream else "BUFFERED",
                 "metadata": {
                     "title": track.name,
-                    "artist": track.artists[0].name if track.artists else "",
+                    "artist": next(iter(track.artists)).name if track.artists else "",
                 },
                 "duration": int(track.duration),
             },
@@ -449,7 +440,7 @@ class ChromecastPlayer(Player):
         else:
             send_queue()
 
-    async def async_chromecast_command(self, func, *args, **kwargs):
+    async def chromecast_command(self, func, *args, **kwargs):
         """Execute command on Chromecast."""
         # Chromecast socket really doesn't like multiple commands arriving at the same time
         # so we apply some throtling.
index 55c0a984fca4673b562f73088b30fd840c7b1475..3856e07cf8f491eb574ec18c7a09441cf070e342 100755 (executable)
@@ -20,10 +20,10 @@ LOGGER = logging.getLogger(PROV_ID)
 CONFIG_ENTRIES = []
 
 
-async def async_setup(mass) -> None:
+async def setup(mass) -> None:
     """Perform async setup of this Plugin/Provider."""
     prov = FanartTvProvider(mass)
-    await mass.async_register_provider(prov)
+    await mass.register_provider(prov)
 
 
 class FanartTvProvider(MetadataProvider):
@@ -34,7 +34,7 @@ class FanartTvProvider(MetadataProvider):
         self.mass = mass
         self.throttler = Throttler(rate_limit=1, period=2)
 
-    async def async_on_start(self) -> bool:
+    async def on_start(self) -> bool:
         """
         Handle initialization of the provider based on config.
 
@@ -57,10 +57,10 @@ class FanartTvProvider(MetadataProvider):
         """Return Config Entries for this provider."""
         return CONFIG_ENTRIES
 
-    async def async_get_artist_images(self, mb_artist_id: str) -> Dict:
+    async def get_artist_images(self, mb_artist_id: str) -> Dict:
         """Retrieve images by musicbrainz artist id."""
         metadata = {}
-        data = await self.__async_get_data("music/%s" % mb_artist_id)
+        data = await self._get_data("music/%s" % mb_artist_id)
         if data:
             if data.get("hdmusiclogo"):
                 metadata["logo"] = data["hdmusiclogo"][0]["url"]
@@ -79,7 +79,7 @@ class FanartTvProvider(MetadataProvider):
                 metadata["banner"] = data["musicbanner"][0]["url"]
         return metadata
 
-    async def __async_get_data(self, endpoint, params=None):
+    async def _get_data(self, endpoint, params=None):
         """Get data from api."""
         if params is None:
             params = {}
index 75fca69a6fc4954202babadbdb7d7ed6edab228f..68a11b1279ceaa0d4b13a20f6bfdc00901dfa473 100644 (file)
@@ -42,10 +42,10 @@ CONFIG_ENTRIES = [
 ]
 
 
-async def async_setup(mass):
+async def setup(mass):
     """Perform async setup of this Plugin/Provider."""
     prov = FileProvider()
-    await mass.async_register_provider(prov)
+    await mass.register_provider(prov)
 
 
 class FileProvider(MusicProvider):
@@ -85,7 +85,7 @@ class FileProvider(MusicProvider):
         """Return MediaTypes the provider supports."""
         return [MediaType.Album, MediaType.Artist, MediaType.Playlist, MediaType.Track]
 
-    async def async_on_start(self) -> bool:
+    async def on_start(self) -> bool:
         """Handle initialization of the provider based on config."""
         conf = self.mass.config.get_provider_config(self.id)
         if not conf[CONF_MUSIC_DIR]:
@@ -98,11 +98,11 @@ class FileProvider(MusicProvider):
         else:
             self._playlists_dir = None
 
-    async def async_on_stop(self):
+    async def on_stop(self):
         """Handle correct close/cleanup of the provider on exit."""
         # nothing to be done
 
-    async def async_search(
+    async def search(
         self, search_query: str, media_types=Optional[List[MediaType]], limit: int = 5
     ) -> SearchResult:
         """
@@ -116,7 +116,7 @@ class FileProvider(MusicProvider):
         # TODO !
         return result
 
-    async def async_get_library_artists(self) -> List[Artist]:
+    async def get_library_artists(self) -> List[Artist]:
         """Retrieve all library artists."""
         if not os.path.isdir(self._music_dir):
             LOGGER.error("music path does not exist: %s", self._music_dir)
@@ -125,24 +125,24 @@ class FileProvider(MusicProvider):
         for dirname in os.listdir(self._music_dir):
             dirpath = os.path.join(self._music_dir, dirname)
             if os.path.isdir(dirpath) and not dirpath.startswith("."):
-                artist = await self.async_get_artist(dirpath)
+                artist = await self.get_artist(dirpath)
                 if artist:
                     yield artist
 
-    async def async_get_library_albums(self) -> List[Album]:
+    async def get_library_albums(self) -> List[Album]:
         """Get album folders recursively."""
-        async for artist in self.async_get_library_artists():
-            async for album in self.async_get_artist_albums(artist.item_id):
+        async for artist in self.get_library_artists():
+            async for album in self.get_artist_albums(artist.item_id):
                 yield album
 
-    async def async_get_library_tracks(self) -> List[Track]:
+    async def get_library_tracks(self) -> List[Track]:
         """Get all tracks recursively."""
         # TODO: support disk subfolders
-        async for album in self.async_get_library_albums():
-            async for track in self.async_get_album_tracks(album.item_id):
+        async for album in self.get_library_albums():
+            async for track in self.get_album_tracks(album.item_id):
                 yield track
 
-    async def async_get_library_playlists(self) -> List[Playlist]:
+    async def get_library_playlists(self) -> List[Playlist]:
         """Retrieve playlists from disk."""
         if not self._playlists_dir:
             yield None
@@ -154,11 +154,11 @@ class FileProvider(MusicProvider):
                 and not filename.startswith(".")
                 and filename.lower().endswith(".m3u")
             ):
-                playlist = await self.async_get_playlist(filepath)
+                playlist = await self.get_playlist(filepath)
                 if playlist:
                     yield playlist
 
-    async def async_get_artist(self, prov_artist_id: str) -> Artist:
+    async def get_artist(self, prov_artist_id: str) -> Artist:
         """Get full artist details by id."""
         if os.sep not in prov_artist_id:
             itempath = base64.b64decode(prov_artist_id).decode("utf-8")
@@ -173,12 +173,12 @@ class FileProvider(MusicProvider):
         artist.item_id = prov_artist_id
         artist.provider = PROV_ID
         artist.name = name
-        artist.provider_ids.append(
+        artist.provider_ids.add(
             MediaItemProviderId(provider=PROV_ID, item_id=artist.item_id)
         )
         return artist
 
-    async def async_get_album(self, prov_album_id: str) -> Album:
+    async def get_album(self, prov_album_id: str) -> Album:
         """Get full album details by id."""
         if os.sep not in prov_album_id:
             itempath = base64.b64decode(prov_album_id).decode("utf-8")
@@ -194,15 +194,15 @@ class FileProvider(MusicProvider):
         album.item_id = prov_album_id
         album.provider = PROV_ID
         album.name, album.version = parse_title_and_version(name)
-        album.artist = await self.async_get_artist(artistpath)
+        album.artist = await self.get_artist(artistpath)
         if not album.artist:
             raise Exception("No album artist ! %s" % artistpath)
-        album.provider_ids.append(
+        album.provider_ids.add(
             MediaItemProviderId(provider=PROV_ID, item_id=prov_album_id)
         )
         return album
 
-    async def async_get_track(self, prov_track_id: str) -> Track:
+    async def get_track(self, prov_track_id: str) -> Track:
         """Get full track details by id."""
         if os.sep not in prov_track_id:
             itempath = base64.b64decode(prov_track_id).decode("utf-8")
@@ -211,9 +211,9 @@ class FileProvider(MusicProvider):
         if not os.path.isfile(itempath):
             LOGGER.error("track path does not exist: %s", itempath)
             return None
-        return await self.__async_parse_track(itempath)
+        return await self._parse_track(itempath)
 
-    async def async_get_playlist(self, prov_playlist_id: str) -> Playlist:
+    async def get_playlist(self, prov_playlist_id: str) -> Playlist:
         """Get full playlist details by id."""
         if os.sep not in prov_playlist_id:
             itempath = base64.b64decode(prov_playlist_id).decode("utf-8")
@@ -230,14 +230,14 @@ class FileProvider(MusicProvider):
         playlist.provider = PROV_ID
         playlist.name = itempath.split(os.sep)[-1].replace(".m3u", "")
         playlist.is_editable = True
-        playlist.provider_ids.append(
+        playlist.provider_ids.add(
             MediaItemProviderId(provider=PROV_ID, item_id=prov_playlist_id)
         )
         playlist.owner = "disk"
         playlist.checksum = os.path.getmtime(itempath)
         return playlist
 
-    async def async_get_album_tracks(self, prov_album_id) -> List[Track]:
+    async def get_album_tracks(self, prov_album_id) -> List[Track]:
         """Get album tracks for given album id."""
         if os.sep not in prov_album_id:
             albumpath = base64.b64decode(prov_album_id).decode("utf-8")
@@ -246,16 +246,16 @@ class FileProvider(MusicProvider):
         if not os.path.isdir(albumpath):
             LOGGER.error("album path does not exist: %s", albumpath)
             return
-        album = await self.async_get_album(albumpath)
+        album = await self.get_album(albumpath)
         for filename in os.listdir(albumpath):
             filepath = os.path.join(albumpath, filename)
             if os.path.isfile(filepath) and not filepath.startswith("."):
-                track = await self.__async_parse_track(filepath)
+                track = await self._parse_track(filepath)
                 if track:
                     track.album = album
                     yield track
 
-    async def async_get_playlist_tracks(self, prov_playlist_id: str) -> List[Track]:
+    async def get_playlist_tracks(self, prov_playlist_id: str) -> List[Track]:
         """Get playlist tracks for given playlist id."""
         if os.sep not in prov_playlist_id:
             itempath = base64.b64decode(prov_playlist_id).decode("utf-8")
@@ -269,12 +269,12 @@ class FileProvider(MusicProvider):
             for line in _file.readlines():
                 line = line.strip()
                 if line and not line.startswith("#"):
-                    track = await self.__async_parse_track_from_uri(line)
+                    track = await self._parse_track_from_uri(line)
                     if track:
                         yield track
                         index += 1
 
-    async def async_get_artist_albums(self, prov_artist_id: str) -> List[Album]:
+    async def get_artist_albums(self, prov_artist_id: str) -> List[Album]:
         """Get a list of albums for the given artist."""
         if os.sep not in prov_artist_id:
             artistpath = base64.b64decode(prov_artist_id).decode("utf-8")
@@ -286,17 +286,17 @@ class FileProvider(MusicProvider):
         for dirname in os.listdir(artistpath):
             dirpath = os.path.join(artistpath, dirname)
             if os.path.isdir(dirpath) and not dirpath.startswith("."):
-                album = await self.async_get_album(dirpath)
+                album = await self.get_album(dirpath)
                 if album:
                     yield album
 
-    async def async_get_artist_toptracks(self, prov_artist_id: str) -> List[Track]:
+    async def get_artist_toptracks(self, prov_artist_id: str) -> List[Track]:
         """Get a list of random tracks as we have no clue about preference."""
-        async for album in self.async_get_artist_albums(prov_artist_id):
-            async for track in self.async_get_album_tracks(album.item_id):
+        async for album in self.get_artist_albums(prov_artist_id):
+            async for track in self.get_album_tracks(album.item_id):
                 yield track
 
-    async def async_get_stream_details(self, item_id: str) -> StreamDetails:
+    async def get_stream_details(self, item_id: str) -> StreamDetails:
         """Return the content details for the given track when it will be streamed."""
         if os.sep not in item_id:
             track_id = base64.b64decode(item_id).decode("utf-8")
@@ -313,7 +313,7 @@ class FileProvider(MusicProvider):
             bit_depth=16,
         )
 
-    async def __async_parse_track(self, filename):
+    async def _parse_track(self, filename):
         """Try to parse a track from a filename with taglib."""
         track = Track()
         # pylint: disable=broad-except
@@ -328,18 +328,18 @@ class FileProvider(MusicProvider):
         name = song.tags["TITLE"][0]
         track.name, track.version = parse_title_and_version(name)
         albumpath = filename.rsplit(os.sep, 1)[0]
-        track.album = await self.async_get_album(albumpath)
-        artists = []
+        track.album = await self.get_album(albumpath)
+        artists = set()
         for artist_str in song.tags["ARTIST"]:
             local_artist_path = os.path.join(self._music_dir, artist_str)
             if os.path.isfile(local_artist_path):
-                artist = await self.async_get_artist(local_artist_path)
+                artist = await self.get_artist(local_artist_path)
             else:
                 artist = Artist()
                 artist.name = artist_str
                 fake_artistpath = os.path.join(self._music_dir, artist_str)
                 artist.item_id = fake_artistpath  # temporary id
-                artist.provider_ids.append(
+                artist.provider_ids.add(
                     MediaItemProviderId(
                         provider=PROV_ID,
                         item_id=base64.b64encode(
@@ -347,7 +347,7 @@ class FileProvider(MusicProvider):
                         ).decode("utf-8"),
                     )
                 )
-            artists.append(artist)
+            artists.add(artist)
         track.artists = artists
         if "GENRE" in song.tags:
             track.metadata["genres"] = song.tags["GENRE"]
@@ -377,7 +377,7 @@ class FileProvider(MusicProvider):
         else:
             quality = TrackQuality.LOSSY_MP3
             quality_details = "%s kbps" % (song.bitrate)
-        track.provider_ids.append(
+        track.provider_ids.add(
             MediaItemProviderId(
                 provider=PROV_ID,
                 item_id=prov_item_id,
@@ -387,7 +387,7 @@ class FileProvider(MusicProvider):
         )
         return track
 
-    async def __async_parse_track_from_uri(self, uri):
+    async def _parse_track_from_uri(self, uri):
         """Try to parse a track from an uri found in playlist."""
         # pylint: disable=broad-except
         if "://" in uri:
@@ -395,16 +395,16 @@ class FileProvider(MusicProvider):
             prov_id = uri.split("://")[0]
             prov_item_id = uri.split("/")[-1].split(".")[0].split(":")[-1]
             try:
-                return await self.mass.music.async_get_track(prov_item_id, prov_id)
+                return await self.mass.music.get_track(prov_item_id, prov_id)
             except Exception as exc:
                 LOGGER.warning("Could not parse uri %s to track: %s", uri, str(exc))
                 return None
         # try to treat uri as filename
         # TODO: filename could be related to musicdir or full path
-        track = await self.async_get_track(uri)
+        track = await self.get_track(uri)
         if track:
             return track
-        track = await self.async_get_track(os.path.join(self._music_dir, uri))
+        track = await self.get_track(os.path.join(self._music_dir, uri))
         if track:
             return track
         return None
index 7557310d09dcca260439e8c8e23d8abd0fc66407..9ddf77ee26d1d9d5451b4d065b16abd45d55d468 100644 (file)
@@ -48,10 +48,10 @@ CONFIG_ENTRIES = [
 ]
 
 
-async def async_setup(mass):
+async def setup(mass):
     """Perform async setup of this Plugin/Provider."""
     prov = QobuzProvider()
-    await mass.async_register_provider(prov)
+    await mass.register_provider(prov)
 
 
 class QobuzProvider(MusicProvider):
@@ -81,7 +81,7 @@ class QobuzProvider(MusicProvider):
         """Return MediaTypes the provider supports."""
         return [MediaType.Album, MediaType.Artist, MediaType.Playlist, MediaType.Track]
 
-    async def async_on_start(self) -> bool:
+    async def on_start(self) -> bool:
         """Handle initialization of the provider based on config."""
         # pylint: disable=attribute-defined-outside-init
         config = self.mass.config.get_provider_config(self.id)
@@ -95,11 +95,11 @@ class QobuzProvider(MusicProvider):
         self.__logged_in = False
         self._throttler = Throttler(rate_limit=4, period=1)
         self.mass.add_event_listener(
-            self.async_mass_event, [EVENT_STREAM_STARTED, EVENT_STREAM_ENDED]
+            self.mass_event, (EVENT_STREAM_STARTED, EVENT_STREAM_ENDED)
         )
         return True
 
-    async def async_search(
+    async def search(
         self, search_query: str, media_types=Optional[List[MediaType]], limit: int = 5
     ) -> SearchResult:
         """
@@ -121,151 +121,147 @@ class QobuzProvider(MusicProvider):
                 params["type"] = "tracks"
             if media_types[0] == MediaType.Playlist:
                 params["type"] = "playlists"
-        searchresult = await self.__async_get_data("catalog/search", params)
+        searchresult = await self._get_data("catalog/search", params)
         if searchresult:
             if "artists" in searchresult:
                 result.artists = [
-                    await self.__async_parse_artist(item)
+                    await self._parse_artist(item)
                     for item in searchresult["artists"]["items"]
                     if (item and item["id"])
                 ]
             if "albums" in searchresult:
                 result.albums = [
-                    await self.__async_parse_album(item)
+                    await self._parse_album(item)
                     for item in searchresult["albums"]["items"]
                     if (item and item["id"])
                 ]
             if "tracks" in searchresult:
                 result.tracks = [
-                    await self.__async_parse_track(item)
+                    await self._parse_track(item)
                     for item in searchresult["tracks"]["items"]
                     if (item and item["id"])
                 ]
             if "playlists" in searchresult:
                 result.playlists = [
-                    await self.__async_parse_playlist(item)
+                    await self._parse_playlist(item)
                     for item in searchresult["playlists"]["items"]
                     if (item and item["id"])
                 ]
         return result
 
-    async def async_get_library_artists(self) -> List[Artist]:
+    async def get_library_artists(self) -> List[Artist]:
         """Retrieve all library artists from Qobuz."""
         params = {"type": "artists"}
         endpoint = "favorite/getUserFavorites"
         return [
-            await self.__async_parse_artist(item)
-            for item in await self.__async_get_all_items(
-                endpoint, params, key="artists"
-            )
+            await self._parse_artist(item)
+            for item in await self._get_all_items(endpoint, params, key="artists")
             if (item and item["id"])
         ]
 
-    async def async_get_library_albums(self) -> List[Album]:
+    async def get_library_albums(self) -> List[Album]:
         """Retrieve all library albums from Qobuz."""
         params = {"type": "albums"}
         endpoint = "favorite/getUserFavorites"
         return [
-            await self.__async_parse_album(item)
-            for item in await self.__async_get_all_items(endpoint, params, key="albums")
+            await self._parse_album(item)
+            for item in await self._get_all_items(endpoint, params, key="albums")
             if (item and item["id"])
         ]
 
-    async def async_get_library_tracks(self) -> List[Track]:
+    async def get_library_tracks(self) -> List[Track]:
         """Retrieve library tracks from Qobuz."""
         params = {"type": "tracks"}
         endpoint = "favorite/getUserFavorites"
         return [
-            await self.__async_parse_track(item)
-            for item in await self.__async_get_all_items(endpoint, params, key="tracks")
+            await self._parse_track(item)
+            for item in await self._get_all_items(endpoint, params, key="tracks")
             if (item and item["id"])
         ]
 
-    async def async_get_library_playlists(self) -> List[Playlist]:
+    async def get_library_playlists(self) -> List[Playlist]:
         """Retrieve all library playlists from the provider."""
         endpoint = "playlist/getUserPlaylists"
         return [
-            await self.__async_parse_playlist(item)
-            for item in await self.__async_get_all_items(endpoint, key="playlists")
+            await self._parse_playlist(item)
+            for item in await self._get_all_items(endpoint, key="playlists")
             if (item and item["id"])
         ]
 
-    async def async_get_radios(self) -> List[Radio]:
+    async def get_radios(self) -> List[Radio]:
         """Retrieve library/subscribed radio stations from the provider."""
         return []  # TODO
 
-    async def async_get_artist(self, prov_artist_id) -> Artist:
+    async def get_artist(self, prov_artist_id) -> Artist:
         """Get full artist details by id."""
         params = {"artist_id": prov_artist_id}
-        artist_obj = await self.__async_get_data("artist/get", params)
+        artist_obj = await self._get_data("artist/get", params)
         return (
-            await self.__async_parse_artist(artist_obj)
+            await self._parse_artist(artist_obj)
             if artist_obj and artist_obj["id"]
             else None
         )
 
-    async def async_get_album(self, prov_album_id) -> Album:
+    async def get_album(self, prov_album_id) -> Album:
         """Get full album details by id."""
         params = {"album_id": prov_album_id}
-        album_obj = await self.__async_get_data("album/get", params)
+        album_obj = await self._get_data("album/get", params)
         return (
-            await self.__async_parse_album(album_obj)
+            await self._parse_album(album_obj)
             if album_obj and album_obj["id"]
             else None
         )
 
-    async def async_get_track(self, prov_track_id) -> Track:
+    async def get_track(self, prov_track_id) -> Track:
         """Get full track details by id."""
         params = {"track_id": prov_track_id}
-        track_obj = await self.__async_get_data("track/get", params)
+        track_obj = await self._get_data("track/get", params)
         return (
-            await self.__async_parse_track(track_obj)
+            await self._parse_track(track_obj)
             if track_obj and track_obj["id"]
             else None
         )
 
-    async def async_get_playlist(self, prov_playlist_id) -> Playlist:
+    async def get_playlist(self, prov_playlist_id) -> Playlist:
         """Get full playlist details by id."""
         params = {"playlist_id": prov_playlist_id}
-        playlist_obj = await self.__async_get_data("playlist/get", params)
+        playlist_obj = await self._get_data("playlist/get", params)
         return (
-            await self.__async_parse_playlist(playlist_obj)
+            await self._parse_playlist(playlist_obj)
             if playlist_obj and playlist_obj["id"]
             else None
         )
 
-    async def async_get_album_tracks(self, prov_album_id) -> List[Track]:
+    async def get_album_tracks(self, prov_album_id) -> List[Track]:
         """Get all album tracks for given album id."""
         params = {"album_id": prov_album_id}
         return [
-            await self.__async_parse_track(item)
-            for item in await self.__async_get_all_items(
-                "album/get", params, key="tracks"
-            )
+            await self._parse_track(item)
+            for item in await self._get_all_items("album/get", params, key="tracks")
             if (item and item["id"])
         ]
 
-    async def async_get_playlist_tracks(self, prov_playlist_id) -> List[Track]:
+    async def get_playlist_tracks(self, prov_playlist_id) -> List[Track]:
         """Get all playlist tracks for given playlist id."""
         params = {"playlist_id": prov_playlist_id, "extra": "tracks"}
         endpoint = "playlist/get"
         return [
-            await self.__async_parse_track(item)
-            for item in await self.__async_get_all_items(endpoint, params, key="tracks")
+            await self._parse_track(item)
+            for item in await self._get_all_items(endpoint, params, key="tracks")
             if (item and item["id"])
         ]
 
-    async def async_get_artist_albums(self, prov_artist_id) -> List[Album]:
+    async def get_artist_albums(self, prov_artist_id) -> List[Album]:
         """Get a list of albums for the given artist."""
         params = {"artist_id": prov_artist_id, "extra": "albums"}
         endpoint = "artist/get"
         return [
-            await self.__async_parse_album(item)
-            for item in await self.__async_get_all_items(endpoint, params, key="albums")
+            await self._parse_album(item)
+            for item in await self._get_all_items(endpoint, params, key="albums")
             if (item and item["id"])
         ]
 
-    async def async_get_artist_toptracks(self, prov_artist_id) -> List[Track]:
+    async def get_artist_toptracks(self, prov_artist_id) -> List[Track]:
         """Get a list of most popular tracks for the given artist."""
         params = {
             "artist_id": prov_artist_id,
@@ -273,19 +269,19 @@ class QobuzProvider(MusicProvider):
             "offset": 0,
             "limit": 25,
         }
-        result = await self.__async_get_data("artist/get", params)
+        result = await self._get_data("artist/get", params)
         if result and result["playlists"]:
             return [
-                await self.__async_parse_track(item)
+                await self._parse_track(item)
                 for item in result["playlists"][0]["tracks"]["items"]
                 if (item and item["id"])
             ]
         # fallback to search
-        artist = await self.async_get_artist(prov_artist_id)
+        artist = await self.get_artist(prov_artist_id)
         params = {"query": artist.name, "limit": 25, "type": "tracks"}
-        searchresult = await self.__async_get_data("catalog/search", params)
+        searchresult = await self._get_data("catalog/search", params)
         return [
-            await self.__async_parse_track(item)
+            await self._parse_track(item)
             for item in searchresult["tracks"]["items"]
             if (
                 item
@@ -295,91 +291,87 @@ class QobuzProvider(MusicProvider):
             )
         ]
 
-    async def async_get_similar_artists(self, prov_artist_id):
+    async def get_similar_artists(self, prov_artist_id):
         """Get similar artists for given artist."""
         # https://www.qobuz.com/api.json/0.2/artist/getSimilarArtists?artist_id=220020&offset=0&limit=3
 
-    async def async_library_add(self, prov_item_id, media_type: MediaType):
+    async def library_add(self, prov_item_id, media_type: MediaType):
         """Add item to library."""
         result = None
         if media_type == MediaType.Artist:
-            result = await self.__async_get_data(
+            result = await self._get_data(
                 "favorite/create", {"artist_ids": prov_item_id}
             )
         elif media_type == MediaType.Album:
-            result = await self.__async_get_data(
+            result = await self._get_data(
                 "favorite/create", {"album_ids": prov_item_id}
             )
         elif media_type == MediaType.Track:
-            result = await self.__async_get_data(
+            result = await self._get_data(
                 "favorite/create", {"track_ids": prov_item_id}
             )
         elif media_type == MediaType.Playlist:
-            result = await self.__async_get_data(
+            result = await self._get_data(
                 "playlist/subscribe", {"playlist_id": prov_item_id}
             )
         return result
 
-    async def async_library_remove(self, prov_item_id, media_type: MediaType):
+    async def library_remove(self, prov_item_id, media_type: MediaType):
         """Remove item from library."""
         result = None
         if media_type == MediaType.Artist:
-            result = await self.__async_get_data(
+            result = await self._get_data(
                 "favorite/delete", {"artist_ids": prov_item_id}
             )
         elif media_type == MediaType.Album:
-            result = await self.__async_get_data(
+            result = await self._get_data(
                 "favorite/delete", {"album_ids": prov_item_id}
             )
         elif media_type == MediaType.Track:
-            result = await self.__async_get_data(
+            result = await self._get_data(
                 "favorite/delete", {"track_ids": prov_item_id}
             )
         elif media_type == MediaType.Playlist:
-            playlist = await self.async_get_playlist(prov_item_id)
+            playlist = await self.get_playlist(prov_item_id)
             if playlist.is_editable:
-                result = await self.__async_get_data(
+                result = await self._get_data(
                     "playlist/delete", {"playlist_id": prov_item_id}
                 )
             else:
-                result = await self.__async_get_data(
+                result = await self._get_data(
                     "playlist/unsubscribe", {"playlist_id": prov_item_id}
                 )
         return result
 
-    async def async_add_playlist_tracks(self, prov_playlist_id, prov_track_ids):
+    async def add_playlist_tracks(self, prov_playlist_id, prov_track_ids):
         """Add track(s) to playlist."""
         params = {
             "playlist_id": prov_playlist_id,
             "track_ids": ",".join(prov_track_ids),
         }
-        return await self.__async_get_data("playlist/addTracks", params)
+        return await self._get_data("playlist/addTracks", params)
 
-    async def async_remove_playlist_tracks(self, prov_playlist_id, prov_track_ids):
+    async def remove_playlist_tracks(self, prov_playlist_id, prov_track_ids):
         """Remove track(s) from playlist."""
-        playlist_track_ids = []
+        playlist_track_ids = set()
         params = {"playlist_id": prov_playlist_id, "extra": "tracks"}
-        for track in await self.__async_get_all_items(
-            "playlist/get", params, key="tracks"
-        ):
+        for track in await self._get_all_items("playlist/get", params, key="tracks"):
             if track["id"] in prov_track_ids:
-                playlist_track_ids.append(track["playlist_track_id"])
+                playlist_track_ids.add(track["playlist_track_id"])
         params = {
             "playlist_id": prov_playlist_id,
             "track_ids": ",".join(playlist_track_ids),
         }
-        return await self.__async_get_data("playlist/deleteTracks", params)
+        return await self._get_data("playlist/deleteTracks", params)
 
-    async def async_get_stream_details(self, item_id: str) -> StreamDetails:
+    async def get_stream_details(self, item_id: str) -> StreamDetails:
         """Return the content details for the given track when it will be streamed."""
         streamdata = None
         for format_id in [27, 7, 6, 5]:
             # it seems that simply requesting for highest available quality does not work
             # from time to time the api response is empty for this request ?!
             params = {"format_id": format_id, "track_id": item_id, "intent": "stream"}
-            result = await self.__async_get_data(
-                "track/getFileUrl", params, sign_request=True
-            )
+            result = await self._get_data("track/getFileUrl", params, sign_request=True)
             if result and result.get("url"):
                 streamdata = result
                 break
@@ -404,7 +396,7 @@ class QobuzProvider(MusicProvider):
             details=streamdata,  # we need these details for reporting playback
         )
 
-    async def async_mass_event(self, msg, msg_details):
+    async def mass_event(self, msg, msg_details):
         """
         Received event from mass.
 
@@ -437,7 +429,7 @@ class QobuzProvider(MusicProvider):
                     "format_id": format_id,
                 }
             ]
-            await self.__async_post_data("track/reportStreamingStart", data=events)
+            await self._post_data("track/reportStreamingStart", data=events)
         elif msg == EVENT_STREAM_ENDED and msg_details.provider == PROV_ID:
             # report streaming ended to qobuz
             # if msg_details.details < 6:
@@ -448,14 +440,14 @@ class QobuzProvider(MusicProvider):
                 "track_id": str(msg_details.item_id),
                 "duration": try_parse_int(msg_details.seconds_played),
             }
-            await self.__async_get_data("/track/reportStreamingEnd", params)
+            await self._get_data("/track/reportStreamingEnd", params)
 
-    async def __async_parse_artist(self, artist_obj):
+    async def _parse_artist(self, artist_obj):
         """Parse qobuz artist object to generic layout."""
         artist = Artist(
             item_id=str(artist_obj["id"]), provider=PROV_ID, name=artist_obj["name"]
         )
-        artist.provider_ids.append(
+        artist.provider_ids.add(
             MediaItemProviderId(provider=PROV_ID, item_id=str(artist_obj["id"]))
         )
         artist.metadata["image"] = self.__get_image(artist_obj)
@@ -465,7 +457,7 @@ class QobuzProvider(MusicProvider):
             artist.metadata["qobuz_url"] = artist_obj["url"]
         return artist
 
-    async def __async_parse_album(self, album_obj: dict, artist_obj: dict = None):
+    async def _parse_album(self, album_obj: dict, artist_obj: dict = None):
         """Parse qobuz album object to generic layout."""
         album = Album(item_id=str(album_obj["id"]), provider=PROV_ID)
         if album_obj["maximum_sampling_rate"] > 192:
@@ -480,7 +472,7 @@ class QobuzProvider(MusicProvider):
             quality = TrackQuality.LOSSY_AAC
         else:
             quality = TrackQuality.FLAC_LOSSLESS
-        album.provider_ids.append(
+        album.provider_ids.add(
             MediaItemProviderId(
                 provider=PROV_ID,
                 item_id=str(album_obj["id"]),
@@ -495,7 +487,7 @@ class QobuzProvider(MusicProvider):
         if artist_obj:
             album.artist = artist_obj
         else:
-            album.artist = await self.__async_parse_artist(album_obj["artist"])
+            album.artist = await self._parse_artist(album_obj["artist"])
         if (
             album_obj.get("product_type", "") == "single"
             or album_obj.get("release_type", "") == "single"
@@ -534,7 +526,7 @@ class QobuzProvider(MusicProvider):
             album.metadata["description"] = album_obj["description"]
         return album
 
-    async def __async_parse_track(self, track_obj):
+    async def _parse_track(self, track_obj):
         """Parse qobuz track object to generic layout."""
         track = Track(
             item_id=str(track_obj["id"]),
@@ -544,9 +536,9 @@ class QobuzProvider(MusicProvider):
             duration=track_obj["duration"],
         )
         if track_obj.get("performer") and "Various " not in track_obj["performer"]:
-            artist = await self.__async_parse_artist(track_obj["performer"])
+            artist = await self._parse_artist(track_obj["performer"])
             if artist:
-                track.artists.append(artist)
+                track.artists.add(artist)
         if not track.artists:
             # try to grab artist from album
             if (
@@ -554,9 +546,9 @@ class QobuzProvider(MusicProvider):
                 and track_obj["album"].get("artist")
                 and "Various " not in track_obj["album"]["artist"]
             ):
-                artist = await self.__async_parse_artist(track_obj["album"]["artist"])
+                artist = await self._parse_artist(track_obj["album"]["artist"])
                 if artist:
-                    track.artists.append(artist)
+                    track.artists.add(artist)
         if not track.artists:
             # last resort: parse from performers string
             for performer_str in track_obj["performers"].split(" - "):
@@ -566,13 +558,13 @@ class QobuzProvider(MusicProvider):
                     artist = Artist()
                     artist.name = name
                     artist.item_id = name
-                track.artists.append(artist)
+                track.artists.add(artist)
         # TODO: fix grabbing composer from details
         track.name, track.version = parse_title_and_version(
             track_obj["title"], track_obj.get("version")
         )
         if "album" in track_obj:
-            album = await self.__async_parse_album(track_obj["album"])
+            album = await self._parse_album(track_obj["album"])
             if album:
                 track.album = album
         if track_obj.get("hires"):
@@ -605,7 +597,7 @@ class QobuzProvider(MusicProvider):
             quality = TrackQuality.LOSSY_AAC
         else:
             quality = TrackQuality.FLAC_LOSSLESS
-        track.provider_ids.append(
+        track.provider_ids.add(
             MediaItemProviderId(
                 provider=PROV_ID,
                 item_id=str(track_obj["id"]),
@@ -616,7 +608,7 @@ class QobuzProvider(MusicProvider):
         )
         return track
 
-    async def __async_parse_playlist(self, playlist_obj):
+    async def _parse_playlist(self, playlist_obj):
         """Parse qobuz playlist object to generic layout."""
         playlist = Playlist(
             item_id=playlist_obj["id"],
@@ -624,7 +616,7 @@ class QobuzProvider(MusicProvider):
             name=playlist_obj["name"],
             owner=playlist_obj["owner"]["name"],
         )
-        playlist.provider_ids.append(
+        playlist.provider_ids.add(
             MediaItemProviderId(provider=PROV_ID, item_id=str(playlist_obj["id"]))
         )
         playlist.is_editable = (
@@ -637,7 +629,7 @@ class QobuzProvider(MusicProvider):
         playlist.checksum = playlist_obj["updated_at"]
         return playlist
 
-    async def __async_auth_token(self):
+    async def _auth_token(self):
         """Login to qobuz and store the token."""
         if self.__user_auth_info:
             return self.__user_auth_info["user_auth_token"]
@@ -646,7 +638,7 @@ class QobuzProvider(MusicProvider):
             "password": self.__password,
             "device_manufacturer_id": "music_assistant",
         }
-        details = await self.__async_get_data("user/login", params)
+        details = await self._get_data("user/login", params)
         if details and "user" in details:
             self.__user_auth_info = details
             LOGGER.info(
@@ -654,7 +646,7 @@ class QobuzProvider(MusicProvider):
             )
             return details["user_auth_token"]
 
-    async def __async_get_all_items(self, endpoint, params=None, key="tracks"):
+    async def _get_all_items(self, endpoint, params=None, key="tracks"):
         """Get all items from a paged list."""
         if not params:
             params = {}
@@ -664,7 +656,7 @@ class QobuzProvider(MusicProvider):
         while True:
             params["limit"] = limit
             params["offset"] = offset
-            result = await self.__async_get_data(endpoint, params=params)
+            result = await self._get_data(endpoint, params=params)
             offset += limit
             if not result:
                 break
@@ -675,14 +667,14 @@ class QobuzProvider(MusicProvider):
                 break
         return all_items
 
-    async def __async_get_data(self, endpoint, params=None, sign_request=False):
+    async def _get_data(self, endpoint, params=None, sign_request=False):
         """Get data from api."""
         if not params:
             params = {}
         url = "http://www.qobuz.com/api.json/0.2/%s" % endpoint
         headers = {"X-App-Id": get_app_var(0)}
         if endpoint != "user/login":
-            auth_token = await self.__async_auth_token()
+            auth_token = await self._auth_token()
             if not auth_token:
                 LOGGER.debug("Not logged in")
                 return None
@@ -699,7 +691,7 @@ class QobuzProvider(MusicProvider):
             params["request_ts"] = request_ts
             params["request_sig"] = request_sig
             params["app_id"] = get_app_var(0)
-            params["user_auth_token"] = await self.__async_auth_token()
+            params["user_auth_token"] = await self._auth_token()
         async with self._throttler:
             async with self.mass.http_session.get(
                 url, headers=headers, params=params, verify_ssl=False
@@ -712,7 +704,7 @@ class QobuzProvider(MusicProvider):
                     return None
                 return result
 
-    async def __async_post_data(self, endpoint, params=None, data=None):
+    async def _post_data(self, endpoint, params=None, data=None):
         """Post data to api."""
         if not params:
             params = {}
@@ -720,7 +712,7 @@ class QobuzProvider(MusicProvider):
             data = {}
         url = "http://www.qobuz.com/api.json/0.2/%s" % endpoint
         params["app_id"] = get_app_var(0)
-        params["user_auth_token"] = await self.__async_auth_token()
+        params["user_auth_token"] = await self._auth_token()
         async with self.mass.http_session.post(
             url, params=params, json=data, verify_ssl=False
         ) as response:
index 71f6909663f9855d03d8439263c71dbc49d3c1c5..782c345164a7ebdb401530b2c576de788dc3f768 100644 (file)
@@ -3,7 +3,7 @@
 from .sonos import SonosProvider
 
 
-async def async_setup(mass):
+async def setup(mass):
     """Perform async setup of this Plugin/Provider."""
     prov = SonosProvider()
-    await mass.async_register_provider(prov)
+    await mass.register_provider(prov)
index 8d8603bd7cf5fe3f91345cccc54a4faeee402d6d..aae18158ee900422b8596c2dd7b091194ced7e8f 100644 (file)
@@ -51,16 +51,16 @@ class SonosProvider(PlayerProvider):
         """Return Config Entries for this provider."""
         return CONFIG_ENTRIES
 
-    async def async_on_start(self) -> bool:
+    async def on_start(self) -> bool:
         """Handle initialization of the provider."""
-        self._tasks.append(self.mass.add_job(self.__async_periodic_discovery()))
+        self._tasks.append(self.mass.add_job(self._periodic_discovery()))
 
-    async def async_on_stop(self):
+    async def on_stop(self):
         """Handle correct close/cleanup of the provider on exit."""
         for task in self._tasks:
             task.cancel()
 
-    async def async_cmd_play_uri(self, player_id: str, uri: str):
+    async def cmd_play_uri(self, player_id: str, uri: str):
         """
         Play the specified uri/url on the goven player.
 
@@ -72,7 +72,7 @@ class SonosProvider(PlayerProvider):
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_stop(self, player_id: str) -> None:
+    async def cmd_stop(self, player_id: str) -> None:
         """
         Send STOP command to given player.
 
@@ -84,7 +84,7 @@ class SonosProvider(PlayerProvider):
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_play(self, player_id: str) -> None:
+    async def cmd_play(self, player_id: str) -> None:
         """
         Send STOP command to given player.
 
@@ -96,7 +96,7 @@ class SonosProvider(PlayerProvider):
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_pause(self, player_id: str):
+    async def cmd_pause(self, player_id: str):
         """
         Send PAUSE command to given player.
 
@@ -108,7 +108,7 @@ class SonosProvider(PlayerProvider):
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_next(self, player_id: str):
+    async def cmd_next(self, player_id: str):
         """
         Send NEXT TRACK command to given player.
 
@@ -120,7 +120,7 @@ class SonosProvider(PlayerProvider):
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_previous(self, player_id: str):
+    async def cmd_previous(self, player_id: str):
         """
         Send PREVIOUS TRACK command to given player.
 
@@ -132,7 +132,7 @@ class SonosProvider(PlayerProvider):
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_power_on(self, player_id: str) -> None:
+    async def cmd_power_on(self, player_id: str) -> None:
         """
         Send POWER ON command to given player.
 
@@ -146,7 +146,7 @@ class SonosProvider(PlayerProvider):
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_power_off(self, player_id: str) -> None:
+    async def cmd_power_off(self, player_id: str) -> None:
         """
         Send POWER OFF command to given player.
 
@@ -160,7 +160,7 @@ class SonosProvider(PlayerProvider):
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_volume_set(self, player_id: str, volume_level: int) -> None:
+    async def cmd_volume_set(self, player_id: str, volume_level: int) -> None:
         """
         Send volume level command to given player.
 
@@ -173,7 +173,7 @@ class SonosProvider(PlayerProvider):
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_volume_mute(self, player_id: str, is_muted=False):
+    async def cmd_volume_mute(self, player_id: str, is_muted=False):
         """
         Send volume MUTE command to given player.
 
@@ -186,7 +186,7 @@ class SonosProvider(PlayerProvider):
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_queue_play_index(self, player_id: str, index: int):
+    async def cmd_queue_play_index(self, player_id: str, index: int):
         """
         Play item at index X on player's queue.
 
@@ -199,7 +199,7 @@ class SonosProvider(PlayerProvider):
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_queue_load(self, player_id: str, queue_items: List[QueueItem]):
+    async def cmd_queue_load(self, player_id: str, queue_items: List[QueueItem]):
         """
         Load/overwrite given items in the player's queue implementation.
 
@@ -214,7 +214,7 @@ class SonosProvider(PlayerProvider):
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_queue_insert(
+    async def cmd_queue_insert(
         self, player_id: str, queue_items: List[QueueItem], insert_at_index: int
     ):
         """
@@ -234,9 +234,7 @@ class SonosProvider(PlayerProvider):
         else:
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_queue_append(
-        self, player_id: str, queue_items: List[QueueItem]
-    ):
+    async def cmd_queue_append(self, player_id: str, queue_items: List[QueueItem]):
         """
         Append new items at the end of the queue.
 
@@ -245,12 +243,12 @@ class SonosProvider(PlayerProvider):
         """
         player_queue = self.mass.players.get_player_queue(player_id)
         if player_queue:
-            return await self.async_cmd_queue_insert(
+            return await self.cmd_queue_insert(
                 player_id, queue_items, len(player_queue.items)
             )
         LOGGER.warning("Received command for unavailable player: %s", player_id)
 
-    async def async_cmd_queue_clear(self, player_id: str):
+    async def cmd_queue_clear(self, player_id: str):
         """
         Clear the player's queue.
 
@@ -263,7 +261,7 @@ class SonosProvider(PlayerProvider):
             LOGGER.warning("Received command for unavailable player: %s", player_id)
 
     @run_periodic(1800)
-    async def __async_periodic_discovery(self):
+    async def _periodic_discovery(self):
         """Run Sonos discovery at interval."""
         self._tasks.append(self.mass.add_job(None, self.__run_discovery))
 
@@ -281,9 +279,7 @@ class SonosProvider(PlayerProvider):
         # remove any disconnected players...
         for player in list(self._players.values()):
             if not player.is_group and player.soco.uid not in new_device_ids:
-                self.mass.add_job(
-                    self.mass.players.async_remove_player(player.player_id)
-                )
+                self.mass.add_job(self.mass.players.remove_player(player.player_id))
                 for sub in player.subscriptions:
                     sub.unsubscribe()
                 self._players.pop(player, None)
@@ -326,7 +322,7 @@ class SonosProvider(PlayerProvider):
         subscribe(soco_device.avTransport, self.__player_event)
         subscribe(soco_device.renderingControl, self.__player_event)
         subscribe(soco_device.zoneGroupTopology, self.__topology_changed)
-        self.mass.run_task(self.mass.players.async_add_player(player))
+        self.mass.run_task(self.mass.players.add_player(player))
         return player
 
     def __player_event(self, player_id: str, event):
@@ -358,8 +354,8 @@ class SonosProvider(PlayerProvider):
             rel_time = __timespan_secs(position_info.get("RelTime"))
             player.elapsed_time = rel_time
             if player.state == PlaybackState.Playing:
-                self.mass.add_job(self.__async_report_progress(player_id))
-        self.mass.add_job(self.mass.players.async_update_player(player))
+                self.mass.add_job(self._report_progress(player_id))
+        player.update_state()
 
     def __process_groups(self, sonos_groups):
         """Process all sonos groups."""
@@ -375,7 +371,7 @@ class SonosProvider(PlayerProvider):
             group_player.is_group_player = True
             group_player.name = group.label
             group_player.group_childs = [item.uid for item in group.members]
-            self.mass.run_task(self.mass.players.async_update_player(group_player))
+            self.mass.run_task(self.mass.players.update_player(group_player))
 
     async def __topology_changed(self, player_id, event=None):
         """Received topology changed event from one of the sonos players."""
@@ -383,7 +379,7 @@ class SonosProvider(PlayerProvider):
         # Schedule discovery to work out the changes.
         self.mass.add_job(self.__run_discovery)
 
-    async def __async_report_progress(self, player_id: str):
+    async def _report_progress(self, player_id: str):
         """Report current progress while playing."""
         if player_id in self._report_progress_tasks:
             return  # already running
index d1642a0b802f16ca499f52db795114a042ba45d6..3e835b725b8453e49fea5b39a68cc2a1b42c3941 100644 (file)
@@ -49,10 +49,10 @@ CONFIG_ENTRIES = [
 ]
 
 
-async def async_setup(mass):
+async def setup(mass):
     """Perform async setup of this Plugin/Provider."""
     prov = SpotifyProvider()
-    await mass.async_register_provider(prov)
+    await mass.register_provider(prov)
 
 
 class SpotifyProvider(MusicProvider):
@@ -91,7 +91,7 @@ class SpotifyProvider(MusicProvider):
             MediaType.Track,
         ]
 
-    async def async_on_start(self) -> bool:
+    async def on_start(self) -> bool:
         """Handle initialization of the provider based on config."""
         config = self.mass.config.get_provider_config(self.id)
         # pylint: disable=attribute-defined-outside-init
@@ -104,11 +104,11 @@ class SpotifyProvider(MusicProvider):
         self._password = config[CONF_PASSWORD]
         self.__auth_token = {}
         self._throttler = Throttler(rate_limit=4, period=1)
-        token = await self.async_get_token()
+        token = await self.get_token()
 
         return token is not None
 
-    async def async_search(
+    async def search(
         self, search_query: str, media_types=Optional[List[MediaType]], limit: int = 5
     ) -> SearchResult:
         """
@@ -130,196 +130,188 @@ class SpotifyProvider(MusicProvider):
             searchtypes.append("playlist")
         searchtype = ",".join(searchtypes)
         params = {"q": search_query, "type": searchtype, "limit": limit}
-        searchresult = await self.__async_get_data("search", params=params)
+        searchresult = await self._get_data("search", params=params)
         if searchresult:
             if "artists" in searchresult:
                 result.artists = [
-                    await self.__async_parse_artist(item)
+                    await self._parse_artist(item)
                     for item in searchresult["artists"]["items"]
                     if (item and item["id"])
                 ]
             if "albums" in searchresult:
                 result.albums = [
-                    await self.__async_parse_album(item)
+                    await self._parse_album(item)
                     for item in searchresult["albums"]["items"]
                     if (item and item["id"])
                 ]
             if "tracks" in searchresult:
                 result.tracks = [
-                    await self.__async_parse_track(item)
+                    await self._parse_track(item)
                     for item in searchresult["tracks"]["items"]
                     if (item and item["id"])
                 ]
             if "playlists" in searchresult:
                 result.playlists = [
-                    await self.__async_parse_playlist(item)
+                    await self._parse_playlist(item)
                     for item in searchresult["playlists"]["items"]
                     if (item and item["id"])
                 ]
         return result
 
-    async def async_get_library_artists(self) -> List[Artist]:
+    async def get_library_artists(self) -> List[Artist]:
         """Retrieve library artists from spotify."""
-        spotify_artists = await self.__async_get_data(
-            "me/following?type=artist&limit=50"
-        )
+        spotify_artists = await self._get_data("me/following?type=artist&limit=50")
         return [
-            await self.__async_parse_artist(item)
+            await self._parse_artist(item)
             for item in spotify_artists["artists"]["items"]
             if (item and item["id"])
         ]
 
-    async def async_get_library_albums(self) -> List[Album]:
+    async def get_library_albums(self) -> List[Album]:
         """Retrieve library albums from the provider."""
         return [
-            await self.__async_parse_album(item["album"])
-            for item in await self.__async_get_all_items("me/albums")
+            await self._parse_album(item["album"])
+            for item in await self._get_all_items("me/albums")
             if (item["album"] and item["album"]["id"])
         ]
 
-    async def async_get_library_tracks(self) -> List[Track]:
+    async def get_library_tracks(self) -> List[Track]:
         """Retrieve library tracks from the provider."""
         return [
-            await self.__async_parse_track(item["track"])
-            for item in await self.__async_get_all_items("me/tracks")
+            await self._parse_track(item["track"])
+            for item in await self._get_all_items("me/tracks")
             if (item and item["track"]["id"])
         ]
 
-    async def async_get_library_playlists(self) -> List[Playlist]:
+    async def get_library_playlists(self) -> List[Playlist]:
         """Retrieve playlists from the provider."""
         return [
-            await self.__async_parse_playlist(item)
-            for item in await self.__async_get_all_items("me/playlists")
+            await self._parse_playlist(item)
+            for item in await self._get_all_items("me/playlists")
             if (item and item["id"])
         ]
 
-    async def async_get_radios(self) -> List[Radio]:
+    async def get_radios(self) -> List[Radio]:
         """Retrieve library/subscribed radio stations from the provider."""
         return []  # TODO: Return spotify radio
 
-    async def async_get_artist(self, prov_artist_id) -> Artist:
+    async def get_artist(self, prov_artist_id) -> Artist:
         """Get full artist details by id."""
-        artist_obj = await self.__async_get_data("artists/%s" % prov_artist_id)
-        return await self.__async_parse_artist(artist_obj) if artist_obj else None
+        artist_obj = await self._get_data("artists/%s" % prov_artist_id)
+        return await self._parse_artist(artist_obj) if artist_obj else None
 
-    async def async_get_album(self, prov_album_id) -> Album:
+    async def get_album(self, prov_album_id) -> Album:
         """Get full album details by id."""
-        album_obj = await self.__async_get_data("albums/%s" % prov_album_id)
-        return await self.__async_parse_album(album_obj) if album_obj else None
+        album_obj = await self._get_data("albums/%s" % prov_album_id)
+        return await self._parse_album(album_obj) if album_obj else None
 
-    async def async_get_track(self, prov_track_id) -> Track:
+    async def get_track(self, prov_track_id) -> Track:
         """Get full track details by id."""
-        track_obj = await self.__async_get_data("tracks/%s" % prov_track_id)
-        return await self.__async_parse_track(track_obj) if track_obj else None
+        track_obj = await self._get_data("tracks/%s" % prov_track_id)
+        return await self._parse_track(track_obj) if track_obj else None
 
-    async def async_get_playlist(self, prov_playlist_id) -> Playlist:
+    async def get_playlist(self, prov_playlist_id) -> Playlist:
         """Get full playlist details by id."""
-        playlist_obj = await self.__async_get_data(f"playlists/{prov_playlist_id}")
-        return await self.__async_parse_playlist(playlist_obj) if playlist_obj else None
+        playlist_obj = await self._get_data(f"playlists/{prov_playlist_id}")
+        return await self._parse_playlist(playlist_obj) if playlist_obj else None
 
-    async def async_get_album_tracks(self, prov_album_id) -> List[Track]:
+    async def get_album_tracks(self, prov_album_id) -> List[Track]:
         """Get all album tracks for given album id."""
         return [
-            await self.__async_parse_track(item)
-            for item in await self.__async_get_all_items(
-                f"albums/{prov_album_id}/tracks"
-            )
+            await self._parse_track(item)
+            for item in await self._get_all_items(f"albums/{prov_album_id}/tracks")
             if (item and item["id"])
         ]
 
-    async def async_get_playlist_tracks(self, prov_playlist_id) -> List[Track]:
+    async def get_playlist_tracks(self, prov_playlist_id) -> List[Track]:
         """Get all playlist tracks for given playlist id."""
         return [
-            await self.__async_parse_track(item["track"])
-            for item in await self.__async_get_all_items(
+            await self._parse_track(item["track"])
+            for item in await self._get_all_items(
                 f"playlists/{prov_playlist_id}/tracks"
             )
             if (item and item["track"] and item["track"]["id"])
         ]
 
-    async def async_get_artist_albums(self, prov_artist_id) -> List[Album]:
+    async def get_artist_albums(self, prov_artist_id) -> List[Album]:
         """Get a list of all albums for the given artist."""
         return [
-            await self.__async_parse_album(item)
-            for item in await self.__async_get_all_items(
+            await self._parse_album(item)
+            for item in await self._get_all_items(
                 f"artists/{prov_artist_id}/albums?include_groups=album,single,compilation"
             )
             if (item and item["id"])
         ]
 
-    async def async_get_artist_toptracks(self, prov_artist_id) -> List[Track]:
+    async def get_artist_toptracks(self, prov_artist_id) -> List[Track]:
         """Get a list of 10 most popular tracks for the given artist."""
-        artist = await self.async_get_artist(prov_artist_id)
+        artist = await self.get_artist(prov_artist_id)
         endpoint = f"artists/{prov_artist_id}/top-tracks"
-        items = await self.__async_get_data(endpoint)
+        items = await self._get_data(endpoint)
         return [
-            await self.__async_parse_track(item, artist=artist)
+            await self._parse_track(item, artist=artist)
             for item in items["tracks"]
             if (item and item["id"])
         ]
 
-    async def async_library_add(self, prov_item_id, media_type: MediaType):
+    async def library_add(self, prov_item_id, media_type: MediaType):
         """Add item to library."""
         result = False
         if media_type == MediaType.Artist:
-            result = await self.__async_put_data(
+            result = await self._put_data(
                 "me/following", {"ids": prov_item_id, "type": "artist"}
             )
         elif media_type == MediaType.Album:
-            result = await self.__async_put_data("me/albums", {"ids": prov_item_id})
+            result = await self._put_data("me/albums", {"ids": prov_item_id})
         elif media_type == MediaType.Track:
-            result = await self.__async_put_data("me/tracks", {"ids": prov_item_id})
+            result = await self._put_data("me/tracks", {"ids": prov_item_id})
         elif media_type == MediaType.Playlist:
-            result = await self.__async_put_data(
+            result = await self._put_data(
                 f"playlists/{prov_item_id}/followers", data={"public": False}
             )
         return result
 
-    async def async_library_remove(self, prov_item_id, media_type: MediaType):
+    async def library_remove(self, prov_item_id, media_type: MediaType):
         """Remove item from library."""
         result = False
         if media_type == MediaType.Artist:
-            result = await self.__async_delete_data(
+            result = await self._delete_data(
                 "me/following", {"ids": prov_item_id, "type": "artist"}
             )
         elif media_type == MediaType.Album:
-            result = await self.__async_delete_data("me/albums", {"ids": prov_item_id})
+            result = await self._delete_data("me/albums", {"ids": prov_item_id})
         elif media_type == MediaType.Track:
-            result = await self.__async_delete_data("me/tracks", {"ids": prov_item_id})
+            result = await self._delete_data("me/tracks", {"ids": prov_item_id})
         elif media_type == MediaType.Playlist:
-            result = await self.__async_delete_data(
-                f"playlists/{prov_item_id}/followers"
-            )
+            result = await self._delete_data(f"playlists/{prov_item_id}/followers")
         return result
 
-    async def async_add_playlist_tracks(self, prov_playlist_id, prov_track_ids):
+    async def add_playlist_tracks(self, prov_playlist_id, prov_track_ids):
         """Add track(s) to playlist."""
         track_uris = []
         for track_id in prov_track_ids:
             track_uris.append("spotify:track:%s" % track_id)
         data = {"uris": track_uris}
-        return await self.__async_post_data(
-            f"playlists/{prov_playlist_id}/tracks", data=data
-        )
+        return await self._post_data(f"playlists/{prov_playlist_id}/tracks", data=data)
 
-    async def async_remove_playlist_tracks(self, prov_playlist_id, prov_track_ids):
+    async def remove_playlist_tracks(self, prov_playlist_id, prov_track_ids):
         """Remove track(s) from playlist."""
         track_uris = []
         for track_id in prov_track_ids:
             track_uris.append({"uri": "spotify:track:%s" % track_id})
         data = {"tracks": track_uris}
-        return await self.__async_delete_data(
+        return await self._delete_data(
             f"playlists/{prov_playlist_id}/tracks", data=data
         )
 
-    async def async_get_stream_details(self, item_id: str) -> StreamDetails:
+    async def get_stream_details(self, item_id: str) -> StreamDetails:
         """Return the content details for the given track when it will be streamed."""
         # make sure a valid track is requested.
-        track = await self.async_get_track(item_id)
+        track = await self.get_track(item_id)
         if not track:
             return None
         # make sure that the token is still valid by just requesting it
-        await self.async_get_token()
+        await self.get_token()
         spotty = self.get_spotty_binary()
         spotty_exec = (
             '%s -n temp -c "%s" -b 320 --pass-through --single-track spotify://track:%s'
@@ -339,12 +331,12 @@ class SpotifyProvider(MusicProvider):
             bit_depth=16,
         )
 
-    async def __async_parse_artist(self, artist_obj):
+    async def _parse_artist(self, artist_obj):
         """Parse spotify artist object to generic layout."""
         artist = Artist(
             item_id=artist_obj["id"], provider=self.id, name=artist_obj["name"]
         )
-        artist.provider_ids.append(
+        artist.provider_ids.add(
             MediaItemProviderId(provider=PROV_ID, item_id=artist_obj["id"])
         )
         if "genres" in artist_obj:
@@ -359,12 +351,12 @@ class SpotifyProvider(MusicProvider):
             artist.metadata["spotify_url"] = artist_obj["external_urls"]["spotify"]
         return artist
 
-    async def __async_parse_album(self, album_obj):
+    async def _parse_album(self, album_obj):
         """Parse spotify album object to generic layout."""
         album = Album(item_id=album_obj["id"], provider=self.id)
         album.name, album.version = parse_title_and_version(album_obj["name"])
         for artist in album_obj["artists"]:
-            album.artist = await self.__async_parse_artist(artist)
+            album.artist = await self._parse_artist(artist)
             if album.artist:
                 break
         if album_obj["album_type"] == "single":
@@ -389,7 +381,7 @@ class SpotifyProvider(MusicProvider):
             album.metadata["spotify_url"] = album_obj["external_urls"]["spotify"]
         if album_obj.get("explicit"):
             album.metadata["explicit"] = str(album_obj["explicit"]).lower()
-        album.provider_ids.append(
+        album.provider_ids.add(
             MediaItemProviderId(
                 provider=PROV_ID,
                 item_id=album_obj["id"],
@@ -398,7 +390,7 @@ class SpotifyProvider(MusicProvider):
         )
         return album
 
-    async def __async_parse_track(self, track_obj, artist=None):
+    async def _parse_track(self, track_obj, artist=None):
         """Parse spotify track object to generic layout."""
         track = Track(
             item_id=track_obj["id"],
@@ -408,17 +400,17 @@ class SpotifyProvider(MusicProvider):
             track_number=track_obj["track_number"],
         )
         if artist:
-            track.artists.append(artist)
+            track.artists.add(artist)
         for track_artist in track_obj.get("artists", []):
-            artist = await self.__async_parse_artist(track_artist)
-            if artist and artist.item_id not in [x.item_id for x in track.artists]:
-                track.artists.append(artist)
+            artist = await self._parse_artist(track_artist)
+            if artist and artist.item_id not in {x.item_id for x in track.artists}:
+                track.artists.add(artist)
         track.name, track.version = parse_title_and_version(track_obj["name"])
         track.metadata["explicit"] = str(track_obj["explicit"]).lower()
         if "external_ids" in track_obj and "isrc" in track_obj["external_ids"]:
             track.isrc = track_obj["external_ids"]["isrc"]
         if "album" in track_obj:
-            track.album = await self.__async_parse_album(track_obj["album"])
+            track.album = await self._parse_album(track_obj["album"])
             if track_obj["album"].get("images"):
                 track.metadata["image"] = track_obj["album"]["images"][0]["url"]
         if track_obj.get("copyright"):
@@ -427,7 +419,7 @@ class SpotifyProvider(MusicProvider):
             track.metadata["explicit"] = True
         if track_obj.get("external_urls"):
             track.metadata["spotify_url"] = track_obj["external_urls"]["spotify"]
-        track.provider_ids.append(
+        track.provider_ids.add(
             MediaItemProviderId(
                 provider=PROV_ID,
                 item_id=track_obj["id"],
@@ -437,10 +429,10 @@ class SpotifyProvider(MusicProvider):
         )
         return track
 
-    async def __async_parse_playlist(self, playlist_obj):
+    async def _parse_playlist(self, playlist_obj):
         """Parse spotify playlist object to generic layout."""
         playlist = Playlist(item_id=playlist_obj["id"], provider=self.id)
-        playlist.provider_ids.append(
+        playlist.provider_ids.add(
             MediaItemProviderId(provider=PROV_ID, item_id=playlist_obj["id"])
         )
         playlist.name = playlist_obj["name"]
@@ -456,7 +448,7 @@ class SpotifyProvider(MusicProvider):
         playlist.checksum = playlist_obj["snapshot_id"]
         return playlist
 
-    async def async_get_token(self):
+    async def get_token(self):
         """Get auth token on spotify."""
         # return existing token if we have one in memory
         if self.__auth_token and (
@@ -467,17 +459,17 @@ class SpotifyProvider(MusicProvider):
         if not self._username or not self._password:
             return tokeninfo
         # retrieve token with spotty
-        tokeninfo = await self.__async_get_token()
+        tokeninfo = await self._get_token()
         if tokeninfo:
             self.__auth_token = tokeninfo
-            self.sp_user = await self.__async_get_data("me")
+            self.sp_user = await self._get_data("me")
             LOGGER.info("Succesfully logged in to Spotify as %s", self.sp_user["id"])
             self.__auth_token = tokeninfo
         else:
             LOGGER.error("Login failed for user %s", self._username)
         return tokeninfo
 
-    async def __async_get_token(self):
+    async def _get_token(self):
         """Get spotify auth token with spotty bin."""
         # get token with spotty
         scopes = [
@@ -531,7 +523,7 @@ class SpotifyProvider(MusicProvider):
             return tokeninfo
         return None
 
-    async def __async_get_all_items(self, endpoint, params=None, key="items"):
+    async def _get_all_items(self, endpoint, params=None, key="items"):
         """Get all items from a paged list."""
         if not params:
             params = {}
@@ -541,7 +533,7 @@ class SpotifyProvider(MusicProvider):
         while True:
             params["limit"] = limit
             params["offset"] = offset
-            result = await self.__async_get_data(endpoint, params=params)
+            result = await self._get_data(endpoint, params=params)
             offset += limit
             if not result or key not in result or not result[key]:
                 break
@@ -550,14 +542,14 @@ class SpotifyProvider(MusicProvider):
                 break
         return all_items
 
-    async def __async_get_data(self, endpoint, params=None):
+    async def _get_data(self, endpoint, params=None):
         """Get data from api."""
         if not params:
             params = {}
         url = "https://api.spotify.com/v1/%s" % endpoint
         params["market"] = "from_token"
         params["country"] = "from_token"
-        token = await self.async_get_token()
+        token = await self.get_token()
         if not token:
             return None
         headers = {"Authorization": "Bearer %s" % token["accessToken"]}
@@ -571,12 +563,12 @@ class SpotifyProvider(MusicProvider):
                     result = None
                 return result
 
-    async def __async_delete_data(self, endpoint, params=None, data=None):
+    async def _delete_data(self, endpoint, params=None, data=None):
         """Delete data from api."""
         if not params:
             params = {}
         url = "https://api.spotify.com/v1/%s" % endpoint
-        token = await self.async_get_token()
+        token = await self.get_token()
         if not token:
             return None
         headers = {"Authorization": "Bearer %s" % token["accessToken"]}
@@ -585,12 +577,12 @@ class SpotifyProvider(MusicProvider):
         ) as response:
             return await response.text()
 
-    async def __async_put_data(self, endpoint, params=None, data=None):
+    async def _put_data(self, endpoint, params=None, data=None):
         """Put data on api."""
         if not params:
             params = {}
         url = "https://api.spotify.com/v1/%s" % endpoint
-        token = await self.async_get_token()
+        token = await self.get_token()
         if not token:
             return None
         headers = {"Authorization": "Bearer %s" % token["accessToken"]}
@@ -599,12 +591,12 @@ class SpotifyProvider(MusicProvider):
         ) as response:
             return await response.text()
 
-    async def __async_post_data(self, endpoint, params=None, data=None):
+    async def _post_data(self, endpoint, params=None, data=None):
         """Post data on api."""
         if not params:
             params = {}
         url = "https://api.spotify.com/v1/%s" % endpoint
-        token = await self.async_get_token()
+        token = await self.get_token()
         if not token:
             return None
         headers = {"Authorization": "Bearer %s" % token["accessToken"]}
index 986b2d11f4e12c0d5f65a350ee840b586a45f5f7..00144cb45d4ad89f7c4ad09c4935c728e7adf03d 100644 (file)
@@ -5,7 +5,7 @@ import logging
 from typing import List
 
 from music_assistant.constants import CONF_CROSSFADE_DURATION
-from music_assistant.helpers.typing import MusicAssistantType
+from music_assistant.helpers.typing import MusicAssistant
 from music_assistant.helpers.util import callback
 from music_assistant.models.config_entry import ConfigEntry
 from music_assistant.models.player import (
@@ -31,10 +31,10 @@ PLAYER_FEATURES = [PlayerFeature.QUEUE, PlayerFeature.CROSSFADE, PlayerFeature.G
 PLAYER_CONFIG_ENTRIES = []  # we don't have any player config entries (for now)
 
 
-async def async_setup(mass):
+async def setup(mass):
     """Perform async setup of this Plugin/Provider."""
     prov = PySqueezeProvider()
-    await mass.async_register_provider(prov)
+    await mass.register_provider(prov)
 
 
 class PySqueezeProvider(PlayerProvider):
@@ -57,23 +57,23 @@ class PySqueezeProvider(PlayerProvider):
         """Return Config Entries for this provider."""
         return CONFIG_ENTRIES
 
-    async def async_on_start(self) -> bool:
+    async def on_start(self) -> bool:
         """Handle initialization of the provider. Called on startup."""
         # start slimproto server
         self._tasks.append(
             self.mass.add_job(
-                asyncio.start_server(self.__async_client_connected, "0.0.0.0", 3483)
+                asyncio.start_server(self._client_connected, "0.0.0.0", 3483)
             )
         )
         # setup discovery
-        self._tasks.append(self.mass.add_job(self.async_start_discovery()))
+        self._tasks.append(self.mass.add_job(self.start_discovery()))
 
-    async def async_on_stop(self):
+    async def on_stop(self):
         """Handle correct close/cleanup of the provider on exit."""
         for task in self._tasks:
             task.cancel()
 
-    async def async_start_discovery(self):
+    async def start_discovery(self):
         """Start discovery for players."""
         transport, _ = await self.mass.loop.create_datagram_endpoint(
             lambda: DiscoveryProtocol(self.mass.web.port),
@@ -85,7 +85,7 @@ class PySqueezeProvider(PlayerProvider):
         finally:
             transport.close()
 
-    async def __async_client_connected(self, reader, writer):
+    async def _client_connected(self, reader, writer):
         """Handle a client connection on the socket."""
         addr = writer.get_extra_info("peername")
         LOGGER.debug("Socket client connected: %s", addr)
@@ -109,8 +109,9 @@ class PySqueezeProvider(PlayerProvider):
 class SqueezePlayer(Player):
     """Squeezebox player."""
 
-    def __init__(self, mass: MusicAssistantType, socket_client: SqueezeSocketClient):
+    def __init__(self, mass: MusicAssistant, socket_client: SqueezeSocketClient):
         """Initialize."""
+        super().__init__()
         self.mass = mass
         self._socket_client = socket_client
 
@@ -133,7 +134,7 @@ class SqueezePlayer(Player):
         """Set a (new) socket client to this player."""
         self._socket_client = socket_client
 
-    async def async_on_remove(self) -> None:
+    async def on_remove(self) -> None:
         """Call when player is removed from the player manager."""
         self.socket_client.disconnect()
 
@@ -205,66 +206,64 @@ class SqueezePlayer(Player):
             address=self.socket_client.device_address,
         )
 
-    async def async_cmd_stop(self):
+    async def cmd_stop(self):
         """Send stop command to player."""
-        return await self.socket_client.async_cmd_stop()
+        return await self.socket_client.cmd_stop()
 
-    async def async_cmd_play(self):
+    async def cmd_play(self):
         """Send play (unpause) command to player."""
-        return await self.socket_client.async_cmd_play()
+        return await self.socket_client.cmd_play()
 
-    async def async_cmd_pause(self):
+    async def cmd_pause(self):
         """Send pause command to player."""
-        return await self.socket_client.async_cmd_pause()
+        return await self.socket_client.cmd_pause()
 
-    async def async_cmd_power_on(self) -> None:
+    async def cmd_power_on(self) -> None:
         """Send POWER ON command to player."""
         # save power and volume state in cache
-        cache_str = f"squeezebox_player_state_{self.player_id}"
-        await self.mass.cache.async_set(cache_str, (True, self.volume_level))
-        return await self.socket_client.async_cmd_power(True)
+        cache_str = f"squeezebox_player_{self.player_id}"
+        await self.mass.cache.set(cache_str, (True, self.volume_level))
+        return await self.socket_client.cmd_power(True)
 
-    async def async_cmd_power_off(self) -> None:
+    async def cmd_power_off(self) -> None:
         """Send POWER OFF command to player."""
         # save power and volume state in cache
-        cache_str = f"squeezebox_player_state_{self.player_id}"
-        await self.mass.cache.async_set(cache_str, (False, self.volume_level))
-        return await self.socket_client.async_cmd_power(False)
+        cache_str = f"squeezebox_player_{self.player_id}"
+        await self.mass.cache.set(cache_str, (False, self.volume_level))
+        return await self.socket_client.cmd_power(False)
 
-    async def async_cmd_volume_set(self, volume_level: int):
+    async def cmd_volume_set(self, volume_level: int):
         """Send new volume level command to player."""
-        return await self.socket_client.async_cmd_volume_set(volume_level)
+        return await self.socket_client.cmd_volume_set(volume_level)
 
-    async def async_cmd_mute(self, muted: bool = False):
+    async def cmd_mute(self, muted: bool = False):
         """Send mute command to player."""
-        return await self.socket_client.async_cmd_mute(muted)
+        return await self.socket_client.cmd_mute(muted)
 
-    async def async_cmd_play_uri(self, uri: str):
+    async def cmd_play_uri(self, uri: str):
         """Request player to start playing a single uri."""
         crossfade = self.mass.config.player_settings[self.player_id][
             CONF_CROSSFADE_DURATION
         ]
-        return await self.socket_client.async_play_uri(
-            uri, crossfade_duration=crossfade
-        )
+        return await self.socket_client.play_uri(uri, crossfade_duration=crossfade)
 
-    async def async_cmd_next(self):
+    async def cmd_next(self):
         """Send NEXT TRACK command to player."""
         queue = self.mass.players.get_player_queue(self.player_id)
         if queue:
             new_track = queue.get_item(queue.cur_index + 1)
             if new_track:
-                return await self.async_cmd_play_uri(new_track.uri)
+                return await self.cmd_play_uri(new_track.uri)
 
-    async def async_cmd_previous(self):
+    async def cmd_previous(self):
         """Send PREVIOUS TRACK command to player."""
         queue = self.mass.players.get_player_queue(self.player_id)
         if queue:
             new_track = queue.get_item(queue.cur_index - 1)
             if new_track:
-                return await self.async_cmd_play_uri(new_track.uri)
+                return await self.cmd_play_uri(new_track.uri)
 
-    async def async_cmd_queue_play_index(self, index: int):
+    async def cmd_queue_play_index(self, index: int):
         """
         Play item at index X on player's queue.
 
@@ -274,19 +273,19 @@ class SqueezePlayer(Player):
         if queue:
             new_track = queue.get_item(index)
             if new_track:
-                return await self.async_cmd_play_uri(new_track.uri)
+                return await self.cmd_play_uri(new_track.uri)
 
-    async def async_cmd_queue_load(self, queue_items: List[QueueItem]):
+    async def cmd_queue_load(self, queue_items: List[QueueItem]):
         """
         Load/overwrite given items in the player's queue implementation.
 
             :param queue_items: a list of QueueItems
         """
         if queue_items:
-            await self.async_cmd_play_uri(queue_items[0].uri)
-            return await self.async_cmd_play_uri(queue_items[0].uri)
+            await self.cmd_play_uri(queue_items[0].uri)
+            return await self.cmd_play_uri(queue_items[0].uri)
 
-    async def async_cmd_queue_insert(
+    async def cmd_queue_insert(
         self, queue_items: List[QueueItem], insert_at_index: int
     ):
         """
@@ -300,9 +299,9 @@ class SqueezePlayer(Player):
         # we only check the start index
         queue = self.mass.players.get_player_queue(self.player_id)
         if queue and insert_at_index == queue.cur_index:
-            return await self.async_cmd_queue_play_index(insert_at_index)
+            return await self.cmd_queue_play_index(insert_at_index)
 
-    async def async_cmd_queue_append(self, queue_items: List[QueueItem]):
+    async def cmd_queue_append(self, queue_items: List[QueueItem]):
         """
         Append new items at the end of the queue.
 
@@ -310,7 +309,7 @@ class SqueezePlayer(Player):
         """
         # automagically handled by built-in queue controller
 
-    async def async_cmd_queue_update(self, queue_items: List[QueueItem]):
+    async def cmd_queue_update(self, queue_items: List[QueueItem]):
         """
         Overwrite the existing items in the queue, used for reordering.
 
@@ -318,25 +317,25 @@ class SqueezePlayer(Player):
         """
         # automagically handled by built-in queue controller
 
-    async def async_cmd_queue_clear(self):
+    async def cmd_queue_clear(self):
         """Clear the player's queue."""
         # queue is handled by built-in queue controller but send stop
-        return await self.async_cmd_stop()
+        return await self.cmd_stop()
 
-    async def async_restore_states(self):
+    async def restore_states(self):
         """Restore power/volume states."""
-        cache_str = f"squeezebox_player_state_{self.player_id}"
-        cache_data = await self.mass.cache.async_get(cache_str)
+        cache_str = f"squeezebox_player_{self.player_id}"
+        cache_data = await self.mass.cache.get(cache_str)
         last_power, last_volume = cache_data if cache_data else (False, 40)
-        await self.socket_client.async_cmd_volume_set(last_volume)
-        await self.socket_client.async_cmd_power(last_power)
+        await self.socket_client.cmd_volume_set(last_volume)
+        await self.socket_client.cmd_power(last_power)
 
     @callback
     def handle_socket_client_event(self, event: SqueezeEvent):
         """Process incoming event from the socket client."""
         if event == SqueezeEvent.CONNECTED:
             # restore previous power/volume
-            self.mass.add_job(self.async_restore_states())
+            self.mass.add_job(self.restore_states())
         elif event == SqueezeEvent.DECODER_READY:
             # tell player to load next queue track
             queue = self.mass.players.get_player_queue(self.player_id)
@@ -347,7 +346,7 @@ class SqueezePlayer(Player):
                         CONF_CROSSFADE_DURATION
                     ]
                     self.mass.add_job(
-                        self.socket_client.async_play_uri(
+                        self.socket_client.play_uri(
                             next_item.uri,
                             send_flush=False,
                             crossfade_duration=crossfade,
index 88c271c8b1dedc3a1fdbe57e3a44174a948a1a19..5fe3b99a314098981c5aa701a8f0a91b01500867 100644 (file)
@@ -8,7 +8,7 @@ import time
 from enum import Enum
 from typing import Callable
 
-from music_assistant.helpers.typing import MusicAssistantType
+from music_assistant.helpers.typing import MusicAssistant
 from music_assistant.helpers.util import run_periodic
 
 from .constants import PROV_ID
@@ -50,7 +50,7 @@ class SqueezeSocketClient:
 
     def __init__(
         self,
-        mass: MusicAssistantType,
+        mass: MusicAssistant,
         reader: asyncio.StreamReader,
         writer: asyncio.StreamWriter,
         event_callback: Callable = None,
@@ -74,8 +74,8 @@ class SqueezeSocketClient:
         self._connected = True
         self._event_callbacks = []
         self._tasks = [
-            asyncio.create_task(self.__async_socket_reader()),
-            asyncio.create_task(self.__async_send_heartbeat()),
+            asyncio.create_task(self._socket_reader()),
+            asyncio.create_task(self._send_heartbeat()),
         ]
 
     def disconnect(self) -> None:
@@ -163,60 +163,60 @@ class SqueezeSocketClient:
         """Return uri of currently loaded track."""
         return self._current_uri
 
-    async def __async_initialize_player(self):
+    async def _initialize_player(self):
         """Set some startup settings for the player."""
         # send version
-        await self.__async_send_frame(b"vers", b"7.8")
-        await self.__async_send_frame(b"setd", struct.pack("B", 0))
-        await self.__async_send_frame(b"setd", struct.pack("B", 4))
+        await self._send_frame(b"vers", b"7.8")
+        await self._send_frame(b"setd", struct.pack("B", 0))
+        await self._send_frame(b"setd", struct.pack("B", 4))
 
-    async def async_cmd_stop(self):
+    async def cmd_stop(self):
         """Send stop command to player."""
         data = self.__pack_stream(b"q", autostart=b"0", flags=0)
-        await self.__async_send_frame(b"strm", data)
+        await self._send_frame(b"strm", data)
 
-    async def async_cmd_play(self):
+    async def cmd_play(self):
         """Send play (unpause) command to player."""
         data = self.__pack_stream(b"u", autostart=b"0", flags=0)
-        await self.__async_send_frame(b"strm", data)
+        await self._send_frame(b"strm", data)
 
-    async def async_cmd_pause(self):
+    async def cmd_pause(self):
         """Send pause command to player."""
         data = self.__pack_stream(b"p", autostart=b"0", flags=0)
-        await self.__async_send_frame(b"strm", data)
+        await self._send_frame(b"strm", data)
 
-    async def async_cmd_power(self, powered: bool = True):
+    async def cmd_power(self, powered: bool = True):
         """Send power command to player."""
         # power is not supported so abuse mute instead
         power_int = 1 if powered else 0
-        await self.__async_send_frame(b"aude", struct.pack("2B", power_int, 1))
+        await self._send_frame(b"aude", struct.pack("2B", power_int, 1))
         self._powered = powered
         self.signal_event(SqueezeEvent.STATE_UPDATED)
 
-    async def async_cmd_volume_set(self, volume_level: int):
+    async def cmd_volume_set(self, volume_level: int):
         """Send new volume level command to player."""
         self._volume_control.volume = volume_level
         old_gain = self._volume_control.old_gain()
         new_gain = self._volume_control.new_gain()
-        await self.__async_send_frame(
+        await self._send_frame(
             b"audg",
             struct.pack("!LLBBLL", old_gain, old_gain, 1, 255, new_gain, new_gain),
         )
 
-    async def async_cmd_mute(self, muted: bool = False):
+    async def cmd_mute(self, muted: bool = False):
         """Send mute command to player."""
         muted_int = 0 if muted else 1
-        await self.__async_send_frame(b"aude", struct.pack("2B", muted_int, 0))
+        await self._send_frame(b"aude", struct.pack("2B", muted_int, 0))
         self.muted = muted
         self.signal_event(SqueezeEvent.STATE_UPDATED)
 
-    async def async_play_uri(
+    async def play_uri(
         self, uri: str, send_flush: bool = True, crossfade_duration: int = 0
     ):
         """Request player to start playing a single uri."""
         if send_flush:
             data = self.__pack_stream(b"f", autostart=b"0", flags=0)
-            await self.__async_send_frame(b"strm", data)
+            await self._send_frame(b"strm", data)
         self._current_uri = uri
         self._powered = True
         enable_crossfade = crossfade_duration > 0
@@ -246,18 +246,18 @@ class SqueezeSocketClient:
         headers = f"Connection: close\r\nAccept: */*\r\nHost: {host}:{port}\r\n"
         request = "GET %s HTTP/1.1\r\n%s\r\n" % (uri, headers)
         data = data + request.encode("utf-8")
-        await self.__async_send_frame(b"strm", data)
+        await self._send_frame(b"strm", data)
 
     @run_periodic(5)
-    async def __async_send_heartbeat(self):
+    async def _send_heartbeat(self):
         """Send periodic heartbeat message to player."""
         if not self._connected:
             return
         timestamp = int(time.time())
         data = self.__pack_stream(b"t", replay_gain=timestamp, flags=0)
-        await self.__async_send_frame(b"strm", data)
+        await self._send_frame(b"strm", data)
 
-    async def __async_send_frame(self, command, data):
+    async def _send_frame(self, command, data):
         """Send command to Squeeze player."""
         if self._reader.at_eof() or self._writer.is_closing():
             LOGGER.debug("Socket is disconnected.")
@@ -271,7 +271,7 @@ class SqueezeSocketClient:
             self._connected = False
             self.signal_event(SqueezeEvent.DISCONNECTED)
 
-    async def __async_socket_reader(self):
+    async def _socket_reader(self):
         """Handle incoming data from socket."""
         buffer = b""
         # keep reading bytes from the socket
@@ -341,7 +341,7 @@ class SqueezeSocketClient:
         self._player_id = str(device_mac).lower()
         self._device_type = DEVICE_TYPE.get(dev_id, "unknown device")
         LOGGER.debug("Player connected: %s", self.name)
-        asyncio.create_task(self.__async_initialize_player())
+        asyncio.create_task(self._initialize_player())
         self.signal_event(SqueezeEvent.CONNECTED)
 
     def _process_stat(self, data):
@@ -458,7 +458,7 @@ class SqueezeSocketClient:
         """Process incoming RESP message: Response received at player."""
         # pylint: disable=unused-argument
         # send continue
-        asyncio.create_task(self.__async_send_frame(b"cont", b"0"))
+        asyncio.create_task(self._send_frame(b"cont", b"0"))
 
     def _process_setd(self, data):
         """Process incoming SETD message: Get/set player firmware settings."""
index 25e3792d0a31c429176cb3d78e9299938fe1a67d..dd3307312e4a35b23295cbf4f9d324ed95cec9a9 100644 (file)
@@ -33,10 +33,10 @@ CONFIG_ENTRIES = [
 ]
 
 
-async def async_setup(mass):
+async def setup(mass):
     """Perform async setup of this Plugin/Provider."""
     prov = TuneInProvider()
-    await mass.async_register_provider(prov)
+    await mass.register_provider(prov)
 
 
 class TuneInProvider(MusicProvider):
@@ -68,7 +68,7 @@ class TuneInProvider(MusicProvider):
         """Return MediaTypes the provider supports."""
         return [MediaType.Radio]
 
-    async def async_on_start(self) -> bool:
+    async def on_start(self) -> bool:
         """Handle initialization of the provider based on config."""
         # pylint: disable=attribute-defined-outside-init
         config = self.mass.config.get_provider_config(self.id)
@@ -80,7 +80,7 @@ class TuneInProvider(MusicProvider):
         self._throttler = Throttler(rate_limit=1, period=1)
         return True
 
-    async def async_search(
+    async def search(
         self, search_query: str, media_types=Optional[List[MediaType]], limit: int = 5
     ) -> SearchResult:
         """
@@ -94,29 +94,29 @@ class TuneInProvider(MusicProvider):
         # TODO: search for radio stations
         return result
 
-    async def async_get_library_radios(self) -> List[Radio]:
+    async def get_library_radios(self) -> List[Radio]:
         """Retrieve library/subscribed radio stations from the provider."""
         params = {"c": "presets"}
-        result = await self.__async_get_data("Browse.ashx", params)
+        result = await self._get_data("Browse.ashx", params)
         if result and "body" in result:
             return [
-                await self.__async_parse_radio(item)
+                await self._parse_radio(item)
                 for item in result["body"]
                 if item["type"] == "audio"
             ]
         return []
 
-    async def async_get_radio(self, prov_radio_id: str) -> Radio:
+    async def get_radio(self, prov_radio_id: str) -> Radio:
         """Get radio station details."""
         radio = None
         params = {"c": "composite", "detail": "listing", "id": prov_radio_id}
-        result = await self.__async_get_data("Describe.ashx", params)
+        result = await self._get_data("Describe.ashx", params)
         if result and result.get("body") and result["body"][0].get("children"):
             item = result["body"][0]["children"][0]
-            radio = await self.__async_parse_radio(item)
+            radio = await self._parse_radio(item)
         return radio
 
-    async def __async_parse_radio(self, details: dict) -> Radio:
+    async def _parse_radio(self, details: dict) -> Radio:
         """Parse Radio object from json obj returned from api."""
         radio = Radio(item_id=details["preset_id"], provider=PROV_ID)
         if "name" in details:
@@ -129,7 +129,7 @@ class TuneInProvider(MusicProvider):
             name = name.split(" (")[0]
             radio.name = name
         # parse stream urls and format
-        stream_info = await self.__async_get_stream_urls(radio.item_id)
+        stream_info = await self._get_stream_urls(radio.item_id)
         for stream in stream_info["body"]:
             if stream["media_type"] == "aac":
                 quality = TrackQuality.LOSSY_AAC
@@ -137,7 +137,7 @@ class TuneInProvider(MusicProvider):
                 quality = TrackQuality.LOSSY_OGG
             else:
                 quality = TrackQuality.LOSSY_MP3
-            radio.provider_ids.append(
+            radio.provider_ids.add(
                 MediaItemProviderId(
                     provider=PROV_ID,
                     item_id="%s--%s" % (details["preset_id"], stream["media_type"]),
@@ -152,20 +152,20 @@ class TuneInProvider(MusicProvider):
             radio.metadata["image"] = details["logo"]
         return radio
 
-    async def __async_get_stream_urls(self, radio_id):
+    async def _get_stream_urls(self, radio_id):
         """Return the stream urls for the given radio id."""
         params = {"id": radio_id}
-        res = await self.__async_get_data("Tune.ashx", params)
+        res = await self._get_data("Tune.ashx", params)
         return res
 
-    async def async_get_stream_details(self, item_id: str) -> StreamDetails:
+    async def get_stream_details(self, item_id: str) -> StreamDetails:
         """Get streamdetails for a radio station."""
         radio_id = item_id.split("--")[0]
         if len(item_id.split("--")) > 1:
             media_type = item_id.split("--")[1]
         else:
             media_type = ""
-        stream_info = await self.__async_get_stream_urls(radio_id)
+        stream_info = await self._get_stream_urls(radio_id)
         for stream in stream_info["body"]:
             if stream["media_type"] == media_type or not media_type:
                 return StreamDetails(
@@ -180,7 +180,7 @@ class TuneInProvider(MusicProvider):
                 )
         return None
 
-    async def __async_get_data(self, endpoint, params=None):
+    async def _get_data(self, endpoint, params=None):
         """Get data from api."""
         if not params:
             params = {}
index cbf1c054fb01f16563df8d13b15cb88d5bf35f30..920798dc0623f6d78a311751e97cf09a95ad032b 100644 (file)
@@ -1,10 +1,10 @@
-"""Group player provider: enables grouping of all playertypes."""
+"""Group player provider: enables grouping of all Players."""
 
 import asyncio
 import logging
 from typing import List
 
-from music_assistant.helpers.typing import MusicAssistantType
+from music_assistant.helpers.typing import MusicAssistant
 from music_assistant.models.config_entry import ConfigEntry, ConfigEntryType
 from music_assistant.models.player import DeviceInfo, PlaybackState, Player
 from music_assistant.models.provider import PlayerProvider
@@ -28,10 +28,10 @@ CONFIG_ENTRIES = [
 ]
 
 
-async def async_setup(mass):
+async def setup(mass):
     """Perform async setup of this Plugin/Provider."""
     prov = GroupPlayerProvider()
-    await mass.async_register_provider(prov)
+    await mass.register_provider(prov)
 
 
 class GroupPlayerProvider(PlayerProvider):
@@ -52,25 +52,26 @@ class GroupPlayerProvider(PlayerProvider):
         """Return Config Entries for this provider."""
         return CONFIG_ENTRIES
 
-    async def async_on_start(self) -> bool:
+    async def on_start(self) -> bool:
         """Handle initialization of the provider based on config."""
         conf = self.mass.config.player_providers[PROV_ID]
         for index in range(conf[CONF_PLAYER_COUNT]):
             player = GroupPlayer(self.mass, index)
-            self.mass.add_job(self.mass.players.async_add_player(player))
+            self.mass.add_job(self.mass.players.add_player(player))
         return True
 
-    async def async_on_stop(self):
+    async def on_stop(self):
         """Handle correct close/cleanup of the provider on exit. Called on shutdown."""
         for player in self.players:
-            await player.async_cmd_stop()
+            await player.cmd_stop()
 
 
 class GroupPlayer(Player):
     """Model for a group player."""
 
-    def __init__(self, mass: MusicAssistantType, player_index: int):
+    def __init__(self, mass: MusicAssistant, player_index: int):
         """Initialize."""
+        super().__init__()
         self.mass = mass
         self._player_index = player_index
         self._player_id = f"{PROV_ID}_{player_index}"
@@ -171,7 +172,7 @@ class GroupPlayer(Player):
         """Return config entries for this group player."""
         return self._config_entries
 
-    async def async_on_update(self) -> None:
+    async def on_poll(self) -> None:
         """Call when player is periodically polled by the player manager (should_poll=True)."""
         self._config_entries = self.__get_config_entries()
         self._group_childs = self.__get_group_childs()
@@ -188,7 +189,7 @@ class GroupPlayer(Player):
         """Return config entries for this group player."""
         all_players = [
             {"text": item.name, "value": item.player_id}
-            for item in self.mass.players.player_states
+            for item in self.mass.players
             if item.player_id is not self._player_id
         ]
         selected_players_ids = self.mass.config.get_player_config(self.player_id).get(
@@ -197,10 +198,10 @@ class GroupPlayer(Player):
         # selected_players_ids = []
         selected_players = []
         for player_id in selected_players_ids:
-            player_state = self.mass.players.get_player_state(player_id)
-            if player_state:
+            player = self.mass.players.get_player(player_id)
+            if player:
                 selected_players.append(
-                    {"text": player_state.name, "value": player_state.player_id}
+                    {"text": player.name, "value": player.player_id}
                 )
         default_master = ""
         if selected_players:
@@ -229,9 +230,9 @@ class GroupPlayer(Player):
 
     # SERVICE CALLS / PLAYER COMMANDS
 
-    async def async_cmd_play_uri(self, uri: str):
+    async def cmd_play_uri(self, uri: str):
         """Play the specified uri/url on the player."""
-        await self.async_cmd_stop()
+        await self.cmd_stop()
         self._current_uri = uri
         self._state = PlaybackState.Playing
         self._powered = True
@@ -242,11 +243,11 @@ class GroupPlayer(Player):
             child_player = self.mass.players.get_player(child_player_id)
             if child_player:
                 queue_stream_uri = f"{self.mass.web.stream_url}/group/{self.player_id}?player_id={child_player_id}"
-                await child_player.async_cmd_play_uri(queue_stream_uri)
+                await child_player.cmd_play_uri(queue_stream_uri)
         self.update_state()
-        self.stream_task = self.mass.add_job(self.async_queue_stream_task())
+        self.stream_task = self.mass.add_job(self.queue_stream_task())
 
-    async def async_cmd_stop(self) -> None:
+    async def cmd_stop(self) -> None:
         """Send STOP command to player."""
         self._state = PlaybackState.Stopped
         if self.stream_task:
@@ -261,10 +262,10 @@ class GroupPlayer(Player):
         for child_player_id in self.group_childs:
             child_player = self.mass.players.get_player(child_player_id)
             if child_player:
-                await child_player.async_cmd_stop()
+                await child_player.cmd_stop()
         self.update_state()
 
-    async def async_cmd_play(self) -> None:
+    async def cmd_play(self) -> None:
         """Send PLAY command to player."""
         if not self.state == PlaybackState.Paused:
             return
@@ -272,31 +273,31 @@ class GroupPlayer(Player):
         for child_player_id in self.group_childs:
             child_player = self.mass.players.get_player(child_player_id)
             if child_player:
-                await child_player.async_cmd_play()
+                await child_player.cmd_play()
         self._state = PlaybackState.Playing
         self.update_state()
 
-    async def async_cmd_pause(self):
+    async def cmd_pause(self):
         """Send PAUSE command to player."""
         # forward this command to each child player
         for child_player_id in self.group_childs:
             child_player = self.mass.players.get_player(child_player_id)
             if child_player:
-                await child_player.async_cmd_pause()
+                await child_player.cmd_pause()
         self._state = PlaybackState.Paused
         self.update_state()
 
-    async def async_cmd_power_on(self) -> None:
+    async def cmd_power_on(self) -> None:
         """Send POWER ON command to player."""
         self._powered = True
         self.update_state()
 
-    async def async_cmd_power_off(self) -> None:
+    async def cmd_power_off(self) -> None:
         """Send POWER OFF command to player."""
         self._powered = False
         self.update_state()
 
-    async def async_cmd_volume_set(self, volume_level: int) -> None:
+    async def cmd_volume_set(self, volume_level: int) -> None:
         """
         Send volume level command to player.
 
@@ -304,14 +305,14 @@ class GroupPlayer(Player):
         """
         # this is already handled by the player manager
 
-    async def async_cmd_volume_mute(self, is_muted=False):
+    async def cmd_volume_mute(self, is_muted=False):
         """
         Send volume MUTE command to given player.
 
             :param is_muted: bool with new mute state.
         """
         for child_player_id in self.group_childs:
-            self.mass.players.async_cmd_volume_mute(child_player_id)
+            self.mass.players.cmd_volume_mute(child_player_id)
         self.muted = is_muted
 
     async def subscribe_stream_client(self, child_player_id):
@@ -347,7 +348,7 @@ class GroupPlayer(Player):
                 child_player_id,
             )
 
-    async def async_queue_stream_task(self):
+    async def queue_stream_task(self):
         """Handle streaming queue to connected child players."""
         ticks = 0
         while ticks < 60 and len(self.connected_clients) != len(self.group_childs):
@@ -362,9 +363,7 @@ class GroupPlayer(Player):
         )
         self.sync_task = asyncio.create_task(self.__synchronize_players())
 
-        async for audio_chunk in self.mass.streams.async_queue_stream_flac(
-            self.player_id
-        ):
+        async for audio_chunk in self.mass.streams.queue_stream_flac(self.player_id):
 
             # make sure we still have clients connected
             if not self.connected_clients:
@@ -447,12 +446,12 @@ class GroupPlayer(Player):
                             avg_lag,
                         )
                         # we correct the lag by pausing the master player for a very short time
-                        await master_player.async_cmd_pause()
+                        await master_player.cmd_pause()
                         # sending the command takes some time, account for that too
                         if avg_lag > 20:
                             sleep_time = avg_lag - 20
                             await asyncio.sleep(sleep_time / 1000)
-                        asyncio.create_task(master_player.async_cmd_play())
+                        asyncio.create_task(master_player.cmd_play())
                         break  # no more processing this round if we've just corrected a lag
 
                 # calculate drift (player is going faster in relation to the master)
@@ -475,12 +474,12 @@ class GroupPlayer(Player):
                             avg_drift,
                         )
                         # we correct the drift by pausing the player for a very short time
-                        # this is not the best approach but works with all playertypes
+                        # this is not the best approach but works with all Players
                         # temporary solution until I find something better like sending more/less pcm chunks
-                        await child_player.async_cmd_pause()
+                        await child_player.cmd_pause()
                         # sending the command takes some time, account for that too
                         if avg_drift > 20:
                             sleep_time = drift - 20
                             await asyncio.sleep(sleep_time / 1000)
-                        await child_player.async_cmd_play()
+                        await child_player.cmd_play()
                         break  # no more processing this round if we've just corrected a lag
index 0296de805bdc720fd0171064740b952c62b71b4d..8c3fbb9acda03b1c5da8263676f52786059d4130 100644 (file)
@@ -19,45 +19,45 @@ async def json_rpc_endpoint(request: Request):
     cmds = params[1]
     cmd_str = " ".join(cmds)
     if cmd_str == "play":
-        await request.app["mass"].players.async_cmd_play(player_id)
+        await request.app["mass"].players.cmd_play(player_id)
     elif cmd_str == "pause":
-        await request.app["mass"].players.async_cmd_pause(player_id)
+        await request.app["mass"].players.cmd_pause(player_id)
     elif cmd_str == "stop":
-        await request.app["mass"].players.async_cmd_stop(player_id)
+        await request.app["mass"].players.cmd_stop(player_id)
     elif cmd_str == "next":
-        await request.app["mass"].players.async_cmd_next(player_id)
+        await request.app["mass"].players.cmd_next(player_id)
     elif cmd_str == "previous":
-        await request.app["mass"].players.async_cmd_previous(player_id)
+        await request.app["mass"].players.cmd_previous(player_id)
     elif "power" in cmd_str:
         powered = cmds[1] if len(cmds) > 1 else False
         if powered:
-            await request.app["mass"].players.async_cmd_power_on(player_id)
+            await request.app["mass"].players.cmd_power_on(player_id)
         else:
-            await request.app["mass"].players.async_cmd_power_off(player_id)
+            await request.app["mass"].players.cmd_power_off(player_id)
     elif cmd_str == "playlist index +1":
-        await request.app["mass"].players.async_cmd_next(player_id)
+        await request.app["mass"].players.cmd_next(player_id)
     elif cmd_str == "playlist index -1":
-        await request.app["mass"].players.async_cmd_previous(player_id)
+        await request.app["mass"].players.cmd_previous(player_id)
     elif "mixer volume" in cmd_str and "+" in cmds[2]:
-        player_state = request.app["mass"].players.get_player_state(player_id)
-        volume_level = player_state.volume_level + int(cmds[2].split("+")[1])
-        await request.app["mass"].players.async_cmd_volume_set(player_id, volume_level)
+        player = request.app["mass"].players.get_player(player_id)
+        volume_level = player.volume_level + int(cmds[2].split("+")[1])
+        await request.app["mass"].players.cmd_volume_set(player_id, volume_level)
     elif "mixer volume" in cmd_str and "-" in cmds[2]:
-        player_state = request.app["mass"].players.get_player_state(player_id)
-        volume_level = player_state.volume_level - int(cmds[2].split("-")[1])
-        await request.app["mass"].players.async_cmd_volume_set(player_id, volume_level)
+        player = request.app["mass"].players.get_player(player_id)
+        volume_level = player.volume_level - int(cmds[2].split("-")[1])
+        await request.app["mass"].players.cmd_volume_set(player_id, volume_level)
     elif "mixer volume" in cmd_str:
-        await request.app["mass"].players.async_cmd_volume_set(player_id, cmds[2])
+        await request.app["mass"].players.cmd_volume_set(player_id, cmds[2])
     elif cmd_str == "mixer muting 1":
-        await request.app["mass"].players.async_cmd_volume_mute(player_id, True)
+        await request.app["mass"].players.cmd_volume_mute(player_id, True)
     elif cmd_str == "mixer muting 0":
-        await request.app["mass"].players.async_cmd_volume_mute(player_id, False)
+        await request.app["mass"].players.cmd_volume_mute(player_id, False)
     elif cmd_str == "button volup":
-        await request.app["mass"].players.async_cmd_volume_up(player_id)
+        await request.app["mass"].players.cmd_volume_up(player_id)
     elif cmd_str == "button voldown":
-        await request.app["mass"].players.async_cmd_volume_down(player_id)
+        await request.app["mass"].players.cmd_volume_down(player_id)
     elif cmd_str == "button power":
-        await request.app["mass"].players.async_cmd_power_toggle(player_id)
+        await request.app["mass"].players.cmd_power_toggle(player_id)
     else:
         return Response(text="command not supported")
     return Response(text="success")
index 9ba046631bea4648ba4894c044d512a3e1ecb3c3..076928fae2979488693e18f1d85ff61c2988d229 100755 (executable)
@@ -27,8 +27,8 @@ from music_assistant.constants import (
 from music_assistant.constants import __version__ as MASS_VERSION
 from music_assistant.helpers import repath
 from music_assistant.helpers.encryption import decrypt_string
-from music_assistant.helpers.images import async_get_image_url, async_get_thumb_file
-from music_assistant.helpers.typing import MusicAssistantType
+from music_assistant.helpers.images import get_image_url, get_thumb_file
+from music_assistant.helpers.typing import MusicAssistant
 from music_assistant.helpers.util import get_hostname, get_ip
 from music_assistant.helpers.web import api_route, json_serializer, parse_arguments
 from music_assistant.models.media_types import ItemMapping, MediaItem
@@ -42,7 +42,7 @@ LOGGER = logging.getLogger("webserver")
 class WebServer:
     """Webserver and json/websocket api."""
 
-    def __init__(self, mass: MusicAssistantType, port: int):
+    def __init__(self, mass: MusicAssistant, port: int):
         """Initialize class."""
         self.jwt_key = None
         self.app = None
@@ -55,16 +55,16 @@ class WebServer:
         self._runner = None
         self.api_routes = {}
 
-    async def async_setup(self):
+    async def setup(self):
         """Perform async setup."""
-        self.jwt_key = decrypt_string(self.mass.config.stored_config["jwt_key"])
+        self.jwt_key = await decrypt_string(self.mass.config.stored_config["jwt_key"])
         self.app = web.Application()
         self.app["mass"] = self.mass
         self.app["clients"] = []
         # add all routes
         self.app.add_routes(stream_routes)
         self.app.router.add_route("*", "/jsonrpc.js", json_rpc_endpoint)
-        self.app.router.add_get("/ws", self.__async_websocket_handler)
+        self.app.router.add_get("/ws", self._websocket_handler)
 
         # register all methods decorated as api_route
         for cls in [
@@ -86,14 +86,14 @@ class WebServer:
                 )
             },
         )
-        cors.add(self.app.router.add_get("/info", self.async_info))
+        cors.add(self.app.router.add_get("/info", self.info))
         # Host the frontend app
         webdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static/")
         if os.path.isdir(webdir):
-            self.app.router.add_get("/", self.async_index)
+            self.app.router.add_get("/", self.index)
             self.app.router.add_static("/", webdir, append_version=True)
         else:
-            self.app.router.add_get("/", self.async_info)
+            self.app.router.add_get("/", self.info)
 
         self._runner = web.AppRunner(self.app, access_log=None)
         await self._runner.setup()
@@ -101,9 +101,9 @@ class WebServer:
         http_site = web.TCPSite(self._runner, host=None, port=self.port)
         await http_site.start()
         LOGGER.info("Started Music Assistant server on port %s", self.port)
-        self.mass.add_event_listener(self.__async_handle_mass_events)
+        self.mass.add_event_listener(self._handle_mass_events)
 
-    async def async_stop(self):
+    async def stop(self):
         """Stop the webserver."""
         for ws_client in self.app["clients"]:
             await ws_client.close(message=b"server shutdown")
@@ -170,7 +170,7 @@ class WebServer:
             "initialized": self.mass.config.stored_config["initialized"],
         }
 
-    async def async_index(self, request: web.Request):
+    async def index(self, request: web.Request):
         """Get the index page."""
         # pylint: disable=unused-argument
         html_app = os.path.join(
@@ -179,21 +179,19 @@ class WebServer:
         return web.FileResponse(html_app)
 
     @api_route("info", False)
-    async def async_info(self, request: web.Request = None):
+    async def info(self, request: web.Request = None):
         """Return discovery info on index page."""
         if request:
             return web.json_response(self.discovery_info)
         return self.discovery_info
 
     @api_route("revoke_token")
-    async def async_revoke_token(self, client_id: str):
+    async def revoke_token(self, client_id: str):
         """Revoke token for client."""
         return self.mass.config.security.revoke_app_token(client_id)
 
     @api_route("get_token", False)
-    async def async_get_token(
-        self, username: str, password: str, app_id: str = ""
-    ) -> dict:
+    async def get_token(self, username: str, password: str, app_id: str = "") -> dict:
         """
         Validate given credentials and return JWT token.
 
@@ -225,7 +223,7 @@ class WebServer:
         raise AuthenticationError("Invalid credentials")
 
     @api_route("setup", False)
-    async def async_create_user_setup(self, username: str, password: str):
+    async def create_user_setup(self, username: str, password: str):
         """Handle first-time server setup through onboarding wizard."""
         if self.mass.config.stored_config["initialized"]:
             raise AuthenticationError("Already initialized")
@@ -235,11 +233,11 @@ class WebServer:
         self.mass.config.stored_config["initialized"] = True
         self.mass.config.save()
         # fix discovery info
-        await self.mass.async_setup_discovery()
+        await self.mass.setup_discovery()
         return True
 
     @api_route("images/thumb")
-    async def async_get_image_thumb(
+    async def get_image_thumb(
         self,
         size: int,
         url: Optional[str] = "",
@@ -247,11 +245,11 @@ class WebServer:
     ):
         """Get (resized) thumb image for given URL or media item as base64 encoded string."""
         if not url and item:
-            url = await async_get_image_url(
+            url = await get_image_url(
                 self.mass, item.item_id, item.provider, item.media_type
             )
         if url:
-            img_file = await async_get_thumb_file(self.mass, url, size)
+            img_file = await get_thumb_file(self.mass, url, size)
             if img_file:
                 with open(img_file, "rb") as _file:
                     icon_data = _file.read()
@@ -260,11 +258,11 @@ class WebServer:
         raise KeyError("Invalid item or url")
 
     @api_route("images/provider-icons/:provider_id?")
-    async def async_get_provider_icon(self, provider_id: Optional[str]):
+    async def get_provider_icon(self, provider_id: Optional[str]):
         """Get Provider icon as base64 encoded string."""
         if not provider_id:
             return {
-                prov.id: await self.async_get_provider_icon(prov.id)
+                prov.id: await self.get_provider_icon(prov.id)
                 for prov in self.mass.get_providers(include_unavailable=True)
             }
         base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -276,7 +274,7 @@ class WebServer:
                 return "data:image/png;base64," + icon_data.decode()
         raise KeyError("Invalid provider: %s" % provider_id)
 
-    async def __async_websocket_handler(self, request: web.Request):
+    async def _websocket_handler(self, request: web.Request):
         """Handle websocket client."""
 
         ws_client = WebSocketResponse()
@@ -300,7 +298,7 @@ class WebServer:
                 json_msg = msg.json(loads=ujson.loads)
                 if "command" in json_msg and "data" in json_msg:
                     # handle command
-                    await self.__async_handle_command(
+                    await self._handle_command(
                         ws_client,
                         json_msg["command"],
                         json_msg["data"],
@@ -308,16 +306,16 @@ class WebServer:
                     )
                 elif "event" in json_msg:
                     # handle event
-                    await self.__async_handle_event(
+                    await self._handle_event(
                         ws_client, json_msg["event"], json_msg.get("data")
                     )
         except AuthenticationError as exc:  # pylint:disable=broad-except
             # disconnect client on auth errors
-            await self.__async_send_json(ws_client, error=str(exc), **json_msg)
+            await self._send_json(ws_client, error=str(exc), **json_msg)
             await ws_client.close(message=str(exc).encode())
         except Exception as exc:  # pylint:disable=broad-except
             # log the error only
-            await self.__async_send_json(ws_client, error=str(exc), **json_msg)
+            await self._send_json(ws_client, error=str(exc), **json_msg)
             LOGGER.error("Error with WS client", exc_info=exc)
 
         # websocket disconnected
@@ -326,7 +324,7 @@ class WebServer:
 
         return ws_client
 
-    async def __async_handle_command(
+    async def _handle_command(
         self,
         ws_client: WebSocketResponse,
         command: str,
@@ -336,7 +334,7 @@ class WebServer:
         """Handle websocket command."""
         res = None
         if command == "auth":
-            return await self.__async_handle_auth(ws_client, data)
+            return await self._handle_auth(ws_client, data)
         # work out handler for the given path/command
         for key in self.api_routes:
             match = repath.match_pattern(key, command)
@@ -356,20 +354,18 @@ class WebServer:
                 if asyncio.iscoroutine(res):
                     res = await res
                 # return result of command to client
-                return await self.__async_send_json(
+                return await self._send_json(
                     ws_client, id=msg_id, result=command, data=res
                 )
         raise KeyError("Unknown command")
 
-    async def __async_handle_event(
-        self, ws_client: WebSocketResponse, event: str, data: Any
-    ):
+    async def _handle_event(self, ws_client: WebSocketResponse, event: str, data: Any):
         """Handle event message from ws client."""
         LOGGER.info("received event %s", event)
         if ws_client.authenticated:
             self.mass.signal_event(event, data)
 
-    async def __async_handle_auth(self, ws_client: WebSocketResponse, token: str):
+    async def _handle_auth(self, ws_client: WebSocketResponse, token: str):
         """Handle authentication with JWT token."""
         token_info = jwt.decode(token, self.mass.web.jwt_key, algorithms=["HS256"])
         if self.mass.config.security.is_token_revoked(token_info):
@@ -377,19 +373,19 @@ class WebServer:
         ws_client.authenticated = True
         self.mass.config.security.set_last_login(token_info["client_id"])
         # TODO: store token/app_id on ws_client obj and periodiclaly check if token is expired or revoked
-        await self.__async_send_json(ws_client, result="auth", data=token_info)
+        await self._send_json(ws_client, result="auth", data=token_info)
 
-    async def __async_send_json(self, ws_client: WebSocketResponse, **kwargs):
+    async def _send_json(self, ws_client: WebSocketResponse, **kwargs):
         """Send message (back) to websocket client."""
         await ws_client.send_str(json_serializer(kwargs))
 
-    async def __async_handle_mass_events(self, event: str, event_data: Any):
+    async def _handle_mass_events(self, event: str, event_data: Any):
         """Broadcast events to connected clients."""
         for ws_client in self.app["clients"]:
             if not ws_client.authenticated:
                 continue
             try:
-                await self.__async_send_json(ws_client, event=event, data=event_data)
+                await self._send_json(ws_client, event=event, data=event_data)
             except ConnectionResetError:
                 # client is already disconnected
                 self.app["clients"].remove(ws_client)
index 3681f2e465e193b73bf94cee4bf93918118dde69..76e6cb265c78c2d682974c7114a27b24e4a0c996 100644 (file)
@@ -15,10 +15,8 @@ async def stream_media(request: Request):
         return Response(status=404, reason="Media item is not playable!")
     item_id = request.match_info["item_id"]
     provider = request.rel_url.query.get("provider", "database")
-    media_item = await request.app["mass"].music.async_get_item(
-        item_id, provider, media_type
-    )
-    streamdetails = await request.app["mass"].music.async_get_stream_details(media_item)
+    media_item = await request.app["mass"].music.get_item(item_id, provider, media_type)
+    streamdetails = await request.app["mass"].music.get_stream_details(media_item)
 
     # prepare request
     content_type = streamdetails.content_type.value
@@ -28,7 +26,7 @@ async def stream_media(request: Request):
     await resp.prepare(request)
 
     # stream track
-    async for audio_chunk in request.app["mass"].streams.async_get_media_stream(
+    async for audio_chunk in request.app["mass"].streams.get_media_stream(
         streamdetails
     ):
         await resp.write(audio_chunk)
@@ -50,9 +48,7 @@ async def stream_queue(request: Request):
     await resp.prepare(request)
 
     # stream queue
-    async for audio_chunk in request.app["mass"].streams.async_queue_stream_flac(
-        player_id
-    ):
+    async for audio_chunk in request.app["mass"].streams.queue_stream_flac(player_id):
         await resp.write(audio_chunk)
     return resp
 
@@ -70,7 +66,7 @@ async def stream_queue_item(request: Request):
     )
     await resp.prepare(request)
 
-    async for audio_chunk in request.app["mass"].streams.async_stream_queue_item(
+    async for audio_chunk in request.app["mass"].streams.stream_queue_item(
         player_id, queue_item_id
     ):
         await resp.write(audio_chunk)
@@ -93,7 +89,7 @@ async def stream_group(request: Request):
     await resp.prepare(request)
 
     # stream queue
-    player_state = request.app["mass"].players.get_player(group_player_id)
-    async for audio_chunk in player_state.subscribe_stream_client(child_player_id):
+    player = request.app["mass"].players.get_player(group_player_id)
+    async for audio_chunk in player.subscribe_stream_client(child_player_id):
         await resp.write(audio_chunk)
     return resp
index 521ae59e6b155201e319c063e39f8b44da9de60d..951bff2fa2ec0b65f1e8ed64109af5489b3367a8 100644 (file)
@@ -19,4 +19,4 @@ cryptography==3.3.2
 ujson==4.0.1
 mashumaro==1.24
 typing-inspect==0.6.0; python_version < '3.8'
-uvloop==0.14.0; sys_platform != 'win32'
+uvloop==0.15.1; sys_platform != 'win32'