Skip to content

Commit 02deb46

Browse files
authored
Don't force reload on all config changes (music-assistant#3045)
1 parent 95f0685 commit 02deb46

8 files changed

Lines changed: 265 additions & 46 deletions

File tree

music_assistant/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@
159159
],
160160
default_value="GLOBAL",
161161
category="advanced",
162+
requires_reload=False, # applied dynamically via _set_logger()
162163
)
163164

164165
DEFAULT_PROVIDER_CONFIG_ENTRIES = (CONF_ENTRY_LOG_LEVEL,)
@@ -172,6 +173,7 @@
172173
label="Enforce Gapless playback with Queue Flow Mode streaming",
173174
default_value=False,
174175
category="advanced",
176+
requires_reload=True,
175177
)
176178

177179

@@ -583,6 +585,7 @@ def create_output_codec_config_entry(
583585
],
584586
default_value="default",
585587
category="advanced",
588+
requires_reload=True,
586589
)
587590
CONF_ENTRY_LIBRARY_SYNC_ALBUMS = ConfigEntry(
588591
key="library_sync_albums",

music_assistant/controllers/streams/streams_controller.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ async def get_config_entries(
262262
"otherwise audio streaming will not work.",
263263
required=False,
264264
category="advanced",
265+
requires_reload=True,
265266
),
266267
ConfigEntry(
267268
key=CONF_BIND_PORT,
@@ -272,6 +273,7 @@ async def get_config_entries(
272273
"Make sure that this server can be reached "
273274
"on the given IP and TCP port by players on the local network.",
274275
category="advanced",
276+
requires_reload=True,
275277
),
276278
ConfigEntry(
277279
key=CONF_BIND_IP,
@@ -285,6 +287,7 @@ async def get_config_entries(
285287
"not be adjusted in regular setups.",
286288
category="advanced",
287289
required=False,
290+
requires_reload=True,
288291
),
289292
ConfigEntry(
290293
key=CONF_SMART_FADES_LOG_LEVEL,

music_assistant/controllers/webserver/auth.py

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
)
2626

2727
from music_assistant.constants import (
28-
CONF_AUTH_ALLOW_SELF_REGISTRATION,
2928
DB_TABLE_PLAYLOG,
3029
HOMEASSISTANT_SYSTEM_USER,
3130
MASS_LOGGER_NAME,
@@ -40,7 +39,6 @@
4039
HomeAssistantOAuthProvider,
4140
HomeAssistantProviderConfig,
4241
LoginProvider,
43-
LoginProviderConfig,
4442
normalize_username,
4543
)
4644
from music_assistant.helpers.api import api_command
@@ -81,10 +79,6 @@ def __init__(self, webserver: WebserverController) -> None:
8179

8280
async def setup(self) -> None:
8381
"""Initialize the authentication manager."""
84-
# Get auth settings from config
85-
allow_self_registration = self.webserver.config.get_value(CONF_AUTH_ALLOW_SELF_REGISTRATION)
86-
assert isinstance(allow_self_registration, bool)
87-
8882
# Setup database
8983
db_path = self.mass.storage_path + "/auth.db"
9084
self.database = DatabaseConnection(db_path)
@@ -97,8 +91,8 @@ async def setup(self) -> None:
9791
jwt_secret = await self._get_or_create_jwt_secret()
9892
self.jwt_helper = JWTHelper(jwt_secret)
9993

100-
# Setup login providers based on config
101-
await self._setup_login_providers(allow_self_registration)
94+
# Setup login providers
95+
await self._setup_login_providers()
10296

10397
self._has_users = await self._has_non_system_users()
10498

@@ -286,15 +280,10 @@ async def _get_or_create_jwt_secret(self) -> str:
286280
self.logger.info("Generated new JWT secret key")
287281
return jwt_secret
288282

289-
async def _setup_login_providers(self, allow_self_registration: bool) -> None:
290-
"""
291-
Set up available login providers based on configuration.
292-
293-
:param allow_self_registration: Whether to allow self-registration via OAuth.
294-
"""
283+
async def _setup_login_providers(self) -> None:
284+
"""Set up available login providers based on configuration."""
295285
# Always enable built-in provider
296-
builtin_config: LoginProviderConfig = {"allow_self_registration": False}
297-
self.login_providers["builtin"] = BuiltinLoginProvider(self.mass, "builtin", builtin_config)
286+
self.login_providers["builtin"] = BuiltinLoginProvider(self.mass, "builtin", {})
298287

299288
# Home Assistant OAuth provider
300289
# Automatically enabled if HA provider (plugin) is configured
@@ -308,10 +297,7 @@ async def _setup_login_providers(self, allow_self_registration: bool) -> None:
308297
# Get URL from the HA provider config
309298
ha_url = ha_provider.config.get_value("url")
310299
assert isinstance(ha_url, str)
311-
ha_config: HomeAssistantProviderConfig = {
312-
"ha_url": ha_url,
313-
"allow_self_registration": allow_self_registration,
314-
}
300+
ha_config: HomeAssistantProviderConfig = {"ha_url": ha_url}
315301
self.login_providers["homeassistant"] = HomeAssistantOAuthProvider(
316302
self.mass, "homeassistant", ha_config
317303
)
@@ -336,18 +322,10 @@ async def _sync_ha_oauth_provider(self) -> None:
336322
if ha_provider:
337323
# HA provider exists and is available - ensure OAuth provider is registered
338324
if "homeassistant" not in self.login_providers:
339-
# Get allow_self_registration config
340-
allow_self_registration = bool(
341-
self.webserver.config.get_value(CONF_AUTH_ALLOW_SELF_REGISTRATION, True)
342-
)
343-
344325
# Get URL from the HA provider config
345326
ha_url = ha_provider.config.get_value("url")
346327
assert isinstance(ha_url, str)
347-
ha_config: HomeAssistantProviderConfig = {
348-
"ha_url": ha_url,
349-
"allow_self_registration": allow_self_registration,
350-
}
328+
ha_config: HomeAssistantProviderConfig = {"ha_url": ha_url}
351329
self.login_providers["homeassistant"] = HomeAssistantOAuthProvider(
352330
self.mass, "homeassistant", ha_config
353331
)

music_assistant/controllers/webserver/controller.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,8 @@ def __init__(self, mass: MusicAssistant) -> None:
9898

9999
@property
100100
def base_url(self) -> str:
101-
"""Return the base_url for the streamserver."""
102-
return self._server.base_url
101+
"""Return the base_url for the webserver."""
102+
return str(self.config.get_value(CONF_BASE_URL)).removesuffix("/")
103103

104104
async def get_config_entries(
105105
self,
@@ -131,6 +131,7 @@ async def get_config_entries(
131131
label="Allow User Self-Registration",
132132
description="Allow users to create accounts via Home Assistant OAuth.",
133133
hidden=not any(provider.domain == "hass" for provider in self.mass.providers),
134+
requires_reload=False,
134135
),
135136
ConfigEntry(
136137
key=CONF_BASE_URL,
@@ -140,13 +141,15 @@ async def get_config_entries(
140141
description="The (base) URL to reach this webserver in the network. \n"
141142
"Override this in advanced scenarios where for example you're running "
142143
"the webserver behind a reverse proxy.",
144+
requires_reload=False,
143145
),
144146
ConfigEntry(
145147
key=CONF_BIND_PORT,
146148
type=ConfigEntryType.INTEGER,
147149
default_value=DEFAULT_SERVER_PORT,
148150
label="TCP Port",
149151
description="The TCP port to run the webserver.",
152+
requires_reload=True,
150153
),
151154
ConfigEntry(
152155
key="webserver_warn",
@@ -169,6 +172,7 @@ async def get_config_entries(
169172
label="Enable SSL/TLS",
170173
description="Enable HTTPS by providing an SSL certificate and private key. \n"
171174
"This encrypts all communication with the webserver.",
175+
requires_reload=True,
172176
),
173177
ConfigEntry(
174178
key=CONF_SSL_CERTIFICATE,
@@ -181,6 +185,7 @@ async def get_config_entries(
181185
"Both RSA and ECDSA certificates are supported.",
182186
required=False,
183187
depends_on=CONF_ENABLE_SSL,
188+
requires_reload=True,
184189
),
185190
ConfigEntry(
186191
key=CONF_SSL_PRIVATE_KEY,
@@ -193,6 +198,7 @@ async def get_config_entries(
193198
"This is securely encrypted and stored.",
194199
required=False,
195200
depends_on=CONF_ENABLE_SSL,
201+
requires_reload=True,
196202
),
197203
ConfigEntry(
198204
key=CONF_ACTION_VERIFY_SSL,
@@ -227,6 +233,7 @@ async def get_config_entries(
227233
"This is an advanced setting that should normally "
228234
"not be adjusted in regular setups.",
229235
category="advanced",
236+
requires_reload=True,
230237
),
231238
)
232239

music_assistant/controllers/webserver/helpers/auth_providers.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from music_assistant_models.auth import AuthProviderType, User, UserRole
1919
from music_assistant_models.errors import AuthenticationFailed
2020

21-
from music_assistant.constants import MASS_LOGGER_NAME
21+
from music_assistant.constants import CONF_AUTH_ALLOW_SELF_REGISTRATION, MASS_LOGGER_NAME
2222
from music_assistant.helpers.datetime import utc
2323

2424
if TYPE_CHECKING:
@@ -252,8 +252,6 @@ async def clear_attempts(self, username: str) -> None:
252252
class LoginProviderConfig(TypedDict, total=False):
253253
"""Base configuration for login providers."""
254254

255-
allow_self_registration: bool
256-
257255

258256
class HomeAssistantProviderConfig(LoginProviderConfig):
259257
"""Configuration for Home Assistant OAuth provider."""
@@ -287,7 +285,11 @@ def __init__(self, mass: MusicAssistant, provider_id: str, config: LoginProvider
287285
self.provider_id = provider_id
288286
self.config = config
289287
self.logger = LOGGER
290-
self.allow_self_registration = config.get("allow_self_registration", False)
288+
289+
@property
290+
def allow_self_registration(self) -> bool:
291+
"""Return whether self-registration is allowed for this provider."""
292+
return False
291293

292294
@property
293295
def auth_manager(self) -> AuthenticationManager:
@@ -520,6 +522,11 @@ def __init__(self, mass: MusicAssistant, provider_id: str, config: LoginProvider
520522
# Store OAuth state -> return_url mapping to support concurrent sessions
521523
self._oauth_sessions: dict[str, str | None] = {}
522524

525+
@property
526+
def allow_self_registration(self) -> bool:
527+
"""Return whether self-registration is allowed, read dynamically from config."""
528+
return bool(self.mass.webserver.config.get_value(CONF_AUTH_ALLOW_SELF_REGISTRATION))
529+
523530
@property
524531
def provider_type(self) -> AuthProviderType:
525532
"""Return the provider type."""

music_assistant/models/core_controller.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,13 +63,22 @@ async def reload(self, config: CoreConfig | None = None) -> None:
6363

6464
async def update_config(self, config: CoreConfig, changed_keys: set[str]) -> None:
6565
"""Handle logic when the config is updated."""
66-
# default implementation: perform a full reload on any config change
67-
# TODO: only reload when 'requires_reload' keys changed
68-
if changed_keys == {f"values/{CONF_LOG_LEVEL}"}:
69-
# only log level changed, no need to reload
66+
# always update the stored config so dynamic reads pick up new values
67+
self.config = config
68+
69+
# apply log level change dynamically (doesn't require reload)
70+
if f"values/{CONF_LOG_LEVEL}" in changed_keys:
7071
log_value = str(config.get_value(CONF_LOG_LEVEL))
7172
self._set_logger(log_value)
72-
else:
73+
74+
# reload if any changed value entry has requires_reload set to True
75+
needs_reload = any(
76+
(entry := config.values.get(key.removeprefix("values/"))) is not None
77+
and entry.requires_reload is True
78+
for key in changed_keys
79+
if key.startswith("values/")
80+
)
81+
if needs_reload:
7382
self.logger.info(
7483
"Config updated, reloading %s core controller",
7584
self.manifest.name,

music_assistant/models/provider.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,23 @@ async def update_config(self, config: ProviderConfig, changed_keys: set[str]) ->
6565
Override this method in your provider implementation if you need
6666
to perform any additional setup logic after the provider is registered and
6767
the self.config was loaded, and whenever the config changes.
68+
69+
The default implementation reloads the provider on any config change
70+
(except log-level-only changes), since provider reloads are lightweight
71+
and most providers cache config values at setup time.
6872
"""
69-
# default implementation: perform a full reload on any config change
70-
# override in your provider if you need more fine-grained control
71-
# such as checking the changed_keys set and only reload when 'requires_reload' keys changed
72-
if changed_keys == {f"values/{CONF_LOG_LEVEL}"}:
73-
# only log level changed, no need to reload
73+
# always update the stored config so dynamic reads pick up new values
74+
self.config = config
75+
76+
# update log level if changed
77+
if f"values/{CONF_LOG_LEVEL}" in changed_keys:
7478
self._set_log_level_from_config(config)
75-
else:
79+
80+
# reload if any non-log-level value keys changed
81+
value_keys_changed = {
82+
k for k in changed_keys if k.startswith("values/") and k != f"values/{CONF_LOG_LEVEL}"
83+
}
84+
if value_keys_changed:
7685
self.logger.info(
7786
"Config updated, reloading provider %s (instance_id=%s)",
7887
self.domain,

0 commit comments

Comments
 (0)