Update HACS

This commit is contained in:
root
2022-10-30 09:47:33 -07:00
parent 791369c66a
commit 108f9c24cc
18 changed files with 146 additions and 49 deletions

View File

@@ -29,7 +29,6 @@ from .enums import ConfigurationType, HacsDisabledReason, HacsStage, LovelaceMod
from .frontend import async_register_frontend from .frontend import async_register_frontend
from .utils.configuration_schema import hacs_config_combined from .utils.configuration_schema import hacs_config_combined
from .utils.data import HacsData from .utils.data import HacsData
from .utils.platform_setup import async_setup_entity_platforms
from .utils.queue_manager import QueueManager from .utils.queue_manager import QueueManager
from .utils.version import version_left_higher_or_equal_then_right from .utils.version import version_left_higher_or_equal_then_right
from .websocket import async_register_websocket_commands from .websocket import async_register_websocket_commands
@@ -169,9 +168,7 @@ async def async_initialize_integration(
hacs.log.info("Update entities are only supported when using UI configuration") hacs.log.info("Update entities are only supported when using UI configuration")
else: else:
await async_setup_entity_platforms( hass.config_entries.async_setup_platforms(
hacs,
hass,
config_entry, config_entry,
[Platform.SENSOR, Platform.UPDATE] [Platform.SENSOR, Platform.UPDATE]
if hacs.configuration.experimental if hacs.configuration.experimental

View File

@@ -28,10 +28,11 @@ from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import EVENT_HOMEASSISTANT_FINAL_WRITE, Platform from homeassistant.const import EVENT_HOMEASSISTANT_FINAL_WRITE, Platform
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.issue_registry import async_create_issue, IssueSeverity
from homeassistant.loader import Integration from homeassistant.loader import Integration
from homeassistant.util import dt from homeassistant.util import dt
from .const import TV from .const import DOMAIN, TV
from .enums import ( from .enums import (
ConfigurationType, ConfigurationType,
HacsCategory, HacsCategory,
@@ -54,7 +55,6 @@ from .repositories import RERPOSITORY_CLASSES
from .utils.decode import decode_content from .utils.decode import decode_content
from .utils.json import json_loads from .utils.json import json_loads
from .utils.logger import LOGGER from .utils.logger import LOGGER
from .utils.platform_setup import async_setup_entity_platforms
from .utils.queue_manager import QueueManager from .utils.queue_manager import QueueManager
from .utils.store import async_load_from_store, async_save_to_store from .utils.store import async_load_from_store, async_save_to_store
@@ -187,8 +187,8 @@ class HacsRepositories:
_default_repositories: set[str] = field(default_factory=set) _default_repositories: set[str] = field(default_factory=set)
_repositories: list[HacsRepository] = field(default_factory=list) _repositories: list[HacsRepository] = field(default_factory=list)
_repositories_by_full_name: dict[str, str] = field(default_factory=dict) _repositories_by_full_name: dict[str, HacsRepository] = field(default_factory=dict)
_repositories_by_id: dict[str, str] = field(default_factory=dict) _repositories_by_id: dict[str, HacsRepository] = field(default_factory=dict)
_removed_repositories: list[RemovedRepository] = field(default_factory=list) _removed_repositories: list[RemovedRepository] = field(default_factory=list)
@property @property
@@ -213,8 +213,15 @@ class HacsRepositories:
if repo_id == "0": if repo_id == "0":
return return
if self.is_registered(repository_id=repo_id): if registered_repo := self._repositories_by_id.get(repo_id):
return if registered_repo.data.full_name == repository.data.full_name:
return
self.unregister(registered_repo)
registered_repo.data.full_name = repository.data.full_name
registered_repo.data.new = False
repository = registered_repo
if repository not in self._repositories: if repository not in self._repositories:
self._repositories.append(repository) self._repositories.append(repository)
@@ -563,11 +570,6 @@ class HacsBase:
if repository_id is not None: if repository_id is not None:
repository.data.id = repository_id repository.data.id = repository_id
if str(repository.data.id) != "0" and (
exists := self.repositories.get_by_id(repository.data.id)
):
self.repositories.unregister(exists)
else: else:
if self.hass is not None and ((check and repository.data.new) or self.status.new): if self.hass is not None and ((check and repository.data.new) or self.status.new):
self.async_dispatch( self.async_dispatch(
@@ -730,10 +732,7 @@ class HacsBase:
entry=self.configuration.config_entry, entry=self.configuration.config_entry,
platforms=platforms, platforms=platforms,
) )
self.hass.config_entries.async_setup_platforms(self.configuration.config_entry, platforms)
await async_setup_entity_platforms(
self, self.hass, self.configuration.config_entry, platforms
)
@callback @callback
def async_dispatch(self, signal: HacsDispatchEvent, data: dict | None = None) -> None: def async_dispatch(self, signal: HacsDispatchEvent, data: dict | None = None) -> None:
@@ -881,14 +880,30 @@ class HacsBase:
continue continue
if repository.data.full_name in self.common.ignored_repositories: if repository.data.full_name in self.common.ignored_repositories:
continue continue
if repository.data.installed and removed.removal_type != "critical": if repository.data.installed:
self.log.warning( if removed.removal_type != "critical":
"You have '%s' installed with HACS " if self.configuration.experimental:
"this repository has been removed from HACS, please consider removing it. " async_create_issue(
"Removal reason (%s)", hass=self.hass,
repository.data.full_name, domain=DOMAIN,
removed.reason, issue_id=f"removed_{repository.data.id}",
) is_fixable=False,
issue_domain=DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="removed",
translation_placeholders={
"name": repository.data.full_name,
"reason": removed.reason,
"repositry_id": repository.data.id,
},
)
self.log.warning(
"You have '%s' installed with HACS "
"this repository has been removed from HACS, please consider removing it. "
"Removal reason (%s)",
repository.data.full_name,
removed.reason,
)
else: else:
need_to_save = True need_to_save = True
repository.remove() repository.remove()

View File

@@ -6,7 +6,7 @@ from aiogithubapi.common.const import ACCEPT_HEADERS
NAME_SHORT = "HACS" NAME_SHORT = "HACS"
DOMAIN = "hacs" DOMAIN = "hacs"
CLIENT_ID = "395a8e669c5de9f7c6e8" CLIENT_ID = "395a8e669c5de9f7c6e8"
MINIMUM_HA_VERSION = "2022.4.0" MINIMUM_HA_VERSION = "2022.10.0"
TV = TypeVar("TV") TV = TypeVar("TV")

View File

@@ -8,8 +8,11 @@ from homeassistant.components.http import HomeAssistantView
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from .const import DOMAIN from .const import DOMAIN
from .hacs_frontend import locate_dir from .hacs_frontend import locate_dir, VERSION as FE_VERSION
from .hacs_frontend.version import VERSION as FE_VERSION from .hacs_frontend_experimental import (
locate_dir as experimental_locate_dir,
VERSION as EXPERIMENTAL_FE_VERSION,
)
URL_BASE = "/hacsfiles" URL_BASE = "/hacsfiles"
@@ -30,6 +33,11 @@ def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
"<HacsFrontend> Frontend development mode enabled. Do not run in production!" "<HacsFrontend> Frontend development mode enabled. Do not run in production!"
) )
hass.http.register_view(HacsFrontendDev()) hass.http.register_view(HacsFrontendDev())
elif hacs.configuration.experimental:
hacs.log.info("<HacsFrontend> Using experimental frontend")
hass.http.register_static_path(
f"{URL_BASE}/frontend", experimental_locate_dir(), cache_headers=False
)
else: else:
# #
hass.http.register_static_path(f"{URL_BASE}/frontend", locate_dir(), cache_headers=False) hass.http.register_static_path(f"{URL_BASE}/frontend", locate_dir(), cache_headers=False)
@@ -56,7 +64,9 @@ def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
cache_headers=use_cache, cache_headers=use_cache,
) )
hacs.frontend_version = FE_VERSION hacs.frontend_version = (
FE_VERSION if not hacs.configuration.experimental else EXPERIMENTAL_FE_VERSION
)
# Add to sidepanel if needed # Add to sidepanel if needed
if DOMAIN not in hass.data.get("frontend_panels", {}): if DOMAIN not in hass.data.get("frontend_panels", {}):
@@ -70,7 +80,7 @@ def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
"name": "hacs-frontend", "name": "hacs-frontend",
"embed_iframe": True, "embed_iframe": True,
"trust_external": False, "trust_external": False,
"js_url": f"/hacsfiles/frontend/entrypoint.js?hacstag={FE_VERSION}", "js_url": f"/hacsfiles/frontend/entrypoint.js?hacstag={hacs.frontend_version}",
} }
}, },
require_admin=True, require_admin=True,

