From 87c3e2c7cee85a640c200e505d71faf2298fd4d6 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Mon, 26 May 2025 14:56:37 +0300 Subject: [PATCH] Download backup if restore fails in Z-Wave migration (#145434) * ZWaveJS migration: Download backup if restore fails * update test * PR comment --- homeassistant/components/zwave_js/config_flow.py | 15 +++++++++++---- homeassistant/components/zwave_js/strings.json | 2 +- tests/components/zwave_js/test_config_flow.py | 2 ++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/zwave_js/config_flow.py b/homeassistant/components/zwave_js/config_flow.py index b539c747c4f..3e899da0538 100644 --- a/homeassistant/components/zwave_js/config_flow.py +++ b/homeassistant/components/zwave_js/config_flow.py @@ -3,6 +3,7 @@ from __future__ import annotations import asyncio +import base64 from contextlib import suppress from datetime import datetime import logging @@ -192,7 +193,7 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN): self.backup_task: asyncio.Task | None = None self.restore_backup_task: asyncio.Task | None = None self.backup_data: bytes | None = None - self.backup_filepath: str | None = None + self.backup_filepath: Path | None = None self.use_addon = False self._migrating = False self._reconfigure_config_entry: ConfigEntry | None = None @@ -1241,11 +1242,15 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN): """Restore failed.""" if user_input is not None: return await self.async_step_restore_nvm() + assert self.backup_filepath is not None + assert self.backup_data is not None return self.async_show_form( step_id="restore_failed", description_placeholders={ "file_path": str(self.backup_filepath), + "file_url": f"data:application/octet-stream;base64,{base64.b64encode(self.backup_data).decode('ascii')}", + "file_name": self.backup_filepath.name, }, ) @@ -1383,12 +1388,14 @@ class ZWaveJSConfigFlow(ConfigFlow, domain=DOMAIN): unsub() # save the backup to a file just in case - self.backup_filepath = self.hass.config.path( - f"zwavejs_nvm_backup_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.bin" + self.backup_filepath = Path( + self.hass.config.path( + f"zwavejs_nvm_backup_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.bin" + ) ) try: await self.hass.async_add_executor_job( - Path(self.backup_filepath).write_bytes, + self.backup_filepath.write_bytes, self.backup_data, ) except OSError as err: diff --git a/homeassistant/components/zwave_js/strings.json b/homeassistant/components/zwave_js/strings.json index ac5de91d6e8..fbe43af1f6f 100644 --- a/homeassistant/components/zwave_js/strings.json +++ b/homeassistant/components/zwave_js/strings.json @@ -123,7 +123,7 @@ }, "restore_failed": { "title": "Restoring unsuccessful", - "description": "Your Z-Wave network could not be restored to the new controller. This means that your Z-Wave devices are not connected to Home Assistant.\n\nThe backup is saved to ”{file_path}”", + "description": "Your Z-Wave network could not be restored to the new controller. This means that your Z-Wave devices are not connected to Home Assistant.\n\nThe backup is saved to ”{file_path}”\n\n'<'a href=\"{file_url}\" download=\"{file_name}\"'>'Download backup file'<'/a'>'", "submit": "Try again" }, "choose_serial_port": { diff --git a/tests/components/zwave_js/test_config_flow.py b/tests/components/zwave_js/test_config_flow.py index 5a1e7b217e0..bae8ae55034 100644 --- a/tests/components/zwave_js/test_config_flow.py +++ b/tests/components/zwave_js/test_config_flow.py @@ -4231,6 +4231,8 @@ async def test_reconfigure_migrate_restore_failure( description_placeholders = result["description_placeholders"] assert description_placeholders is not None assert description_placeholders["file_path"] + assert description_placeholders["file_url"] + assert description_placeholders["file_name"] result = await hass.config_entries.flow.async_configure(result["flow_id"], {})