Skip to content

Commit

Permalink
Add update entities (#2570)
Browse files Browse the repository at this point in the history
  • Loading branch information
ludeeus authored Mar 27, 2022
1 parent d81f4a3 commit 57997eb
Show file tree
Hide file tree
Showing 12 changed files with 321 additions and 62 deletions.
8 changes: 6 additions & 2 deletions custom_components/hacs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import voluptuous as vol

from .base import HacsBase
from .const import DOMAIN, PLATFORMS, STARTUP
from .const import DOMAIN, STARTUP
from .enums import ConfigurationType, HacsDisabledReason, HacsStage, LovelaceMode
from .tasks.manager import HacsTaskManager
from .utils.configuration_schema import hacs_config_combined
Expand Down Expand Up @@ -189,7 +189,11 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
except AttributeError:
pass

unload_ok = await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
platforms = ["sensor"]
if hacs.core.ha_version >= "2022.4.0.dev0" and hacs.configuration.experimental:
platforms.append("update")

unload_ok = await hass.config_entries.async_unload_platforms(config_entry, platforms)

await hacs.async_set_stage(None)
hacs.disable_hacs(HacsDisabledReason.REMOVED)
Expand Down
35 changes: 34 additions & 1 deletion custom_components/hacs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from aiogithubapi.objects.repository import AIOGitHubAPIRepository
from aiohttp.client import ClientSession, ClientTimeout
from awesomeversion import AwesomeVersion
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.loader import Integration
from homeassistant.util import dt
Expand Down Expand Up @@ -98,7 +99,7 @@ class HacsConfiguration:
appdaemon_path: str = "appdaemon/apps/"
appdaemon: bool = False
config: dict[str, Any] = field(default_factory=dict)
config_entry: dict[str, str] = field(default_factory=dict)
config_entry: ConfigEntry | None = None
config_type: ConfigurationType | None = None
country: str = "ALL"
debug: bool = False
Expand Down Expand Up @@ -285,6 +286,20 @@ def is_registered(
return repository_full_name in self._repositories_by_full_name
return False

def is_downloaded(
self,
repository_id: str | None = None,
repository_full_name: str | None = None,
) -> bool:
"""Check if a repository is registered."""
if repository_id is not None:
repo = self.get_by_id(repository_id)
if repository_full_name is not None:
repo = self.get_by_full_name(repository_full_name)
if repo is None:
return False
return repo.data.installed

def get_by_id(self, repository_id: str | None) -> HacsRepository | None:
"""Get repository by id."""
if not repository_id:
Expand Down Expand Up @@ -607,3 +622,21 @@ async def async_download_file(self, url: str, *, headers: dict | None = None) ->
self.log.exception("Download failed - %s", exception)

return None

async def async_recreate_entities(self) -> None:
"""Recreate entities."""
if (
self.configuration == ConfigurationType.YAML
or self.core.ha_version < "2022.4.0.dev0"
or not self.configuration.experimental
):
return

platforms = ["sensor", "update"]

await self.hass.config_entries.async_unload_platforms(
entry=self.configuration.config_entry,
platforms=platforms,
)

self.hass.config_entries.async_setup_platforms(self.configuration.config_entry, platforms)
4 changes: 2 additions & 2 deletions custom_components/hacs/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
DEFAULT_CONCURRENT_TASKS = 15
DEFAULT_CONCURRENT_BACKOFF_TIME = 1

PLATFORMS = ["sensor"]

HACS_ACTION_GITHUB_API_HEADERS = {
"User-Agent": "HACS/action",
"Accept": ACCEPT_HEADERS["preview"],
Expand All @@ -25,6 +23,8 @@
VERSION_STORAGE = "6"
STORENAME = "hacs"

HACS_SYSTEM_ID = "0717a0cd-745c-48fd-9b16-c8534c9704f9-bc944b0f-fd42-4a58-a072-ade38d1444cd"

STARTUP = """
-------------------------------------------------------------------
HACS (Home Assistant Community Store)
Expand Down
113 changes: 113 additions & 0 deletions custom_components/hacs/entity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
"""HACS Base entities."""
from __future__ import annotations

from homeassistant.core import callback
from homeassistant.helpers.entity import Entity

from custom_components.hacs.enums import HacsGitHubRepo

from .base import HacsBase
from .const import DOMAIN, HACS_SYSTEM_ID, NAME_SHORT
from .repositories.base import HacsRepository


def system_info(hacs: HacsBase) -> dict:
"""Return system info."""
info = {
"identifiers": {(DOMAIN, HACS_SYSTEM_ID)},
"name": NAME_SHORT,
"manufacturer": "hacs.xyz",
"model": "",
"sw_version": str(hacs.version),
"configuration_url": "homeassistant://hacs",
}
# LEGACY can be removed when min HA version is 2021.12
if hacs.core.ha_version >= "2021.12.0b0":
# pylint: disable=import-outside-toplevel
from homeassistant.helpers.device_registry import DeviceEntryType

info["entry_type"] = DeviceEntryType.SERVICE
else:
info["entry_type"] = "service"
return info


class HacsBaseEntity(Entity):
"""Base HACS entity."""

_attr_should_poll = False

def __init__(self, hacs: HacsBase) -> None:
"""Initialize."""
self.hacs = hacs

async def async_added_to_hass(self) -> None:
"""Register for status events."""
self.async_on_remove(
self.hass.bus.async_listen("hacs/repository", self._update_and_write_state)
)

@callback
def _update(self) -> None:
"""Update the sensor."""

async def async_update(self) -> None:
"""Manual updates of the sensor."""
self._update()

@callback
def _update_and_write_state(self, *_) -> None:
"""Update the sensor and write state."""
self._update()
self.async_write_ha_state()


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

_attr_icon = "hacs:hacs"
_attr_unique_id = HACS_SYSTEM_ID

@property
def device_info(self) -> dict[str, any]:
"""Return device information about HACS."""
return system_info(self.hacs)


class HacsRepositoryEntity(HacsBaseEntity):
"""Base repository entity."""

def __init__(self, hacs: HacsBase, repository: HacsRepository) -> None:
"""Initialize."""
super().__init__(hacs=hacs)
self.repository = repository
self._attr_unique_id = str(repository.data.id)

@property
def available(self) -> bool:
return self.hacs.repositories.is_downloaded(repository_id=str(self.repository.data.id))

@property
def device_info(self) -> dict[str, any]:
"""Return device information about HACS."""
if self.repository.data.full_name == HacsGitHubRepo.INTEGRATION:
return system_info(self.hacs)

info = {
"identifiers": {(DOMAIN, str(self.repository.data.id))},
"name": self.repository.display_name,
"model": self.repository.data.category,
"manufacturer": ", ".join(
author.replace("@", "") for author in self.repository.data.authors
),
"configuration_url": "homeassistant://hacs",
}
# LEGACY can be removed when min HA version is 2021.12
if self.hacs.core.ha_version >= "2021.12.0b0":
# pylint: disable=import-outside-toplevel
from homeassistant.helpers.device_registry import DeviceEntryType

info["entry_type"] = DeviceEntryType.SERVICE
else:
info["entry_type"] = "service"
return info
23 changes: 22 additions & 1 deletion custom_components/hacs/repositories/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
from aiogithubapi.const import BASE_API_URL
from aiogithubapi.objects.repository import AIOGitHubAPIRepository
import attr
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.json import JSONEncoder

from ..enums import HacsCategory, RepositoryFile
from ..const import DOMAIN
from ..enums import ConfigurationType, HacsCategory, RepositoryFile
from ..exceptions import (
HacsException,
HacsNotModifiedException,
Expand Down Expand Up @@ -777,6 +779,8 @@ async def uninstall(self) -> None:
{"id": 1337, "action": "uninstall", "repository": self.data.full_name},
)

await self.async_remove_entity_device()

async def remove_local_directory(self) -> None:
"""Check the local directory."""

Expand Down Expand Up @@ -1169,3 +1173,20 @@ async def dowload_repository_content(self, content: FileInformation) -> None:

except BaseException as exception: # lgtm [py/catch-base-exception] pylint: disable=broad-except
self.validate.errors.append(f"Download was not completed [{exception}]")

async def async_remove_entity_device(self) -> None:
"""Remove the entity device."""
if (
self.hacs.configuration == ConfigurationType.YAML
or self.hacs.core.ha_version < "2022.4.0.dev0"
or not self.hacs.configuration.experimental
):
return

device_registry: dr.DeviceRegistry = dr.async_get(hass=self.hacs.hass)
device = device_registry.async_get_device(identifiers={(DOMAIN, str(self.data.id))})

if device is None:
return

device_registry.async_remove_device(device_id=device.id)
52 changes: 4 additions & 48 deletions custom_components/hacs/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
from homeassistant.components.sensor import SensorEntity
from homeassistant.core import callback

from .base import HacsBase
from .const import DOMAIN, NAME_SHORT
from .const import DOMAIN
from .entity import HacsSystemEntity


async def async_setup_platform(hass, _config, async_add_entities, _discovery_info=None):
Expand All @@ -18,50 +18,12 @@ async def async_setup_entry(hass, _config_entry, async_add_devices):
async_add_devices([HACSSensor(hacs=hass.data.get(DOMAIN))])


class HACSSensor(SensorEntity):
class HACSSensor(HacsSystemEntity, SensorEntity):
"""HACS Sensor class."""

_attr_should_poll = False
_attr_unique_id = "0717a0cd-745c-48fd-9b16-c8534c9704f9-bc944b0f-fd42-4a58-a072-ade38d1444cd"
_attr_name = "hacs"
_attr_icon = "hacs:hacs"
_attr_unit_of_measurement = "pending update(s)"

def __init__(self, hacs: HacsBase) -> None:
"""Initialize."""
self.hacs = hacs
self._attr_native_value = None

async def async_update(self) -> None:
"""Manual updates of the sensor."""
self._update()

@callback
def _update_and_write_state(self, *_) -> None:
"""Update the sensor and write state."""
self._update()
self.async_write_ha_state()

@property
def device_info(self) -> dict[str, any]:
"""Return device information about HACS."""
info = {
"identifiers": {(DOMAIN, self.unique_id)},
"name": NAME_SHORT,
"manufacturer": "hacs.xyz",
"model": "",
"sw_version": str(self.hacs.version),
"configuration_url": "homeassistant://hacs",
}
# LEGACY can be removed when min HA version is 2021.12
if self.hacs.core.ha_version >= "2021.12.0b0":
# pylint: disable=import-outside-toplevel
from homeassistant.helpers.device_registry import DeviceEntryType

info["entry_type"] = DeviceEntryType.SERVICE
else:
info["entry_type"] = "service"
return info
_attr_native_value = None

@callback
def _update(self) -> None:
Expand All @@ -84,9 +46,3 @@ def _update(self) -> None:
for repository in repositories
]
}

async def async_added_to_hass(self) -> None:
"""Register for status events."""
self.async_on_remove(
self.hass.bus.async_listen("hacs/repository", self._update_and_write_state)
)
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
from homeassistant.helpers.discovery import async_load_platform

from ..base import HacsBase
from ..const import DOMAIN, PLATFORMS
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."""
Expand All @@ -24,9 +26,11 @@ 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, {}, self.hacs.configuration.config)
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, PLATFORMS
self.hacs.configuration.config_entry, [SENSOR_DOMAIN]
)
32 changes: 32 additions & 0 deletions custom_components/hacs/tasks/setup_update_platform.py
Original file line number Diff line number Diff line change
@@ -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]
)
Loading

0 comments on commit 57997eb

Please sign in to comment.