From d1362760298e4ffc6709f9dcd2e1e3efa4bd73f2 Mon Sep 17 00:00:00 2001 From: Marcel van der Veldt Date: Sun, 15 May 2022 01:34:46 +0200 Subject: [PATCH] Follow up fixes (#311) * fix library add/remove on Qobux provider * fix errors when data in nfo metadata file sis marformed * remove redundant extra cache save * fix some small glitches * fix path for artist tracks * cache filesystem playlist tracks * fix filesystem album tracks * silence stream error a bit --- music_assistant/controllers/music/albums.py | 6 ++-- music_assistant/controllers/music/artists.py | 4 +-- .../controllers/music/providers/filesystem.py | 36 +++++++++++-------- .../controllers/music/providers/qobuz.py | 30 +++++----------- music_assistant/controllers/stream.py | 4 +-- 5 files changed, 36 insertions(+), 44 deletions(-) diff --git a/music_assistant/controllers/music/albums.py b/music_assistant/controllers/music/albums.py index fb34a950..7832c93e 100644 --- a/music_assistant/controllers/music/albums.py +++ b/music_assistant/controllers/music/albums.py @@ -170,10 +170,8 @@ class AlbumsController(MediaControllerBase[Album]): self.db_table, {"item_id": item_id}, { - "name": album.name if overwrite and album.name else cur_item.name, - "sort_name": album.sort_name - if overwrite and album.sort_name - else cur_item.sort_name, + "name": album.name if overwrite else cur_item.name, + "sort_name": album.sort_name if overwrite else cur_item.sort_name, "version": album.version if overwrite else cur_item.version, "year": album.year or cur_item.year, "upc": album.upc or cur_item.upc, diff --git a/music_assistant/controllers/music/artists.py b/music_assistant/controllers/music/artists.py index 4dedbe8f..fbb1c626 100644 --- a/music_assistant/controllers/music/artists.py +++ b/music_assistant/controllers/music/artists.py @@ -117,7 +117,7 @@ class ArtistsController(MediaControllerBase[Artist]): ) -> List[Track]: """Return tracks for an artist in database.""" query = f"SELECT * FROM tracks WHERE artists LIKE '%\"{artist_id}\"%'" - query += " and artists LIKE '%\"{provider.value}\"%'" + query += f" and artists LIKE '%\"{provider.value}\"%'" return await self.mass.music.tracks.get_db_items(query) async def get_database_artist_albums( @@ -125,7 +125,7 @@ class ArtistsController(MediaControllerBase[Artist]): ) -> List[Track]: """Return tracks for an artist in database.""" query = f"SELECT * FROM albums WHERE artist LIKE '%\"{artist_id}\"%'" - query += " and artist LIKE '%\"{provider.value}\"%'" + query += f" and artist LIKE '%\"{provider.value}\"%'" return await self.mass.music.albums.get_db_items(query) async def get_provider_artist_albums( diff --git a/music_assistant/controllers/music/providers/filesystem.py b/music_assistant/controllers/music/providers/filesystem.py index 8aa24d83..da06216e 100644 --- a/music_assistant/controllers/music/providers/filesystem.py +++ b/music_assistant/controllers/music/providers/filesystem.py @@ -5,7 +5,6 @@ import asyncio import os import urllib.parse from contextlib import asynccontextmanager -from time import time from typing import Generator, List, Optional, Tuple import aiofiles @@ -108,13 +107,12 @@ class FileSystemProvider(MusicProvider): async def sync_library(self) -> None: """Run library sync for this provider.""" - last_save = 0 cache_key = f"{self.id}.checksums" checksums = await self.mass.cache.get(cache_key) if checksums is None: checksums = {} # find all music files in the music directory and all subfolders - # we work bottom down, as-in we derive all info from the tracks + # we work bottom up, as-in we derive all info from the tracks for entry in scantree(self.config.path): # mtime is used as file checksum @@ -141,11 +139,6 @@ class FileSystemProvider(MusicProvider): # we don't want the whole sync to crash on one file so we catch all exceptions here self.logger.exception("Error processing %s", entry.path) - # save current checksum cache every 5 mins for large listings - checksums[entry.path] = checksum - if (time() - last_save) > 60: - await self.mass.cache.set(cache_key, checksums) - last_save = time() # TODO: Handle deletions await self.mass.cache.set(cache_key, checksums) @@ -172,6 +165,10 @@ class FileSystemProvider(MusicProvider): async def get_album_tracks(self, prov_album_id: str) -> List[Track]: """Get album tracks for given album id.""" itempath = await self.get_filepath(prov_album_id) + if not self.exists(itempath): + query = f"SELECT * FROM tracks WHERE album LIKE '%\"{prov_album_id}\"%'" + query += f" and album LIKE '%\"{self.type.value}\"%'" + return await self.mass.music.tracks.get_db_items(query) result = [] for entry in scantree(itempath): # mtime is used as file checksum @@ -183,11 +180,15 @@ class FileSystemProvider(MusicProvider): async def get_playlist_tracks(self, prov_playlist_id: str) -> List[Track]: """Get playlist tracks for given playlist id.""" result = [] - itempath = await self.get_filepath(prov_playlist_id) - if not self.exists(itempath): - raise MediaNotFoundError(f"playlist path does not exist: {itempath}") + playlist_path = await self.get_filepath(prov_playlist_id) + if not self.exists(playlist_path): + raise MediaNotFoundError(f"playlist path does not exist: {playlist_path}") + checksum = self._get_checksum(playlist_path) + cache_key = f"{self.id}_playlist_tracks_{prov_playlist_id}" + if cache := await self.mass.cache.get(cache_key, checksum): + return [Track.from_dict(x) for x in cache] index = 0 - async with self.open_file(itempath, "r") as _file: + async with self.open_file(playlist_path, "r") as _file: for line in await _file.readlines(): line = urllib.parse.unquote(line.strip()) if line and not line.startswith("#"): @@ -195,6 +196,7 @@ class FileSystemProvider(MusicProvider): track.position = index result.append(track) index += 1 + await self.mass.cache.set(cache_key, [x.to_dict() for x in result], checksum) return result async def get_artist_albums(self, prov_artist_id: str) -> List[Album]: @@ -219,7 +221,7 @@ class FileSystemProvider(MusicProvider): prov_artist_id, self.type ) result = [] - for entry in scantree(self.config.path): + for entry in scantree(itempath): # mtime is used as file checksum checksum = str(entry.stat().st_mtime) if track := await self._parse_track(entry.path, checksum): @@ -464,7 +466,9 @@ class FileSystemProvider(MusicProvider): if genre := info.get("genre"): artist.metadata.genres = set(split_items(genre)) if not artist.musicbrainz_id: - for uid in info.get("uniqueid", []): + for uid in info.get("uniqueid") or []: + if not uid.get("@type"): + continue if uid["@type"] == "MusicBrainzArtist": artist.musicbrainz_id = uid["#text"] # find local images @@ -539,7 +543,9 @@ class FileSystemProvider(MusicProvider): album.year = int(year) if genre := info.get("genre"): album.metadata.genres = set(split_items(genre)) - for uid in info.get("uniqueid", []): + for uid in info.get("uniqueid") or []: + if not uid.get("@type"): + continue if uid["@type"] == "MusicBrainzReleaseGroup": if not album.musicbrainz_id: album.musicbrainz_id = uid["#text"] diff --git a/music_assistant/controllers/music/providers/qobuz.py b/music_assistant/controllers/music/providers/qobuz.py index 2747bc75..2bb04b4f 100644 --- a/music_assistant/controllers/music/providers/qobuz.py +++ b/music_assistant/controllers/music/providers/qobuz.py @@ -261,20 +261,14 @@ class QobuzProvider(MusicProvider): """Add item to library.""" result = None if media_type == MediaType.ARTIST: - result = await self._get_data( - "favorite/create", {"artist_ids": prov_item_id} - ) + result = await self._get_data("favorite/create", artist_id=prov_item_id) elif media_type == MediaType.ALBUM: - result = await self._get_data( - "favorite/create", {"album_ids": prov_item_id} - ) + result = await self._get_data("favorite/create", album_ids=prov_item_id) elif media_type == MediaType.TRACK: - result = await self._get_data( - "favorite/create", {"track_ids": prov_item_id} - ) + result = await self._get_data("favorite/create", track_ids=prov_item_id) elif media_type == MediaType.PLAYLIST: result = await self._get_data( - "playlist/subscribe", {"playlist_id": prov_item_id} + "playlist/subscribe", playlist_id=prov_item_id ) return result @@ -282,26 +276,20 @@ class QobuzProvider(MusicProvider): """Remove item from library.""" result = None if media_type == MediaType.ARTIST: - result = await self._get_data( - "favorite/delete", {"artist_ids": prov_item_id} - ) + result = await self._get_data("favorite/delete", artist_ids=prov_item_id) elif media_type == MediaType.ALBUM: - result = await self._get_data( - "favorite/delete", {"album_ids": prov_item_id} - ) + result = await self._get_data("favorite/delete", album_ids=prov_item_id) elif media_type == MediaType.TRACK: - result = await self._get_data( - "favorite/delete", {"track_ids": prov_item_id} - ) + result = await self._get_data("favorite/delete", track_ids=prov_item_id) elif media_type == MediaType.PLAYLIST: playlist = await self.get_playlist(prov_item_id) if playlist.is_editable: result = await self._get_data( - "playlist/delete", {"playlist_id": prov_item_id} + "playlist/delete", playlist_id=prov_item_id ) else: result = await self._get_data( - "playlist/unsubscribe", {"playlist_id": prov_item_id} + "playlist/unsubscribe", playlist_id=prov_item_id ) return result diff --git a/music_assistant/controllers/stream.py b/music_assistant/controllers/stream.py index 7243ae22..60f8fb86 100644 --- a/music_assistant/controllers/stream.py +++ b/music_assistant/controllers/stream.py @@ -496,9 +496,9 @@ class StreamController: cur_chunk += 1 # HANDLE FIRST PART OF TRACK - if not chunk and bytes_written == 0: + if not chunk and bytes_written == 0 and is_last_chunk: # stream error: got empy first chunk - self.logger.error("Stream error on track %s", queue_track.item_id) + self.logger.warning("Stream error on track %s", queue_track.item_id) # prevent player queue get stuck by just skipping to the next track queue_track.duration = 0 continue -- 2.34.1