View File

@@ -1,9 +1,9 @@
try { try {
new Function("import('/hacsfiles/frontend/main-7bc9a818.js')")(); new Function("import('/hacsfiles/frontend/main-ad130be7.js')")();
} catch (err) { } catch (err) {
var el = document.createElement('script'); var el = document.createElement('script');
el.src = '/hacsfiles/frontend/main-7bc9a818.js'; el.src = '/hacsfiles/frontend/main-ad130be7.js';
el.type = 'module'; el.type = 'module';
document.body.appendChild(el); document.body.appendChild(el);
} }

View File

@@ -1,3 +1,3 @@
{ {
"./src/main.ts": "main-7bc9a818.js" "./src/main.ts": "main-ad130be7.js"
} }

View File

@@ -1 +1 @@
VERSION="20220714083628" VERSION="20220906112053"

View File

@@ -8,7 +8,8 @@
"websocket_api", "websocket_api",
"frontend", "frontend",
"persistent_notification", "persistent_notification",
"lovelace" "lovelace",
"repairs"
], ],
"documentation": "https://hacs.xyz/docs/configuration/start", "documentation": "https://hacs.xyz/docs/configuration/start",
"domain": "hacs", "domain": "hacs",
@@ -18,5 +19,5 @@
"requirements": [ "requirements": [
"aiogithubapi>=22.2.4" "aiogithubapi>=22.2.4"
], ],
"version": "1.26.2" "version": "1.28.3"
} }

