otg msd: notify about free space while uploading

This commit is contained in:
Devaev Maxim 2019-11-08 03:53:00 +03:00
parent 13dcbc0c62
commit fa40676136
3 changed files with 88 additions and 19 deletions

View File

@ -24,6 +24,7 @@ import os
import asyncio import asyncio
import contextlib import contextlib
import dataclasses import dataclasses
import time
from typing import List from typing import List
from typing import Dict from typing import Dict
@ -56,10 +57,10 @@ from .. import MsdImageExistsError
from .. import MsdIsBusyError from .. import MsdIsBusyError
from .. import BaseMsd from .. import BaseMsd
from .drive import Drive from . import fs
from . import helpers
from .helpers import remount_storage from .drive import Drive
from .helpers import unlock_drive
# ===== # =====
@ -152,6 +153,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
self.__new_file: Optional[aiofiles.base.AiofilesContextManager] = None self.__new_file: Optional[aiofiles.base.AiofilesContextManager] = None
self.__new_file_written = 0 self.__new_file_written = 0
self.__new_file_tick = 0.0
self.__changes_queue: asyncio.queues.Queue = asyncio.Queue() self.__changes_queue: asyncio.queues.Queue = asyncio.Queue()
@ -179,7 +181,12 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
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["uploading"] = bool(self.__new_file) storage["uploading"] = bool(self.__new_file)
if self.__new_file: # При загрузке файла показываем размер вручную
space = fs.get_fs_space(self.__storage_path, fatal=False)
if space:
storage.update(dataclasses.asdict(space))
vd: Optional[Dict] = None vd: Optional[Dict] = None
if self.__state.vd: if self.__state.vd:
@ -331,6 +338,9 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
except Exception: except Exception:
pass pass
finally: finally:
# Между закрытием файла и эвентом айнотифи состояние может быть не обновлено,
# так что форсим обновление вручную, чтобы получить актуальное состояние.
await self.__reload_state()
await self.__changes_queue.put(None) await self.__changes_queue.put(None)
@aiotools.atomic @aiotools.atomic
@ -338,6 +348,11 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
assert self.__new_file assert self.__new_file
await aiotools.afile_write_now(self.__new_file, chunk) await aiotools.afile_write_now(self.__new_file, chunk)
self.__new_file_written += len(chunk) self.__new_file_written += len(chunk)
now = time.time()
if self.__new_file_tick + 1 < now:
# Это нужно для ручного оповещения о свободном пространстве на диске, см. get_state()
self.__new_file_tick = now
await self.__changes_queue.put(None)
return self.__new_file_written return self.__new_file_written
async def remove(self, name: str) -> None: async def remove(self, name: str) -> None:
@ -469,7 +484,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
for name in os.listdir(self.__images_path): for name in os.listdir(self.__images_path):
path = os.path.join(self.__images_path, name) path = os.path.join(self.__images_path, name)
if os.path.exists(path): if os.path.exists(path):
size = self.__get_file_size(path) size = fs.get_file_size(path)
if size >= 0: if size >= 0:
images[name] = _DriveImage( images[name] = _DriveImage(
name=name, name=name,
@ -478,10 +493,11 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
complete=self.__is_image_complete(name), complete=self.__is_image_complete(name),
in_storage=True, in_storage=True,
) )
st = os.statvfs(self.__storage_path) space = fs.get_fs_space(self.__storage_path, fatal=True)
assert space
return _StorageState( return _StorageState(
size=(st.f_blocks * st.f_frsize), size=space.size,
free=(st.f_bavail * st.f_frsize), free=space.free,
images=images, images=images,
) )
@ -494,7 +510,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
image = _DriveImage( image = _DriveImage(
name=name, name=name,
path=path, path=path,
size=max(self.__get_file_size(path), 0), size=max(fs.get_file_size(path), 0),
complete=(self.__is_image_complete(name) if in_storage else True), complete=(self.__is_image_complete(name) if in_storage else True),
in_storage=in_storage, in_storage=in_storage,
) )
@ -506,13 +522,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
# ===== # =====
def __get_file_size(self, path: str) -> int:
try:
return os.path.getsize(path)
except Exception as err:
get_logger().warning("Can't get size of file %s: %s", path, err)
return -1
def __is_image_complete(self, name: str) -> bool: def __is_image_complete(self, name: str) -> bool:
return os.path.exists(os.path.join(self.__meta_path, name + ".complete")) return os.path.exists(os.path.join(self.__meta_path, name + ".complete"))
@ -527,7 +536,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
# ===== # =====
async def __remount_storage(self, rw: bool) -> None: async def __remount_storage(self, rw: bool) -> None:
await remount_storage(self.__remount_cmd, rw) await helpers.remount_storage(self.__remount_cmd, rw)
async def __unlock_drive(self) -> None: async def __unlock_drive(self) -> None:
await unlock_drive(self.__unlock_cmd) await helpers.unlock_drive(self.__unlock_cmd)

View File

@ -0,0 +1,58 @@
# ========================================================================== #
# #
# KVMD - The main Pi-KVM daemon. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
# ========================================================================== #
import os
import dataclasses
from typing import Optional
from ....logging import get_logger
# =====
@dataclasses.dataclass(frozen=True)
class FsSpace:
size: int
free: int
# =====
def get_file_size(path: str) -> int:
try:
return os.path.getsize(path)
except Exception as err:
get_logger().warning("Can't get size of file %s: %s", path, err)
return -1
def get_fs_space(path: str, fatal: bool) -> Optional[FsSpace]:
try:
st = os.statvfs(path)
except Exception as err:
if fatal:
raise
get_logger().warning("Can't get free space of filesystem %s: %s", path, err)
return None
return FsSpace(
size=(st.f_blocks * st.f_frsize),
free=(st.f_bavail * st.f_frsize),
)

View File

@ -195,8 +195,10 @@ export function Msd() {
$("msd-emulate-cdrom-checkbox").checked = (__state.online && __state.features.cdrom && __state.drive.cdrom); $("msd-emulate-cdrom-checkbox").checked = (__state.online && __state.features.cdrom && __state.drive.cdrom);
$("msd-new-image").style.display = (__image_file ? "block" : "none"); $("msd-new-image").style.display = (__image_file ? "block" : "none");
$("msd-uploading-progress").setAttribute("data-label", "Waiting for upload ..."); if (!__upload_http) {
$("msd-uploading-progress-value").style.width = "0%"; $("msd-uploading-progress").setAttribute("data-label", "Waiting for upload ...");
$("msd-uploading-progress-value").style.width = "0%";
}
$("msd-new-image-name").innerHTML = (__image_file ? __image_file.name : ""); $("msd-new-image-name").innerHTML = (__image_file ? __image_file.name : "");
$("msd-new-image-size").innerHTML = (__image_file ? __formatSize(__image_file.size) : ""); $("msd-new-image-size").innerHTML = (__image_file ? __formatSize(__image_file.size) : "");