/
/
/
1"""Pure helper functions for the config controller package."""
2
3from __future__ import annotations
4
5from dataclasses import replace
6from typing import TYPE_CHECKING
7
8from music_assistant_models.enums import ProviderStatus
9from music_assistant_models.errors import (
10 AuthenticationFailed,
11 AuthenticationRequired,
12 LoginFailed,
13 UnsupportedSystemError,
14)
15from music_assistant_models.errors import (
16 InvalidToken as InvalidTokenError,
17)
18
19if TYPE_CHECKING:
20 from music_assistant_models.config_entries import (
21 ConfigEntry,
22 ProviderConfig,
23 )
24
25
26def _with_translation_owner(
27 entries: list[ConfigEntry],
28 owner: str,
29) -> list[ConfigEntry]:
30 """Return entry copies stamped with the owner namespace used to resolve their strings."""
31 result: list[ConfigEntry] = []
32 for entry in entries:
33 # replace() returns a copy so we never mutate the shared (often module-level) entry defs.
34 # An entry that already declares an owner (e.g. an injected protocol entry that belongs to
35 # its origin provider, not the host player) keeps it; everything else gets the passed owner.
36 result.append(replace(entry, translation_owner=entry.translation_owner or owner))
37 return result
38
39
40_AUTH_ERROR_CODES = frozenset(
41 {
42 AuthenticationRequired.error_code,
43 AuthenticationFailed.error_code,
44 LoginFailed.error_code,
45 InvalidTokenError.error_code,
46 }
47)
48
49
50def _provider_status(conf: ProviderConfig, is_loaded: bool) -> ProviderStatus:
51 """Derive the (lifecycle) status of a provider from its config and load state."""
52 if not conf.enabled:
53 return ProviderStatus.DISABLED
54 # a recorded error wins over being loaded: a provider that hit a problem the user has to
55 # act on (e.g. one unloading itself after an auth failure) must not read as healthy, or
56 # the UI has no way to point at it - the status is what flags it in the providers list
57 if conf.last_error is not None:
58 if conf.last_error.error_code in _AUTH_ERROR_CODES:
59 return ProviderStatus.AUTH_REQUIRED
60 if conf.last_error.error_code == UnsupportedSystemError.error_code:
61 return ProviderStatus.INCOMPATIBLE
62 return ProviderStatus.ERROR
63 if is_loaded:
64 # runtime (un)availability of a loaded provider is conveyed via ProviderInstance.available
65 return ProviderStatus.LOADED
66 return ProviderStatus.LOADING
67