From: Marcel van der Veldt Date: Wed, 25 May 2022 23:03:19 +0000 (+0200) Subject: Several small fixes (#342) X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=683bf8e4365fbdba94779ad42a689d162b6d2a33;p=music-assistant-server.git Several small fixes (#342) * Add check before stop command * fix typo * fix metadata merging * loosen aiofiles requirement * add function to start specific sync task --- diff --git a/examples/full.py b/examples/full.py index 4d41873d..c265dc3e 100644 --- a/examples/full.py +++ b/examples/full.py @@ -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() diff --git a/examples/simple.py b/examples/simple.py index c2ea8127..825023df 100644 --- a/examples/simple.py +++ b/examples/simple.py @@ -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() diff --git a/music_assistant/controllers/music/__init__.py b/music_assistant/controllers/music/__init__.py index 94d4288c..f785b037 100755 --- a/music_assistant/controllers/music/__init__.py +++ b/music_assistant/controllers/music/__init__.py @@ -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, ) diff --git a/music_assistant/controllers/music/providers/filesystem.py b/music_assistant/controllers/music/providers/filesystem.py index 6cf3583d..ba281327 100644 --- a/music_assistant/controllers/music/providers/filesystem.py +++ b/music_assistant/controllers/music/providers/filesystem.py @@ -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: diff --git a/music_assistant/helpers/audio.py b/music_assistant/helpers/audio.py index 727cc4be..9886ff3b 100644 --- a/music_assistant/helpers/audio.py +++ b/music_assistant/helpers/audio.py @@ -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, ) ) diff --git a/music_assistant/models/media_items.py b/music_assistant/models/media_items.py index 4cb39d1f..7cbc3fae 100755 --- a/music_assistant/models/media_items.py +++ b/music_assistant/models/media_items.py @@ -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 diff --git a/music_assistant/models/player_queue.py b/music_assistant/models/player_queue.py index f524afc2..38fac759 100644 --- a/music_assistant/models/player_queue.py +++ b/music_assistant/models/player_queue.py @@ -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: diff --git a/music_assistant/models/provider.py b/music_assistant/models/provider.py index ffded981..62ba1f4e 100644 --- a/music_assistant/models/provider.py +++ b/music_assistant/models/provider.py @@ -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) diff --git a/requirements.txt b/requirements.txt index 96883b9c..bce42e2d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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