mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2025-12-12 01:00:29 +08:00
removed _StorageState
This commit is contained in:
parent
2189512e0b
commit
166cb8e3b7
@ -67,14 +67,6 @@ class _DriveState:
|
|||||||
rw: bool
|
rw: bool
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True)
|
|
||||||
class _StorageState:
|
|
||||||
size: int
|
|
||||||
free: int
|
|
||||||
images: dict[str, Image]
|
|
||||||
|
|
||||||
|
|
||||||
# =====
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class _VirtualDriveState:
|
class _VirtualDriveState:
|
||||||
image: (Image | None)
|
image: (Image | None)
|
||||||
@ -96,7 +88,7 @@ class _State:
|
|||||||
def __init__(self, notifier: aiotools.AioNotifier) -> None:
|
def __init__(self, notifier: aiotools.AioNotifier) -> None:
|
||||||
self.__notifier = notifier
|
self.__notifier = notifier
|
||||||
|
|
||||||
self.storage: (_StorageState | None) = None
|
self.storage: (Storage | None) = None
|
||||||
self.vd: (_VirtualDriveState | None) = None
|
self.vd: (_VirtualDriveState | None) = None
|
||||||
|
|
||||||
self._region = aiotools.AioExclusiveRegion(MsdIsBusyError)
|
self._region = aiotools.AioExclusiveRegion(MsdIsBusyError)
|
||||||
@ -175,21 +167,17 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
|||||||
async with self.__state._lock: # pylint: disable=protected-access
|
async with self.__state._lock: # pylint: disable=protected-access
|
||||||
storage: (dict | None) = None
|
storage: (dict | None) = None
|
||||||
if self.__state.storage:
|
if self.__state.storage:
|
||||||
|
if self.__writer:
|
||||||
|
# При загрузке файла показываем актуальную статистику вручную
|
||||||
|
await self.__storage.reload_size_only()
|
||||||
|
|
||||||
storage = dataclasses.asdict(self.__state.storage)
|
storage = dataclasses.asdict(self.__state.storage)
|
||||||
for name in list(storage["images"]):
|
for name in list(storage["images"]):
|
||||||
del storage["images"][name]["path"]
|
del storage["images"][name]["path"]
|
||||||
del storage["images"][name]["in_storage"]
|
del storage["images"][name]["in_storage"]
|
||||||
|
|
||||||
storage["downloading"] = (self.__reader.get_state() if self.__reader else None)
|
storage["downloading"] = (self.__reader.get_state() if self.__reader else None)
|
||||||
|
storage["uploading"] = (self.__writer.get_state() if self.__writer else None)
|
||||||
if self.__writer:
|
|
||||||
# При загрузке файла показываем актуальную статистику вручную
|
|
||||||
storage["uploading"] = self.__writer.get_state()
|
|
||||||
space = self.__storage.get_space(fatal=False)
|
|
||||||
if space:
|
|
||||||
storage.update(dataclasses.asdict(space))
|
|
||||||
else:
|
|
||||||
storage["uploading"] = None
|
|
||||||
|
|
||||||
vd: (dict | None) = None
|
vd: (dict | None) = None
|
||||||
if self.__state.vd:
|
if self.__state.vd:
|
||||||
@ -373,7 +361,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
|||||||
await image.remount_rw(True)
|
await image.remount_rw(True)
|
||||||
try:
|
try:
|
||||||
await image.remove(fatal=True)
|
await image.remove(fatal=True)
|
||||||
del self.__state.storage.images[name]
|
|
||||||
finally:
|
finally:
|
||||||
await aiotools.shield_fg(image.remount_rw(False, fatal=False))
|
await aiotools.shield_fg(image.remount_rw(False, fatal=False))
|
||||||
|
|
||||||
@ -431,7 +418,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
|||||||
|
|
||||||
with Inotify() as inotify:
|
with Inotify() as inotify:
|
||||||
for path in [
|
for path in [
|
||||||
*(await self.__storage.get_watchable_paths()),
|
*self.__storage.get_watchable_paths(),
|
||||||
*self.__drive.get_watchable_paths(),
|
*self.__drive.get_watchable_paths(),
|
||||||
]:
|
]:
|
||||||
await inotify.watch(path, InotifyMask.ALL_MODIFY_EVENTS)
|
await inotify.watch(path, InotifyMask.ALL_MODIFY_EVENTS)
|
||||||
@ -461,7 +448,14 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
|||||||
logger = get_logger(0)
|
logger = get_logger(0)
|
||||||
async with self.__state._lock: # pylint: disable=protected-access
|
async with self.__state._lock: # pylint: disable=protected-access
|
||||||
try:
|
try:
|
||||||
drive_state = await self.__make_init_drive_state()
|
path = self.__drive.get_image_path()
|
||||||
|
drive_state = _DriveState(
|
||||||
|
image=((await self.__storage.make_image_by_path(path)) if path else None),
|
||||||
|
cdrom=self.__drive.get_cdrom_flag(),
|
||||||
|
rw=self.__drive.get_rw_flag(),
|
||||||
|
)
|
||||||
|
|
||||||
|
await self.__storage.reload()
|
||||||
|
|
||||||
if self.__state.vd is None and drive_state.image is None:
|
if self.__state.vd is None and drive_state.image is None:
|
||||||
# Если только что включились и образ не подключен - попробовать
|
# Если только что включились и образ не подключен - попробовать
|
||||||
@ -471,15 +465,13 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
|||||||
await self.__storage.remount_rw(False)
|
await self.__storage.remount_rw(False)
|
||||||
await self.__setup_initial()
|
await self.__setup_initial()
|
||||||
|
|
||||||
storage_state = await self.__make_init_storage_state()
|
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Error while reloading MSD state; switching to offline")
|
logger.exception("Error while reloading MSD state; switching to offline")
|
||||||
self.__state.storage = None
|
self.__state.storage = None
|
||||||
self.__state.vd = None
|
self.__state.vd = None
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.__state.storage = storage_state
|
self.__state.storage = self.__storage
|
||||||
if drive_state.image:
|
if drive_state.image:
|
||||||
# При подключенном образе виртуальный стейт заменяется реальным
|
# При подключенном образе виртуальный стейт заменяется реальным
|
||||||
self.__state.vd = _VirtualDriveState.from_drive_state(drive_state)
|
self.__state.vd = _VirtualDriveState.from_drive_state(drive_state)
|
||||||
@ -511,23 +503,3 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
|||||||
logger.exception("Can't setup initial image: ignored")
|
logger.exception("Can't setup initial image: ignored")
|
||||||
else:
|
else:
|
||||||
logger.error("Can't find initial image %r: ignored", self.__initial_image)
|
logger.error("Can't find initial image %r: ignored", self.__initial_image)
|
||||||
|
|
||||||
# =====
|
|
||||||
|
|
||||||
async def __make_init_storage_state(self) -> _StorageState:
|
|
||||||
images = await self.__storage.get_images()
|
|
||||||
space = self.__storage.get_space(fatal=True)
|
|
||||||
assert space
|
|
||||||
return _StorageState(
|
|
||||||
size=space.size,
|
|
||||||
free=space.free,
|
|
||||||
images=images,
|
|
||||||
)
|
|
||||||
|
|
||||||
async def __make_init_drive_state(self) -> _DriveState:
|
|
||||||
path = self.__drive.get_image_path()
|
|
||||||
return _DriveState(
|
|
||||||
image=((await self.__storage.make_image_by_path(path)) if path else None),
|
|
||||||
cdrom=self.__drive.get_cdrom_flag(),
|
|
||||||
rw=self.__drive.get_rw_flag(),
|
|
||||||
)
|
|
||||||
|
|||||||
@ -31,8 +31,6 @@ from typing import Optional
|
|||||||
import aiofiles
|
import aiofiles
|
||||||
import aiofiles.os
|
import aiofiles.os
|
||||||
|
|
||||||
from ....logging import get_logger
|
|
||||||
|
|
||||||
from .... import aiotools
|
from .... import aiotools
|
||||||
from .... import aiohelpers
|
from .... import aiohelpers
|
||||||
|
|
||||||
@ -59,7 +57,7 @@ class Image(_Image):
|
|||||||
self.__complete_path = os.path.join(self.__dir_path, f".__{file_name}.complete")
|
self.__complete_path = os.path.join(self.__dir_path, f".__{file_name}.complete")
|
||||||
self.__adopted = False
|
self.__adopted = False
|
||||||
|
|
||||||
async def _update(self) -> None:
|
async def _reload(self) -> None: # Only for Storage() and set_complete()
|
||||||
# adopted используется в последующих проверках
|
# adopted используется в последующих проверках
|
||||||
self.__adopted = await aiotools.run_async(self.__is_adopted)
|
self.__adopted = await aiotools.run_async(self.__is_adopted)
|
||||||
(complete, removable, (size, mod_ts)) = await asyncio.gather(
|
(complete, removable, (size, mod_ts)) = await asyncio.gather(
|
||||||
@ -80,7 +78,7 @@ class Image(_Image):
|
|||||||
path = self.path
|
path = self.path
|
||||||
while not os.path.ismount(path):
|
while not os.path.ismount(path):
|
||||||
path = os.path.dirname(path)
|
path = os.path.dirname(path)
|
||||||
return (self.__storage._get_root_path() != path)
|
return (self.__storage._get_root_path() != path) # pylint: disable=protected-access
|
||||||
|
|
||||||
async def __is_complete(self) -> bool:
|
async def __is_complete(self) -> bool:
|
||||||
if self.__storage:
|
if self.__storage:
|
||||||
@ -119,6 +117,7 @@ class Image(_Image):
|
|||||||
assert self.__storage
|
assert self.__storage
|
||||||
try:
|
try:
|
||||||
await aiofiles.os.remove(self.path)
|
await aiofiles.os.remove(self.path)
|
||||||
|
self.__storage.images.pop(self.name, None)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
@ -136,45 +135,60 @@ class Image(_Image):
|
|||||||
await aiofiles.os.remove(self.__complete_path)
|
await aiofiles.os.remove(self.__complete_path)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
pass
|
pass
|
||||||
await self._update()
|
await self._reload()
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True, eq=False)
|
||||||
class StorageSpace:
|
class _Storage:
|
||||||
size: int
|
size: int
|
||||||
free: int
|
free: int
|
||||||
|
images: dict[str, Image] = dataclasses.field(init=False)
|
||||||
|
|
||||||
|
|
||||||
class Storage:
|
class Storage(_Storage):
|
||||||
def __init__(self, path: str, remount_cmd: list[str]) -> None:
|
def __init__(self, path: str, remount_cmd: list[str]) -> None:
|
||||||
|
super().__init__(0, 0)
|
||||||
self.__path = path
|
self.__path = path
|
||||||
self.__remount_cmd = remount_cmd
|
self.__remount_cmd = remount_cmd
|
||||||
|
self.__watchable_paths: (list[str] | None) = None
|
||||||
|
self.__images: (dict[str, Image] | None) = None
|
||||||
|
|
||||||
def _get_root_path(self) -> str:
|
@property
|
||||||
return self.__path
|
def images(self) -> dict[str, Image]:
|
||||||
|
assert self.__watchable_paths is not None
|
||||||
|
assert self.__images is not None
|
||||||
|
return self.__images
|
||||||
|
|
||||||
async def get_watchable_paths(self) -> list[str]:
|
async def reload(self) -> None:
|
||||||
return (await aiotools.run_async(self.__inner_get_watchable_paths))
|
self.__watchable_paths = None
|
||||||
|
self.__images = {}
|
||||||
|
|
||||||
async def get_images(self) -> dict[str, Image]:
|
watchable_paths: list[str] = []
|
||||||
return {
|
images: dict[str, Image] = {}
|
||||||
name: (await self.make_image_by_name(name))
|
for (root_path, files) in (await aiotools.run_async(self.__walk)):
|
||||||
for name in (await aiotools.run_async(self.__inner_get_images))
|
watchable_paths.append(root_path)
|
||||||
}
|
for path in files:
|
||||||
|
name = os.path.relpath(path, self.__path)
|
||||||
|
images[name] = await self.make_image_by_name(name)
|
||||||
|
|
||||||
def __inner_get_watchable_paths(self) -> list[str]:
|
await self.reload_size_only()
|
||||||
return list(map(operator.itemgetter(0), self.__walk(with_files=False)))
|
|
||||||
|
|
||||||
def __inner_get_images(self) -> list[str]:
|
self.__watchable_paths = watchable_paths
|
||||||
return [
|
self.__images = images
|
||||||
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]:
|
async def reload_size_only(self) -> None:
|
||||||
if root_path is None:
|
st = os.statvfs(self.__path) # FIXME
|
||||||
root_path = self.__path
|
object.__setattr__(self, "size", st.f_blocks * st.f_frsize)
|
||||||
|
object.__setattr__(self, "free", st.f_bavail * st.f_frsize)
|
||||||
|
|
||||||
|
def get_watchable_paths(self) -> list[str]:
|
||||||
|
assert self.__watchable_paths is not None
|
||||||
|
return list(self.__watchable_paths)
|
||||||
|
|
||||||
|
def __walk(self) -> list[tuple[str, list[str]]]:
|
||||||
|
return list(self.__inner_walk(self.__path))
|
||||||
|
|
||||||
|
def __inner_walk(self, root_path: str) -> Generator[tuple[str, list[str]], None, None]:
|
||||||
files: list[str] = []
|
files: list[str] = []
|
||||||
with os.scandir(root_path) as dir_iter:
|
with os.scandir(root_path) as dir_iter:
|
||||||
for item in sorted(dir_iter, key=operator.attrgetter("name")):
|
for item in sorted(dir_iter, key=operator.attrgetter("name")):
|
||||||
@ -183,8 +197,8 @@ class Storage:
|
|||||||
try:
|
try:
|
||||||
if item.is_dir(follow_symlinks=False):
|
if item.is_dir(follow_symlinks=False):
|
||||||
item.stat() # Проверяем, не сдохла ли смонтированная NFS
|
item.stat() # Проверяем, не сдохла ли смонтированная NFS
|
||||||
yield from self.__walk(with_files, item.path)
|
yield from self.__inner_walk(item.path)
|
||||||
elif with_files and item.is_file(follow_symlinks=False):
|
elif item.is_file(follow_symlinks=False):
|
||||||
files.append(item.path)
|
files.append(item.path)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@ -195,7 +209,7 @@ class Storage:
|
|||||||
async def make_image_by_name(self, name: str) -> Image:
|
async def make_image_by_name(self, name: str) -> Image:
|
||||||
assert name
|
assert name
|
||||||
path = os.path.join(self.__path, name)
|
path = os.path.join(self.__path, name)
|
||||||
return (await self.__get_image(name, path, True))
|
return (await self.__make_image(name, path, True))
|
||||||
|
|
||||||
async def make_image_by_path(self, path: str) -> Image:
|
async def make_image_by_path(self, path: str) -> Image:
|
||||||
assert path
|
assert path
|
||||||
@ -204,29 +218,19 @@ class Storage:
|
|||||||
name = os.path.relpath(path, self.__path)
|
name = os.path.relpath(path, self.__path)
|
||||||
else:
|
else:
|
||||||
name = os.path.basename(path)
|
name = os.path.basename(path)
|
||||||
return (await self.__get_image(name, path, in_storage))
|
return (await self.__make_image(name, path, in_storage))
|
||||||
|
|
||||||
async def __get_image(self, name: str, path: str, in_storage: bool) -> Image:
|
async def __make_image(self, name: str, path: str, in_storage: bool) -> Image:
|
||||||
assert name
|
assert name
|
||||||
assert path
|
assert path
|
||||||
image = 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
|
await image._reload() # pylint: disable=protected-access
|
||||||
return image
|
return image
|
||||||
|
|
||||||
# =====
|
def _get_root_path(self) -> str: # Only for Image()
|
||||||
|
return self.__path
|
||||||
|
|
||||||
def get_space(self, fatal: bool) -> (StorageSpace | None):
|
# =====
|
||||||
try:
|
|
||||||
st = os.statvfs(self.__path)
|
|
||||||
except Exception as err:
|
|
||||||
if fatal:
|
|
||||||
raise
|
|
||||||
get_logger().warning("Can't get free space of filesystem %s: %s", self.__path, err)
|
|
||||||
return None
|
|
||||||
return StorageSpace(
|
|
||||||
size=(st.f_blocks * st.f_frsize),
|
|
||||||
free=(st.f_bavail * st.f_frsize),
|
|
||||||
)
|
|
||||||
|
|
||||||
async def remount_rw(self, rw: bool, fatal: bool=True) -> None:
|
async def remount_rw(self, rw: bool, fatal: bool=True) -> None:
|
||||||
if not (await aiohelpers.remount("MSD", self.__remount_cmd, rw)):
|
if not (await aiohelpers.remount("MSD", self.__remount_cmd, rw)):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user