View File

@@ -18,7 +18,7 @@ from aiogithubapi import (
from aiogithubapi.const import BASE_API_URL from aiogithubapi.const import BASE_API_URL
from aiogithubapi.objects.repository import AIOGitHubAPIRepository from aiogithubapi.objects.repository import AIOGitHubAPIRepository
import attr import attr
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr, issue_registry as ir
from ..const import DOMAIN from ..const import DOMAIN
from ..enums import ConfigurationType, HacsDispatchEvent, RepositoryFile from ..enums import ConfigurationType, HacsDispatchEvent, RepositoryFile
@@ -734,6 +734,7 @@ class HacsRepository:
) )
await self.async_remove_entity_device() await self.async_remove_entity_device()
ir.async_delete_issue(self.hacs.hass, DOMAIN, f"removed_{self.data.id}")
async def remove_local_directory(self) -> None: async def remove_local_directory(self) -> None:
"""Check the local directory.""" """Check the local directory."""
@@ -993,7 +994,12 @@ class HacsRepository:
releases.append(release) releases.append(release)
return releases return releases
async def common_update_data(self, ignore_issues: bool = False, force: bool = False) -> None: async def common_update_data(
self,
ignore_issues: bool = False,
force: bool = False,
retry=False,
) -> None:
"""Common update data.""" """Common update data."""
releases = [] releases = []
try: try:
@@ -1072,6 +1078,20 @@ class HacsRepository:
for treefile in self.tree: for treefile in self.tree:
self.treefiles.append(treefile.full_path) self.treefiles.append(treefile.full_path)
except (AIOGitHubAPIException, HacsException) as exception: except (AIOGitHubAPIException, HacsException) as exception:
if (
not retry
and self.ref is not None
and str(exception).startswith("GitHub returned 404")
):
# Handle tags/branches being deleted.
self.data.selected_tag = None
self.ref = self.version_to_download()
self.logger.warning(
"%s Selected version/branch %s has been removed, falling back to default",
self.string,
self.ref,
)
return await self.common_update_data(ignore_issues, force, True)
if not self.hacs.status.startup and not ignore_issues: if not self.hacs.status.startup and not ignore_issues:
self.logger.error("%s %s", self.string, exception) self.logger.error("%s %s", self.string, exception)
if not ignore_issues: if not ignore_issues:

View File

@@ -3,8 +3,10 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
from homeassistant.helpers.issue_registry import async_create_issue, IssueSeverity
from homeassistant.loader import async_get_custom_components from homeassistant.loader import async_get_custom_components
from ..const import DOMAIN
from ..enums import HacsCategory, HacsDispatchEvent, HacsGitHubRepo, RepositoryFile from ..enums import HacsCategory, HacsDispatchEvent, HacsGitHubRepo, RepositoryFile
from ..exceptions import AddonRepositoryException, HacsException from ..exceptions import AddonRepositoryException, HacsException
from ..utils.decode import decode_content from ..utils.decode import decode_content
@@ -36,13 +38,27 @@ class HacsIntegrationRepository(HacsRepository):
async def async_post_installation(self): async def async_post_installation(self):
"""Run post installation steps.""" """Run post installation steps."""
self.pending_restart = True
if self.data.config_flow: if self.data.config_flow:
if self.data.full_name != HacsGitHubRepo.INTEGRATION: if self.data.full_name != HacsGitHubRepo.INTEGRATION:
await self.reload_custom_components() await self.reload_custom_components()
if self.data.first_install: if self.data.first_install:
self.pending_restart = False self.pending_restart = False
return
self.pending_restart = True if self.pending_restart and self.hacs.configuration.experimental:
self.logger.debug("%s Creating restart_required issue", self.string)
async_create_issue(
hass=self.hacs.hass,
domain=DOMAIN,
issue_id=f"restart_required_{self.data.id}_{self.ref}",
is_fixable=True,
issue_domain=self.data.domain or DOMAIN,
severity=IssueSeverity.WARNING,
translation_key="restart_required",
translation_placeholders={
"name": self.display_name,
},
)
async def validate_repository(self): async def validate_repository(self):
"""Validate.""" """Validate."""

