summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PKGBUILD2
-rw-r--r--kvmd/plugins/msd/otg/__init__.py10
-rw-r--r--kvmd/plugins/msd/otg/storage.py107
3 files changed, 67 insertions, 52 deletions
diff --git a/PKGBUILD b/PKGBUILD
index 16cf9554..9ffe4af5 100644
--- a/PKGBUILD
+++ b/PKGBUILD
@@ -43,7 +43,7 @@ depends=(
"python<3.11"
python-yaml
"python-aiohttp>=3.7.4.post0-1.1"
- python-aiofiles
+ "python-aiofiles>=23.1.0-1"
python-passlib
python-pyotp
python-qrcode
diff --git a/kvmd/plugins/msd/otg/__init__.py b/kvmd/plugins/msd/otg/__init__.py
index 53082cc6..02b9ceeb 100644
--- a/kvmd/plugins/msd/otg/__init__.py
+++ b/kvmd/plugins/msd/otg/__init__.py
@@ -399,7 +399,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
async def __STORAGE_create_new_image(self, name: str) -> Image: # pylint: disable=invalid-name
assert self.__state.storage
- image = self.__storage.get_image_by_name(name)
+ image = await self.__storage.get_image_by_name(name)
if image.name in self.__state.storage.images or (await image.exists()):
raise MsdImageExistsError()
return image
@@ -461,7 +461,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
logger = get_logger(0)
async with self.__state._lock: # pylint: disable=protected-access
try:
- drive_state = self.__get_drive_state()
+ drive_state = await self.__get_drive_state()
if self.__state.vd is None and drive_state.image is None:
# Если только что включились и образ не подключен - попробовать
@@ -500,7 +500,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
async def __setup_initial(self) -> None:
if self.__initial_image:
logger = get_logger(0)
- image = self.__storage.get_image_by_name(self.__initial_image)
+ image = await self.__storage.get_image_by_name(self.__initial_image)
if (await image.exists()):
logger.info("Setting up initial image %r ...", self.__initial_image)
try:
@@ -524,10 +524,10 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
images=images,
)
- def __get_drive_state(self) -> _DriveState:
+ async def __get_drive_state(self) -> _DriveState:
path = self.__drive.get_image_path()
return _DriveState(
- image=(self.__storage.get_image_by_path(path) if path else None),
+ image=((await self.__storage.get_image_by_path(path)) if path else None),
cdrom=self.__drive.get_cdrom_flag(),
rw=self.__drive.get_rw_flag(),
)
diff --git a/kvmd/plugins/msd/otg/storage.py b/kvmd/plugins/msd/otg/storage.py
index c6a3e34b..d766537c 100644
--- a/kvmd/plugins/msd/otg/storage.py
+++ b/kvmd/plugins/msd/otg/storage.py
@@ -21,6 +21,7 @@
import os
+import asyncio
import operator
import dataclasses
@@ -43,7 +44,7 @@ from .. import MsdError
class _Image:
name: str
path: str
- in_storage: bool = dataclasses.field(init=False)
+ in_storage: bool = dataclasses.field(init=False, compare=False)
complete: bool = dataclasses.field(init=False, compare=False)
removable: bool = dataclasses.field(init=False, compare=False)
size: int = dataclasses.field(init=False, compare=False)
@@ -56,39 +57,55 @@ class Image(_Image):
self.__storage = storage
(self.__dir_path, file_name) = os.path.split(path)
self.__complete_path = os.path.join(self.__dir_path, f".__{file_name}.complete")
- self.__adopted = (storage._is_adopted(self) if storage else True)
+ self.__adopted = False
+
+ async def _update(self) -> None:
+ # adopted используется в последующих проверках
+ self.__adopted = await aiotools.run_async(self.__is_adopted)
+ (complete, removable, (size, mod_ts)) = await asyncio.gather(
+ self.__is_complete(),
+ self.__is_removable(),
+ self.__get_stat(),
+ )
+ object.__setattr__(self, "complete", complete)
+ object.__setattr__(self, "removable", removable)
+ object.__setattr__(self, "size", size)
+ object.__setattr__(self, "mod_ts", mod_ts)
- @property
- def in_storage(self) -> bool:
- return bool(self.__storage)
+ def __is_adopted(self) -> bool:
+ # True, если образ находится вне хранилища
+ # или в другой точке монтирования под ним
+ if self.__storage is None:
+ return True
+ path = self.path
+ while not os.path.ismount(path):
+ path = os.path.dirname(path)
+ return (self.__storage.get_root_path() != path)
- @property
- def complete(self) -> bool:
+ async def __is_complete(self) -> bool:
if self.__storage:
- return os.path.exists(self.__complete_path)
+ return (await aiofiles.os.path.exists(self.__complete_path))
return True
- @property
- def removable(self) -> bool:
+ async def __is_removable(self) -> bool:
if not self.__storage:
return False
if not self.__adopted:
return True
- return os.access(self.__dir_path, os.W_OK)
+ return (await aiofiles.os.access(self.__dir_path, os.W_OK)) # type: ignore
- @property
- def size(self) -> int:
+ async def __get_stat(self) -> tuple[int, float]:
try:
- return os.stat(self.path).st_size
+ st = (await aiofiles.os.stat(self.path))
+ return (st.st_size, st.st_mtime)
except Exception:
- return 0
+ return (0, 0.0)
+
+ # =====
@property
- def mod_ts(self) -> float:
- try:
- return os.stat(self.path).st_mtime
- except Exception:
- return 0.0
+ def in_storage(self) -> bool:
+ return bool(self.__storage)
async def exists(self) -> bool:
return (await aiofiles.os.path.exists(self.path))
@@ -119,6 +136,7 @@ class Image(_Image):
await aiofiles.os.remove(self.__complete_path)
except FileNotFoundError:
pass
+ await self._update()
@dataclasses.dataclass(frozen=True)
@@ -132,22 +150,27 @@ class Storage:
self.__path = path
self.__remount_cmd = remount_cmd
+ def get_root_path(self) -> str:
+ return self.__path
+
async def get_watchable_paths(self) -> list[str]:
- return (await aiotools.run_async(self.__get_watchable_paths))
+ return (await aiotools.run_async(self.__inner_get_watchable_paths))
async def get_images(self) -> dict[str, Image]:
- return (await aiotools.run_async(self.__get_images))
+ return {
+ name: (await self.get_image_by_name(name))
+ for name in (await aiotools.run_async(self.__inner_get_images))
+ }
- def __get_watchable_paths(self) -> list[str]:
+ def __inner_get_watchable_paths(self) -> list[str]:
return list(map(operator.itemgetter(0), self.__walk(with_files=False)))
- def __get_images(self) -> dict[str, Image]:
- images: dict[str, Image] = {}
- for (_, files) in self.__walk(with_files=True):
- for path in files:
- name = os.path.relpath(path, self.__path)
- images[name] = self.get_image_by_name(name)
- return images
+ def __inner_get_images(self) -> list[str]:
+ return [
+ os.path.relpath(path, self.__path) # == name
+ for (_, files) in self.__walk(with_files=True)
+ for path in files
+ ]
def __walk(self, with_files: bool, root_path: (str | None)=None) -> Generator[tuple[str, list[str]], None, None]:
if root_path is None:
@@ -169,24 +192,26 @@ class Storage:
# =====
- def get_image_by_name(self, name: str) -> Image:
+ async def get_image_by_name(self, name: str) -> Image:
assert name
path = os.path.join(self.__path, name)
- return self.__get_image(name, path, True)
+ return (await self.__get_image(name, path, True))
- def get_image_by_path(self, path: str) -> Image:
+ async def get_image_by_path(self, path: str) -> Image:
assert path
in_storage = (os.path.commonpath([self.__path, path]) == self.__path)
if in_storage:
name = os.path.relpath(path, self.__path)
else:
name = os.path.basename(path)
- return self.__get_image(name, path, in_storage)
+ return (await self.__get_image(name, path, in_storage))
- def __get_image(self, name: str, path: str, in_storage: bool) -> Image:
+ async def __get_image(self, name: str, path: str, in_storage: bool) -> Image:
assert name
assert path
- return Image(name, path, (self if in_storage else None))
+ image = Image(name, path, (self if in_storage else None))
+ await image._update() # pylint: disable=protected-access
+ return image
# =====
@@ -203,16 +228,6 @@ class Storage:
free=(st.f_bavail * st.f_frsize),
)
- def _is_adopted(self, image: Image) -> bool:
- # True, если образ находится вне хранилища
- # или в другой точке монтирования под ним
- if not image.in_storage:
- return True
- path = image.path
- while not os.path.ismount(path):
- path = os.path.dirname(path)
- return (self.__path != path)
-
async def remount_rw(self, rw: bool, fatal: bool=True) -> None:
if not (await aiohelpers.remount("MSD", self.__remount_cmd, rw)):
if fatal: