Skip to content

Commit

Permalink
Add coordinator for update entities
Browse files Browse the repository at this point in the history
  • Loading branch information
emontnemery committed Apr 10, 2024
1 parent 3f09f5f commit 985d3f9
Show file tree
Hide file tree
Showing 11 changed files with 82 additions and 15 deletions.
12 changes: 12 additions & 0 deletions custom_components/hacs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
)

from .const import DOMAIN, TV, URL_BASE
from .coordinator import HacsUpdateCoordinator
from .data_client import HacsDataClient
from .enums import (
ConfigurationType,
Expand Down Expand Up @@ -373,6 +374,7 @@ def __init__(self) -> None:
"""Initialize."""
self.common = HacsCommon()
self.configuration = HacsConfiguration()
self.coordinators: dict[HacsCategory, HacsUpdateCoordinator] = {}
self.core = HacsCore()
self.log = LOGGER
self.recuring_tasks: list[Callable[[], None]] = []
Expand Down Expand Up @@ -423,12 +425,14 @@ def enable_hacs_category(self, category: HacsCategory) -> None:
if category not in self.common.categories:
self.log.info("Enable category: %s", category)
self.common.categories.add(category)
self.coordinators[category] = HacsUpdateCoordinator()

def disable_hacs_category(self, category: HacsCategory) -> None:
"""Disable HACS category."""
if category in self.common.categories:
self.log.info("Disabling category: %s", category)
self.common.categories.pop(category)
self.coordinators.pop(category)

async def async_save_file(self, file_path: str, content: Any) -> bool:
"""Save a file."""
Expand Down Expand Up @@ -907,6 +911,7 @@ async def async_get_category_repositories_experimental(self, category: str) -> N
self.repositories.unregister(repository)

self.async_dispatch(HacsDispatchEvent.REPOSITORY, {})
self.coordinators[category].async_update_listeners()

async def async_get_category_repositories(self, category: HacsCategory) -> None:
"""Get repositories from category."""
Expand Down Expand Up @@ -1079,6 +1084,13 @@ async def async_update_downloaded_custom_repositories(self, _=None) -> None:
):
self.queue.add(repository.update_repository(ignore_issues=True))

async def update_coordinators() -> None:
"""Update all coordinators."""
for coordinator in self.coordinators.values():
coordinator.async_update_listeners()

self.queue.add(update_coordinators())

self.log.debug("Recurring background task for downloaded custom repositories done")