View File

@@ -1,9 +1,13 @@
"""Sensor platform for HACS.""" """Sensor platform for HACS."""
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorEntity
from homeassistant.core import callback from homeassistant.core import callback
if TYPE_CHECKING:
from .base import HacsBase
from .const import DOMAIN from .const import DOMAIN
from .entity import HacsSystemEntity from .entity import HacsSystemEntity
from .enums import ConfigurationType from .enums import ConfigurationType
@@ -16,6 +20,10 @@ async def async_setup_platform(hass, _config, async_add_entities, _discovery_inf
async def async_setup_entry(hass, _config_entry, async_add_devices): async def async_setup_entry(hass, _config_entry, async_add_devices):
"""Setup sensor platform.""" """Setup sensor platform."""
hacs: HacsBase = hass.data.get(DOMAIN)
if hacs.configuration.experimental:
return
async_add_devices([HACSSensor(hacs=hass.data.get(DOMAIN))]) async_add_devices([HACSSensor(hacs=hass.data.get(DOMAIN))])

View File

@@ -53,5 +53,22 @@
} }
} }
} }
},
"issues": {
"restart_required": {
"title": "Restart required",
"fix_flow": {
"step": {
"confirm_restart": {
"title": "Restart required",
"description": "Restart of Home Assistant is required to finish download/update of {name}, click submit to restart now."
}
}
}
},
"removed": {
"title": "Repository removed from HACS",
"description": "{name} has been removed from HACS for {reason} visit the [HACS Panel](/hacs/repository/{repositry_id}) to remove it."
}
} }
} }

View File

@@ -3,10 +3,11 @@ import asyncio
from datetime import datetime from datetime import datetime
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util import json as json_util from homeassistant.util import json as json_util
from ..base import HacsBase from ..base import HacsBase
from ..enums import HacsDispatchEvent, HacsGitHubRepo from ..enums import HacsDisabledReason, HacsDispatchEvent, HacsGitHubRepo
from ..repositories.base import TOPIC_FILTER, HacsManifest, HacsRepository from ..repositories.base import TOPIC_FILTER, HacsManifest, HacsRepository
from .logger import LOGGER from .logger import LOGGER
from .path import is_safe from .path import is_safe
@@ -116,8 +117,21 @@ class HacsData:
async def restore(self): async def restore(self):
"""Restore saved data.""" """Restore saved data."""
self.hacs.status.new = False self.hacs.status.new = False
hacs = await async_load_from_store(self.hacs.hass, "hacs") or {} try:
repositories = await async_load_from_store(self.hacs.hass, "repositories") or {} hacs = await async_load_from_store(self.hacs.hass, "hacs") or {}
except HomeAssistantError:
hacs = {}
try:
repositories = await async_load_from_store(self.hacs.hass, "repositories") or {}
except HomeAssistantError as exception:
self.hacs.log.error(
"Could not read %s, restore the file from a backup - %s",
self.hacs.hass.config.path(".storage/hacs.repositories"),
exception,
)
self.hacs.disable_hacs(HacsDisabledReason.RESTORE)
return False
if not hacs and not repositories: if not hacs and not repositories:
# Assume new install # Assume new install

File diff suppressed because one or more lines are too long

View File

@@ -21,7 +21,7 @@ def version_left_higher_then_right(left: str, right: str) -> bool | None:
and right_version.strategy != AwesomeVersionStrategy.UNKNOWN and right_version.strategy != AwesomeVersionStrategy.UNKNOWN
): ):
return left_version > right_version return left_version > right_version
except (AwesomeVersionException, AttributeError): except (AwesomeVersionException, AttributeError, KeyError):
pass pass
return None return None

View File

@@ -26,4 +26,4 @@ class Validator(ActionValidationBase):
if [ignore for ignore in IGNORED if ignore in line]: if [ignore for ignore in IGNORED if ignore in line]:
continue continue
return return
raise ValidationException("The repository does not have issues enabled") raise ValidationException("The repository does not have images in the Readme file")

View File

@@ -9,7 +9,6 @@ from typing import TYPE_CHECKING
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from ..enums import HacsGitHubRepo
from ..repositories.base import HacsRepository from ..repositories.base import HacsRepository
from .base import ActionValidationBase from .base import ActionValidationBase