mirror of
https://github.com/home-assistant/supervisor.git
synced 2025-07-19 07:06:30 +00:00
File open calls to executor (#5678)
This commit is contained in:
parent
dfed251c7a
commit
2274de969f
@ -2,6 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
from collections.abc import Awaitable
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from aiohttp import web
|
||||
@ -68,6 +69,15 @@ SCHEMA_ADD_REPOSITORY = vol.Schema(
|
||||
)
|
||||
|
||||
|
||||
def _read_static_file(path: Path) -> Any:
|
||||
"""Read in a static file asset for API output.
|
||||
|
||||
Must be run in executor.
|
||||
"""
|
||||
with path.open("r") as asset:
|
||||
return asset.read()
|
||||
|
||||
|
||||
class APIStore(CoreSysAttributes):
|
||||
"""Handle RESTful API for store functions."""
|
||||
|
||||
@ -233,8 +243,7 @@ class APIStore(CoreSysAttributes):
|
||||
if not addon.with_icon:
|
||||
raise APIError(f"No icon found for add-on {addon.slug}!")
|
||||
|
||||
with addon.path_icon.open("rb") as png:
|
||||
return png.read()
|
||||
return await self.sys_run_in_executor(_read_static_file, addon.path_icon)
|
||||
|
||||
@api_process_raw(CONTENT_TYPE_PNG)
|
||||
async def addons_addon_logo(self, request: web.Request) -> bytes:
|
||||
@ -243,8 +252,7 @@ class APIStore(CoreSysAttributes):
|
||||
if not addon.with_logo:
|
||||
raise APIError(f"No logo found for add-on {addon.slug}!")
|
||||
|
||||
with addon.path_logo.open("rb") as png:
|
||||
return png.read()
|
||||
return await self.sys_run_in_executor(_read_static_file, addon.path_logo)
|
||||
|
||||
@api_process_raw(CONTENT_TYPE_TEXT)
|
||||
async def addons_addon_changelog(self, request: web.Request) -> str:
|
||||
@ -258,8 +266,7 @@ class APIStore(CoreSysAttributes):
|
||||
if not addon.with_changelog:
|
||||
return f"No changelog found for add-on {addon.slug}!"
|
||||
|
||||
with addon.path_changelog.open("r") as changelog:
|
||||
return changelog.read()
|
||||
return await self.sys_run_in_executor(_read_static_file, addon.path_changelog)
|
||||
|
||||
@api_process_raw(CONTENT_TYPE_TEXT)
|
||||
async def addons_addon_documentation(self, request: web.Request) -> str:
|
||||
@ -273,8 +280,9 @@ class APIStore(CoreSysAttributes):
|
||||
if not addon.with_documentation:
|
||||
return f"No documentation found for add-on {addon.slug}!"
|
||||
|
||||
with addon.path_documentation.open("r") as documentation:
|
||||
return documentation.read()
|
||||
return await self.sys_run_in_executor(
|
||||
_read_static_file, addon.path_documentation
|
||||
)
|
||||
|
||||
@api_process
|
||||
async def repositories_list(self, request: web.Request) -> list[dict[str, Any]]:
|
||||
|
@ -71,7 +71,9 @@ class AppArmorControl(CoreSysAttributes):
|
||||
|
||||
async def load_profile(self, profile_name: str, profile_file: Path) -> None:
|
||||
"""Load/Update a new/exists profile into AppArmor."""
|
||||
if not validate_profile(profile_name, profile_file):
|
||||
if not await self.sys_run_in_executor(
|
||||
validate_profile, profile_name, profile_file
|
||||
):
|
||||
raise HostAppArmorError(
|
||||
f"AppArmor profile '{profile_name}' is not valid", _LOGGER.error
|
||||
)
|
||||
|
@ -278,6 +278,8 @@ class Mount(CoreSysAttributes, ABC):
|
||||
"""Mount using systemd."""
|
||||
# If supervisor can see where it will mount, ensure there's an empty folder there
|
||||
if self.local_where:
|
||||
|
||||
def ensure_empty_folder() -> None:
|
||||
if not self.local_where.exists():
|
||||
_LOGGER.info(
|
||||
"Creating folder for mount: %s", self.local_where.as_posix()
|
||||
@ -294,6 +296,8 @@ class Mount(CoreSysAttributes, ABC):
|
||||
_LOGGER.error,
|
||||
)
|
||||
|
||||
await self.sys_run_in_executor(ensure_empty_folder)
|
||||
|
||||
try:
|
||||
options = (
|
||||
[(DBUS_ATTR_OPTIONS, Variant("s", ",".join(self.options)))]
|
||||
@ -488,17 +492,23 @@ class CIFSMount(NetworkMount):
|
||||
async def mount(self) -> None:
|
||||
"""Mount using systemd."""
|
||||
if self.username and self.password:
|
||||
|
||||
def write_credentials() -> None:
|
||||
if not self.path_credentials.exists():
|
||||
self.path_credentials.touch(mode=0o600)
|
||||
|
||||
with self.path_credentials.open(mode="w") as cred_file:
|
||||
cred_file.write(f"username={self.username}\npassword={self.password}")
|
||||
cred_file.write(
|
||||
f"username={self.username}\npassword={self.password}"
|
||||
)
|
||||
|
||||
await self.sys_run_in_executor(write_credentials)
|
||||
|
||||
await super().mount()
|
||||
|
||||
async def unmount(self) -> None:
|
||||
"""Unmount using systemd."""
|
||||
self.path_credentials.unlink(missing_ok=True)
|
||||
await self.sys_run_in_executor(self.path_credentials.unlink, missing_ok=True)
|
||||
await super().unmount()
|
||||
|
||||
|
||||
|
@ -217,12 +217,15 @@ class OSManager(CoreSysAttributes):
|
||||
)
|
||||
|
||||
# Download RAUCB file
|
||||
with raucb.open("wb") as ota_file:
|
||||
ota_file = await self.sys_run_in_executor(raucb.open, "wb")
|
||||
try:
|
||||
while True:
|
||||
chunk = await request.content.read(1_048_576)
|
||||
if not chunk:
|
||||
break
|
||||
ota_file.write(chunk)
|
||||
await self.sys_run_in_executor(ota_file.write, chunk)
|
||||
finally:
|
||||
await self.sys_run_in_executor(ota_file.close)
|
||||
|
||||
_LOGGER.info("Completed download of OTA update file %s", raucb)
|
||||
|
||||
|
@ -74,7 +74,10 @@ def _read_addon_translations(addon_path: Path) -> dict:
|
||||
|
||||
|
||||
def _read_git_repository(path: Path) -> ProcessedRepository | None:
|
||||
"""Process a custom repository folder."""
|
||||
"""Process a custom repository folder.
|
||||
|
||||
Must be run in executor.
|
||||
"""
|
||||
slug = extract_hash_from_path(path)
|
||||
|
||||
# exists repository json
|
||||
|
@ -74,7 +74,10 @@ class Repository(CoreSysAttributes):
|
||||
return self.data.get(ATTR_MAINTAINER, UNKNOWN)
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""Check if store is valid."""
|
||||
"""Check if store is valid.
|
||||
|
||||
Must be run in executor.
|
||||
"""
|
||||
if self.type != StoreType.GIT:
|
||||
return True
|
||||
|
||||
@ -104,7 +107,7 @@ class Repository(CoreSysAttributes):
|
||||
|
||||
async def update(self) -> bool:
|
||||
"""Update add-on repository."""
|
||||
if not self.validate():
|
||||
if not await self.sys_run_in_executor(self.validate):
|
||||
return False
|
||||
return self.type == StoreType.LOCAL or await self.git.pull()
|
||||
|
||||
|
@ -12,7 +12,10 @@ RE_PROFILE = re.compile(r"^profile ([^ ]+).*$")
|
||||
|
||||
|
||||
def get_profile_name(profile_file: Path) -> str:
|
||||
"""Read the profile name from file."""
|
||||
"""Read the profile name from file.
|
||||
|
||||
Must be run in executor.
|
||||
"""
|
||||
profiles = set()
|
||||
|
||||
try:
|
||||
@ -42,14 +45,20 @@ def get_profile_name(profile_file: Path) -> str:
|
||||
|
||||
|
||||
def validate_profile(profile_name: str, profile_file: Path) -> bool:
|
||||
"""Check if profile from file is valid with profile name."""
|
||||
"""Check if profile from file is valid with profile name.
|
||||
|
||||
Must be run in executor.
|
||||
"""
|
||||
if profile_name == get_profile_name(profile_file):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def adjust_profile(profile_name: str, profile_file: Path, profile_new: Path) -> None:
|
||||
"""Fix the profile name."""
|
||||
"""Fix the profile name.
|
||||
|
||||
Must be run in executor.
|
||||
"""
|
||||
org_profile = get_profile_name(profile_file)
|
||||
profile_data = []
|
||||
|
||||
|
@ -19,7 +19,10 @@ _DEFAULT: dict[str, Any] = {}
|
||||
|
||||
|
||||
def find_one_filetype(path: Path, filename: str, filetypes: list[str]) -> Path:
|
||||
"""Find first file matching filetypes."""
|
||||
"""Find first file matching filetypes.
|
||||
|
||||
Must be run in executor.
|
||||
"""
|
||||
for file in path.glob(f"**/{filename}.*"):
|
||||
if file.suffix in filetypes:
|
||||
return file
|
||||
@ -27,7 +30,10 @@ def find_one_filetype(path: Path, filename: str, filetypes: list[str]) -> Path:
|
||||
|
||||
|
||||
def read_json_or_yaml_file(path: Path) -> dict:
|
||||
"""Read JSON or YAML file."""
|
||||
"""Read JSON or YAML file.
|
||||
|
||||
Must be run in executor.
|
||||
"""
|
||||
if path.suffix == ".json":
|
||||
return read_json_file(path)
|
||||
|
||||
@ -38,7 +44,10 @@ def read_json_or_yaml_file(path: Path) -> dict:
|
||||
|
||||
|
||||
def write_json_or_yaml_file(path: Path, data: dict) -> None:
|
||||
"""Write JSON or YAML file."""
|
||||
"""Write JSON or YAML file.
|
||||
|
||||
Must be run in executor.
|
||||
"""
|
||||
if path.suffix == ".json":
|
||||
return write_json_file(path, data)
|
||||
|
||||
|
@ -17,7 +17,10 @@ _LOGGER: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def read_yaml_file(path: Path) -> dict:
|
||||
"""Read YAML file from path."""
|
||||
"""Read YAML file from path.
|
||||
|
||||
Must be run in executor.
|
||||
"""
|
||||
try:
|
||||
with open(path, encoding="utf-8") as yaml_file:
|
||||
return load(yaml_file, Loader=SafeLoader) or {}
|
||||
@ -29,7 +32,10 @@ def read_yaml_file(path: Path) -> dict:
|
||||
|
||||
|
||||
def write_yaml_file(path: Path, data: dict) -> None:
|
||||
"""Write a YAML file."""
|
||||
"""Write a YAML file.
|
||||
|
||||
Must be run in executor.
|
||||
"""
|
||||
try:
|
||||
with atomic_write(path, overwrite=True) as fp:
|
||||
dump(data, fp, Dumper=Dumper)
|
||||
|
@ -3,6 +3,7 @@
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from unittest.util import unorderable_list_difference
|
||||
|
||||
from dbus_fast import DBusError, ErrorType, Variant
|
||||
from dbus_fast.aio.message_bus import MessageBus
|
||||
@ -111,7 +112,9 @@ async def test_load(
|
||||
assert media_test.local_where.is_dir()
|
||||
assert (coresys.config.path_media / "media_test").is_dir()
|
||||
|
||||
assert systemd_service.StartTransientUnit.calls == [
|
||||
assert unorderable_list_difference(
|
||||
systemd_service.StartTransientUnit.calls,
|
||||
[
|
||||
(
|
||||
"mnt-data-supervisor-mounts-backup_test.mount",
|
||||
"fail",
|
||||
@ -139,12 +142,16 @@ async def test_load(
|
||||
"fail",
|
||||
[
|
||||
["Options", Variant("s", "bind")],
|
||||
["Description", Variant("s", "Supervisor bind mount: bind_media_test")],
|
||||
[
|
||||
"Description",
|
||||
Variant("s", "Supervisor bind mount: bind_media_test"),
|
||||
],
|
||||
["What", Variant("s", "/mnt/data/supervisor/mounts/media_test")],
|
||||
],
|
||||
[],
|
||||
),
|
||||
]
|
||||
],
|
||||
) == ([], [])
|
||||
|
||||
|
||||
async def test_load_share_mount(
|
||||
|
Loading…
x
Reference in New Issue
Block a user