async def async_handle_critical_repositories(self, _=None) -> None:
Expand Down
37 changes: 37 additions & 0 deletions custom_components/hacs/coordinator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Coordinator to trigger entity updates."""
from __future__ import annotations

from collections.abc import Callable
from typing import Any, Callable

from homeassistant.core import CALLBACK_TYPE, callback
from homeassistant.helpers.update_coordinator import BaseDataUpdateCoordinatorProtocol


class HacsUpdateCoordinator(BaseDataUpdateCoordinatorProtocol):
"""Dispatch updates to update entities."""

def __init__(self) -> None:
"""Initialize."""
self._listeners: dict[CALLBACK_TYPE, tuple[CALLBACK_TYPE, object | None]] = {}

@callback
def async_add_listener(
self, update_callback: CALLBACK_TYPE, context: Any = None
) -> Callable[[], None]:
"""Listen for data updates."""

@callback
def remove_listener() -> None:
"""Remove update listener."""
self._listeners.pop(remove_listener)

self._listeners[remove_listener] = (update_callback, context)

return remove_listener

@callback
def async_update_listeners(self) -> None:
"""Update all registered listeners."""
for update_callback, _ in list(self._listeners.values()):
update_callback()
32 changes: 24 additions & 8 deletions custom_components/hacs/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
from homeassistant.helpers.device_registry import DeviceEntryType
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.update_coordinator import BaseCoordinatorEntity

from .const import DOMAIN, HACS_SYSTEM_ID, NAME_SHORT
from .coordinator import HacsUpdateCoordinator
from .enums import HacsDispatchEvent, HacsGitHubRepo

if TYPE_CHECKING:
Expand Down Expand Up @@ -39,6 +41,10 @@ def __init__(self, hacs: HacsBase) -> None:
"""Initialize."""
self.hacs = hacs


class HacsDispatcherEntity(HacsBaseEntity):
"""Base HACS entity listening to dispatcher signals."""

async def async_added_to_hass(self) -> None:
"""Register for status events."""
self.async_on_remove(
Expand All @@ -64,7 +70,7 @@ def _update_and_write_state(self, _: Any) -> None:
self.async_write_ha_state()


class HacsSystemEntity(HacsBaseEntity):
class HacsSystemEntity(HacsDispatcherEntity):
"""Base system entity."""

_attr_icon = "hacs:hacs"
Expand All @@ -76,7 +82,7 @@ def device_info(self) -> dict[str, any]:
return system_info(self.hacs)


class HacsRepositoryEntity(HacsBaseEntity):
class HacsRepositoryEntity(BaseCoordinatorEntity[HacsUpdateCoordinator], HacsBaseEntity):
"""Base repository entity."""

def __init__(
Expand All @@ -85,9 +91,11 @@ def __init__(
repository: HacsRepository,
) -> None:
"""Initialize."""
super().__init__(hacs=hacs)
BaseCoordinatorEntity.__init__(self, hacs.coordinators[repository.data.category])
HacsBaseEntity.__init__(self, hacs=hacs)
self.repository = repository
self._attr_unique_id = str(repository.data.id)
self._repo_last_fetched = repository.data.last_fetched

@property
def available(self) -> bool:
Expand All @@ -112,8 +120,16 @@ def device_info(self) -> dict[str, any]:
}

@callback
def _update_and_write_state(self, data: dict) -> None:
"""Update the entity and write state."""
if data.get("repository_id") == self.repository.data.id:
self._update()
self.async_write_ha_state()
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
if self._repo_last_fetched >= self.repository.data.last_fetched:
return

self._repo_last_fetched = self.repository.data.last_fetched
self.async_write_ha_state()

async def async_update(self) -> None:
"""Update the entity.
Only used by the generic entity update service.
"""
2 changes: 1 addition & 1 deletion custom_components/hacs/repositories/appdaemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ async def update_repository(self, ignore_issues=False, force=False):
# Set local path
self.content.path.local = self.localpath

# Signal entities to refresh
# Signal frontend to refresh
if self.data.installed:
self.hacs.async_dispatch(
HacsDispatchEvent.REPOSITORY,
Expand Down
2 changes: 1 addition & 1 deletion custom_components/hacs/repositories/integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ async def update_repository(self, ignore_issues=False, force=False):
# Set local path
self.content.path.local = self.localpath

# Signal entities to refresh
# Signal frontend to refresh
if self.data.installed:
self.hacs.async_dispatch(
HacsDispatchEvent.REPOSITORY,
Expand Down
2 changes: 1 addition & 1 deletion custom_components/hacs/repositories/netdaemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ async def update_repository(self, ignore_issues=False, force=False):
# Set local path
self.content.path.local = self.localpath

# Signal entities to refresh
# Signal frontend to refresh
if self.data.installed:
self.hacs.async_dispatch(
HacsDispatchEvent.REPOSITORY,
Expand Down
2 changes: 1 addition & 1 deletion custom_components/hacs/repositories/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ async def update_repository(self, ignore_issues=False, force=False):
if self.content.path.remote == "release":
self.content.single = True

# Signal entities to refresh
# Signal frontend to refresh
if self.data.installed:
self.hacs.async_dispatch(
HacsDispatchEvent.REPOSITORY,
Expand Down
2 changes: 1 addition & 1 deletion custom_components/hacs/repositories/python_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ async def update_repository(self, ignore_issues=False, force=False):
# Update name
self.update_filenames()

# Signal entities to refresh
# Signal frontend to refresh
if self.data.installed:
self.hacs.async_dispatch(
HacsDispatchEvent.REPOSITORY,
Expand Down
2 changes: 1 addition & 1 deletion custom_components/hacs/repositories/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ async def update_repository(self, ignore_issues=False, force=False):
self.data.file_name = self.repository_manifest.filename
self.content.path.local = self.localpath

# Signal entities to refresh
# Signal frontend to refresh
if self.data.installed:
self.hacs.async_dispatch(
HacsDispatchEvent.REPOSITORY,
Expand Down
2 changes: 1 addition & 1 deletion custom_components/hacs/repositories/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ async def update_repository(self, ignore_issues=False, force=False):
self.update_filenames()
self.content.path.local = self.localpath

# Signal entities to refresh
# Signal frontend to refresh
if self.data.installed:
self.hacs.async_dispatch(
HacsDispatchEvent.REPOSITORY,
Expand Down
2 changes: 2 additions & 0 deletions custom_components/hacs/websocket/repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,8 @@ async def hacs_repository_refresh(

await repository.update_repository(ignore_issues=True, force=True)
await hacs.data.async_write()
# Update state of update entity
hacs.coordinators[repository.data.category].async_update_listeners()

connection.send_message(websocket_api.result_message(msg["id"], {}))

Expand Down

0 comments on commit 985d3f9

Please sign in to comment.