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

Add async wrapper for commonly used filesystem functions #3584

Merged
merged 9 commits into from
May 22, 2024
Merged
5 changes: 3 additions & 2 deletions custom_components/hacs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from .frontend import async_register_frontend
from .utils.configuration_schema import hacs_config_combined
from .utils.data import HacsData
from .utils.file_system import async_exists
from .utils.logger import LOGGER
from .utils.queue_manager import QueueManager
from .utils.version import version_left_higher_or_equal_then_right
Expand Down Expand Up @@ -131,7 +132,7 @@ async def async_startup():
hass.config.path("custom_components/custom_updater.py"),
hass.config.path("custom_components/custom_updater/__init__.py"),
):
if os.path.exists(location):
if await async_exists(hass, location):
hacs.log.critical(
"This cannot be used with custom_updater. "
"To use this you need to remove custom_updater form %s",
Expand Down Expand Up @@ -163,7 +164,7 @@ async def async_startup():
hacs.set_active_categories()

async_register_websocket_commands(hass)
async_register_frontend(hass, hacs)
await async_register_frontend(hass, hacs)

if hacs.configuration.config_type == ConfigurationType.YAML:
hass.async_create_task(
Expand Down
15 changes: 7 additions & 8 deletions custom_components/hacs/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
)
from .repositories import REPOSITORY_CLASSES
from .utils.decode import decode_content
from .utils.file_system import async_exists
from .utils.json import json_loads
from .utils.logger import LOGGER
from .utils.queue_manager import QueueManager
Expand Down Expand Up @@ -466,7 +467,7 @@ def _write_file():
self.log.error("Could not write data to %s - %s", file_path, error)
return False

return os.path.exists(file_path)
return await async_exists(self.hass, file_path)
ludeeus marked this conversation as resolved.
Show resolved Hide resolved

async def async_can_update(self) -> int:
"""Helper to calculate the number of repositories we can fetch data for."""
Expand Down Expand Up @@ -1148,11 +1149,10 @@ async def async_handle_critical_repositories(self, _=None) -> None:
self.log.critical("Restarting Home Assistant")
self.hass.async_create_task(self.hass.async_stop(100))

@callback
def async_setup_frontend_endpoint_plugin(self) -> None:
async def async_setup_frontend_endpoint_plugin(self) -> None:
"""Setup the http endpoints for plugins if its not already handled."""
if self.status.active_frontend_endpoint_plugin or not os.path.exists(
self.hass.config.path("www/community")
if self.status.active_frontend_endpoint_plugin or not await async_exists(
self.hass, self.hass.config.path("www/community")
ludeeus marked this conversation as resolved.
Show resolved Hide resolved
):
return

Expand All @@ -1172,13 +1172,12 @@ def async_setup_frontend_endpoint_plugin(self) -> None:

self.status.active_frontend_endpoint_plugin = True

@callback
def async_setup_frontend_endpoint_themes(self) -> None:
async def async_setup_frontend_endpoint_themes(self) -> None:
"""Setup the http endpoints for themes if its not already handled."""
if (
self.configuration.experimental
or self.status.active_frontend_endpoint_theme
or not os.path.exists(self.hass.config.path("themes"))
or not await async_exists(self.hass, self.hass.config.path("themes"))
):
return

Expand Down
7 changes: 3 additions & 4 deletions custom_components/hacs/frontend.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,11 @@ def add_extra_js_url(hass: HomeAssistant, url: str, es5: bool = False) -> None:
from .base import HacsBase


@callback
def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
async def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
"""Register the frontend."""

# Setup themes endpoint if needed
hacs.async_setup_frontend_endpoint_themes()
await hacs.async_setup_frontend_endpoint_themes()

# Register frontend
if hacs.configuration.dev and (frontend_path := os.getenv("HACS_FRONTEND_DIR")):
Expand Down Expand Up @@ -82,4 +81,4 @@ def async_register_frontend(hass: HomeAssistant, hacs: HacsBase) -> None:
)

# Setup plugin endpoint if needed
hacs.async_setup_frontend_endpoint_plugin()
await hacs.async_setup_frontend_endpoint_plugin()
16 changes: 9 additions & 7 deletions custom_components/hacs/repositories/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from ..utils.backup import Backup, BackupNetDaemon
from ..utils.decode import decode_content
from ..utils.decorator import concurrent
from ..utils.file_system import async_exists, async_remove
from ..utils.filters import filter_content_return_one_of_type
from ..utils.json import json_loads
from ..utils.logger import LOGGER
Expand Down Expand Up @@ -796,8 +797,8 @@ async def remove_local_directory(self) -> None:
f"{self.hacs.configuration.theme_path}/"
f"{self.data.name}.yaml"
)
if os.path.exists(path):
os.remove(path)
if await async_exists(self.hacs.hass, path):
await async_remove(self.hacs.hass, path)
ludeeus marked this conversation as resolved.
Show resolved Hide resolved
local_path = self.content.path.local
elif self.data.category == "integration":
if not self.data.domain:
Expand All @@ -811,18 +812,18 @@ async def remove_local_directory(self) -> None:
else:
local_path = self.content.path.local

if os.path.exists(local_path):
if await async_exists(self.hacs.hass, local_path):
balloob marked this conversation as resolved.
Show resolved Hide resolved
if not is_safe(self.hacs, local_path):
self.logger.error("%s Path %s is blocked from removal", self.string, local_path)
return False
self.logger.debug("%s Removing %s", self.string, local_path)

if self.data.category in ["python_script", "template"]:
os.remove(local_path)
await async_remove(self.hacs.hass, local_path)
else:
shutil.rmtree(local_path)

while os.path.exists(local_path):
while await async_exists(self.hacs.hass, local_path):
await sleep(1)
else:
self.logger.debug(
Expand Down Expand Up @@ -942,8 +943,9 @@ async def async_install_repository(self, *, version: str | None = None, **_) ->
await self.hacs.hass.async_add_executor_job(persistent_directory.create)

elif self.repository_manifest.persistent_directory:
if os.path.exists(
f"{self.content.path.local}/{self.repository_manifest.persistent_directory}"
if await async_exists(
self.hacs.hass,
f"{self.content.path.local}/{self.repository_manifest.persistent_directory}",
):
persistent_directory = Backup(
hacs=self.hacs,
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 @@ -55,7 +55,7 @@ async def validate_repository(self):

async def async_post_installation(self):
"""Run post installation steps."""
self.hacs.async_setup_frontend_endpoint_plugin()
await self.hacs.async_setup_frontend_endpoint_plugin()

@concurrent(concurrenttasks=10, backoff_time=5)
async def update_repository(self, ignore_issues=False, force=False):
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 @@ -37,7 +37,7 @@ async def async_post_installation(self):
except BaseException: # lgtm [py/catch-base-exception] pylint: disable=broad-except
pass

self.hacs.async_setup_frontend_endpoint_themes()
await self.hacs.async_setup_frontend_endpoint_themes()

async def validate_repository(self):
"""Validate."""
Expand Down
24 changes: 24 additions & 0 deletions custom_components/hacs/utils/file_system.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
"""File system functions."""

from __future__ import annotations

import os
from typing import TypeAlias

from homeassistant.core import HomeAssistant

# From typeshed
StrOrBytesPath: TypeAlias = str | bytes | os.PathLike[str] | os.PathLike[bytes]
FileDescriptorOrPath: TypeAlias = int | StrOrBytesPath


async def async_exists(hass: HomeAssistant, path: FileDescriptorOrPath) -> bool:
"""Test whether a path exists."""
return await hass.async_add_executor_job(os.path.exists, path)


async def async_remove(
hass: HomeAssistant, path: StrOrBytesPath, *, dir_fd: int | None = None
) -> None:
"""Remove a path."""
return await hass.async_add_executor_job(os.remove, path)
Loading