From: Marcel van der Veldt Date: Fri, 17 Jan 2025 14:14:30 +0000 (+0100) Subject: Chore: Small optimization to sync logic X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=4b1dcae8512b3b3621c528a29551b445a6f518ce;p=music-assistant-server.git Chore: Small optimization to sync logic --- diff --git a/music_assistant/controllers/media/albums.py b/music_assistant/controllers/media/albums.py index b81fa7d9..48170793 100644 --- a/music_assistant/controllers/media/albums.py +++ b/music_assistant/controllers/media/albums.py @@ -6,26 +6,11 @@ import contextlib from collections.abc import Iterable from typing import TYPE_CHECKING, Any -from music_assistant_models.enums import ( - AlbumType, - CacheCategory, - MediaType, - ProviderFeature, -) -from music_assistant_models.errors import InvalidDataError, MediaNotFoundError -from music_assistant_models.media_items import ( - Album, - Artist, - ItemMapping, - Track, - UniqueList, -) +from music_assistant_models.enums import AlbumType, CacheCategory, MediaType, ProviderFeature +from music_assistant_models.errors import InvalidDataError, MediaNotFoundError, MusicAssistantError +from music_assistant_models.media_items import Album, Artist, ItemMapping, Track, UniqueList -from music_assistant.constants import ( - DB_TABLE_ALBUM_ARTISTS, - DB_TABLE_ALBUM_TRACKS, - DB_TABLE_ALBUMS, -) +from music_assistant.constants import DB_TABLE_ALBUM_ARTISTS, DB_TABLE_ALBUM_TRACKS, DB_TABLE_ALBUMS from music_assistant.controllers.media.base import MediaControllerBase from music_assistant.helpers.compare import ( compare_album, @@ -199,11 +184,13 @@ class AlbumsController(MediaControllerBase[Album, Album]): sql_query += f" WHERE {' AND '.join(query_parts)}" return await self.mass.music.database.get_count_from_query(sql_query, query_params) - async def remove_item_from_library(self, item_id: str | int) -> None: + async def remove_item_from_library(self, item_id: str | int, recursive: bool = True) -> None: """Delete record from the database.""" db_id = int(item_id) # ensure integer # recursively also remove album tracks for db_track in await self.get_library_album_tracks(db_id): + if not recursive: + raise MusicAssistantError("Album still has tracks linked") with contextlib.suppress(MediaNotFoundError): await self.mass.music.tracks.remove_item_from_library(db_track.item_id) # delete entry(s) from albumtracks table @@ -211,6 +198,7 @@ class AlbumsController(MediaControllerBase[Album, Album]): # delete entry(s) from album artists table await self.mass.music.database.delete(DB_TABLE_ALBUM_ARTISTS, {"album_id": db_id}) # delete the album itself from db + # this will raise if the item still has references and recursive is false await super().remove_item_from_library(item_id) async def tracks( diff --git a/music_assistant/controllers/media/artists.py b/music_assistant/controllers/media/artists.py index 4f558a3f..80095719 100644 --- a/music_assistant/controllers/media/artists.py +++ b/music_assistant/controllers/media/artists.py @@ -6,20 +6,13 @@ import asyncio import contextlib from typing import TYPE_CHECKING, Any -from music_assistant_models.enums import ( - AlbumType, - CacheCategory, - MediaType, - ProviderFeature, -) -from music_assistant_models.errors import MediaNotFoundError, ProviderUnavailableError -from music_assistant_models.media_items import ( - Album, - Artist, - ItemMapping, - Track, - UniqueList, +from music_assistant_models.enums import AlbumType, CacheCategory, MediaType, ProviderFeature +from music_assistant_models.errors import ( + MediaNotFoundError, + MusicAssistantError, + ProviderUnavailableError, ) +from music_assistant_models.media_items import Album, Artist, ItemMapping, Track, UniqueList from music_assistant.constants import ( DB_TABLE_ALBUM_ARTISTS, @@ -176,26 +169,31 @@ class ArtistsController(MediaControllerBase[Artist, Artist | ItemMapping]): result.append(provider_album) return result - async def remove_item_from_library(self, item_id: str | int) -> None: + async def remove_item_from_library(self, item_id: str | int, recursive: bool = True) -> None: """Delete record from the database.""" db_id = int(item_id) # ensure integer + # recursively also remove artist albums for db_row in await self.mass.music.database.get_rows_from_query( f"SELECT album_id FROM {DB_TABLE_ALBUM_ARTISTS} WHERE artist_id = {db_id}", limit=5000, ): + if not recursive: + raise MusicAssistantError("Artist still has albums linked") with contextlib.suppress(MediaNotFoundError): await self.mass.music.albums.remove_item_from_library(db_row["album_id"]) - # recursively also remove artist tracks for db_row in await self.mass.music.database.get_rows_from_query( f"SELECT track_id FROM {DB_TABLE_TRACK_ARTISTS} WHERE artist_id = {db_id}", limit=5000, ): + if not recursive: + raise MusicAssistantError("Artist still has tracks linked") with contextlib.suppress(MediaNotFoundError): await self.mass.music.tracks.remove_item_from_library(db_row["track_id"]) # delete the artist itself from db + # this will raise if the item still has references and recursive is false await super().remove_item_from_library(db_id) async def get_provider_artist_toptracks( diff --git a/music_assistant/controllers/media/base.py b/music_assistant/controllers/media/base.py index 16481dcd..e3d5291b 100644 --- a/music_assistant/controllers/media/base.py +++ b/music_assistant/controllers/media/base.py @@ -26,11 +26,7 @@ from music_assistant_models.media_items import ( Track, ) -from music_assistant.constants import ( - DB_TABLE_PLAYLOG, - DB_TABLE_PROVIDER_MAPPINGS, - MASS_LOGGER_NAME, -) +from music_assistant.constants import DB_TABLE_PLAYLOG, DB_TABLE_PROVIDER_MAPPINGS, MASS_LOGGER_NAME from music_assistant.helpers.compare import compare_media_item from music_assistant.helpers.json import json_loads, serialize_to_json @@ -177,7 +173,7 @@ class MediaControllerBase(Generic[ItemCls, LibraryUpdate], metaclass=ABCMeta): ) return library_item - async def remove_item_from_library(self, item_id: str | int) -> None: + async def remove_item_from_library(self, item_id: str | int, recursive: bool = True) -> None: """Delete library record from the database.""" db_id = int(item_id) # ensure integer library_item = await self.get_library_item(db_id) diff --git a/music_assistant/controllers/media/tracks.py b/music_assistant/controllers/media/tracks.py index 13cf6464..9842d1bb 100644 --- a/music_assistant/controllers/media/tracks.py +++ b/music_assistant/controllers/media/tracks.py @@ -315,7 +315,7 @@ class TracksController(MediaControllerBase[Track, Track]): return [] - async def remove_item_from_library(self, item_id: str | int) -> None: + async def remove_item_from_library(self, item_id: str | int, recursive: bool = True) -> None: """Delete record from the database.""" db_id = int(item_id) # ensure integer # delete entry(s) from albumtracks table diff --git a/music_assistant/controllers/music.py b/music_assistant/controllers/music.py index e5aa6c4b..1d28ee6d 100644 --- a/music_assistant/controllers/music.py +++ b/music_assistant/controllers/music.py @@ -576,7 +576,7 @@ class MusicController(CoreController): @api_command("music/library/remove_item") async def remove_item_from_library( - self, media_type: MediaType, library_item_id: str | int + self, media_type: MediaType, library_item_id: str | int, recursive: bool = True ) -> None: """ Remove item from the library. @@ -593,7 +593,7 @@ class MusicController(CoreController): # so we need to be a bit forgiving here with suppress(NotImplementedError): await prov_controller.library_remove(provider_mapping.item_id, item.media_type) - await ctrl.remove_item_from_library(library_item_id) + await ctrl.remove_item_from_library(library_item_id, recursive) @api_command("music/library/add_item") async def add_item_to_library( diff --git a/music_assistant/models/music_provider.py b/music_assistant/models/music_provider.py index 785645ff..8ed2131b 100644 --- a/music_assistant/models/music_provider.py +++ b/music_assistant/models/music_provider.py @@ -655,17 +655,23 @@ class MusicProvider(Provider): for x in item.provider_mappings if x.provider_domain != self.domain } - if not remaining_providers and media_type != MediaType.ARTIST: - # this item is removed from the provider's library - # and we have no other providers attached to it - # it is safe to remove it from the MA library too - # note we skip artists here to prevent a recursive removal - # of all albums and tracks underneath this artist - await controller.remove_item_from_library(db_id) - else: - # otherwise: just unmark favorite + if remaining_providers: + continue + # this item is removed from the provider's library + # and we have no other providers attached to it + # it is safe to remove it from the MA library too + # note that we do not remove item's recursively on purpose + try: + await controller.remove_item_from_library(db_id, recursive=False) + except MusicAssistantError as err: + # this is probably because the item still has dependents + self.logger.warning( + "Error removing item %s from library: %s", db_id, str(err) + ) + # just un-favorite the item if we can't remove it await controller.set_favorite(db_id, False) - await asyncio.sleep(0) # yield to eventloop + await asyncio.sleep(0) # yield to eventloop + await self.mass.cache.set( media_type.value, list(cur_db_ids),