From 37327f6cbd1175aedc915923b7130d19fad59074 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 23 Nov 2018 22:56:58 +0100 Subject: [PATCH] Add save command to lovelace (#18655) * Add save command to lovelace * Default for save should by json * typing --- homeassistant/components/lovelace/__init__.py | 32 +++++++++++++++++-- homeassistant/util/ruamel_yaml.py | 10 ++++-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index 5234dbaf29d..72b19235c30 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -31,6 +31,7 @@ FORMAT_JSON = 'json' OLD_WS_TYPE_GET_LOVELACE_UI = 'frontend/lovelace_config' WS_TYPE_GET_LOVELACE_UI = 'lovelace/config' WS_TYPE_MIGRATE_CONFIG = 'lovelace/config/migrate' +WS_TYPE_SAVE_CONFIG = 'lovelace/config/save' WS_TYPE_GET_CARD = 'lovelace/config/card/get' WS_TYPE_UPDATE_CARD = 'lovelace/config/card/update' @@ -53,6 +54,13 @@ SCHEMA_MIGRATE_CONFIG = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): WS_TYPE_MIGRATE_CONFIG, }) +SCHEMA_SAVE_CONFIG = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_SAVE_CONFIG, + vol.Required('config'): vol.Any(str, Dict), + vol.Optional('format', default=FORMAT_JSON): + vol.Any(FORMAT_JSON, FORMAT_YAML), +}) + SCHEMA_GET_CARD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): WS_TYPE_GET_CARD, vol.Required('card_id'): str, @@ -204,6 +212,13 @@ def migrate_config(fname: str) -> None: yaml.save_yaml(fname, config) +def save_config(fname: str, config, data_format: str = FORMAT_JSON) -> None: + """Save config to file.""" + if data_format == FORMAT_YAML: + config = yaml.yaml_to_object(config) + yaml.save_yaml(fname, config) + + def get_card(fname: str, card_id: str, data_format: str = FORMAT_YAML)\ -> JSON_TYPE: """Load a specific card config for id.""" @@ -422,13 +437,17 @@ async def async_setup(hass, config): OLD_WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config, SCHEMA_GET_LOVELACE_UI) + hass.components.websocket_api.async_register_command( + WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config, + SCHEMA_GET_LOVELACE_UI) + hass.components.websocket_api.async_register_command( WS_TYPE_MIGRATE_CONFIG, websocket_lovelace_migrate_config, SCHEMA_MIGRATE_CONFIG) hass.components.websocket_api.async_register_command( - WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config, - SCHEMA_GET_LOVELACE_UI) + WS_TYPE_SAVE_CONFIG, websocket_lovelace_save_config, + SCHEMA_SAVE_CONFIG) hass.components.websocket_api.async_register_command( WS_TYPE_GET_CARD, websocket_lovelace_get_card, SCHEMA_GET_CARD) @@ -516,6 +535,15 @@ async def websocket_lovelace_migrate_config(hass, connection, msg): migrate_config, hass.config.path(LOVELACE_CONFIG_FILE)) +@websocket_api.async_response +@handle_yaml_errors +async def websocket_lovelace_save_config(hass, connection, msg): + """Save Lovelace UI configuration.""" + return await hass.async_add_executor_job( + save_config, hass.config.path(LOVELACE_CONFIG_FILE), msg['config'], + msg.get('format', FORMAT_JSON)) + + @websocket_api.async_response @handle_yaml_errors async def websocket_lovelace_get_card(hass, connection, msg): diff --git a/homeassistant/util/ruamel_yaml.py b/homeassistant/util/ruamel_yaml.py index 8211252a516..0659e3d8054 100644 --- a/homeassistant/util/ruamel_yaml.py +++ b/homeassistant/util/ruamel_yaml.py @@ -1,7 +1,7 @@ """ruamel.yaml utility functions.""" import logging import os -from os import O_CREAT, O_TRUNC, O_WRONLY +from os import O_CREAT, O_TRUNC, O_WRONLY, stat_result from collections import OrderedDict from typing import Union, List, Dict @@ -104,13 +104,17 @@ def save_yaml(fname: str, data: JSON_TYPE) -> None: yaml.indent(sequence=4, offset=2) tmp_fname = fname + "__TEMP__" try: - file_stat = os.stat(fname) + try: + file_stat = os.stat(fname) + except OSError: + file_stat = stat_result( + (0o644, -1, -1, -1, -1, -1, -1, -1, -1, -1)) with open(os.open(tmp_fname, O_WRONLY | O_CREAT | O_TRUNC, file_stat.st_mode), 'w', encoding='utf-8') \ as temp_file: yaml.dump(data, temp_file) os.replace(tmp_fname, fname) - if hasattr(os, 'chown'): + if hasattr(os, 'chown') and file_stat.st_ctime > -1: try: os.chown(fname, file_stat.st_uid, file_stat.st_gid) except OSError: