Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
gieljnssns committed Apr 20, 2023
2 parents 5d4b68b + 21192b1 commit f6ceac3
Show file tree
Hide file tree
Showing 20 changed files with 992 additions and 729 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ repos:
- --branch=main
- id: debug-statements
- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: 'v0.0.261'
rev: 'v0.0.262'
hooks:
- id: ruff
- repo: https://github.com/psf/black
Expand Down
2 changes: 2 additions & 0 deletions music_assistant/common/models/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class ProviderManifest(DataClassORJSONMixin):
multi_instance: bool = False
# builtin: whether this provider is a system/builtin and can not disabled/removed
builtin: bool = False
# hidden: hide entry in the UI
hidden: bool = False
# load_by_default: load this provider by default (mostly used together with `builtin`)
load_by_default: bool = False
# depends_on: depends on another provider to function
Expand Down
2 changes: 1 addition & 1 deletion music_assistant/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import pathlib
from typing import Final

__version__: Final[str] = "2.0.0b28"
__version__: Final[str] = "2.0.0b29"

SCHEMA_VERSION: Final[int] = 22

Expand Down
7 changes: 5 additions & 2 deletions music_assistant/server/controllers/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,14 @@ async def reload_provider(self, instance_id: str) -> None:
@api_command("config/players")
def get_player_configs(self, provider: str | None = None) -> list[PlayerConfig]:
"""Return all known player configurations, optionally filtered by provider domain."""
available_providers = {x.domain for x in self.mass.providers}
return [
self.get_player_config(player_id)
for player_id, raw_conf in self.get(CONF_PLAYERS).items()
if (provider in (None, raw_conf["provider"]))
# filter out unavailable providers
if raw_conf["provider"] in available_providers
# optional provider filter
and (provider in (None, raw_conf["provider"]))
]

@api_command("config/players/get")
Expand All @@ -282,7 +286,6 @@ def get_player_config(self, player_id: str) -> PlayerConfig:
raw_conf["available"] = False
raw_conf["name"] = raw_conf.get("name")
raw_conf["default_name"] = raw_conf.get("default_name") or raw_conf["player_id"]
prov_entries = prov.get_player_config_entries(player_id)
prov_entries_keys = {x.key for x in prov_entries}
# combine provider defined entries with default player config entries
entries = prov_entries + tuple(
Expand Down
97 changes: 51 additions & 46 deletions music_assistant/server/controllers/media/albums.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class AlbumsController(MediaControllerBase[Album]):
db_table = DB_TABLE_ALBUMS
media_type = MediaType.ALBUM
item_cls = DbAlbum
_db_add_lock = asyncio.Lock()

def __init__(self, *args, **kwargs):
"""Initialize class."""
Expand Down Expand Up @@ -95,7 +96,9 @@ async def add(self, item: Album, skip_metadata_lookup: bool = False) -> Album:
# grab additional metadata
if not skip_metadata_lookup:
await self.mass.metadata.get_album_metadata(item)
existing = await self.get_db_item_by_prov_id(item.item_id, item.provider)
async with self._db_add_lock:
# use the lock to prevent a race condition of the same item being added twice
existing = await self.get_db_item_by_prov_id(item.item_id, item.provider)
if existing:
db_item = await self._update_db_item(existing.item_id, item)
else:
Expand Down Expand Up @@ -200,25 +203,28 @@ async def _add_db_item(self, item: Album) -> Album:
assert item.provider_mappings, "Item is missing provider mapping(s)"
assert item.artists, f"Album {item.name} is missing artists"
cur_item = None
# always try to grab existing item by musicbrainz_id
if item.musicbrainz_id:
match = {"musicbrainz_id": item.musicbrainz_id}
cur_item = await self.mass.music.database.get_row(self.db_table, match)
# try barcode/upc
if not cur_item and item.barcode:
for barcode in item.barcode:
if search_result := await self.mass.music.database.search(
self.db_table, barcode, "barcode"
):
cur_item = Album.from_db_row(search_result[0])
break
if not cur_item:
# fallback to search and match
for row in await self.mass.music.database.search(self.db_table, item.name):
row_album = Album.from_db_row(row)
if compare_album(row_album, item):
cur_item = row_album
break
# safety guard: check for existing item first
# use the lock to prevent a race condition of the same item being added twice
async with self._db_add_lock:
# always try to grab existing item by musicbrainz_id
if item.musicbrainz_id:
match = {"musicbrainz_id": item.musicbrainz_id}
cur_item = await self.mass.music.database.get_row(self.db_table, match)
# try barcode/upc
if not cur_item and item.barcode:
for barcode in item.barcode:
if search_result := await self.mass.music.database.search(
self.db_table, barcode, "barcode"
):
cur_item = Album.from_db_row(search_result[0])
break
if not cur_item:
# fallback to search and match
for row in await self.mass.music.database.search(self.db_table, item.name):
row_album = Album.from_db_row(row)
if compare_album(row_album, item):
cur_item = row_album
break
if cur_item:
# update existing
return await self._update_db_item(cur_item.item_id, item)
Expand All @@ -237,10 +243,10 @@ async def _add_db_item(self, item: Album) -> Album:
"timestamp_modified": int(utc_timestamp()),
},
)
item_id = new_item["item_id"]
# update/set provider_mappings table
await self._set_provider_mappings(item_id, item.provider_mappings)
self.logger.debug("added %s to database", item.name)
item_id = new_item["item_id"]
# update/set provider_mappings table
await self._set_provider_mappings(item_id, item.provider_mappings)
self.logger.debug("added %s to database", item.name)
# return created object
return await self.get_db_item(item_id)

