Follow up fixes (#311)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Sat, 14 May 2022 23:34:46 +0000 (01:34 +0200)
committerGitHub <noreply@github.com>
Sat, 14 May 2022 23:34:46 +0000 (01:34 +0200)
* 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
music_assistant/controllers/music/artists.py
music_assistant/controllers/music/providers/filesystem.py
music_assistant/controllers/music/providers/qobuz.py
music_assistant/controllers/stream.py

index fb34a950dae5819ae0f7b698125251c3d0f52d16..7832c93e7356ed0b38d67c394fdce8e02d17f69d 100644 (file)
@@ -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,
index 4dedbe8ff8784ddf71e2f1180e6423cb55b3d4b5..fbb1c6266e2926a4f03461b44994f71570c65914 100644 (file)
@@ -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(
index 8aa24d83442ff97bca5769e73080fa72ecc26b2d..da06216ee485e98bb1cf0b80be56b5f89b36483e 100644 (file)
@@ -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"]
index 2747bc752549beb1c1a2142b595f8bd87a4db660..2bb04b4f15c04102c93453e694df8275bab45f8d 100644 (file)
@@ -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
 
index 7243ae222f82c21e124466ea79629f9fee56afe2..60f8fb86ec647a46a6d4dbf90b0a267478fa2ecf 100644 (file)
@@ -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