mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2025-12-12 01:00:29 +08:00
extended msd api for future otg
This commit is contained in:
parent
5d437c58e3
commit
5c4e8f7962
@ -183,6 +183,7 @@ def _get_config_scheme() -> Dict:
|
||||
"unix_rm": Option(False, type=valid_bool),
|
||||
"unix_mode": Option(0, type=valid_unix_mode),
|
||||
"heartbeat": Option(3.0, type=valid_float_f01),
|
||||
"sync_chunk_size": Option(65536, type=(lambda arg: valid_number(arg, min=1024))),
|
||||
"access_log_format": Option("[%P / %{X-Real-IP}i] '%r' => %s; size=%b ---"
|
||||
" referer='%{Referer}i'; user_agent='%{User-Agent}i'"),
|
||||
},
|
||||
|
||||
@ -67,6 +67,7 @@ from ...validators.kvm import valid_atx_button
|
||||
from ...validators.kvm import valid_log_seek
|
||||
from ...validators.kvm import valid_stream_quality
|
||||
from ...validators.kvm import valid_stream_fps
|
||||
from ...validators.kvm import valid_msd_image_name
|
||||
from ...validators.kvm import valid_hid_key
|
||||
from ...validators.kvm import valid_hid_mouse_move
|
||||
from ...validators.kvm import valid_hid_mouse_button
|
||||
@ -250,6 +251,7 @@ class Server: # pylint: disable=too-many-instance-attributes
|
||||
self.__streamer = streamer
|
||||
|
||||
self.__heartbeat: Optional[float] = None # Assigned in run() for consistance
|
||||
self.__sync_chunk_size: Optional[int] = None # Ditto
|
||||
self.__sockets: Set[aiohttp.web.WebSocketResponse] = set()
|
||||
self.__sockets_lock = asyncio.Lock()
|
||||
|
||||
@ -266,6 +268,7 @@ class Server: # pylint: disable=too-many-instance-attributes
|
||||
unix_rm: bool,
|
||||
unix_mode: int,
|
||||
heartbeat: float,
|
||||
sync_chunk_size: int,
|
||||
access_log_format: str,
|
||||
) -> None:
|
||||
|
||||
@ -274,6 +277,7 @@ class Server: # pylint: disable=too-many-instance-attributes
|
||||
setproctitle.setproctitle("[main] " + setproctitle.getproctitle())
|
||||
|
||||
self.__heartbeat = heartbeat
|
||||
self.__sync_chunk_size = sync_chunk_size
|
||||
|
||||
assert port or unix_path
|
||||
if unix_path:
|
||||
@ -474,31 +478,40 @@ class Server: # pylint: disable=too-many-instance-attributes
|
||||
async def __msd_disconnect_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||
return _json(await self.__msd.disconnect())
|
||||
|
||||
@_exposed("POST", "/msd/select")
|
||||
async def __msd_select_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||
return _json(await self.__msd.select(valid_msd_image_name(request.query.get("image_name"))))
|
||||
|
||||
@_exposed("POST", "/msd/remove")
|
||||
async def __msd_remove_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||
return _json(await self.__msd.remove(valid_msd_image_name(request.query.get("image_name"))))
|
||||
|
||||
@_exposed("POST", "/msd/write")
|
||||
async def __msd_write_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||
assert self.__sync_chunk_size is not None
|
||||
logger = get_logger(0)
|
||||
reader = await request.multipart()
|
||||
image_name = ""
|
||||
written = 0
|
||||
try:
|
||||
async with self.__msd:
|
||||
name_field = await _get_multipart_field(reader, "image_name")
|
||||
image_name = (await name_field.read()).decode("utf-8")[:256]
|
||||
image_name = valid_msd_image_name((await name_field.read()).decode("utf-8"))
|
||||
|
||||
data_field = await _get_multipart_field(reader, "image_data")
|
||||
|
||||
logger.info("Writing image %r to MSD ...", image_name)
|
||||
await self.__msd.write_image_info(image_name, False)
|
||||
chunk_size = self.__msd.get_chunk_size()
|
||||
while True:
|
||||
chunk = await data_field.read_chunk(chunk_size)
|
||||
chunk = await data_field.read_chunk(self.__sync_chunk_size)
|
||||
if not chunk:
|
||||
break
|
||||
written = await self.__msd.write_image_chunk(chunk)
|
||||
await self.__msd.write_image_info(image_name, True)
|
||||
finally:
|
||||
if written != 0:
|
||||
logger.info("Written %d bytes to MSD", written)
|
||||
return _json({"written": written})
|
||||
logger.info("Written image %r with size=%d bytes to MSD", image_name, written)
|
||||
return _json({"image": {"name": image_name, "size": written}})
|
||||
|
||||
@_exposed("POST", "/msd/reset")
|
||||
async def __msd_reset_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||
|
||||
@ -64,6 +64,11 @@ class MsdIsBusyError(MsdOperationError):
|
||||
super().__init__("Performing another MSD operation, please try again later")
|
||||
|
||||
|
||||
class MsdMultiNotSupported(MsdOperationError):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("This MSD does not support storing multiple images")
|
||||
|
||||
|
||||
# =====
|
||||
class BaseMsd(BasePlugin):
|
||||
def get_state(self) -> Dict:
|
||||
@ -73,24 +78,29 @@ class BaseMsd(BasePlugin):
|
||||
yield {}
|
||||
raise NotImplementedError
|
||||
|
||||
async def reset(self) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
async def cleanup(self) -> None:
|
||||
pass
|
||||
|
||||
# =====
|
||||
|
||||
async def connect(self) -> Dict:
|
||||
raise NotImplementedError
|
||||
|
||||
async def disconnect(self) -> Dict:
|
||||
raise NotImplementedError
|
||||
|
||||
async def reset(self) -> None:
|
||||
async def select(self, name: str) -> Dict:
|
||||
raise NotImplementedError
|
||||
|
||||
async def remove(self, name: str) -> Dict:
|
||||
raise NotImplementedError
|
||||
|
||||
async def __aenter__(self) -> "BaseMsd":
|
||||
raise NotImplementedError
|
||||
|
||||
def get_chunk_size(self) -> int:
|
||||
raise NotImplementedError
|
||||
|
||||
async def write_image_info(self, name: str, complete: bool) -> None:
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
@ -42,6 +42,7 @@ class Plugin(BaseMsd):
|
||||
def get_state(self) -> Dict:
|
||||
return {
|
||||
"enabled": False,
|
||||
"multi": False,
|
||||
"online": False,
|
||||
"busy": False,
|
||||
"uploading": False,
|
||||
@ -56,21 +57,26 @@ class Plugin(BaseMsd):
|
||||
yield self.get_state()
|
||||
await asyncio.sleep(60)
|
||||
|
||||
async def reset(self) -> None:
|
||||
raise MsdDisabledError()
|
||||
|
||||
# =====
|
||||
|
||||
async def connect(self) -> Dict:
|
||||
raise MsdDisabledError()
|
||||
|
||||
async def disconnect(self) -> Dict:
|
||||
raise MsdDisabledError()
|
||||
|
||||
async def reset(self) -> None:
|
||||
async def select(self, name: str) -> Dict:
|
||||
raise MsdDisabledError()
|
||||
|
||||
async def remove(self, name: str) -> Dict:
|
||||
raise MsdDisabledError()
|
||||
|
||||
async def __aenter__(self) -> BaseMsd:
|
||||
raise MsdDisabledError()
|
||||
|
||||
def get_chunk_size(self) -> int:
|
||||
raise MsdDisabledError()
|
||||
|
||||
async def write_image_info(self, name: str, complete: bool) -> None:
|
||||
raise MsdDisabledError()
|
||||
|
||||
|
||||
@ -48,7 +48,6 @@ from ... import gpio
|
||||
|
||||
from ...yamlconf import Option
|
||||
|
||||
from ...validators.basic import valid_number
|
||||
from ...validators.basic import valid_int_f1
|
||||
from ...validators.basic import valid_float_f01
|
||||
|
||||
@ -62,6 +61,7 @@ from . import MsdAlreadyConnectedError
|
||||
from . import MsdAlreadyDisconnectedError
|
||||
from . import MsdConnectedError
|
||||
from . import MsdIsBusyError
|
||||
from . import MsdMultiNotSupported
|
||||
from . import BaseMsd
|
||||
|
||||
|
||||
@ -170,7 +170,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
||||
init_delay: float,
|
||||
init_retries: int,
|
||||
reset_delay: float,
|
||||
chunk_size: int,
|
||||
) -> None:
|
||||
|
||||
self.__target_pin = gpio.set_output(target_pin)
|
||||
@ -180,7 +179,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
||||
self.__init_delay = init_delay
|
||||
self.__init_retries = init_retries
|
||||
self.__reset_delay = reset_delay
|
||||
self.__chunk_size = chunk_size
|
||||
|
||||
self.__region = aioregion.AioExclusiveRegion(MsdIsBusyError)
|
||||
|
||||
@ -209,8 +207,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
||||
"init_delay": Option(1.0, type=valid_float_f01),
|
||||
"init_retries": Option(5, type=valid_int_f1),
|
||||
"reset_delay": Option(1.0, type=valid_float_f01),
|
||||
|
||||
"chunk_size": Option(65536, type=(lambda arg: valid_number(arg, min=1024))),
|
||||
}
|
||||
|
||||
def get_state(self) -> Dict:
|
||||
@ -225,6 +221,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
||||
current = dataclasses.asdict(self._device_info.image)
|
||||
return {
|
||||
"enabled": True,
|
||||
"multi": False,
|
||||
"online": bool(self._device_info),
|
||||
"busy": self.__region.is_busy(),
|
||||
"uploading": bool(self.__device_file),
|
||||
@ -238,12 +235,39 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
||||
while True:
|
||||
yield (await self.__state_queue.get())
|
||||
|
||||
@aiotools.atomic
|
||||
async def reset(self) -> None:
|
||||
with aiotools.unregion_only_on_exception(self.__region):
|
||||
await self.__inner_reset()
|
||||
|
||||
@aiotools.tasked
|
||||
@aiotools.muted("Can't reset MSD or operation was not completed")
|
||||
async def __inner_reset(self) -> None:
|
||||
try:
|
||||
gpio.write(self.__reset_pin, True)
|
||||
await asyncio.sleep(self.__reset_delay)
|
||||
gpio.write(self.__reset_pin, False)
|
||||
|
||||
gpio.write(self.__target_pin, False)
|
||||
self.__on_kvm = True
|
||||
|
||||
await self.__load_device_info()
|
||||
get_logger(0).info("MSD reset has been successful")
|
||||
finally:
|
||||
try:
|
||||
gpio.write(self.__reset_pin, False)
|
||||
finally:
|
||||
self.__region.exit()
|
||||
await self.__state_queue.put(self.get_state())
|
||||
|
||||
@aiotools.atomic
|
||||
async def cleanup(self) -> None:
|
||||
await self.__close_device_file()
|
||||
gpio.write(self.__target_pin, False)
|
||||
gpio.write(self.__reset_pin, False)
|
||||
|
||||
# =====
|
||||
|
||||
@_msd_working
|
||||
@aiotools.atomic
|
||||
async def connect(self) -> Dict:
|
||||
@ -292,30 +316,13 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
||||
if notify:
|
||||
await self.__state_queue.put(state or self.get_state())
|
||||
|
||||
@aiotools.atomic
|
||||
async def reset(self) -> None:
|
||||
with aiotools.unregion_only_on_exception(self.__region):
|
||||
await self.__inner_reset()
|
||||
@_msd_working
|
||||
async def select(self, name: str) -> Dict:
|
||||
raise MsdMultiNotSupported()
|
||||
|
||||
@aiotools.tasked
|
||||
@aiotools.muted("Can't reset MSD or operation was not completed")
|
||||
async def __inner_reset(self) -> None:
|
||||
try:
|
||||
gpio.write(self.__reset_pin, True)
|
||||
await asyncio.sleep(self.__reset_delay)
|
||||
gpio.write(self.__reset_pin, False)
|
||||
|
||||
gpio.write(self.__target_pin, False)
|
||||
self.__on_kvm = True
|
||||
|
||||
await self.__load_device_info()
|
||||
get_logger(0).info("MSD reset has been successful")
|
||||
finally:
|
||||
try:
|
||||
gpio.write(self.__reset_pin, False)
|
||||
finally:
|
||||
self.__region.exit()
|
||||
await self.__state_queue.put(self.get_state())
|
||||
@_msd_working
|
||||
async def remove(self, name: str) -> Dict:
|
||||
raise MsdMultiNotSupported()
|
||||
|
||||
@_msd_working
|
||||
@aiotools.atomic
|
||||
@ -334,9 +341,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
|
||||
finally:
|
||||
await self.__state_queue.put(self.get_state())
|
||||
|
||||
def get_chunk_size(self) -> int:
|
||||
return self.__chunk_size
|
||||
|
||||
@aiotools.atomic
|
||||
async def write_image_info(self, name: str, complete: bool) -> None:
|
||||
assert self.__device_file
|
||||
|
||||
@ -20,10 +20,13 @@
|
||||
# ========================================================================== #
|
||||
|
||||
|
||||
import re
|
||||
|
||||
from typing import Any
|
||||
|
||||
from .. import keymap
|
||||
|
||||
from . import check_not_none_string
|
||||
from . import check_string_in_list
|
||||
|
||||
from .basic import valid_number
|
||||
@ -38,6 +41,18 @@ def valid_atx_button(arg: Any) -> str:
|
||||
return check_string_in_list(arg, "ATX button", ["power", "power_long", "reset"])
|
||||
|
||||
|
||||
def valid_msd_image_name(arg: Any) -> str:
|
||||
if len(str(arg).strip()) == 0:
|
||||
arg = None
|
||||
arg = check_not_none_string(arg, "MSD image name", strip=True)
|
||||
arg = re.sub(r"[^\w\.+@()\[\]-]", "_", arg)
|
||||
if arg == ".":
|
||||
arg = "_"
|
||||
if arg == "..":
|
||||
arg = "__"
|
||||
return arg[:255]
|
||||
|
||||
|
||||
def valid_log_seek(arg: Any) -> int:
|
||||
return int(valid_number(arg, min=0, name="log seek"))
|
||||
|
||||
|
||||
@ -32,6 +32,7 @@ from kvmd.validators.kvm import valid_atx_button
|
||||
from kvmd.validators.kvm import valid_log_seek
|
||||
from kvmd.validators.kvm import valid_stream_quality
|
||||
from kvmd.validators.kvm import valid_stream_fps
|
||||
from kvmd.validators.kvm import valid_msd_image_name
|
||||
from kvmd.validators.kvm import valid_hid_key
|
||||
from kvmd.validators.kvm import valid_hid_mouse_move
|
||||
from kvmd.validators.kvm import valid_hid_mouse_button
|
||||
@ -104,6 +105,30 @@ def test_fail__valid_stream_fps(arg: Any) -> None:
|
||||
print(valid_stream_fps(arg))
|
||||
|
||||
|
||||
# =====
|
||||
@pytest.mark.parametrize("arg, retval", [
|
||||
("archlinux-2018.07.01-i686.iso", "archlinux-2018.07.01-i686.iso"),
|
||||
("archlinux-2018.07.01-x86_64.iso", "archlinux-2018.07.01-x86_64.iso"),
|
||||
("dsl-4.11.rc1.iso", "dsl-4.11.rc1.iso"),
|
||||
("systemrescuecd-x86-5.3.1.iso", "systemrescuecd-x86-5.3.1.iso"),
|
||||
("ubuntu-16.04.5-desktop-i386.iso", "ubuntu-16.04.5-desktop-i386.iso"),
|
||||
(".", "_"),
|
||||
("..", "__"),
|
||||
("/..", "_.."),
|
||||
("/root/..", "_root_.."),
|
||||
(" тест(){}[ \t].iso\t", "тест()__[__].iso"),
|
||||
("?" * 1000, "_" * 255),
|
||||
])
|
||||
def test_ok__valid_msd_image_name(arg: Any, retval: str) -> None:
|
||||
assert valid_msd_image_name(arg) == retval
|
||||
|
||||
|
||||
@pytest.mark.parametrize("arg", ["", None])
|
||||
def test_fail__valid_msd_image_name(arg: Any) -> None:
|
||||
with pytest.raises(ValidatorError):
|
||||
print(valid_msd_image_name(arg))
|
||||
|
||||
|
||||
# =====
|
||||
def test_ok__valid_hid_key() -> None:
|
||||
for key in KEYMAP:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user