Update HACS
This commit is contained in:
@@ -160,9 +160,9 @@ class HacsCommon:
|
|||||||
|
|
||||||
categories: set[str] = field(default_factory=set)
|
categories: set[str] = field(default_factory=set)
|
||||||
renamed_repositories: dict[str, str] = field(default_factory=dict)
|
renamed_repositories: dict[str, str] = field(default_factory=dict)
|
||||||
archived_repositories: list[str] = field(default_factory=list)
|
archived_repositories: set[str] = field(default_factory=set)
|
||||||
ignored_repositories: list[str] = field(default_factory=list)
|
ignored_repositories: set[str] = field(default_factory=set)
|
||||||
skip: list[str] = field(default_factory=list)
|
skip: set[str] = field(default_factory=set)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -197,20 +197,20 @@ class HacsRepositories:
|
|||||||
"""HACS Repositories."""
|
"""HACS Repositories."""
|
||||||
|
|
||||||
_default_repositories: set[str] = field(default_factory=set)
|
_default_repositories: set[str] = field(default_factory=set)
|
||||||
_repositories: list[HacsRepository] = field(default_factory=list)
|
_repositories: set[HacsRepository] = field(default_factory=set)
|
||||||
_repositories_by_full_name: dict[str, HacsRepository] = field(default_factory=dict)
|
_repositories_by_full_name: dict[str, HacsRepository] = field(default_factory=dict)
|
||||||
_repositories_by_id: dict[str, HacsRepository] = field(default_factory=dict)
|
_repositories_by_id: dict[str, HacsRepository] = field(default_factory=dict)
|
||||||
_removed_repositories: list[RemovedRepository] = field(default_factory=list)
|
_removed_repositories_by_full_name: dict[str, RemovedRepository] = field(default_factory=dict)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def list_all(self) -> list[HacsRepository]:
|
def list_all(self) -> list[HacsRepository]:
|
||||||
"""Return a list of repositories."""
|
"""Return a list of repositories."""
|
||||||
return self._repositories
|
return list(self._repositories)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def list_removed(self) -> list[RemovedRepository]:
|
def list_removed(self) -> list[RemovedRepository]:
|
||||||
"""Return a list of removed repositories."""
|
"""Return a list of removed repositories."""
|
||||||
return self._removed_repositories
|
return list(self._removed_repositories_by_full_name.values())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def list_downloaded(self) -> list[HacsRepository]:
|
def list_downloaded(self) -> list[HacsRepository]:
|
||||||
@@ -235,7 +235,7 @@ class HacsRepositories:
|
|||||||
repository = registered_repo
|
repository = registered_repo
|
||||||
|
|
||||||
if repository not in self._repositories:
|
if repository not in self._repositories:
|
||||||
self._repositories.append(repository)
|
self._repositories.add(repository)
|
||||||
|
|
||||||
self._repositories_by_id[repo_id] = repository
|
self._repositories_by_id[repo_id] = repository
|
||||||
self._repositories_by_full_name[repository.data.full_name_lower] = repository
|
self._repositories_by_full_name[repository.data.full_name_lower] = repository
|
||||||
@@ -333,22 +333,15 @@ class HacsRepositories:
|
|||||||
|
|
||||||
def is_removed(self, repository_full_name: str) -> bool:
|
def is_removed(self, repository_full_name: str) -> bool:
|
||||||
"""Check if a repository is removed."""
|
"""Check if a repository is removed."""
|
||||||
return repository_full_name in (
|
return repository_full_name in self._removed_repositories_by_full_name
|
||||||
repository.repository for repository in self._removed_repositories
|
|
||||||
)
|
|
||||||
|
|
||||||
def removed_repository(self, repository_full_name: str) -> RemovedRepository:
|
def removed_repository(self, repository_full_name: str) -> RemovedRepository:
|
||||||
"""Get repository by full name."""
|
"""Get repository by full name."""
|
||||||
if self.is_removed(repository_full_name):
|
if removed := self._removed_repositories_by_full_name.get(repository_full_name):
|
||||||
if removed := [
|
return removed
|
||||||
repository
|
|
||||||
for repository in self._removed_repositories
|
|
||||||
if repository.repository == repository_full_name
|
|
||||||
]:
|
|
||||||
return removed[0]
|
|
||||||
|
|
||||||
removed = RemovedRepository(repository=repository_full_name)
|
removed = RemovedRepository(repository=repository_full_name)
|
||||||
self._removed_repositories.append(removed)
|
self._removed_repositories_by_full_name[repository_full_name] = removed
|
||||||
return removed
|
return removed
|
||||||
|
|
||||||
|
|
||||||
@@ -553,7 +546,12 @@ class HacsBase:
|
|||||||
raise AddonRepositoryException()
|
raise AddonRepositoryException()
|
||||||
|
|
||||||
if category not in RERPOSITORY_CLASSES:
|
if category not in RERPOSITORY_CLASSES:
|
||||||
raise HacsException(f"{category} is not a valid repository category.")
|
self.log.warning(
|
||||||
|
"%s is not a valid repository category, %s will not be registered.",
|
||||||
|
category,
|
||||||
|
repository_full_name,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
if (renamed := self.common.renamed_repositories.get(repository_full_name)) is not None:
|
if (renamed := self.common.renamed_repositories.get(repository_full_name)) is not None:
|
||||||
repository_full_name = renamed
|
repository_full_name = renamed
|
||||||
@@ -563,7 +561,7 @@ class HacsBase:
|
|||||||
try:
|
try:
|
||||||
await repository.async_registration(ref)
|
await repository.async_registration(ref)
|
||||||
if repository.validate.errors:
|
if repository.validate.errors:
|
||||||
self.common.skip.append(repository.data.full_name)
|
self.common.skip.add(repository.data.full_name)
|
||||||
if not self.status.startup:
|
if not self.status.startup:
|
||||||
self.log.error("Validation for %s failed.", repository_full_name)
|
self.log.error("Validation for %s failed.", repository_full_name)
|
||||||
if self.system.action:
|
if self.system.action:
|
||||||
@@ -582,7 +580,7 @@ class HacsBase:
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
except AIOGitHubAPIException as exception:
|
except AIOGitHubAPIException as exception:
|
||||||
self.common.skip.append(repository.data.full_name)
|
self.common.skip.add(repository.data.full_name)
|
||||||
raise HacsException(
|
raise HacsException(
|
||||||
f"Validation for {repository_full_name} failed with {exception}."
|
f"Validation for {repository_full_name} failed with {exception}."
|
||||||
) from exception
|
) from exception
|
||||||
@@ -765,6 +763,9 @@ class HacsBase:
|
|||||||
for category in (HacsCategory.INTEGRATION, HacsCategory.PLUGIN):
|
for category in (HacsCategory.INTEGRATION, HacsCategory.PLUGIN):
|
||||||
self.enable_hacs_category(HacsCategory(category))
|
self.enable_hacs_category(HacsCategory(category))
|
||||||
|
|
||||||
|
if self.configuration.experimental and self.core.ha_version >= "2023.4.0b0":
|
||||||
|
self.enable_hacs_category(HacsCategory.TEMPLATE)
|
||||||
|
|
||||||
if HacsCategory.PYTHON_SCRIPT in self.hass.config.components:
|
if HacsCategory.PYTHON_SCRIPT in self.hass.config.components:
|
||||||
self.enable_hacs_category(HacsCategory.PYTHON_SCRIPT)
|
self.enable_hacs_category(HacsCategory.PYTHON_SCRIPT)
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class HacsCategory(StrEnum):
|
|||||||
PLUGIN = "plugin" # Kept for legacy purposes
|
PLUGIN = "plugin" # Kept for legacy purposes
|
||||||
NETDAEMON = "netdaemon"
|
NETDAEMON = "netdaemon"
|
||||||
PYTHON_SCRIPT = "python_script"
|
PYTHON_SCRIPT = "python_script"
|
||||||
|
TEMPLATE = "template"
|
||||||
THEME = "theme"
|
THEME = "theme"
|
||||||
REMOVED = "removed"
|
REMOVED = "removed"
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
""""Starting setup task: Frontend"."""
|
""""Starting setup task: Frontend"."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from aiohttp import web
|
|
||||||
from homeassistant.components.http import HomeAssistantView
|
|
||||||
from homeassistant.core import HomeAssistant, callback
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
|
||||||
from .const import DOMAIN, URL_BASE
|
from .const import DOMAIN, URL_BASE
|
||||||
@@ -26,11 +25,13 @@ def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
|
|||||||
hacs.async_setup_frontend_endpoint_themes()
|
hacs.async_setup_frontend_endpoint_themes()
|
||||||
|
|
||||||
# Register frontend
|
# Register frontend
|
||||||
if hacs.configuration.frontend_repo_url:
|
if hacs.configuration.dev and (frontend_path := os.getenv("HACS_FRONTEND_DIR")):
|
||||||
hacs.log.warning(
|
hacs.log.warning(
|
||||||
"<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_static_path(
|
||||||
|
f"{URL_BASE}/frontend", f"{frontend_path}/hacs_frontend", cache_headers=False
|
||||||
|
)
|
||||||
elif hacs.configuration.experimental:
|
elif hacs.configuration.experimental:
|
||||||
hacs.log.info("<HacsFrontend> Using experimental frontend")
|
hacs.log.info("<HacsFrontend> Using experimental frontend")
|
||||||
hass.http.register_static_path(
|
hass.http.register_static_path(
|
||||||
@@ -72,23 +73,3 @@ def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
|
|||||||
|
|
||||||
# Setup plugin endpoint if needed
|
# Setup plugin endpoint if needed
|
||||||
hacs.async_setup_frontend_endpoint_plugin()
|
hacs.async_setup_frontend_endpoint_plugin()
|
||||||
|
|
||||||
|
|
||||||
class HacsFrontendDev(HomeAssistantView):
|
|
||||||
"""Dev View Class for HACS."""
|
|
||||||
|
|
||||||
requires_auth = False
|
|
||||||
name = "hacs_files:frontend"
|
|
||||||
url = r"/hacsfiles/frontend/{requested_file:.+}"
|
|
||||||
|
|
||||||
async def get(self, request, requested_file): # pylint: disable=unused-argument
|
|
||||||
"""Handle HACS Web requests."""
|
|
||||||
hacs: HacsBase = request.app["hass"].data.get(DOMAIN)
|
|
||||||
requested = requested_file.split("/")[-1]
|
|
||||||
request = await hacs.session.get(f"{hacs.configuration.frontend_repo_url}/{requested}")
|
|
||||||
if request.status == 200:
|
|
||||||
result = await request.read()
|
|
||||||
response = web.Response(body=result)
|
|
||||||
response.headers["Content-Type"] = "application/javascript"
|
|
||||||
|
|
||||||
return response
|
|
||||||
|
|||||||
@@ -19,5 +19,5 @@
|
|||||||
"requirements": [
|
"requirements": [
|
||||||
"aiogithubapi>=22.10.1"
|
"aiogithubapi>=22.10.1"
|
||||||
],
|
],
|
||||||
"version": "1.31.0"
|
"version": "1.32.1"
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ from .integration import HacsIntegrationRepository
|
|||||||
from .netdaemon import HacsNetdaemonRepository
|
from .netdaemon import HacsNetdaemonRepository
|
||||||
from .plugin import HacsPluginRepository
|
from .plugin import HacsPluginRepository
|
||||||
from .python_script import HacsPythonScriptRepository
|
from .python_script import HacsPythonScriptRepository
|
||||||
|
from .template import HacsTemplateRepository
|
||||||
from .theme import HacsThemeRepository
|
from .theme import HacsThemeRepository
|
||||||
|
|
||||||
RERPOSITORY_CLASSES: dict[HacsCategory, HacsRepository] = {
|
RERPOSITORY_CLASSES: dict[HacsCategory, HacsRepository] = {
|
||||||
@@ -17,4 +18,5 @@ RERPOSITORY_CLASSES: dict[HacsCategory, HacsRepository] = {
|
|||||||
HacsCategory.APPDAEMON: HacsAppdaemonRepository,
|
HacsCategory.APPDAEMON: HacsAppdaemonRepository,
|
||||||
HacsCategory.NETDAEMON: HacsNetdaemonRepository,
|
HacsCategory.NETDAEMON: HacsNetdaemonRepository,
|
||||||
HacsCategory.PLUGIN: HacsPluginRepository,
|
HacsCategory.PLUGIN: HacsPluginRepository,
|
||||||
|
HacsCategory.TEMPLATE: HacsTemplateRepository,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,8 @@ TOPIC_FILTER = (
|
|||||||
"sensor",
|
"sensor",
|
||||||
"smart-home",
|
"smart-home",
|
||||||
"smarthome",
|
"smarthome",
|
||||||
|
"template",
|
||||||
|
"templates",
|
||||||
"theme",
|
"theme",
|
||||||
"themes",
|
"themes",
|
||||||
)
|
)
|
||||||
@@ -772,6 +774,8 @@ class HacsRepository:
|
|||||||
await self.hacs.hass.services.async_call("frontend", "reload_themes", {})
|
await self.hacs.hass.services.async_call("frontend", "reload_themes", {})
|
||||||
except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||||
pass
|
pass
|
||||||
|
elif self.data.category == "template":
|
||||||
|
await self.hacs.hass.services.async_call("homeassistant", "reload_custom_templates", {})
|
||||||
|
|
||||||
await async_remove_store(self.hacs.hass, f"hacs/{self.data.id}.hacs")
|
await async_remove_store(self.hacs.hass, f"hacs/{self.data.id}.hacs")
|
||||||
|
|
||||||
@@ -796,6 +800,8 @@ class HacsRepository:
|
|||||||
try:
|
try:
|
||||||
if self.data.category == "python_script":
|
if self.data.category == "python_script":
|
||||||
local_path = f"{self.content.path.local}/{self.data.name}.py"
|
local_path = f"{self.content.path.local}/{self.data.name}.py"
|
||||||
|
elif self.data.category == "template":
|
||||||
|
local_path = f"{self.content.path.local}/{self.data.file_name}"
|
||||||
elif self.data.category == "theme":
|
elif self.data.category == "theme":
|
||||||
path = (
|
path = (
|
||||||
f"{self.hacs.core.config_path}/"
|
f"{self.hacs.core.config_path}/"
|
||||||
@@ -823,7 +829,7 @@ class HacsRepository:
|
|||||||
return False
|
return False
|
||||||
self.logger.debug("%s Removing %s", self.string, local_path)
|
self.logger.debug("%s Removing %s", self.string, local_path)
|
||||||
|
|
||||||
if self.data.category in ["python_script"]:
|
if self.data.category in ["python_script", "template"]:
|
||||||
os.remove(local_path)
|
os.remove(local_path)
|
||||||
else:
|
else:
|
||||||
shutil.rmtree(local_path)
|
shutil.rmtree(local_path)
|
||||||
@@ -1093,7 +1099,7 @@ class HacsRepository:
|
|||||||
if self.data.archived and not ignore_issues:
|
if self.data.archived and not ignore_issues:
|
||||||
self.validate.errors.append("Repository is archived.")
|
self.validate.errors.append("Repository is archived.")
|
||||||
if self.data.full_name not in self.hacs.common.archived_repositories:
|
if self.data.full_name not in self.hacs.common.archived_repositories:
|
||||||
self.hacs.common.archived_repositories.append(self.data.full_name)
|
self.hacs.common.archived_repositories.add(self.data.full_name)
|
||||||
raise HacsRepositoryArchivedException(f"{self} Repository is archived.")
|
raise HacsRepositoryArchivedException(f"{self} Repository is archived.")
|
||||||
|
|
||||||
# Make sure the repository is not in the blacklist.
|
# Make sure the repository is not in the blacklist.
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
},
|
},
|
||||||
"removed": {
|
"removed": {
|
||||||
"title": "Repository removed from HACS",
|
"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."
|
"description": "Because {reason}, '{name}' has been removed from HACS. Please visit the [HACS Panel](/hacs/repository/{repositry_id}) to remove it."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,8 +207,8 @@ class HacsData:
|
|||||||
self.logger.info("<HacsData restore> Restore started")
|
self.logger.info("<HacsData restore> Restore started")
|
||||||
|
|
||||||
# Hacs
|
# Hacs
|
||||||
self.hacs.common.archived_repositories = []
|
self.hacs.common.archived_repositories = set()
|
||||||
self.hacs.common.ignored_repositories = []
|
self.hacs.common.ignored_repositories = set()
|
||||||
self.hacs.common.renamed_repositories = {}
|
self.hacs.common.renamed_repositories = {}
|
||||||
|
|
||||||
# Clear out doubble renamed values
|
# Clear out doubble renamed values
|
||||||
@@ -219,14 +219,14 @@ class HacsData:
|
|||||||
self.hacs.common.renamed_repositories[entry] = value
|
self.hacs.common.renamed_repositories[entry] = value
|
||||||
|
|
||||||
# Clear out doubble archived values
|
# Clear out doubble archived values
|
||||||
for entry in hacs.get("archived_repositories", []):
|
for entry in hacs.get("archived_repositories", set()):
|
||||||
if entry not in self.hacs.common.archived_repositories:
|
if entry not in self.hacs.common.archived_repositories:
|
||||||
self.hacs.common.archived_repositories.append(entry)
|
self.hacs.common.archived_repositories.add(entry)
|
||||||
|
|
||||||
# Clear out doubble ignored values
|
# Clear out doubble ignored values
|
||||||
for entry in hacs.get("ignored_repositories", []):
|
for entry in hacs.get("ignored_repositories", set()):
|
||||||
if entry not in self.hacs.common.ignored_repositories:
|
if entry not in self.hacs.common.ignored_repositories:
|
||||||
self.hacs.common.ignored_repositories.append(entry)
|
self.hacs.common.ignored_repositories.add(entry)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.register_unknown_repositories(repositories)
|
await self.register_unknown_repositories(repositories)
|
||||||
|
|||||||
@@ -17,4 +17,5 @@ def is_safe(hacs: HacsBase, path: str | Path) -> bool:
|
|||||||
Path(f"{hacs.core.config_path}/{hacs.configuration.python_script_path}").as_posix(),
|
Path(f"{hacs.core.config_path}/{hacs.configuration.python_script_path}").as_posix(),
|
||||||
Path(f"{hacs.core.config_path}/{hacs.configuration.theme_path}").as_posix(),
|
Path(f"{hacs.core.config_path}/{hacs.configuration.theme_path}").as_posix(),
|
||||||
Path(f"{hacs.core.config_path}/custom_components/").as_posix(),
|
Path(f"{hacs.core.config_path}/custom_components/").as_posix(),
|
||||||
|
Path(f"{hacs.core.config_path}/custom_templates/").as_posix(),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user