summaryrefslogtreecommitdiff
path: root/kvmd/plugins
diff options
context:
space:
mode:
authorMaxim Devaev <[email protected]>2023-03-06 03:01:12 +0200
committerMaxim Devaev <[email protected]>2023-03-06 03:16:37 +0200
commit5495f70564e03ae0d6ac81866a37cdd79858d4e4 (patch)
tree5f72e11179000691bad65ff3d69647ae706e2237 /kvmd/plugins
parentc63bb2adb788ed07d12f446ee12bd692004b59d6 (diff)
msd images tree
Diffstat (limited to 'kvmd/plugins')
-rw-r--r--kvmd/plugins/msd/otg/__init__.py6
-rw-r--r--kvmd/plugins/msd/otg/storage.py67
2 files changed, 48 insertions, 25 deletions
diff --git a/kvmd/plugins/msd/otg/__init__.py b/kvmd/plugins/msd/otg/__init__.py
index 4c0a7863..ce43d3d8 100644
--- a/kvmd/plugins/msd/otg/__init__.py
+++ b/kvmd/plugins/msd/otg/__init__.py
@@ -442,9 +442,9 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
need_reload_state = False
for event in (await inotify.get_series(timeout=1)):
need_reload_state = True
- if event.mask & (InotifyMask.DELETE_SELF | InotifyMask.MOVE_SELF | InotifyMask.UNMOUNT):
- # Если выгрузили OTG, что-то отмонтировали или делают еще какую-то странную фигню
- logger.warning("Got fatal inotify event: %s; reinitializing MSD ...", event)
+ if event.mask & (InotifyMask.DELETE_SELF | InotifyMask.MOVE_SELF | InotifyMask.UNMOUNT | InotifyMask.ISDIR):
+ # Если выгрузили OTG, изменили каталоги, что-то отмонтировали или делают еще какую-то странную фигню
+ logger.info("Got a big inotify event: %s; reinitializing MSD ...", event)
need_restart = True
break
if need_restart:
diff --git a/kvmd/plugins/msd/otg/storage.py b/kvmd/plugins/msd/otg/storage.py
index 2820c18d..d5f90334 100644
--- a/kvmd/plugins/msd/otg/storage.py
+++ b/kvmd/plugins/msd/otg/storage.py
@@ -23,6 +23,7 @@
import os
import dataclasses
+from typing import Generator
from typing import Optional
from ....logging import get_logger
@@ -39,25 +40,25 @@ class _Image:
path: str
storage: Optional["Storage"] = dataclasses.field(compare=False)
- complete: bool = dataclasses.field(init=False, compare=False)
- in_storage: bool = dataclasses.field(init=False, compare=False)
+ in_storage: bool = dataclasses.field(init=False)
+ complete: bool = dataclasses.field(init=False, compare=False)
size: int = dataclasses.field(init=False, compare=False)
mod_ts: float = dataclasses.field(init=False, compare=False)
class Image(_Image):
@property
+ def in_storage(self) -> bool:
+ return (self.storage is not None)
+
+ @property
def complete(self) -> bool:
if self.storage is not None:
return os.path.exists(self.__get_complete_path())
return True
@property
- def in_storage(self) -> bool:
- return (self.storage is not None)
-
- @property
def size(self) -> int:
try:
return os.stat(self.path).st_size
@@ -69,14 +70,15 @@ class Image(_Image):
try:
return os.stat(self.path).st_mtime
except Exception:
- return 0
+ return 0.0
def exists(self) -> bool:
return os.path.exists(self.path)
async def remount_rw(self, rw: bool, fatal: bool=True) -> None:
assert self.storage
- await self.storage.remount_rw(rw, fatal)
+ if self.storage._is_mounted(self): # pylint: disable=protected-access
+ await self.storage.remount_rw(rw, fatal)
def remove(self, fatal: bool) -> None:
assert self.storage is not None
@@ -101,7 +103,10 @@ class Image(_Image):
pass
def __get_complete_path(self) -> str:
- return os.path.join(os.path.dirname(self.path), f".__{self.name}.complete")
+ return os.path.join(
+ os.path.dirname(self.path),
+ ".__" + os.path.basename(self.path) + ".complete",
+ )
@dataclasses.dataclass(frozen=True)
@@ -116,31 +121,43 @@ class Storage:
self.__remount_cmd = remount_cmd
def get_watchable_paths(self) -> list[str]:
- return [self.__path]
+ paths: list[str] = []
+ for (root_path, dirs, _) in os.walk(self.__path):
+ dirs[:] = list(self.__filter(dirs))
+ paths.append(root_path)
+ return paths
def get_images(self) -> dict[str, Image]:
- return {
- name: self.get_image_by_name(name)
- for name in os.listdir(self.__path)
- if not name.startswith(".__") and name != "lost+found"
- }
+ images: dict[str, Image] = {}
+ for (root_path, dirs, files) in os.walk(self.__path):
+ dirs[:] = list(self.__filter(dirs))
+ for file in self.__filter(files):
+ name = os.path.relpath(os.path.join(root_path, file), self.__path)
+ images[name] = self.get_image_by_name(name)
+ return images
+
+ def __filter(self, items: list[str]) -> Generator[str, None, None]:
+ for item in sorted(map(str.strip, items)):
+ if not item.startswith(".__") and item != "lost+found":
+ yield item
def get_image_by_name(self, name: str) -> Image:
assert name
path = os.path.join(self.__path, name)
- return self.__get_image(name, path)
+ return self.__get_image(name, path, True)
def get_image_by_path(self, path: str) -> Image:
assert path
- name = os.path.basename(path)
- return self.__get_image(name, 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)
- def __get_image(self, name: str, path: str) -> Image:
+ def __get_image(self, name: str, path: str, in_storage: bool) -> Image:
assert name
- assert not name.startswith(".__")
- assert name != "lost+found"
assert path
- in_storage = (os.path.dirname(path) == self.__path)
return Image(name, path, (self if in_storage else None))
def get_space(self, fatal: bool) -> (StorageSpace | None):
@@ -156,6 +173,12 @@ class Storage:
free=(st.f_bavail * st.f_frsize),
)
+ def _is_mounted(self, image: Image) -> bool:
+ path = image.path
+ while not os.path.ismount(path):
+ path = os.path.dirname(path)
+ return (path == self.__path)
+
async def remount_rw(self, rw: bool, fatal: bool=True) -> None:
if not (await aiohelpers.remount("MSD", self.__remount_cmd, rw)):
if fatal: