return []
return await prov.get_album_tracks(item_id)
- async def add_db_item(self, item: Album, db: Optional[Db] = None) -> Album:
+ async def add_db_item(
+ self, item: Album, overwrite_existing: bool = False, db: Optional[Db] = None
+ ) -> Album:
"""Add a new record to the database."""
assert item.provider_ids, f"Album {item.name} is missing provider id(s)"
assert item.artist, f"Album {item.name} is missing artist"
break
if cur_item:
# update existing
- return await self.update_db_item(cur_item.item_id, item, db=db)
+ return await self.update_db_item(
+ cur_item.item_id, item, overwrite=overwrite_existing, db=db
+ )
# insert new item
album_artists = await self._get_album_artists(item, cur_item, db=db)
assert item.artist, f"Album {item.name} is missing artist"
async with self.mass.database.get_db(db) as db:
cur_item = await self.get_db_item(item_id)
- album_artists = await self._get_album_artists(item, cur_item, db=db)
+
if overwrite:
metadata = item.metadata
+ metadata.last_refresh = None
provider_ids = item.provider_ids
+ album_artists = await self._get_album_artists(cur_item, db=db)
else:
metadata = cur_item.metadata.update(item.metadata)
provider_ids = {*cur_item.provider_ids, *item.provider_ids}
+ album_artists = await self._get_album_artists(item, cur_item, db=db)
if item.album_type != AlbumType.UNKNOWN:
album_type = item.album_type
return []
return await provider.get_artist_albums(item_id)
- async def add_db_item(self, item: Artist, db: Optional[Db] = None) -> Artist:
+ async def add_db_item(
+ self, item: Artist, overwrite_existing: bool = False, db: Optional[Db] = None
+ ) -> Artist:
"""Add a new item record to the database."""
assert item.provider_ids, "Album is missing provider id(s)"
async with self.mass.database.get_db(db) as db:
break
if cur_item:
# update existing
- return await self.update_db_item(cur_item.item_id, item, db=db)
+ return await self.update_db_item(
+ cur_item.item_id, item, overwrite=overwrite_existing, db=db
+ )
# insert item
new_item = await self.mass.database.insert(
)
)
- async def add_db_item(self, item: Playlist, db: Optional[Db] = None) -> Playlist:
+ async def add_db_item(
+ self, item: Playlist, overwrite_existing: bool = False, db: Optional[Db] = None
+ ) -> Playlist:
"""Add a new record to the database."""
async with self.mass.database.get_db(db) as db:
match = {"name": item.name, "owner": item.owner}
self.db_table, match, db=db
):
# update existing
- return await self.update_db_item(cur_item["item_id"], item, db=db)
+ return await self.update_db_item(
+ cur_item["item_id"], item, overwrite=overwrite_existing, db=db
+ )
# insert new item
new_item = await self.mass.database.insert(
await self.mass.metadata.get_radio_metadata(item)
return await self.add_db_item(item)
- async def add_db_item(self, item: Radio, db: Optional[Db] = None) -> Radio:
+ async def add_db_item(
+ self, item: Radio, overwrite_existing: bool = False, db: Optional[Db] = None
+ ) -> Radio:
"""Add a new item record to the database."""
assert item.provider_ids
async with self.mass.database.get_db(db) as db:
self.db_table, match, db=db
):
# update existing
- return await self.update_db_item(cur_item["item_id"], item, db=db)
+ return await self.update_db_item(
+ cur_item["item_id"], item, overwrite=overwrite_existing, db=db
+ )
# insert new item
new_item = await self.mass.database.insert(
provider.name,
)
- async def add_db_item(self, item: Track, db: Optional[Db] = None) -> Track:
+ async def add_db_item(
+ self, item: Track, overwrite_existing: bool = False, db: Optional[Db] = None
+ ) -> Track:
"""Add a new item record to the database."""
assert item.artists, "Track is missing artist(s)"
assert item.provider_ids, "Track is missing provider id(s)"
break
if cur_item:
# update existing
- return await self.update_db_item(cur_item.item_id, item, db=db)
+ return await self.update_db_item(
+ cur_item.item_id, item, overwrite=overwrite_existing, db=db
+ )
# no existing match found: insert new item
track_artists = await self._get_track_artists(item, db=db)
"""Update Track record in the database, merging data."""
async with self.mass.database.get_db(db) as db:
cur_item = await self.get_db_item(item_id, db=db)
+
if overwrite:
+ metadata = item.metadata
provider_ids = item.provider_ids
+ metadata.last_refresh = None
+ # we store a mapping to artists/albums on the item for easier access/listings
+ track_artists = await self._get_track_artists(item, db=db)
+ track_albums = await self._get_track_albums(item, db=db)
else:
+ metadata = cur_item.metadata.update(item.metadata, overwrite)
provider_ids = {*cur_item.provider_ids, *item.provider_ids}
- metadata = cur_item.metadata.update(item.metadata, overwrite)
+ track_artists = await self._get_track_artists(cur_item, item, db=db)
+ track_albums = await self._get_track_albums(cur_item, item, db=db)
- # we store a mapping to artists/albums on the item for easier access/listings
- track_artists = await self._get_track_artists(cur_item, item, db=db)
- track_albums = await self._get_track_albums(cur_item, item, db=db)
await self.mass.database.update(
self.db_table,
{"item_id": item_id},
raise NotImplementedError
@abstractmethod
- async def add_db_item(self, item: ItemCls, db: Optional[Db] = None) -> ItemCls:
+ async def add_db_item(
+ self, item: ItemCls, overwrite_existing: bool = False, db: Optional[Db] = None
+ ) -> ItemCls:
"""Add a new record for this mediatype to the database."""
raise NotImplementedError
from music_assistant.helpers.audio import get_file_stream
from music_assistant.helpers.compare import compare_strings
-from music_assistant.helpers.database import SCHEMA_VERSION
from music_assistant.helpers.util import (
create_clean_string,
parse_title_and_version,
"ogg": ContentType.OGG,
"wma": ContentType.WMA,
}
+SCHEMA_VERSION = 17
async def scantree(path: str) -> AsyncGenerator[os.DirEntry, None]:
"""Run library sync for this provider."""
cache_key = f"{self.id}.checksums"
prev_checksums = await self.mass.cache.get(cache_key, SCHEMA_VERSION)
+ save_checksum_interval = 0
if prev_checksums is None:
prev_checksums = {}
# find all music files in the music directory and all subfolders
# process album
if track.album:
db_album = await self.mass.music.albums.add_db_item(
- track.album, db=db
+ track.album, allow_overwrite=True, db=db
)
if not db_album.in_library:
await self.mass.music.albums.set_db_library(
)
# add/update track to db
db_track = await self.mass.music.tracks.add_db_item(
- track, db=db
+ track, allow_overwrite=True, db=db
)
if not db_track.in_library:
await self.mass.music.tracks.set_db_library(
# 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 checksums for next sync
- await self.mass.cache.set(cache_key, cur_checksums, SCHEMA_VERSION)
+ # save checksums every 50 processed items
+ # this allows us to pickup where we leftoff when initial scan gets intterrupted
+ if save_checksum_interval == 50:
+ await self.mass.cache.set(cache_key, cur_checksums, SCHEMA_VERSION)
+ save_checksum_interval = 0
+ else:
+ save_checksum_interval += 1
+ await self.mass.cache.set(cache_key, cur_checksums, SCHEMA_VERSION)
# work out deletions
deleted_files = set(prev_checksums.keys()) - set(cur_checksums.keys())
artists: Set[ItemMapping] = set()
albums: Set[ItemMapping] = set()
- # process deleted tracks
+ # process deleted tracks/playlists
for file_path in deleted_files:
item_id = self._get_item_id(file_path)
+ # try track first
if db_item := await self.mass.music.tracks.get_db_item_by_prov_id(
item_id, self.type
):
albums.add(db_item.album.item_id)
for artist in db_item.album.artists:
artists.add(artist.item_id)
+ # fallback to playlist
+ elif db_item := await self.mass.music.playlists.get_db_item_by_prov_id(
+ item_id, self.type
+ ):
+ await self.mass.music.playlists.remove_prov_mapping(
+ db_item.item_id, self.id
+ )
# check if albums are deleted
for album_id in albums:
album = await self.mass.music.albums.get_db_item(album_id)