Expand All @@ -260,28 +266,27 @@ async def _update_db_item(
else:
album_type = cur_item.album_type
sort_artist = album_artists[0].sort_name if album_artists else ""
async with self._db_add_lock:
await self.mass.music.database.update(
self.db_table,
{"item_id": db_id},
{
"name": item.name if overwrite else cur_item.name,
"sort_name": item.sort_name if overwrite else cur_item.sort_name,
"sort_artist": sort_artist,
"version": item.version if overwrite else cur_item.version,
"year": item.year if overwrite else cur_item.year or item.year,
"barcode": ";".join(cur_item.barcode),
"album_type": album_type.value,
"artists": serialize_to_json(album_artists) or None,
"metadata": serialize_to_json(metadata),
"provider_mappings": serialize_to_json(provider_mappings),
"musicbrainz_id": item.musicbrainz_id or cur_item.musicbrainz_id,
"timestamp_modified": int(utc_timestamp()),
},
)
# update/set provider_mappings table
await self._set_provider_mappings(db_id, provider_mappings)
self.logger.debug("updated %s in database: %s", item.name, db_id)
await self.mass.music.database.update(
self.db_table,
{"item_id": db_id},
{
"name": item.name if overwrite else cur_item.name,
"sort_name": item.sort_name if overwrite else cur_item.sort_name,
"sort_artist": sort_artist,
"version": item.version if overwrite else cur_item.version,
"year": item.year if overwrite else cur_item.year or item.year,
"barcode": ";".join(cur_item.barcode),
"album_type": album_type.value,
"artists": serialize_to_json(album_artists) or None,
"metadata": serialize_to_json(metadata),
"provider_mappings": serialize_to_json(provider_mappings),
"musicbrainz_id": item.musicbrainz_id or cur_item.musicbrainz_id,
"timestamp_modified": int(utc_timestamp()),
},
)
# update/set provider_mappings table
await self._set_provider_mappings(db_id, provider_mappings)
self.logger.debug("updated %s in database: %s", item.name, db_id)
return await self.get_db_item(db_id)

async def _get_provider_album_tracks(
Expand Down
80 changes: 42 additions & 38 deletions music_assistant/server/controllers/media/artists.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class ArtistsController(MediaControllerBase[Artist]):
db_table = DB_TABLE_ARTISTS
media_type = MediaType.ARTIST
item_cls = Artist
_db_add_lock = asyncio.Lock()

def __init__(self, *args, **kwargs):
"""Initialize class."""
Expand All @@ -59,7 +60,9 @@ async def add(self, item: Artist | ItemMapping, skip_metadata_lookup: bool = Fal
# grab musicbrainz id and additional metadata
if not skip_metadata_lookup:
await self.mass.metadata.get_artist_metadata(item)
existing = await self.get_db_item_by_prov_id(item.item_id, item.provider)
async with self._db_add_lock:
# use the lock to prevent a race condition of the same item being added twice
existing = await self.get_db_item_by_prov_id(item.item_id, item.provider)
if existing:
db_item = await self._update_db_item(existing.item_id, item)
else:
Expand Down Expand Up @@ -288,23 +291,25 @@ async def _add_db_item(self, item: Artist | ItemMapping) -> Artist:
item.musicbrainz_id = VARIOUS_ARTISTS_ID
if item.musicbrainz_id == VARIOUS_ARTISTS_ID:
item.name = VARIOUS_ARTISTS

# always try to grab existing item by musicbrainz_id
cur_item = None
if musicbrainz_id := getattr(item, "musicbrainz_id", None):
match = {"musicbrainz_id": musicbrainz_id}
cur_item = await self.mass.music.database.get_row(self.db_table, match)
if not cur_item:
# fallback to exact name match
# NOTE: we match an artist by name which could theoretically lead to collisions
# but the chance is so small it is not worth the additional overhead of grabbing
# the musicbrainz id upfront
match = {"sort_name": item.sort_name}
for row in await self.mass.music.database.get_rows(self.db_table, match):
row_artist = Artist.from_db_row(row)
if row_artist.sort_name == item.sort_name:
cur_item = row_artist
break
# safety guard: check for existing item first
# use the lock to prevent a race condition of the same item being added twice
async with self._db_add_lock:
# always try to grab existing item by musicbrainz_id
cur_item = None
if musicbrainz_id := getattr(item, "musicbrainz_id", None):
match = {"musicbrainz_id": musicbrainz_id}
cur_item = await self.mass.music.database.get_row(self.db_table, match)
if not cur_item:
# fallback to exact name match
# NOTE: we match an artist by name which could theoretically lead to collisions
# but the chance is so small it is not worth the additional overhead of grabbing
# the musicbrainz id upfront
match = {"sort_name": item.sort_name}
for row in await self.mass.music.database.get_rows(self.db_table, match):
row_artist = Artist.from_db_row(row)
if row_artist.sort_name == item.sort_name:
cur_item = row_artist
break
if cur_item:
# update existing
return await self._update_db_item(cur_item.item_id, item)
Expand All @@ -318,10 +323,10 @@ async def _add_db_item(self, item: Artist | ItemMapping) -> Artist:
item = Artist.from_dict(item.to_dict())
async with self._db_add_lock:
new_item = await self.mass.music.database.insert(self.db_table, item.to_db_row())
item_id = new_item["item_id"]
# update/set provider_mappings table
await self._set_provider_mappings(item_id, item.provider_mappings)
self.logger.debug("added %s to database", item.name)
item_id = new_item["item_id"]
# update/set provider_mappings table
await self._set_provider_mappings(item_id, item.provider_mappings)
self.logger.debug("added %s to database", item.name)
# return created object
return await self.get_db_item(item_id)

Expand All @@ -341,22 +346,21 @@ async def _update_db_item(
item.musicbrainz_id = VARIOUS_ARTISTS_ID
if item.musicbrainz_id == VARIOUS_ARTISTS_ID:
item.name = VARIOUS_ARTISTS
async with self._db_add_lock:
await self.mass.music.database.update(
self.db_table,
{"item_id": db_id},
{
"name": item.name if overwrite else cur_item.name,
"sort_name": item.sort_name if overwrite else cur_item.sort_name,
"musicbrainz_id": musicbrainz_id,
"metadata": serialize_to_json(metadata),
"provider_mappings": serialize_to_json(provider_mappings),
"timestamp_modified": int(utc_timestamp()),
},
)
# update/set provider_mappings table
await self._set_provider_mappings(db_id, provider_mappings)
self.logger.debug("updated %s in database: %s", item.name, db_id)
await self.mass.music.database.update(
self.db_table,
{"item_id": db_id},
{
"name": item.name if overwrite else cur_item.name,
"sort_name": item.sort_name if overwrite else cur_item.sort_name,
"musicbrainz_id": musicbrainz_id,
"metadata": serialize_to_json(metadata),
"provider_mappings": serialize_to_json(provider_mappings),
"timestamp_modified": int(utc_timestamp()),
},
)
# update/set provider_mappings table
await self._set_provider_mappings(db_id, provider_mappings)
self.logger.debug("updated %s in database: %s", item.name, db_id)
return await self.get_db_item(db_id)

async def _get_provider_dynamic_tracks(
Expand Down
Loading

0 comments on commit f6ceac3

Please sign in to comment.