Skip to content

Commit

Permalink
Fix webserver configuration (#742)
Browse files Browse the repository at this point in the history
* fix default webserver configuration

* Fix editing of core controller configs through frontend

* some typos

* bump frontend to 20230707.1
  • Loading branch information
marcelveldt authored Jul 7, 2023
1 parent 8c67d74 commit 8c17a3a
Show file tree
Hide file tree
Showing 14 changed files with 219 additions and 98 deletions.
27 changes: 16 additions & 11 deletions music_assistant/common/models/config_entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from mashumaro import DataClassDictMixin

from music_assistant.common.models.enums import ProviderType
from music_assistant.common.models.provider import ProviderManifest
from music_assistant.constants import (
CONF_CROSSFADE_DURATION,
CONF_EQ_BASS,
Expand Down Expand Up @@ -167,7 +168,9 @@ def parse(
for entry in config_entries:
# create a copy of the entry
conf.values[entry.key] = ConfigEntry.from_dict(entry.to_dict())
conf.values[entry.key].parse_value(raw["values"].get(entry.key), allow_none=True)
conf.values[entry.key].parse_value(
raw.get("values", {}).get(entry.key), allow_none=True
)
return conf

def to_raw(self) -> dict[str, Any]:
Expand All @@ -179,14 +182,14 @@ def _handle_value(value: ConfigEntry):
return ENCRYPT_CALLBACK(value.value)
return value.value

return {
**self.to_dict(),
"values": {
x.key: _handle_value(x)
for x in self.values.values()
if (x.value != x.default_value and x.type not in UI_ONLY)
},
res = self.to_dict()
res.pop("manifest", None) # filter out from storage
res["values"] = {
x.key: _handle_value(x)
for x in self.values.values()
if (x.value != x.default_value and x.type not in UI_ONLY)
}
return res

def __post_serialize__(self, d: dict[str, Any]) -> dict[str, Any]:
"""Adjust dict object after it has been serialized."""
Expand All @@ -205,9 +208,9 @@ def update(self, update: dict[str, ConfigValueType]) -> set[str]:
# root values (enabled, name)
root_values = ("enabled", "name")
for key in root_values:
cur_val = getattr(self, key)
if key not in update:
continue
cur_val = getattr(self, key)
new_val = update[key]
if new_val == cur_val:
continue
Expand Down Expand Up @@ -241,6 +244,7 @@ class ProviderConfig(Config):
type: ProviderType
domain: str
instance_id: str
manifest: ProviderManifest | None = None # copied here for UI convenience only
# enabled: boolean to indicate if the provider is enabled
enabled: bool = True
# name: an (optional) custom name for this provider instance/config
Expand Down Expand Up @@ -269,8 +273,9 @@ class PlayerConfig(Config):
class CoreConfig(Config):
"""CoreController Configuration."""

module: str # name of the core module
friendly_name: str # friendly name of the core module
domain: str # domain/name of the core module
manifest: ProviderManifest | None = None # copied here for UI convenience only
# last_error: an optional error message if the module could not be setup with this config
last_error: str | None = None


Expand Down
1 change: 1 addition & 0 deletions music_assistant/common/models/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ class ProviderType(StrEnum):
PLAYER = "player"
METADATA = "metadata"
PLUGIN = "plugin"
CORE = "core"


class ConfigEntryType(StrEnum):
Expand Down
8 changes: 6 additions & 2 deletions music_assistant/server/controllers/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,18 @@
class CacheController(CoreController):
"""Basic cache controller using both memory and database."""

name: str = "cache"
friendly_name: str = "Cache controller"
domain: str = "cache"

def __init__(self, *args, **kwargs) -> None:
"""Initialize core controller."""
super().__init__(*args, **kwargs)
self.database: DatabaseConnection | None = None
self._mem_cache = MemoryCache(500)
self.manifest.name = "Cache controller"
self.manifest.description = (
"Music Assistant's core controller for caching data throughout the application."
)
self.manifest.icon = "mdi-memory"

async def setup(self) -> None:
"""Async initialize of cache module."""
Expand Down
53 changes: 39 additions & 14 deletions music_assistant/server/controllers/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
remove = wrap(os.remove)
rename = wrap(os.rename)

CONFIGURABLE_CORE_CONTROLLERS = ("streams", "webserver", "players", "metadata", "cache")


class ConfigController:
"""Controller that handles storage of persistent configuration settings."""
Expand Down Expand Up @@ -179,7 +181,16 @@ async def get_provider_config(self, instance_id: str) -> ProviderConfig:
config_entries = await self.get_provider_config_entries(
raw_conf["domain"], instance_id=instance_id, values=raw_conf.get("values")
)
return ProviderConfig.parse(config_entries, raw_conf)
for prov in self.mass.get_available_providers():
if prov.domain == raw_conf["domain"]:
manifest = prov
break
else:
raise KeyError(f'Unknown provider domain: {raw_conf["domain"]}')
conf: ProviderConfig = ProviderConfig.parse(config_entries, raw_conf)
# always copy the manifest to help the UI a bit
conf.manifest = manifest
return conf
raise KeyError(f"No config found for provider id {instance_id}")

@api_command("config/providers/get_value")
Expand Down Expand Up @@ -455,20 +466,34 @@ async def get_core_configs(
"""Return all core controllers config options."""
return [
await self.get_core_config(core_controller)
for core_controller in ("streams", "webserver")
for core_controller in CONFIGURABLE_CORE_CONTROLLERS
]

@api_command("config/core/get")
async def get_core_config(self, core_controller: str) -> CoreConfig:
async def get_core_config(self, domain: str) -> CoreConfig:
"""Return configuration for a single core controller."""
raw_conf = self.get(f"{CONF_CORE}/{core_controller}", {})
config_entries = await self.get_core_config_entries(core_controller)
return CoreConfig.parse(config_entries, raw_conf)
core_controller: CoreController = getattr(self.mass, domain)
raw_conf = self.get(f"{CONF_CORE}/{domain}", {"domain": domain})
config_entries = await self.get_core_config_entries(domain)
conf: CoreConfig = CoreConfig.parse(config_entries, raw_conf)
# always copy the manifest to help the UI a bit
conf.manifest = core_controller.manifest
return conf

@api_command("config/core/get_value")
async def get_core_config_value(self, domain: str, key: str) -> ConfigValueType:
"""Return single configentry value for a core controller."""
conf = await self.get_core_config(domain)
return (
conf.values[key].value
if conf.values[key].value is not None
else conf.values[key].default_value
)

@api_command("config/core/get_entries")
async def get_core_config_entries(
self,
core_controller: str,
domain: str,
action: str | None = None,
values: dict[str, ConfigValueType] | None = None,
) -> tuple[ConfigEntry, ...]:
Expand All @@ -480,8 +505,8 @@ async def get_core_config_entries(
values: the (intermediate) raw values for config entries sent with the action.
"""
if values is None:
values = self.get(f"{CONF_CORE}/{core_controller}/values", {})
controller: CoreController = getattr(self.mass, core_controller)
values = self.get(f"{CONF_CORE}/{domain}/values", {})
controller: CoreController = getattr(self.mass, domain)
return (
await controller.get_config_entries(action=action, values=values)
+ DEFAULT_CORE_CONFIG_ENTRIES
Expand All @@ -490,26 +515,26 @@ async def get_core_config_entries(
@api_command("config/core/save")
async def save_core_config(
self,
core_controller: str,
domain: str,
values: dict[str, ConfigValueType],
) -> CoreConfig:
"""Save CoreController Config values."""
config = await self.get_core_config(core_controller)
config = await self.get_core_config(domain)
changed_keys = config.update(values)
# validate the new config
config.validate()
if not changed_keys:
# no changes
return config
# try to load the provider first to catch errors before we save it.
controller: CoreController = getattr(self.mass, core_controller)
controller: CoreController = getattr(self.mass, domain)
await controller.reload()
# reload succeeded, save new config
config.last_error = None
conf_key = f"{CONF_CORE}/{core_controller}"
conf_key = f"{CONF_CORE}/{domain}"
self.set(conf_key, config.to_raw())
# return full config, just in case
return await self.get_core_config(core_controller)
return await self.get_core_config(domain)

def get_raw_core_config_value(
self, core_module: str, key: str, default: ConfigValueType = None
Expand Down
8 changes: 6 additions & 2 deletions music_assistant/server/controllers/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,19 @@
class MetaDataController(CoreController):
"""Several helpers to search and store metadata for mediaitems."""

name: str = "metadata"
friendly_name: str = "Metadata controller"
domain: str = "metadata"

def __init__(self, *args, **kwargs) -> None:
"""Initialize class."""
super().__init__(*args, **kwargs)
self.cache = self.mass.cache
self._pref_lang: str | None = None
self.scan_busy: bool = False
self.manifest.name = "Metadata controller"
self.manifest.description = (
"Music Assistant's core controller which handles all metadata for music."
)
self.manifest.icon = "mdi-book-information-variant"

async def setup(self) -> None:
"""Async initialize of module."""
Expand Down
8 changes: 6 additions & 2 deletions music_assistant/server/controllers/music.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@
class MusicController(CoreController):
"""Several helpers around the musicproviders."""

name: str = "music"
friendly_name: str = "Music library"
domain: str = "music"

database: DatabaseConnection | None = None

Expand All @@ -64,6 +63,11 @@ def __init__(self, *args, **kwargs) -> None:
self.playlists = PlaylistController(self.mass)
self.in_progress_syncs: list[SyncTask] = []
self._sync_lock = asyncio.Lock()
self.manifest.name = "Music controller"
self.manifest.description = (
"Music Assistant's core controller which manages all music from all providers."
)
self.manifest.icon = "mdi-archive-music"

async def setup(self):
"""Async initialize of module."""
Expand Down
8 changes: 6 additions & 2 deletions music_assistant/server/controllers/players.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,19 @@
class PlayerController(CoreController):
"""Controller holding all logic to control registered players."""

name: str = "players"
friendly_name: str = "Players controller"
domain: str = "players"

def __init__(self, *args, **kwargs) -> None:
"""Initialize core controller."""
super().__init__(*args, **kwargs)
self._players: dict[str, Player] = {}
self._prev_states: dict[str, dict] = {}
self.queues = PlayerQueuesController(self)
self.manifest.name = "Players controller"
self.manifest.description = (
"Music Assistant's core controller which manages all players from all providers."
)
self.manifest.icon = "mdi-speaker-multiple"

async def setup(self) -> None:
"""Async initialize of module."""
Expand Down
Loading

0 comments on commit 8c17a3a

Please sign in to comment.