Add HACS, Themes
This commit is contained in:
1
custom_components/hacs/tasks/__init__.py
Normal file
1
custom_components/hacs/tasks/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""Init HACS tasks."""
|
||||
36
custom_components/hacs/tasks/activate_categories.py
Normal file
36
custom_components/hacs/tasks/activate_categories.py
Normal file
@@ -0,0 +1,36 @@
|
||||
"""Starting setup task: extra stores."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..enums import HacsCategory, HacsStage
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Set up extra stores in HACS if enabled in Home Assistant."""
|
||||
|
||||
stages = [HacsStage.SETUP]
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
self.hacs.common.categories = set()
|
||||
for category in (HacsCategory.INTEGRATION, HacsCategory.PLUGIN):
|
||||
self.hacs.enable_hacs_category(HacsCategory(category))
|
||||
|
||||
if HacsCategory.PYTHON_SCRIPT in self.hacs.hass.config.components:
|
||||
self.hacs.enable_hacs_category(HacsCategory.PYTHON_SCRIPT)
|
||||
|
||||
if self.hacs.hass.services.has_service("frontend", "reload_themes"):
|
||||
self.hacs.enable_hacs_category(HacsCategory.THEME)
|
||||
|
||||
if self.hacs.configuration.appdaemon:
|
||||
self.hacs.enable_hacs_category(HacsCategory.APPDAEMON)
|
||||
if self.hacs.configuration.netdaemon:
|
||||
self.hacs.enable_hacs_category(HacsCategory.NETDAEMON)
|
||||
59
custom_components/hacs/tasks/base.py
Normal file
59
custom_components/hacs/tasks/base.py
Normal file
@@ -0,0 +1,59 @@
|
||||
""""Hacs base setup task."""
|
||||
# pylint: disable=abstract-method
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
from logging import Handler
|
||||
from time import monotonic
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..enums import HacsStage
|
||||
|
||||
|
||||
class HacsTask:
|
||||
"""Hacs task base."""
|
||||
|
||||
events: list[str] | None = None
|
||||
schedule: timedelta | None = None
|
||||
stages: list[HacsStage] | None = None
|
||||
_can_run_disabled = False ## Set to True if task can run while disabled
|
||||
|
||||
def __init__(self, hacs: HacsBase, hass: HomeAssistant) -> None:
|
||||
self.hacs = hacs
|
||||
self.hass = hass
|
||||
|
||||
@property
|
||||
def slug(self) -> str:
|
||||
"""Return the check slug."""
|
||||
return self.__class__.__module__.rsplit(".", maxsplit=1)[-1]
|
||||
|
||||
def task_logger(self, handler: Handler, msg: str) -> None:
|
||||
"""Log message from task"""
|
||||
handler("<HacsTask %s> %s", self.slug, msg)
|
||||
|
||||
async def execute_task(self, *_, **__) -> None:
|
||||
"""Execute the task defined in subclass."""
|
||||
if not self._can_run_disabled and self.hacs.system.disabled:
|
||||
self.task_logger(
|
||||
self.hacs.log.debug,
|
||||
f"Skipping task, HACS is disabled {self.hacs.system.disabled_reason}",
|
||||
)
|
||||
return
|
||||
self.task_logger(self.hacs.log.debug, "Executing task")
|
||||
start_time = monotonic()
|
||||
|
||||
try:
|
||||
if task := getattr(self, "async_execute", None):
|
||||
await task() # pylint: disable=not-callable
|
||||
elif task := getattr(self, "execute", None):
|
||||
await self.hass.async_add_executor_job(task)
|
||||
|
||||
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
self.task_logger(self.hacs.log.error, f"failed: {exception}")
|
||||
|
||||
else:
|
||||
self.hacs.log.debug(
|
||||
"<HacsTask %s> took %.3f seconds to complete", self.slug, monotonic() - start_time
|
||||
)
|
||||
48
custom_components/hacs/tasks/check_constrains.py
Normal file
48
custom_components/hacs/tasks/check_constrains.py
Normal file
@@ -0,0 +1,48 @@
|
||||
""""Starting setup task: Constrains"."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..const import MINIMUM_HA_VERSION
|
||||
from ..enums import HacsDisabledReason, HacsStage
|
||||
from ..utils.version import version_left_higher_or_equal_then_right
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Check env Constrains."""
|
||||
|
||||
stages = [HacsStage.SETUP]
|
||||
|
||||
def execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
for location in (
|
||||
self.hass.config.path("custom_components/custom_updater.py"),
|
||||
self.hass.config.path("custom_components/custom_updater/__init__.py"),
|
||||
):
|
||||
if os.path.exists(location):
|
||||
self.task_logger(
|
||||
self.hacs.log.critical,
|
||||
"This cannot be used with custom_updater. "
|
||||
f"To use this you need to remove custom_updater form {location}",
|
||||
)
|
||||
|
||||
self.hacs.disable_hacs(HacsDisabledReason.CONSTRAINS)
|
||||
|
||||
if not version_left_higher_or_equal_then_right(
|
||||
self.hacs.core.ha_version.string,
|
||||
MINIMUM_HA_VERSION,
|
||||
):
|
||||
self.task_logger(
|
||||
self.hacs.log.critical,
|
||||
f"You need HA version {MINIMUM_HA_VERSION} or newer to use this integration.",
|
||||
)
|
||||
self.hacs.disable_hacs(HacsDisabledReason.CONSTRAINS)
|
||||
36
custom_components/hacs/tasks/check_ratelimit.py
Normal file
36
custom_components/hacs/tasks/check_ratelimit.py
Normal file
@@ -0,0 +1,36 @@
|
||||
""""Hacs base setup task."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..enums import HacsDisabledReason
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
""" "Hacs task base."""
|
||||
|
||||
_can_run_disabled = True
|
||||
schedule = timedelta(minutes=5)
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
if (
|
||||
not self.hacs.system.disabled
|
||||
or self.hacs.system.disabled_reason != HacsDisabledReason.RATE_LIMIT
|
||||
):
|
||||
return
|
||||
|
||||
self.task_logger(self.hacs.log.debug, "Checking if ratelimit has lifted")
|
||||
can_update = await self.hacs.async_can_update()
|
||||
self.task_logger(self.hacs.log.debug, f"Ratelimit indicate we can update {can_update}")
|
||||
if can_update > 0:
|
||||
self.hacs.enable_hacs()
|
||||
29
custom_components/hacs/tasks/clear_old_storage.py
Normal file
29
custom_components/hacs/tasks/clear_old_storage.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""Starting setup task: clear storage."""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..enums import HacsStage
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Clear old files from storage."""
|
||||
|
||||
stages = [HacsStage.SETUP]
|
||||
|
||||
def execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
for storage_file in ("hacs",):
|
||||
path = f"{self.hacs.core.config_path}/.storage/{storage_file}"
|
||||
if os.path.isfile(path):
|
||||
self.task_logger(self.hacs.log.info, f"Cleaning up old storage file: {path}")
|
||||
os.remove(path)
|
||||
35
custom_components/hacs/tasks/handle_critical_notification.py
Normal file
35
custom_components/hacs/tasks/handle_critical_notification.py
Normal file
@@ -0,0 +1,35 @@
|
||||
""""Hacs base setup task."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..enums import HacsStage
|
||||
from ..utils.store import async_load_from_store
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Hacs notify critical during startup task."""
|
||||
|
||||
stages = [HacsStage.STARTUP]
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
alert = False
|
||||
critical = await async_load_from_store(self.hass, "critical")
|
||||
if not critical:
|
||||
return
|
||||
for repo in critical:
|
||||
if not repo["acknowledged"]:
|
||||
alert = True
|
||||
if alert:
|
||||
self.hacs.log.critical("URGENT!: Check the HACS panel!")
|
||||
self.hass.components.persistent_notification.create(
|
||||
title="URGENT!", message="**Check the HACS panel!**"
|
||||
)
|
||||
51
custom_components/hacs/tasks/load_hacs_repository.py
Normal file
51
custom_components/hacs/tasks/load_hacs_repository.py
Normal file
@@ -0,0 +1,51 @@
|
||||
"""Starting setup task: load HACS repository."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..enums import HacsCategory, HacsDisabledReason, HacsGitHubRepo, HacsStage
|
||||
from ..exceptions import HacsException
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Load HACS repositroy."""
|
||||
|
||||
stages = [HacsStage.STARTUP]
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
try:
|
||||
repository = self.hacs.repositories.get_by_full_name(HacsGitHubRepo.INTEGRATION)
|
||||
if repository is None:
|
||||
await self.hacs.async_register_repository(
|
||||
repository_full_name=HacsGitHubRepo.INTEGRATION,
|
||||
category=HacsCategory.INTEGRATION,
|
||||
default=True,
|
||||
)
|
||||
repository = self.hacs.repositories.get_by_full_name(HacsGitHubRepo.INTEGRATION)
|
||||
if repository is None:
|
||||
raise HacsException("Unknown error")
|
||||
|
||||
repository.data.installed = True
|
||||
repository.data.installed_version = self.hacs.integration.version
|
||||
repository.data.new = False
|
||||
repository.data.releases = True
|
||||
|
||||
self.hacs.repository = repository.repository_object
|
||||
self.hacs.repositories.mark_default(repository)
|
||||
except HacsException as exception:
|
||||
if "403" in f"{exception}":
|
||||
self.task_logger(
|
||||
self.hacs.log.critical,
|
||||
"GitHub API is ratelimited, or the token is wrong.",
|
||||
)
|
||||
else:
|
||||
self.task_logger(self.hacs.log.critical, f"[{exception}] - Could not load HACS!")
|
||||
self.hacs.disable_hacs(HacsDisabledReason.LOAD_HACS)
|
||||
74
custom_components/hacs/tasks/manager.py
Normal file
74
custom_components/hacs/tasks/manager.py
Normal file
@@ -0,0 +1,74 @@
|
||||
"""Hacs task manager."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from importlib import import_module
|
||||
from pathlib import Path
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
class HacsTaskManager:
|
||||
"""Hacs task manager."""
|
||||
|
||||
def __init__(self, hacs: HacsBase, hass: HomeAssistant) -> None:
|
||||
"""Initialize the setup manager class."""
|
||||
self.hacs = hacs
|
||||
self.hass = hass
|
||||
self.__tasks: dict[str, HacsTask] = {}
|
||||
|
||||
@property
|
||||
def tasks(self) -> list[HacsTask]:
|
||||
"""Return all list of all tasks."""
|
||||
return list(self.__tasks.values())
|
||||
|
||||
async def async_load(self) -> None:
|
||||
"""Load all tasks."""
|
||||
task_files = Path(__file__).parent
|
||||
task_modules = (
|
||||
module.stem
|
||||
for module in task_files.glob("*.py")
|
||||
if module.name not in ("base.py", "__init__.py", "manager.py")
|
||||
)
|
||||
|
||||
async def _load_module(module: str):
|
||||
task_module = import_module(f"{__package__}.{module}")
|
||||
if task := await task_module.async_setup_task(hacs=self.hacs, hass=self.hass):
|
||||
self.__tasks[task.slug] = task
|
||||
|
||||
await asyncio.gather(*[_load_module(task) for task in task_modules])
|
||||
self.hacs.log.info("Loaded %s tasks", len(self.tasks))
|
||||
|
||||
schedule_tasks = len(self.hacs.recuring_tasks) == 0
|
||||
|
||||
for task in self.tasks:
|
||||
if task.events is not None:
|
||||
for event in task.events:
|
||||
self.hass.bus.async_listen_once(event, task.execute_task)
|
||||
|
||||
if task.schedule is not None and schedule_tasks:
|
||||
self.hacs.log.debug(
|
||||
"Scheduling <HacsTask %s> to run every %s", task.slug, task.schedule
|
||||
)
|
||||
self.hacs.recuring_tasks.append(
|
||||
self.hacs.hass.helpers.event.async_track_time_interval(
|
||||
task.execute_task, task.schedule
|
||||
)
|
||||
)
|
||||
|
||||
def get(self, slug: str) -> HacsTask | None:
|
||||
"""Return a task."""
|
||||
return self.__tasks.get(slug)
|
||||
|
||||
async def async_execute_runtume_tasks(self) -> None:
|
||||
"""Execute the the execute methods of each runtime task if the stage matches."""
|
||||
await asyncio.gather(
|
||||
*(
|
||||
task.execute_task()
|
||||
for task in self.tasks
|
||||
if task.stages is not None and self.hacs.stage in task.stages
|
||||
)
|
||||
)
|
||||
49
custom_components/hacs/tasks/prosess_queue.py
Normal file
49
custom_components/hacs/tasks/prosess_queue.py
Normal file
@@ -0,0 +1,49 @@
|
||||
""""Hacs base setup task."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..exceptions import HacsExecutionStillInProgress
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
""" "Hacs task base."""
|
||||
|
||||
schedule = timedelta(minutes=10)
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
if not self.hacs.queue.has_pending_tasks:
|
||||
self.task_logger(self.hacs.log.debug, "Nothing in the queue")
|
||||
return
|
||||
if self.hacs.queue.running:
|
||||
self.task_logger(self.hacs.log.debug, "Queue is already running")
|
||||
return
|
||||
|
||||
async def _handle_queue():
|
||||
if not self.hacs.queue.has_pending_tasks:
|
||||
return
|
||||
can_update = await self.hacs.async_can_update()
|
||||
self.task_logger(
|
||||
self.hacs.log.debug,
|
||||
f"Can update {can_update} repositories, "
|
||||
f"items in queue {self.hacs.queue.pending_tasks}",
|
||||
)
|
||||
if can_update != 0:
|
||||
try:
|
||||
await self.hacs.queue.execute(can_update)
|
||||
except HacsExecutionStillInProgress:
|
||||
return
|
||||
|
||||
await _handle_queue()
|
||||
|
||||
await _handle_queue()
|
||||
24
custom_components/hacs/tasks/restore_data.py
Normal file
24
custom_components/hacs/tasks/restore_data.py
Normal file
@@ -0,0 +1,24 @@
|
||||
""""Starting setup task: Restore"."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..enums import HacsDisabledReason, HacsStage
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Restore HACS data."""
|
||||
|
||||
stages = [HacsStage.SETUP]
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
if not await self.hacs.data.restore():
|
||||
self.hacs.disable_hacs(HacsDisabledReason.RESTORE)
|
||||
106
custom_components/hacs/tasks/setup_frontend.py
Normal file
106
custom_components/hacs/tasks/setup_frontend.py
Normal file
@@ -0,0 +1,106 @@
|
||||
""""Starting setup task: Frontend"."""
|
||||
from __future__ import annotations
|
||||
|
||||
from aiohttp import web
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..const import DOMAIN
|
||||
from ..enums import HacsStage
|
||||
from ..hacs_frontend import locate_dir
|
||||
from ..hacs_frontend.version import VERSION as FE_VERSION
|
||||
from .base import HacsTask
|
||||
|
||||
URL_BASE = "/hacsfiles"
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Setup the HACS frontend."""
|
||||
|
||||
stages = [HacsStage.SETUP]
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
|
||||
# Register themes
|
||||
self.hass.http.register_static_path(f"{URL_BASE}/themes", self.hass.config.path("themes"))
|
||||
|
||||
# Register frontend
|
||||
if self.hacs.configuration.frontend_repo_url:
|
||||
self.task_logger(
|
||||
self.hacs.log.warning,
|
||||
"Frontend development mode enabled. Do not run in production!",
|
||||
)
|
||||
self.hass.http.register_view(HacsFrontendDev())
|
||||
else:
|
||||
#
|
||||
self.hass.http.register_static_path(
|
||||
f"{URL_BASE}/frontend", locate_dir(), cache_headers=False
|
||||
)
|
||||
|
||||
# Custom iconset
|
||||
self.hass.http.register_static_path(
|
||||
f"{URL_BASE}/iconset.js", str(self.hacs.integration_dir / "iconset.js")
|
||||
)
|
||||
if "frontend_extra_module_url" not in self.hass.data:
|
||||
self.hass.data["frontend_extra_module_url"] = set()
|
||||
self.hass.data["frontend_extra_module_url"].add(f"{URL_BASE}/iconset.js")
|
||||
|
||||
# Register www/community for all other files
|
||||
use_cache = self.hacs.core.lovelace_mode == "storage"
|
||||
self.task_logger(
|
||||
self.hacs.log.info,
|
||||
f"{self.hacs.core.lovelace_mode} mode, cache for /hacsfiles/: {use_cache}",
|
||||
)
|
||||
|
||||
self.hass.http.register_static_path(
|
||||
URL_BASE,
|
||||
self.hass.config.path("www/community"),
|
||||
cache_headers=use_cache,
|
||||
)
|
||||
|
||||
self.hacs.frontend_version = FE_VERSION
|
||||
|
||||
# Add to sidepanel if needed
|
||||
if DOMAIN not in self.hass.data.get("frontend_panels", {}):
|
||||
self.hass.components.frontend.async_register_built_in_panel(
|
||||
component_name="custom",
|
||||
sidebar_title=self.hacs.configuration.sidepanel_title,
|
||||
sidebar_icon=self.hacs.configuration.sidepanel_icon,
|
||||
frontend_url_path=DOMAIN,
|
||||
config={
|
||||
"_panel_custom": {
|
||||
"name": "hacs-frontend",
|
||||
"embed_iframe": True,
|
||||
"trust_external": False,
|
||||
"js_url": f"/hacsfiles/frontend/entrypoint.js?hacstag={FE_VERSION}",
|
||||
}
|
||||
},
|
||||
require_admin=True,
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
36
custom_components/hacs/tasks/setup_sensor_platform.py
Normal file
36
custom_components/hacs/tasks/setup_sensor_platform.py
Normal file
@@ -0,0 +1,36 @@
|
||||
""""Starting setup task: Sensor"."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.discovery import async_load_platform
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..const import DOMAIN
|
||||
from ..enums import ConfigurationType, HacsStage
|
||||
from .base import HacsTask
|
||||
|
||||
SENSOR_DOMAIN = "sensor"
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Setup the HACS sensor platform."""
|
||||
|
||||
stages = [HacsStage.SETUP]
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
if self.hacs.configuration.config_type == ConfigurationType.YAML:
|
||||
self.hass.async_create_task(
|
||||
async_load_platform(
|
||||
self.hass, SENSOR_DOMAIN, DOMAIN, {}, self.hacs.configuration.config
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.hass.config_entries.async_setup_platforms(
|
||||
self.hacs.configuration.config_entry, [SENSOR_DOMAIN]
|
||||
)
|
||||
32
custom_components/hacs/tasks/setup_update_platform.py
Normal file
32
custom_components/hacs/tasks/setup_update_platform.py
Normal file
@@ -0,0 +1,32 @@
|
||||
""""Starting setup task: Update"."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..enums import ConfigurationType, HacsStage
|
||||
from .base import HacsTask
|
||||
|
||||
UPDATE_DOMAIN = "update"
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Setup the HACS update platform."""
|
||||
|
||||
stages = [HacsStage.RUNNING]
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
if self.hacs.configuration.config_type == ConfigurationType.YAML:
|
||||
self.task_logger(
|
||||
self.hacs.log.info, "Update entities are only supported when using UI configuration"
|
||||
)
|
||||
elif self.hacs.core.ha_version >= "2022.4.0.dev0" and self.hacs.configuration.experimental:
|
||||
self.hass.config_entries.async_setup_platforms(
|
||||
self.hacs.configuration.config_entry, [UPDATE_DOMAIN]
|
||||
)
|
||||
490
custom_components/hacs/tasks/setup_websocket_api.py
Normal file
490
custom_components/hacs/tasks/setup_websocket_api.py
Normal file
@@ -0,0 +1,490 @@
|
||||
"""Register WS API endpoints for HACS."""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
|
||||
from aiogithubapi import AIOGitHubAPIException
|
||||
from homeassistant.components import websocket_api
|
||||
from homeassistant.components.websocket_api import async_register_command
|
||||
from homeassistant.core import HomeAssistant
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
import voluptuous as vol
|
||||
|
||||
from custom_components.hacs.const import DOMAIN
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..enums import HacsStage
|
||||
from ..exceptions import HacsException
|
||||
from ..utils import regex
|
||||
from ..utils.store import async_load_from_store, async_save_to_store
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Setup the HACS websocket API."""
|
||||
|
||||
stages = [HacsStage.SETUP]
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
async_register_command(self.hass, hacs_settings)
|
||||
async_register_command(self.hass, hacs_config)
|
||||
async_register_command(self.hass, hacs_repositories)
|
||||
async_register_command(self.hass, hacs_repository)
|
||||
async_register_command(self.hass, hacs_repository_data)
|
||||
async_register_command(self.hass, hacs_status)
|
||||
async_register_command(self.hass, hacs_removed)
|
||||
async_register_command(self.hass, acknowledge_critical_repository)
|
||||
async_register_command(self.hass, get_critical_repositories)
|
||||
async_register_command(self.hass, hacs_repository_ignore)
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "hacs/critical",
|
||||
vol.Optional("repository"): cv.string,
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def acknowledge_critical_repository(hass, connection, msg):
|
||||
"""Handle get media player cover command."""
|
||||
repository = msg["repository"]
|
||||
|
||||
critical = await async_load_from_store(hass, "critical")
|
||||
for repo in critical:
|
||||
if repository == repo["repository"]:
|
||||
repo["acknowledged"] = True
|
||||
await async_save_to_store(hass, "critical", critical)
|
||||
connection.send_message(websocket_api.result_message(msg["id"], critical))
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "hacs/get_critical",
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def get_critical_repositories(hass, connection, msg):
|
||||
"""Handle get media player cover command."""
|
||||
critical = await async_load_from_store(hass, "critical")
|
||||
if not critical:
|
||||
critical = []
|
||||
connection.send_message(websocket_api.result_message(msg["id"], critical))
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "hacs/config",
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def hacs_config(hass, connection, msg):
|
||||
"""Handle get media player cover command."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
connection.send_message(
|
||||
websocket_api.result_message(
|
||||
msg["id"],
|
||||
{
|
||||
"frontend_mode": hacs.configuration.frontend_mode,
|
||||
"frontend_compact": hacs.configuration.frontend_compact,
|
||||
"onboarding_done": hacs.configuration.onboarding_done,
|
||||
"version": hacs.version,
|
||||
"frontend_expected": hacs.frontend_version,
|
||||
"frontend_running": hacs.frontend_version,
|
||||
"dev": hacs.configuration.dev,
|
||||
"debug": hacs.configuration.debug,
|
||||
"country": hacs.configuration.country,
|
||||
"experimental": hacs.configuration.experimental,
|
||||
"categories": hacs.common.categories,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "hacs/removed",
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def hacs_removed(hass, connection, msg):
|
||||
"""Get information about removed repositories."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
content = []
|
||||
for repo in hacs.repositories.list_removed:
|
||||
if repo.repository not in hacs.common.ignored_repositories:
|
||||
content.append(repo.to_json())
|
||||
connection.send_message(websocket_api.result_message(msg["id"], content))
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "hacs/repositories",
|
||||
vol.Optional("categories"): [str],
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def hacs_repositories(hass, connection, msg):
|
||||
"""Handle get media player cover command."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
connection.send_message(
|
||||
websocket_api.result_message(
|
||||
msg["id"],
|
||||
[
|
||||
{
|
||||
"additional_info": repo.additional_info,
|
||||
"authors": repo.data.authors,
|
||||
"available_version": repo.display_available_version,
|
||||
"beta": repo.data.show_beta,
|
||||
"can_install": repo.can_download,
|
||||
"category": repo.data.category,
|
||||
"config_flow": repo.data.config_flow,
|
||||
"country": repo.data.country,
|
||||
"custom": not hacs.repositories.is_default(str(repo.data.id)),
|
||||
"default_branch": repo.data.default_branch,
|
||||
"description": repo.data.description,
|
||||
"domain": repo.data.domain,
|
||||
"downloads": repo.data.downloads,
|
||||
"file_name": repo.data.file_name,
|
||||
"first_install": repo.status.first_install,
|
||||
"full_name": repo.data.full_name,
|
||||
"hide_default_branch": repo.data.hide_default_branch,
|
||||
"hide": repo.data.hide,
|
||||
"homeassistant": repo.data.homeassistant,
|
||||
"id": repo.data.id,
|
||||
"info": None,
|
||||
"installed_version": repo.display_installed_version,
|
||||
"installed": repo.data.installed,
|
||||
"issues": repo.data.open_issues,
|
||||
"javascript_type": None,
|
||||
"last_updated": repo.data.last_updated,
|
||||
"local_path": repo.content.path.local,
|
||||
"main_action": repo.main_action,
|
||||
"name": repo.display_name,
|
||||
"new": repo.data.new,
|
||||
"pending_upgrade": repo.pending_update,
|
||||
"releases": repo.data.published_tags,
|
||||
"selected_tag": repo.data.selected_tag,
|
||||
"stars": repo.data.stargazers_count,
|
||||
"state": repo.state,
|
||||
"status_description": repo.display_status_description,
|
||||
"status": repo.display_status,
|
||||
"topics": repo.data.topics,
|
||||
"updated_info": repo.status.updated_info,
|
||||
"version_or_commit": repo.display_version_or_commit,
|
||||
}
|
||||
for repo in hacs.repositories.list_all
|
||||
if repo.data.category in (msg.get("categories") or hacs.common.categories)
|
||||
and not repo.ignored_by_country_configuration
|
||||
],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "hacs/repository/data",
|
||||
vol.Optional("action"): cv.string,
|
||||
vol.Optional("repository"): cv.string,
|
||||
vol.Optional("data"): cv.string,
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def hacs_repository_data(hass, connection, msg):
|
||||
"""Handle get media player cover command."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
repo_id = msg.get("repository")
|
||||
action = msg.get("action")
|
||||
data = msg.get("data")
|
||||
|
||||
if repo_id is None:
|
||||
return
|
||||
|
||||
if action == "add":
|
||||
repo_id = regex.extract_repository_from_url(repo_id)
|
||||
if repo_id is None:
|
||||
return
|
||||
|
||||
if repo_id in hacs.common.skip:
|
||||
hacs.common.skip.remove(repo_id)
|
||||
|
||||
if hacs.common.renamed_repositories.get(repo_id):
|
||||
repo_id = hacs.common.renamed_repositories[repo_id]
|
||||
|
||||
if not hacs.repositories.get_by_full_name(repo_id):
|
||||
try:
|
||||
registration = await hacs.async_register_repository(
|
||||
repository_full_name=repo_id, category=data.lower()
|
||||
)
|
||||
if registration is not None:
|
||||
raise HacsException(registration)
|
||||
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
hass.bus.async_fire(
|
||||
"hacs/error",
|
||||
{
|
||||
"action": "add_repository",
|
||||
"exception": str(sys.exc_info()[0].__name__),
|
||||
"message": str(exception),
|
||||
},
|
||||
)
|
||||
else:
|
||||
hass.bus.async_fire(
|
||||
"hacs/error",
|
||||
{
|
||||
"action": "add_repository",
|
||||
"message": f"Repository '{repo_id}' exists in the store.",
|
||||
},
|
||||
)
|
||||
|
||||
repository = hacs.repositories.get_by_full_name(repo_id)
|
||||
else:
|
||||
repository = hacs.repositories.get_by_id(repo_id)
|
||||
|
||||
if repository is None:
|
||||
hass.bus.async_fire("hacs/repository", {})
|
||||
return
|
||||
|
||||
hacs.log.debug("Running %s for %s", action, repository.data.full_name)
|
||||
try:
|
||||
if action == "set_state":
|
||||
repository.state = data
|
||||
|
||||
elif action == "set_version":
|
||||
repository.data.selected_tag = data
|
||||
await repository.update_repository(force=True)
|
||||
|
||||
repository.state = None
|
||||
|
||||
elif action == "install":
|
||||
was_installed = repository.data.installed
|
||||
repository.data.selected_tag = data
|
||||
await repository.update_repository(force=True)
|
||||
await repository.async_install()
|
||||
repository.state = None
|
||||
if not was_installed:
|
||||
hass.bus.async_fire("hacs/reload", {"force": True})
|
||||
await hacs.async_recreate_entities()
|
||||
|
||||
elif action == "add":
|
||||
repository.state = None
|
||||
|
||||
else:
|
||||
repository.state = None
|
||||
hacs.log.error("WS action '%s' is not valid", action)
|
||||
|
||||
message = None
|
||||
except AIOGitHubAPIException as exception:
|
||||
message = exception
|
||||
except AttributeError as exception:
|
||||
message = f"Could not use repository with ID {repo_id} ({exception})"
|
||||
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
message = exception
|
||||
|
||||
if message is not None:
|
||||
hacs.log.error(message)
|
||||
hass.bus.async_fire("hacs/error", {"message": str(message)})
|
||||
|
||||
await hacs.data.async_write()
|
||||
connection.send_message(websocket_api.result_message(msg["id"], {}))
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "hacs/repository",
|
||||
vol.Optional("action"): cv.string,
|
||||
vol.Optional("repository"): cv.string,
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def hacs_repository(hass, connection, msg):
|
||||
"""Handle get media player cover command."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
data = {}
|
||||
repository = None
|
||||
|
||||
repo_id = msg.get("repository")
|
||||
action = msg.get("action")
|
||||
if repo_id is None or action is None:
|
||||
return
|
||||
|
||||
try:
|
||||
repository = hacs.repositories.get_by_id(repo_id)
|
||||
hacs.log.debug(f"Running {action} for {repository.data.full_name}")
|
||||
|
||||
if action == "update":
|
||||
await repository.update_repository(ignore_issues=True, force=True)
|
||||
repository.status.updated_info = True
|
||||
|
||||
elif action == "install":
|
||||
repository.data.new = False
|
||||
was_installed = repository.data.installed
|
||||
await repository.async_install()
|
||||
if not was_installed:
|
||||
hass.bus.async_fire("hacs/reload", {"force": True})
|
||||
await hacs.async_recreate_entities()
|
||||
|
||||
elif action == "not_new":
|
||||
repository.data.new = False
|
||||
|
||||
elif action == "uninstall":
|
||||
repository.data.new = False
|
||||
await repository.update_repository(ignore_issues=True, force=True)
|
||||
await repository.uninstall()
|
||||
|
||||
elif action == "hide":
|
||||
repository.data.hide = True
|
||||
|
||||
elif action == "unhide":
|
||||
repository.data.hide = False
|
||||
|
||||
elif action == "show_beta":
|
||||
repository.data.show_beta = True
|
||||
await repository.update_repository(force=True)
|
||||
|
||||
elif action == "hide_beta":
|
||||
repository.data.show_beta = False
|
||||
await repository.update_repository(force=True)
|
||||
|
||||
elif action == "toggle_beta":
|
||||
repository.data.show_beta = not repository.data.show_beta
|
||||
await repository.update_repository(force=True)
|
||||
|
||||
elif action == "delete":
|
||||
repository.data.show_beta = False
|
||||
repository.remove()
|
||||
|
||||
elif action == "release_notes":
|
||||
data = [
|
||||
{
|
||||
"name": x.name,
|
||||
"body": x.body,
|
||||
"tag": x.tag_name,
|
||||
}
|
||||
for x in repository.releases.objects
|
||||
]
|
||||
|
||||
elif action == "set_version":
|
||||
if msg["version"] == repository.data.default_branch:
|
||||
repository.data.selected_tag = None
|
||||
else:
|
||||
repository.data.selected_tag = msg["version"]
|
||||
await repository.update_repository(force=True)
|
||||
|
||||
hass.bus.async_fire("hacs/reload", {"force": True})
|
||||
|
||||
else:
|
||||
hacs.log.error(f"WS action '{action}' is not valid")
|
||||
|
||||
await hacs.data.async_write()
|
||||
message = None
|
||||
except AIOGitHubAPIException as exception:
|
||||
message = exception
|
||||
except AttributeError as exception:
|
||||
message = f"Could not use repository with ID {repo_id} ({exception})"
|
||||
except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
|
||||
message = exception
|
||||
|
||||
if message is not None:
|
||||
hacs.log.error(message)
|
||||
hass.bus.async_fire("hacs/error", {"message": str(message)})
|
||||
|
||||
if repository:
|
||||
repository.state = None
|
||||
connection.send_message(websocket_api.result_message(msg["id"], data))
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "hacs/settings",
|
||||
vol.Optional("action"): cv.string,
|
||||
vol.Optional("categories"): cv.ensure_list,
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def hacs_settings(hass, connection, msg):
|
||||
"""Handle get media player cover command."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
|
||||
action = msg["action"]
|
||||
hacs.log.debug("WS action '%s'", action)
|
||||
|
||||
if action == "set_fe_grid":
|
||||
hacs.configuration.frontend_mode = "Grid"
|
||||
|
||||
elif action == "onboarding_done":
|
||||
hacs.configuration.onboarding_done = True
|
||||
|
||||
elif action == "set_fe_table":
|
||||
hacs.configuration.frontend_mode = "Table"
|
||||
|
||||
elif action == "set_fe_compact_true":
|
||||
hacs.configuration.frontend_compact = False
|
||||
|
||||
elif action == "set_fe_compact_false":
|
||||
hacs.configuration.frontend_compact = True
|
||||
|
||||
elif action == "clear_new":
|
||||
for repo in hacs.repositories.list_all:
|
||||
if repo.data.new and repo.data.category in msg.get("categories", []):
|
||||
hacs.log.debug(
|
||||
"Clearing new flag from '%s'",
|
||||
repo.data.full_name,
|
||||
)
|
||||
repo.data.new = False
|
||||
else:
|
||||
hacs.log.error("WS action '%s' is not valid", action)
|
||||
hass.bus.async_fire("hacs/config", {})
|
||||
await hacs.data.async_write()
|
||||
connection.send_message(websocket_api.result_message(msg["id"], {}))
|
||||
|
||||
|
||||
@websocket_api.websocket_command({vol.Required("type"): "hacs/status"})
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def hacs_status(hass, connection, msg):
|
||||
"""Handle get media player cover command."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
connection.send_message(
|
||||
websocket_api.result_message(
|
||||
msg["id"],
|
||||
{
|
||||
"startup": hacs.status.startup,
|
||||
"background_task": False,
|
||||
"lovelace_mode": hacs.core.lovelace_mode,
|
||||
"reloading_data": hacs.status.reloading_data,
|
||||
"upgrading_all": hacs.status.upgrading_all,
|
||||
"disabled": hacs.system.disabled,
|
||||
"disabled_reason": hacs.system.disabled_reason,
|
||||
"has_pending_tasks": hacs.queue.has_pending_tasks,
|
||||
"stage": hacs.stage,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@websocket_api.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "hacs/repository/ignore",
|
||||
vol.Required("repository"): str,
|
||||
}
|
||||
)
|
||||
@websocket_api.require_admin
|
||||
@websocket_api.async_response
|
||||
async def hacs_repository_ignore(hass, connection, msg):
|
||||
"""Ignore a repository."""
|
||||
hacs: HacsBase = hass.data.get(DOMAIN)
|
||||
hacs.common.ignored_repositories.append(msg["repository"])
|
||||
connection.send_message(websocket_api.result_message(msg["id"]))
|
||||
24
custom_components/hacs/tasks/store_hacs_data.py
Normal file
24
custom_components/hacs/tasks/store_hacs_data.py
Normal file
@@ -0,0 +1,24 @@
|
||||
""""Store HACS data."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.const import EVENT_HOMEASSISTANT_FINAL_WRITE
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
""" "Hacs task base."""
|
||||
|
||||
events = [EVENT_HOMEASSISTANT_FINAL_WRITE]
|
||||
_can_run_disabled = True
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
await self.hacs.data.async_write(force=True)
|
||||
34
custom_components/hacs/tasks/update_all_repositories.py
Normal file
34
custom_components/hacs/tasks/update_all_repositories.py
Normal file
@@ -0,0 +1,34 @@
|
||||
""""Hacs base setup task."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Hacs update all task."""
|
||||
|
||||
schedule = timedelta(hours=25)
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
self.task_logger(
|
||||
self.hacs.log.debug, "Starting recurring background task for all repositories"
|
||||
)
|
||||
|
||||
for repository in self.hacs.repositories.list_all:
|
||||
if repository.data.category in self.hacs.common.categories:
|
||||
self.hacs.queue.add(repository.common_update())
|
||||
|
||||
await self.hacs.data.async_write()
|
||||
self.hass.bus.async_fire("hacs/repository", {"action": "reload"})
|
||||
self.task_logger(self.hacs.log.debug, "Recurring background task for all repositories done")
|
||||
92
custom_components/hacs/tasks/update_critical_repositories.py
Normal file
92
custom_components/hacs/tasks/update_critical_repositories.py
Normal file
@@ -0,0 +1,92 @@
|
||||
""""Hacs base setup task."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from aiogithubapi import GitHubNotModifiedException
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from custom_components.hacs.utils.queue_manager import QueueManager
|
||||
from custom_components.hacs.utils.store import (
|
||||
async_load_from_store,
|
||||
async_save_to_store,
|
||||
)
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..enums import HacsStage
|
||||
from ..exceptions import HacsException
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Hacs update critical task."""
|
||||
|
||||
schedule = timedelta(hours=2)
|
||||
stages = [HacsStage.RUNNING]
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
critical_queue = QueueManager(hass=self.hass)
|
||||
instored = []
|
||||
critical = []
|
||||
was_installed = False
|
||||
|
||||
try:
|
||||
critical = await self.hacs.async_github_get_hacs_default_file("critical")
|
||||
except GitHubNotModifiedException:
|
||||
return
|
||||
except HacsException:
|
||||
pass
|
||||
|
||||
if not critical:
|
||||
self.hacs.log.debug("No critical repositories")
|
||||
return
|
||||
|
||||
stored_critical = await async_load_from_store(self.hass, "critical")
|
||||
|
||||
for stored in stored_critical or []:
|
||||
instored.append(stored["repository"])
|
||||
|
||||
stored_critical = []
|
||||
|
||||
for repository in critical:
|
||||
removed_repo = self.hacs.repositories.removed_repository(repository["repository"])
|
||||
removed_repo.removal_type = "critical"
|
||||
repo = self.hacs.repositories.get_by_full_name(repository["repository"])
|
||||
|
||||
stored = {
|
||||
"repository": repository["repository"],
|
||||
"reason": repository["reason"],
|
||||
"link": repository["link"],
|
||||
"acknowledged": True,
|
||||
}
|
||||
if repository["repository"] not in instored:
|
||||
if repo is not None and repo.data.installed:
|
||||
self.hacs.log.critical(
|
||||
"Removing repository %s, it is marked as critical",
|
||||
repository["repository"],
|
||||
)
|
||||
was_installed = True
|
||||
stored["acknowledged"] = False
|
||||
# Remove from HACS
|
||||
critical_queue.add(repo.uninstall())
|
||||
repo.remove()
|
||||
|
||||
stored_critical.append(stored)
|
||||
removed_repo.update_data(stored)
|
||||
|
||||
# Uninstall
|
||||
await critical_queue.execute()
|
||||
|
||||
# Save to FS
|
||||
await async_save_to_store(self.hass, "critical", stored_critical)
|
||||
|
||||
# Restart HASS
|
||||
if was_installed:
|
||||
self.hacs.log.critical("Resarting Home Assistant")
|
||||
self.hass.async_create_task(self.hass.async_stop(100))
|
||||
64
custom_components/hacs/tasks/update_default_repositories.py
Normal file
64
custom_components/hacs/tasks/update_default_repositories.py
Normal file
@@ -0,0 +1,64 @@
|
||||
""""Hacs base setup task."""
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..enums import HacsCategory, HacsStage
|
||||
from ..exceptions import HacsException
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Hacs update default task."""
|
||||
|
||||
schedule = timedelta(hours=3)
|
||||
stages = [HacsStage.STARTUP]
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
self.hacs.log.info("Loading known repositories")
|
||||
|
||||
await asyncio.gather(
|
||||
*[
|
||||
self.async_get_category_repositories(HacsCategory(category))
|
||||
for category in self.hacs.common.categories or []
|
||||
]
|
||||
)
|
||||
|
||||
async def async_get_category_repositories(self, category: HacsCategory) -> None:
|
||||
"""Get repositories from category."""
|
||||
try:
|
||||
repositories = await self.hacs.async_github_get_hacs_default_file(category)
|
||||
except HacsException:
|
||||
return
|
||||
|
||||
for repo in repositories:
|
||||
if self.hacs.common.renamed_repositories.get(repo):
|
||||
repo = self.hacs.common.renamed_repositories[repo]
|
||||
if self.hacs.repositories.is_removed(repo):
|
||||
continue
|
||||
if repo in self.hacs.common.archived_repositories:
|
||||
continue
|
||||
repository = self.hacs.repositories.get_by_full_name(repo)
|
||||
if repository is not None:
|
||||
self.hacs.repositories.mark_default(repository)
|
||||
if self.hacs.status.new and self.hacs.configuration.dev:
|
||||
# Force update for new installations
|
||||
self.hacs.queue.add(repository.common_update())
|
||||
continue
|
||||
self.hacs.queue.add(
|
||||
self.hacs.async_register_repository(
|
||||
repository_full_name=repo,
|
||||
category=category,
|
||||
default=True,
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
""""Hacs base setup task."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..enums import HacsStage
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Hacs update downloaded task."""
|
||||
|
||||
schedule = timedelta(hours=2)
|
||||
stages = [HacsStage.STARTUP]
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
self.task_logger(
|
||||
self.hacs.log.debug, "Starting recurring background task for installed repositories"
|
||||
)
|
||||
|
||||
for repository in self.hacs.repositories.list_downloaded:
|
||||
if repository.data.category in self.hacs.common.categories:
|
||||
self.hacs.queue.add(repository.update_repository())
|
||||
|
||||
await self.hacs.data.async_write()
|
||||
self.task_logger(
|
||||
self.hacs.log.debug, "Recurring background task for installed repositories done"
|
||||
)
|
||||
60
custom_components/hacs/tasks/update_removed_repositories.py
Normal file
60
custom_components/hacs/tasks/update_removed_repositories.py
Normal file
@@ -0,0 +1,60 @@
|
||||
""""Hacs base setup task."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..enums import HacsCategory, HacsStage
|
||||
from ..exceptions import HacsException
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Hacs update removed task."""
|
||||
|
||||
schedule = timedelta(hours=2)
|
||||
stages = [HacsStage.STARTUP]
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
|
||||
need_to_save = False
|
||||
self.hacs.log.info("Loading removed repositories")
|
||||
|
||||
try:
|
||||
removed_repositories = await self.hacs.async_github_get_hacs_default_file(
|
||||
HacsCategory.REMOVED
|
||||
)
|
||||
except HacsException:
|
||||
return
|
||||
|
||||
for item in removed_repositories:
|
||||
removed = self.hacs.repositories.removed_repository(item["repository"])
|
||||
removed.update_data(item)
|
||||
|
||||
for removed in self.hacs.repositories.list_removed:
|
||||
if (repository := self.hacs.repositories.get_by_full_name(removed.repository)) is None:
|
||||
continue
|
||||
if repository.data.full_name in self.hacs.common.ignored_repositories:
|
||||
continue
|
||||
if repository.data.installed and removed.removal_type != "critical":
|
||||
self.hacs.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:
|
||||
need_to_save = True
|
||||
repository.remove()
|
||||
|
||||
if need_to_save:
|
||||
await self.hacs.data.async_write()
|
||||
24
custom_components/hacs/tasks/verify_api.py
Normal file
24
custom_components/hacs/tasks/verify_api.py
Normal file
@@ -0,0 +1,24 @@
|
||||
""""Starting setup task: Verify API"."""
|
||||
from __future__ import annotations
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from ..base import HacsBase
|
||||
from ..enums import HacsStage
|
||||
from .base import HacsTask
|
||||
|
||||
|
||||
async def async_setup_task(hacs: HacsBase, hass: HomeAssistant) -> Task:
|
||||
"""Set up this task."""
|
||||
return Task(hacs=hacs, hass=hass)
|
||||
|
||||
|
||||
class Task(HacsTask):
|
||||
"""Verify the connection to the GitHub API."""
|
||||
|
||||
stages = [HacsStage.SETUP]
|
||||
|
||||
async def async_execute(self) -> None:
|
||||
"""Execute the task."""
|
||||
can_update = await self.hacs.async_can_update()
|
||||
self.task_logger(self.hacs.log.debug, f"Can update {can_update} repositories")
|
||||
Reference in New Issue
Block a user