Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move scheduled tasks out of HacsBase and to dedicated tasks #2488

Merged
1 change: 1 addition & 0 deletions custom_components/hacs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ async def async_initialize_integration(

hacs.integration = integration
hacs.version = integration.version
hacs.configuration.dev = integration.version == "0.0.0"
hacs.hass = hass
hacs.queue = QueueManager(hass=hass)
hacs.data = HacsData(hacs=hacs)
Expand Down
211 changes: 4 additions & 207 deletions custom_components/hacs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ class HacsRepositories:
"""HACS Repositories."""

_default_repositories: set[str] = field(default_factory=set)
_repositories: list[str] = field(default_factory=list)
_repositories: list[HacsRepository] = field(default_factory=list)
_repositories_by_full_name: dict[str, str] = field(default_factory=dict)
_repositories_by_id: dict[str, str] = field(default_factory=dict)
_removed_repositories: list[RemovedRepository] = field(default_factory=list)
Expand Down Expand Up @@ -554,226 +554,23 @@ async def async_register_repository(
self.repositories.register(repository, default)

async def startup_tasks(self, _event=None) -> None:
"""Tasks that are started after startup."""
"""Tasks that are started after setup."""
await self.async_set_stage(HacsStage.STARTUP)
self.status.background_task = True
self.hass.bus.async_fire("hacs/status", {})

try:
await self.handle_critical_repositories_startup()
await self.async_load_default_repositories()
except HacsException as exception:
self.log.warning(
"Could not load default repositories: %s, retrying in 5 minuttes", exception
)
if not self.system.disabled:
async_call_later(self.hass, timedelta(minutes=5), self.startup_tasks)
return

self.recuring_tasks.append(
self.hass.helpers.event.async_track_time_interval(
self.recurring_tasks_installed, timedelta(hours=2)
)
)
self.status.startup = False

self.recuring_tasks.append(
self.hass.helpers.event.async_track_time_interval(
self.recurring_tasks_all, timedelta(hours=25)
)
)
self.hass.bus.async_fire("hacs/status", {})

self.status.startup = False
await self.async_set_stage(HacsStage.RUNNING)

self.hass.bus.async_fire("hacs/reload", {"force": True})
try:
await self.recurring_tasks_installed()
except HacsException as exception:
self.log.warning(
"Could not run initial task for downloaded repositories: %s, retrying in 5 minuttes",
exception,
)
if not self.system.disabled:
async_call_later(self.hass, timedelta(minutes=5), self.startup_tasks)
return

if queue_task := self.tasks.get("prosess_queue"):
await queue_task.execute_task()

self.status.background_task = False
self.hass.bus.async_fire("hacs/status", {})

async def handle_critical_repositories_startup(self) -> None:
"""Handled critical repositories during startup."""
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.log.critical("URGENT!: Check the HACS panel!")
self.hass.components.persistent_notification.create(
title="URGENT!", message="**Check the HACS panel!**"
)

async def handle_critical_repositories(self) -> None:
"""Handled critical repositories during runtime."""
# Get critical repositories
critical_queue = QueueManager(hass=self.hass)
instored = []
critical = []
was_installed = False

try:
critical = await self.async_github_get_hacs_default_file("critical")
except GitHubNotModifiedException:
return
except GitHubException:
pass

if not critical:
self.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.repositories.removed_repository(repository["repository"])
removed_repo.removal_type = "critical"
repo = self.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.installed:
self.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.log.critical("Resarting Home Assistant")
self.hass.async_create_task(self.hass.async_stop(100))

async def recurring_tasks_installed(self, _notarealarg=None) -> None:
"""Recurring tasks for installed repositories."""
self.log.debug("Starting recurring background task for installed repositories")
self.status.background_task = True
self.hass.bus.async_fire("hacs/status", {})

for repository in self.repositories.list_all:
if self.status.startup and repository.data.full_name == HacsGitHubRepo.INTEGRATION:
continue
if repository.data.installed and repository.data.category in self.common.categories:
self.queue.add(repository.update_repository())

await self.handle_critical_repositories()
self.status.background_task = False
self.hass.bus.async_fire("hacs/status", {})
await self.data.async_write()
self.log.debug("Recurring background task for installed repositories done")

async def recurring_tasks_all(self, _notarealarg=None) -> None:
"""Recurring tasks for all repositories."""
self.log.debug("Starting recurring background task for all repositories")
self.status.background_task = True
self.hass.bus.async_fire("hacs/status", {})

for repository in self.repositories.list_all:
if repository.data.category in self.common.categories:
self.queue.add(repository.common_update())

await self.async_load_default_repositories()
self.status.background_task = False
await self.data.async_write()
self.hass.bus.async_fire("hacs/status", {})
self.hass.bus.async_fire("hacs/repository", {"action": "reload"})
self.log.debug("Recurring background task for all repositories done")

async def async_load_default_repositories(self) -> None:
"""Load known repositories."""
need_to_save = False
self.log.info("Loading known repositories")

for item in await self.async_github_get_hacs_default_file(HacsCategory.REMOVED):
removed = self.repositories.removed_repository(item["repository"])
removed.update_data(item)

for category in self.common.categories or []:
self.queue.add(self.async_get_category_repositories(HacsCategory(category)))

if queue_task := self.tasks.get("prosess_queue"):
await queue_task.execute_task()

for removed in self.repositories.list_removed:
if (repository := self.repositories.get_by_full_name(removed.repository)) is None:
continue
if repository.data.installed and removed.removal_type != "critical":
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:
need_to_save = True
repository.remove()

if need_to_save:
await self.data.async_write()

async def async_get_category_repositories(self, category: HacsCategory) -> None:
"""Get repositories from category."""
repositories = await self.async_github_get_hacs_default_file(category)
for repo in repositories:
if self.common.renamed_repositories.get(repo):
repo = self.common.renamed_repositories[repo]
if self.repositories.is_removed(repo):
continue
if repo in self.common.archived_repositories:
continue
repository = self.repositories.get_by_full_name(repo)
if repository is not None:
self.repositories.mark_default(repository)
if self.status.new:
# Force update for new installations
self.queue.add(repository.common_update())
continue
self.queue.add(
self.async_register_repository(
repository_full_name=repo,
category=category,
default=True,
)
)

async def async_download_file(self, url: str) -> bytes | None:
"""Download files, and return the content."""
if url is None:
Expand Down
35 changes: 35 additions & 0 deletions custom_components/hacs/tasks/handle_critical_notification.py
Original file line number Diff line number Diff line change
@@ -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!**"
)
36 changes: 36 additions & 0 deletions custom_components/hacs/tasks/update_all_repositories.py
Original file line number Diff line number Diff line change
@@ -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 .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.hacs.log.debug("Starting recurring background task for all repositories")
self.hacs.status.background_task = True
self.hass.bus.async_fire("hacs/status", {})

for repository in self.hacs.repositories.list_all:
if repository.data.category in self.hacs.common.categories:
self.hacs.queue.add(repository.common_update())

self.hacs.status.background_task = False
await self.hacs.data.async_write()
self.hass.bus.async_fire("hacs/status", {})
self.hass.bus.async_fire("hacs/repository", {"action": "reload"})
self.hacs.log.debug("Recurring background task for all repositories done")
Loading