Several small fixes (#342)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Wed, 25 May 2022 23:03:19 +0000 (01:03 +0200)
committerGitHub <noreply@github.com>
Wed, 25 May 2022 23:03:19 +0000 (01:03 +0200)
* Add check before stop command

* fix typo

* fix metadata merging

* loosen aiofiles requirement

* add function to start specific sync task

examples/full.py
examples/simple.py
music_assistant/controllers/music/__init__.py
music_assistant/controllers/music/providers/filesystem.py
music_assistant/helpers/audio.py
music_assistant/models/media_items.py
music_assistant/models/player_queue.py
music_assistant/models/provider.py
requirements.txt

index 4d41873d4b143e1761c7f85c7b5c9e1051bb58c7..c265dc3ed97807b1e5a291c9c351300f562d3ba4 100644 (file)
@@ -165,7 +165,7 @@ async def main():
     async with MusicAssistant(mass_conf) as mass:
 
         # start sync
-        await mass.music.start_sync()
+        await mass.music.start_sync(schedule=3)
 
         # get some data
         artists = await mass.music.artists.count()
index c2ea812765ef8f5258d688bcbbe1be5b5ffc6c73..825023dfb731892ecbdaae90cc8b81df18c3ac7c 100644 (file)
@@ -71,7 +71,7 @@ async def main():
     await mass.setup()
 
     # start sync
-    await mass.music.start_sync()
+    await mass.music.start_sync(schedule=3)
 
     # get some data
     await mass.music.artists.library()
index 94d4288cf4bcb1acd6131dcb12fa8ac029c7a73a..f785b037b5a3cb7baf7a7b18bac8119fb5be14e9 100755 (executable)
@@ -55,20 +55,28 @@ class MusicController:
         for prov_conf in self.mass.config.providers:
             prov_cls = PROV_MAP[prov_conf.type]
             await self._register_provider(prov_cls(self.mass, prov_conf), prov_conf)
-        # TODO: handle deletion of providers ?
 
-    async def start_sync(self, schedule: Optional[float] = 3) -> None:
+    async def start_sync(
+        self,
+        media_types: Optional[Tuple[MediaType]] = None,
+        prov_types: Optional[Tuple[ProviderType]] = None,
+        schedule: Optional[float] = None,
+    ) -> None:
         """
         Start running the sync of all registred providers.
 
-        :param schedule: schedule syncjob every X hours, set to None for just a manual sync run.
+        media_types: only sync these media types. None for all.
+        prov_types: only sync these provider types. None for all.
+        schedule: schedule syncjob every X hours, set to None for just a manual sync run.
         """
 
         async def do_sync():
             while True:
                 for prov in self.providers:
+                    if prov_types is not None and prov.type not in prov_types:
+                        continue
                     self.mass.add_job(
-                        prov.sync_library(),
+                        prov.sync_library(media_types),
                         f"Library sync for provider {prov.name}",
                         allow_duplicate=False,
                     )
index 6cf3583da013ff70f87ff187fa6f22d10e37d574..ba28132766183b904bce2fdd2115854ee37ab3a7 100644 (file)
@@ -8,8 +8,8 @@ from contextlib import asynccontextmanager
 from typing import AsyncGenerator, List, Optional, Set, Tuple
 
 import aiofiles
-import aiofiles.ospath as aiopath
 import xmltodict
+from aiofiles.os import wrap
 from aiofiles.threadpool.binary import AsyncFileIO
 from tinytag.tinytag import TinyTag
 
@@ -94,7 +94,9 @@ class FileSystemProvider(MusicProvider):
     async def setup(self) -> bool:
         """Handle async initialization of the provider."""
 
-        if not await aiopath.isdir(self.config.path):
+        isdir = wrap(os.path.exists)
+
+        if not await isdir(self.config.path):
             raise MediaNotFoundError(
                 f"Music Directory {self.config.path} does not exist"
             )
@@ -127,7 +129,9 @@ class FileSystemProvider(MusicProvider):
             result += playlists
         return result
 
-    async def sync_library(self) -> None:
+    async def sync_library(
+        self, media_types: Optional[Tuple[MediaType]] = None
+    ) -> None:
         """Run library sync for this provider."""
         cache_key = f"{self.id}.checksums"
         prev_checksums = await self.mass.cache.get(cache_key)
@@ -303,7 +307,8 @@ class FileSystemProvider(MusicProvider):
         playlist_path = await self.get_filepath(MediaType.PLAYLIST, prov_playlist_id)
         if not await self.exists(playlist_path):
             raise MediaNotFoundError(f"Playlist path does not exist: {playlist_path}")
-        mtime = await aiopath.getmtime(playlist_path)
+        getmtime = wrap(os.path.getmtime)
+        mtime = await getmtime(playlist_path)
         checksum = f"{SCHEMA_VERSION}.{int(mtime)}"
         cache_key = f"playlist_{self.id}_tracks_{prov_playlist_id}"
         if cache := await self.mass.cache.get(cache_key, checksum):
@@ -772,7 +777,8 @@ class FileSystemProvider(MusicProvider):
         # ensure we have a full path and not relative
         if self.config.path not in file_path:
             file_path = os.path.join(self.config.path, file_path)
-        return await aiopath.exists(file_path)
+        _exists = wrap(os.path.exists)
+        return await _exists(file_path)
 
     @asynccontextmanager
     async def open_file(self, file_path: str, mode="rb") -> AsyncFileIO:
index 727cc4bea6801965795c7553fc8ea809d0e4fe5b..9886ff3b21284f9b805f97a3cd420478844348fa 100644 (file)
@@ -640,7 +640,7 @@ async def get_preview_stream(
             mass.signal_event(
                 MassEvent(
                     EventType.STREAM_ENDED,
-                    object_id=streamdetails.provider,
+                    object_id=streamdetails.provider.value,
                     data=streamdetails,
                 )
             )
index 4cb39d1f1f9fa5cca297653fe163a5fe7517960c..7cbc3fae42f1b01593231a241025b737591608d3 100755 (executable)
@@ -104,9 +104,11 @@ class MediaItemMetadata(DataClassDictMixin):
                 continue
             cur_val = getattr(self, fld.name)
             if isinstance(cur_val, list):
-                merge_lists(cur_val, new_val)
+                new_val = merge_lists(cur_val, new_val)
+                setattr(self, fld.name, new_val)
             elif isinstance(cur_val, set):
-                cur_val.update(new_val)
+                new_val = cur_val.update(new_val)
+                setattr(self, fld.name, new_val)
             elif cur_val is None or allow_overwrite:
                 setattr(self, fld.name, new_val)
         return self
index f524afc2efb234cfc912f3ed30967100aece6854..38fac75917aee774e4f8d8bb5f59f95a8df16d6d 100644 (file)
@@ -650,7 +650,8 @@ class PlayerQueue:
 
     async def clear(self) -> None:
         """Clear all items in the queue."""
-        await self.stop()
+        if self.player.state not in (PlayerState.IDLE, PlayerState.OFF):
+            await self.stop()
         await self.update([])
 
     def on_player_update(self) -> None:
index ffded981eeb60d6a5e40b2b661e1331076a63a78..62ba1f4e759806f8a82f65564879b3ec39f74280 100644 (file)
@@ -2,7 +2,7 @@
 from __future__ import annotations
 
 from abc import abstractmethod
-from typing import TYPE_CHECKING, Any, AsyncGenerator, Dict, List, Optional
+from typing import TYPE_CHECKING, Any, AsyncGenerator, Dict, List, Optional, Tuple
 
 from music_assistant.models.config import MusicProviderConfig
 from music_assistant.models.enums import MediaType, ProviderType
@@ -187,7 +187,9 @@ class MusicProvider:
         if media_type == MediaType.RADIO:
             return await self.get_radio(prov_item_id)
 
-    async def sync_library(self) -> None:
+    async def sync_library(
+        self, media_types: Optional[Tuple[MediaType]] = None
+    ) -> None:
         """Run library sync for this provider."""
         # this reference implementation can be overridden with provider specific approach
         # this logic is aimed at streaming/online providers,
@@ -195,6 +197,8 @@ class MusicProvider:
         # filesystem implementation(s) just override this.
         async with self.mass.database.get_db() as db:
             for media_type in self.supported_mediatypes:
+                if media_types is not None and media_type not in media_types:
+                    continue
                 self.logger.debug("Start sync of %s items.", media_type.value)
                 controller = self.mass.music.get_controller(media_type)
 
index 96883b9ceb97eec4b0b85fa412cfd94dc19591ec..bce42e2d2cb10a4f3db78583900713487fb3b175 100644 (file)
@@ -1,7 +1,7 @@
 async-timeout>=3.0,<=4.0.2
 aiohttp>=3.7.0,>=3.8.1
 asyncio-throttle>=1.0,<=1.0.2
-aiofiles>=0.8.0
+aiofiles>=0.7.0,<=0.8.5
 databases>=0.5,<=0.5.5
 aiosqlite>=0.13,<=0.17
 python-slugify>=4.0,<6.1.3