Fix playergroup migration (#2469)
authorMarcel van der Veldt <m.vanderveldt@outlook.com>
Thu, 2 Oct 2025 22:39:44 +0000 (00:39 +0200)
committerGitHub <noreply@github.com>
Thu, 2 Oct 2025 22:39:44 +0000 (00:39 +0200)
music_assistant/controllers/config.py
music_assistant/controllers/players/sync_groups.py
music_assistant/models/player.py

index 494d7298b60e1193fddba5c274406e95f7836c6b..09ba41ebf9d5f47662434fb22f663f7de4658c43 100644 (file)
@@ -528,6 +528,11 @@ class ConfigController:
         # Also remove the DSP config if it exists
         self.remove(dsp_conf_key)
 
+    def set_player_default_name(self, player_id: str, default_name: str) -> None:
+        """Set (or update) the default name for a player."""
+        conf_key = f"{CONF_PLAYERS}/{player_id}/default_name"
+        self.set(conf_key, default_name)
+
     @api_command("config/players/dsp/get")
     def get_player_dsp_config(self, player_id: str) -> DSPConfig:
         """
@@ -1005,15 +1010,28 @@ class ConfigController:
 
         # Migrate the crossfade setting into Smart Fade Mode = 'crossfade'
         for player_config in self._data.get(CONF_PLAYERS, {}).values():
+            if (crossfade := player_config.pop(CONF_DEPRECATED_CROSSFADE, None)) is None:
+                continue
             # Check if player has old crossfade enabled but no smart fades mode set
-            if (
-                player_config.get(CONF_DEPRECATED_CROSSFADE) is True
-                and CONF_SMART_FADES_MODE not in player_config
-            ):
+            if crossfade is True and CONF_SMART_FADES_MODE not in player_config:
                 # Set smart fades mode to standard_crossfade
                 player_config[CONF_SMART_FADES_MODE] = "standard_crossfade"
                 changed = True
 
+        # migrate player configs: always use lookup key for provider
+        prov_configs = self._data.get(CONF_PROVIDERS, {})
+        for player_config in self._data.get(CONF_PLAYERS, {}).values():
+            player_provider = player_config["provider"]
+            if prov_conf := prov_configs.get(player_provider):
+                if not (prov_manifest := self.mass.get_provider_manifest(prov_conf["domain"])):
+                    continue
+                if prov_manifest.multi_instance:
+                    # multi instance providers use instance_id as lookup key
+                    continue
+                # single instance providers use domain as lookup key
+                player_config["provider"] = prov_conf["domain"]
+                changed = True
+
         if changed:
             await self._async_save()
 
index 4aad160e7689be74ff57dc698baff11c6322a4c9..de60ff5849490a8fe5d4297da3e5138f05dd169b 100644 (file)
@@ -88,7 +88,7 @@ class SyncGroupPlayer(GroupPlayer):
     ) -> None:
         """Initialize GroupPlayer instance."""
         super().__init__(provider, player_id)
-        self._attr_name = self.config.name or f"SyncGroup {player_id}"
+        self._attr_name = self.config.name or self.config.default_name or f"SyncGroup {player_id}"
         self._attr_available = True
         self._attr_powered = False  # group players are always powered off by default
         self._attr_active_source = None
index d4863934fa506564513c081fa9c8a7ef910ad520..ad59cf1cc4a12857fcb64f5e5f402583824bd3e0 100644 (file)
@@ -1058,6 +1058,9 @@ class Player(ABC):
         changed_values.pop("elapsed_time_last_updated", None)
         changed_values.pop("extra_attributes.seq_no", None)
         changed_values.pop("extra_attributes.last_poll", None)
+        # persist the default name if it changed
+        if self.name and self.config.default_name != self.name:
+            self.mass.config.set_player_default_name(self.player_id, self.name)
         # return early if nothing changed (unless force_update is True)
         if len(changed_values) == 0 and not force_update:
             return