Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 29 additions & 25 deletions custom_components/hacs/repositories/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -965,35 +965,39 @@ async def async_install_repository(self, *, version: str | None = None, **_) ->
{"repository": self.data.full_name, "progress": 50},
)

if self.repository_manifest.zip_release and self.repository_manifest.filename:
await self.download_zip_files(self.validate)
else:
await self.download_content(version_to_install)

self.hacs.async_dispatch(
HacsDispatchEvent.REPOSITORY_DOWNLOAD_PROGRESS,
{"repository": self.data.full_name, "progress": 70},
)
try:
if self.repository_manifest.zip_release and self.repository_manifest.filename:
await self.download_zip_files(self.validate)
else:
await self.download_content(version_to_install)
Comment thread
frenck marked this conversation as resolved.
Outdated

if self.validate.errors:
for error in self.validate.errors:
self.logger.error("%s %s", self.string, error)
if self.data.installed and not self.content.single:
await self.hacs.hass.async_add_executor_job(backup.restore)
await self.hacs.hass.async_add_executor_job(backup.cleanup)
raise HacsException("Could not download, see log for details")
self.hacs.async_dispatch(
HacsDispatchEvent.REPOSITORY_DOWNLOAD_PROGRESS,
{"repository": self.data.full_name, "progress": 70},
)

self.hacs.async_dispatch(
HacsDispatchEvent.REPOSITORY_DOWNLOAD_PROGRESS,
{"repository": self.data.full_name, "progress": 80},
)
if self.validate.errors:
for error in self.validate.errors:
self.logger.error("%s %s", self.string, error)
if self.data.installed and not self.content.single:
await self.hacs.hass.async_add_executor_job(backup.restore)
await self.hacs.hass.async_add_executor_job(backup.cleanup)
raise HacsException("Could not download, see log for details")

if self.data.installed and not self.content.single:
await self.hacs.hass.async_add_executor_job(backup.cleanup)
self.hacs.async_dispatch(
HacsDispatchEvent.REPOSITORY_DOWNLOAD_PROGRESS,
{"repository": self.data.full_name, "progress": 80},
)

if persistent_directory is not None:
await self.hacs.hass.async_add_executor_job(persistent_directory.restore)
await self.hacs.hass.async_add_executor_job(persistent_directory.cleanup)
if self.data.installed and not self.content.single:
await self.hacs.hass.async_add_executor_job(backup.cleanup)
finally:
# The persistent directory is moved out of the way before the download
# and is not part of the backup of the old install, so it needs to be
# moved back in, also when the download failed.
if persistent_directory is not None:
await self.hacs.hass.async_add_executor_job(persistent_directory.restore)
await self.hacs.hass.async_add_executor_job(persistent_directory.cleanup)
Comment thread
frenck marked this conversation as resolved.
Outdated

if self.validate.success:
self.data.installed = True
Expand Down
65 changes: 65 additions & 0 deletions tests/repositories/test_update_repository.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from collections.abc import Generator
import json
from pathlib import Path
import re
from unittest.mock import patch

Expand Down Expand Up @@ -266,6 +267,70 @@ async def test_update_repository_entity_download_failure(
)


async def test_update_repository_entity_download_failure_keeps_persistent_directory(
hass: HomeAssistant,
setup_integration: Generator,
response_mocker: ResponseMocker,
):
"""Ensure the persistent directory survives a failed download."""
hacs = get_hacs(hass)
repo = hacs.repositories.get_by_full_name(
"hacs-test-org/integration-basic")

assert repo is not None

repo.data.installed = True
repo.data.installed_version = "1.0.0"

await hass.config_entries.async_reload(hacs.configuration.config_entry.entry_id)
await hass.async_block_till_done()

# Get a new HACS instance after reload
hacs = get_hacs(hass)
repo = hacs.repositories.get_by_full_name(
"hacs-test-org/integration-basic")

# Set a persistent directory on the manifest, and 404 the hacs.json
# fetch so the update flow keeps the manifest set here.
repo.repository_manifest.persistent_directory = "userfiles"
response_mocker.add(
"https://api.github.com/repos/hacs-test-org/integration-basic/contents/hacs.json",
MockedResponse(status=404, keep=True),
)

response_mocker.add(
"https://github.com/hacs-test-org/integration-basic/archive/refs/tags/2.0.0.zip",
MockedResponse(status=503),
)
response_mocker.add(
"https://github.com/hacs-test-org/integration-basic/archive/refs/heads/2.0.0.zip",
MockedResponse(status=503),
)

persistent_file = Path(repo.localpath) / "userfiles" / "data.txt"
persistent_file.parent.mkdir(parents=True, exist_ok=True)
persistent_file.write_text("Important user data")

er = async_get_entity_registry(hacs.hass)
entity_id = er.async_get_entity_id("update", DOMAIN, repo.data.id)

with pytest.raises(
HomeAssistantError,
match=re.escape(
"Downloading hacs-test-org/integration-basic with version 2.0.0 failed with (Could not download, see log for details)",
),
):
await hass.services.async_call(
"update",
"install",
service_data={"entity_id": entity_id, "version": "2.0.0"},
blocking=True,
)

assert persistent_file.exists()
assert persistent_file.read_text() == "Important user data"


async def test_update_repository_entity_same_provided_version(
hass: HomeAssistant, setup_integration: Generator
):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"tests/repositories/test_update_repository.py::test_update_repository_entity_download_failure_keeps_persistent_directory": {
"https://api.github.com/repos/hacs-test-org/integration-basic": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic/branches/main": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic/contents/custom_components/example/manifest.json": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic/contents/hacs.json": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic/git/trees/1.0.0": 1,
"https://api.github.com/repos/hacs-test-org/integration-basic/releases": 1,
"https://api.github.com/repos/hacs/integration": 1,
"https://api.github.com/repos/hacs/integration/contents/custom_components/hacs/manifest.json": 1,
"https://api.github.com/repos/hacs/integration/contents/hacs.json": 1,
"https://api.github.com/repos/hacs/integration/git/trees/main": 1,
"https://api.github.com/repos/hacs/integration/releases": 1,
"https://data-v2.hacs.xyz/appdaemon/data.json": 1,
"https://data-v2.hacs.xyz/critical/data.json": 1,
"https://data-v2.hacs.xyz/integration/data.json": 1,
"https://data-v2.hacs.xyz/plugin/data.json": 1,
"https://data-v2.hacs.xyz/python_script/data.json": 1,
"https://data-v2.hacs.xyz/removed/data.json": 1,
"https://data-v2.hacs.xyz/template/data.json": 1,
"https://data-v2.hacs.xyz/theme/data.json": 1,
"https://github.com/hacs-test-org/integration-basic/archive/refs/heads/2.0.0.zip": 1,
"https://github.com/hacs-test-org/integration-basic/archive/refs/tags/2.0.0.zip": 1,
"https://raw.githubusercontent.com/hacs-test-org/integration-basic/1.0.0/README.md": 1,
"https://raw.githubusercontent.com/hacs-test-org/integration-basic/1.0.0/custom_components/example/manifest.json": 1,
"https://raw.githubusercontent.com/hacs-test-org/integration-basic/2.0.0/hacs.json": 1
}
}
Loading