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,
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
# 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(
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,
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(
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
)
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)
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
@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.
# 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(
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),