preparing to otg

This commit is contained in:
Devaev Maxim 2019-09-24 00:03:31 +03:00
parent 661828502e
commit aee005787c
9 changed files with 95 additions and 110 deletions

View File

@ -64,7 +64,6 @@ from ...validators.auth import valid_auth_token
from ...validators.kvm import valid_atx_power_action from ...validators.kvm import valid_atx_power_action
from ...validators.kvm import valid_atx_button from ...validators.kvm import valid_atx_button
from ...validators.kvm import valid_kvm_target
from ...validators.kvm import valid_log_seek from ...validators.kvm import valid_log_seek
from ...validators.kvm import valid_stream_quality from ...validators.kvm import valid_stream_quality
from ...validators.kvm import valid_stream_fps from ...validators.kvm import valid_stream_fps
@ -468,12 +467,12 @@ class Server: # pylint: disable=too-many-instance-attributes
return _json(self.__msd.get_state()) return _json(self.__msd.get_state())
@_exposed("POST", "/msd/connect") @_exposed("POST", "/msd/connect")
async def __msd_connect_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response: async def __msd_connect_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
to = valid_kvm_target(request.query.get("to")) return _json(await self.__msd.connect())
return _json(await ({
"kvm": self.__msd.connect_to_kvm, @_exposed("POST", "/msd/disconnect")
"server": self.__msd.connect_to_server, async def __msd_disconnect_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
}[to])()) return _json(await self.__msd.disconnect())
@_exposed("POST", "/msd/write") @_exposed("POST", "/msd/write")
async def __msd_write_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response: async def __msd_write_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:

View File

@ -184,7 +184,7 @@ class Plugin(BaseHid, multiprocessing.Process): # pylint: disable=too-many-inst
} }
def start(self) -> None: def start(self) -> None:
get_logger().info("Starting HID daemon ...") get_logger(0).info("Starting HID daemon ...")
multiprocessing.Process.start(self) multiprocessing.Process.start(self)
def get_state(self) -> Dict: def get_state(self) -> Dict:

View File

@ -44,19 +44,19 @@ class MsdOfflineError(MsdOperationError):
super().__init__("MSD is not found") super().__init__("MSD is not found")
class MsdAlreadyOnServerError(MsdOperationError): class MsdAlreadyConnectedError(MsdOperationError):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("MSD is already connected to Server") super().__init__("MSD is already connected to Server")
class MsdAlreadyOnKvmError(MsdOperationError): class MsdAlreadyDisconnectedError(MsdOperationError):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("MSD is already connected to KVM") super().__init__("MSD is already disconnected from Server")
class MsdNotOnKvmError(MsdOperationError): class MsdConnectedError(MsdOperationError):
def __init__(self) -> None: def __init__(self) -> None:
super().__init__("MSD is not connected to KVM") super().__init__("MSD connected to Server, but should not")
class MsdIsBusyError(MsdOperationError): class MsdIsBusyError(MsdOperationError):
@ -76,10 +76,10 @@ class BaseMsd(BasePlugin):
async def cleanup(self) -> None: async def cleanup(self) -> None:
pass pass
async def connect_to_kvm(self) -> Dict: async def connect(self) -> Dict:
raise NotImplementedError raise NotImplementedError
async def connect_to_server(self) -> Dict: async def disconnect(self) -> Dict:
raise NotImplementedError raise NotImplementedError
async def reset(self) -> None: async def reset(self) -> None:

View File

@ -46,8 +46,9 @@ class Plugin(BaseMsd):
"busy": False, "busy": False,
"uploading": False, "uploading": False,
"written": False, "written": False,
"info": None, "current": None,
"connected_to": None, "storage": None,
"connected": False,
} }
async def poll_state(self) -> AsyncGenerator[Dict, None]: async def poll_state(self) -> AsyncGenerator[Dict, None]:
@ -55,10 +56,10 @@ class Plugin(BaseMsd):
yield self.get_state() yield self.get_state()
await asyncio.sleep(60) await asyncio.sleep(60)
async def connect_to_kvm(self) -> Dict: async def connect(self) -> Dict:
raise MsdDisabledError() raise MsdDisabledError()
async def connect_to_server(self) -> Dict: async def disconnect(self) -> Dict:
raise MsdDisabledError() raise MsdDisabledError()
async def reset(self) -> None: async def reset(self) -> None:

View File

@ -48,7 +48,6 @@ from ... import gpio
from ...yamlconf import Option from ...yamlconf import Option
from ...validators.basic import valid_bool
from ...validators.basic import valid_number from ...validators.basic import valid_number
from ...validators.basic import valid_int_f1 from ...validators.basic import valid_int_f1
from ...validators.basic import valid_float_f01 from ...validators.basic import valid_float_f01
@ -59,9 +58,9 @@ from ...validators.hw import valid_gpio_pin
from . import MsdError from . import MsdError
from . import MsdOfflineError from . import MsdOfflineError
from . import MsdAlreadyOnServerError from . import MsdAlreadyConnectedError
from . import MsdAlreadyOnKvmError from . import MsdAlreadyDisconnectedError
from . import MsdNotOnKvmError from . import MsdConnectedError
from . import MsdIsBusyError from . import MsdIsBusyError
from . import BaseMsd from . import BaseMsd
@ -77,8 +76,8 @@ class _ImageInfo:
@dataclasses.dataclass(frozen=True) @dataclasses.dataclass(frozen=True)
class _DeviceInfo: class _DeviceInfo:
path: str path: str
real: str
size: int size: int
free: int
image: Optional[_ImageInfo] image: Optional[_ImageInfo]
@ -147,8 +146,8 @@ def _explore_device(device_path: str) -> _DeviceInfo:
return _DeviceInfo( return _DeviceInfo(
path=device_path, path=device_path,
real=os.path.realpath(device_path),
size=size, size=size,
free=(size - image_info.size if image_info else size),
image=image_info, image=image_info,
) )
@ -171,7 +170,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
init_delay: float, init_delay: float,
init_retries: int, init_retries: int,
reset_delay: float, reset_delay: float,
write_meta: bool,
chunk_size: int, chunk_size: int,
) -> None: ) -> None:
@ -182,7 +180,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
self.__init_delay = init_delay self.__init_delay = init_delay
self.__init_retries = init_retries self.__init_retries = init_retries
self.__reset_delay = reset_delay self.__reset_delay = reset_delay
self.__write_meta = write_meta
self.__chunk_size = chunk_size self.__chunk_size = chunk_size
self.__region = aioregion.AioExclusiveRegion(MsdIsBusyError) self.__region = aioregion.AioExclusiveRegion(MsdIsBusyError)
@ -198,8 +195,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
logger.info("Using %r as MSD", self.__device_path) logger.info("Using %r as MSD", self.__device_path)
try: try:
aiotools.run_sync(self.__load_device_info()) aiotools.run_sync(self.__load_device_info())
if self.__write_meta:
logger.info("Enabled image metadata writing")
except Exception as err: except Exception as err:
log = (logger.error if isinstance(err, MsdError) else logger.exception) log = (logger.error if isinstance(err, MsdError) else logger.exception)
log("MSD is offline: %s", err) log("MSD is offline: %s", err)
@ -214,20 +209,29 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
"init_delay": Option(1.0, type=valid_float_f01), "init_delay": Option(1.0, type=valid_float_f01),
"init_retries": Option(5, type=valid_int_f1), "init_retries": Option(5, type=valid_int_f1),
"reset_delay": Option(1.0, type=valid_float_f01), "reset_delay": Option(1.0, type=valid_float_f01),
"write_meta": Option(True, type=valid_bool),
"chunk_size": Option(65536, type=(lambda arg: valid_number(arg, min=1024))), "chunk_size": Option(65536, type=(lambda arg: valid_number(arg, min=1024))),
} }
def get_state(self) -> Dict: def get_state(self) -> Dict:
online = bool(self._device_info) current: Optional[Dict] = None
storage: Optional[Dict] = None
if self._device_info:
storage = {
"size": self._device_info.size,
"free": self._device_info.free,
}
if self._device_info.image:
current = dataclasses.asdict(self._device_info.image)
return { return {
"enabled": True, "enabled": True,
"online": online, "online": bool(self._device_info),
"busy": self.__region.is_busy(), "busy": self.__region.is_busy(),
"uploading": bool(self.__device_file), "uploading": bool(self.__device_file),
"written": self.__written, "written": self.__written,
"info": (dataclasses.asdict(self._device_info) if online else None), "current": current,
"connected_to": (("kvm" if self.__on_kvm else "server") if online else None), "storage": storage,
"connected": (not self.__on_kvm),
} }
async def poll_state(self) -> AsyncGenerator[Dict, None]: async def poll_state(self) -> AsyncGenerator[Dict, None]:
@ -242,13 +246,34 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
@_msd_working @_msd_working
@aiotools.atomic @aiotools.atomic
async def connect_to_kvm(self) -> Dict: async def connect(self) -> Dict:
notify = False
state: Dict = {}
try:
with self.__region:
if not self.__on_kvm:
raise MsdAlreadyConnectedError()
notify = True
gpio.write(self.__target_pin, True)
self.__on_kvm = False
get_logger(0).info("MSD switched to Server")
state = self.get_state()
return state
finally:
if notify:
await self.__state_queue.put(state or self.get_state())
@_msd_working
@aiotools.atomic
async def disconnect(self) -> Dict:
notify = False notify = False
state: Dict = {} state: Dict = {}
try: try:
with self.__region: with self.__region:
if self.__on_kvm: if self.__on_kvm:
raise MsdAlreadyOnKvmError() raise MsdAlreadyDisconnectedError()
notify = True notify = True
gpio.write(self.__target_pin, False) gpio.write(self.__target_pin, False)
@ -267,27 +292,6 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
if notify: if notify:
await self.__state_queue.put(state or self.get_state()) await self.__state_queue.put(state or self.get_state())
@_msd_working
@aiotools.atomic
async def connect_to_server(self) -> Dict:
notify = False
state: Dict = {}
try:
with self.__region:
if not self.__on_kvm:
raise MsdAlreadyOnServerError()
notify = True
gpio.write(self.__target_pin, True)
self.__on_kvm = False
get_logger(0).info("MSD switched to Server")
state = self.get_state()
return state
finally:
if notify:
await self.__state_queue.put(state or self.get_state())
@aiotools.atomic @aiotools.atomic
async def reset(self) -> None: async def reset(self) -> None:
with aiotools.unregion_only_on_exception(self.__region): with aiotools.unregion_only_on_exception(self.__region):
@ -310,10 +314,8 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
try: try:
gpio.write(self.__reset_pin, False) gpio.write(self.__reset_pin, False)
finally: finally:
try: self.__region.exit()
await self.__state_queue.put(self.get_state()) await self.__state_queue.put(self.get_state())
finally:
self.__region.exit()
@_msd_working @_msd_working
@aiotools.atomic @aiotools.atomic
@ -322,7 +324,7 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
self.__region.enter() self.__region.enter()
try: try:
if not self.__on_kvm: if not self.__on_kvm:
raise MsdNotOnKvmError() raise MsdConnectedError()
self.__device_file = await aiofiles.open(self._device_info.path, mode="w+b", buffering=0) self.__device_file = await aiofiles.open(self._device_info.path, mode="w+b", buffering=0)
self.__written = 0 self.__written = 0
return self return self
@ -339,13 +341,12 @@ class Plugin(BaseMsd): # pylint: disable=too-many-instance-attributes
async def write_image_info(self, name: str, complete: bool) -> None: async def write_image_info(self, name: str, complete: bool) -> None:
assert self.__device_file assert self.__device_file
assert self._device_info assert self._device_info
if self.__write_meta: if self._device_info.size - self.__written > _IMAGE_INFO_SIZE:
if self._device_info.size - self.__written > _IMAGE_INFO_SIZE: await self.__device_file.seek(self._device_info.size - _IMAGE_INFO_SIZE)
await self.__device_file.seek(self._device_info.size - _IMAGE_INFO_SIZE) await self.__write_to_device_file(_make_image_info_bytes(name, self.__written, complete))
await self.__write_to_device_file(_make_image_info_bytes(name, self.__written, complete)) await self.__device_file.seek(0)
await self.__device_file.seek(0) else:
else: get_logger().error("Can't write image info because device is full")
get_logger().error("Can't write image info because device is full")
@aiotools.atomic @aiotools.atomic
async def write_image_chunk(self, chunk: bytes) -> int: async def write_image_chunk(self, chunk: bytes) -> int:

View File

@ -38,10 +38,6 @@ def valid_atx_button(arg: Any) -> str:
return check_string_in_list(arg, "ATX button", ["power", "power_long", "reset"]) return check_string_in_list(arg, "ATX button", ["power", "power_long", "reset"])
def valid_kvm_target(arg: Any) -> str:
return check_string_in_list(arg, "KVM target", ["kvm", "server"])
def valid_log_seek(arg: Any) -> int: def valid_log_seek(arg: Any) -> int:
return int(valid_number(arg, min=0, name="log seek")) return int(valid_number(arg, min=0, name="log seek"))

View File

@ -29,7 +29,6 @@ from kvmd.keymap import KEYMAP
from kvmd.validators import ValidatorError from kvmd.validators import ValidatorError
from kvmd.validators.kvm import valid_atx_power_action from kvmd.validators.kvm import valid_atx_power_action
from kvmd.validators.kvm import valid_atx_button from kvmd.validators.kvm import valid_atx_button
from kvmd.validators.kvm import valid_kvm_target
from kvmd.validators.kvm import valid_log_seek from kvmd.validators.kvm import valid_log_seek
from kvmd.validators.kvm import valid_stream_quality from kvmd.validators.kvm import valid_stream_quality
from kvmd.validators.kvm import valid_stream_fps from kvmd.validators.kvm import valid_stream_fps
@ -63,18 +62,6 @@ def test_fail__valid_atx_button(arg: Any) -> None:
print(valid_atx_button(arg)) print(valid_atx_button(arg))
# =====
@pytest.mark.parametrize("arg", ["KVM ", "SERVER "])
def test_ok__valid_kvm_target(arg: Any) -> None:
assert valid_kvm_target(arg) == arg.strip().lower()
@pytest.mark.parametrize("arg", ["test", "", None])
def test_fail__valid_kvm_target(arg: Any) -> None:
with pytest.raises(ValidatorError):
print(valid_kvm_target(arg))
# ===== # =====
@pytest.mark.parametrize("arg", ["0 ", 0, 1, 13]) @pytest.mark.parametrize("arg", ["0 ", 0, 1, 13])
def test_ok__valid_log_seek(arg: Any) -> None: def test_ok__valid_log_seek(arg: Any) -> None:

View File

@ -221,7 +221,7 @@
<div id="msd-new-image" class="msd-message"> <div id="msd-new-image" class="msd-message">
<table class="msd-info"> <table class="msd-info">
<tr> <tr>
<td>New name:</td> <td>New image:</td>
<td id="msd-new-image-name" class="msd-info-value"></td> <td id="msd-new-image-name" class="msd-info-value"></td>
</tr> </tr>
<tr> <tr>
@ -239,8 +239,8 @@
</div> </div>
<div class="menu-item-content-buttons buttons-row"> <div class="menu-item-content-buttons buttons-row">
<button disabled data-force-hide-menu id="msd-switch-to-kvm-button" class="row50">&bull; Switch drive to KVM</button> <button disabled data-force-hide-menu id="msd-connect-button" class="row50">&bull; Connect drive to Server</button>
<button disabled data-force-hide-menu id="msd-switch-to-server-button" class="row50">&bull; Switch drive to Server</button> <button disabled data-force-hide-menu id="msd-disconnect-button" class="row50">&bull; Disconnect drive</button>
</div> </div>
</div> </div>
</li> </li>

View File

@ -45,8 +45,8 @@ export function Msd() {
tools.setOnClick($("msd-upload-new-image-button"), __clickUploadNewImageButton); tools.setOnClick($("msd-upload-new-image-button"), __clickUploadNewImageButton);
tools.setOnClick($("msd-abort-uploading-button"), __clickAbortUploadingButton); tools.setOnClick($("msd-abort-uploading-button"), __clickAbortUploadingButton);
tools.setOnClick($("msd-switch-to-kvm-button"), () => __clickSwitchButton("kvm")); tools.setOnClick($("msd-connect-button"), () => __clickConnectButton(true));
tools.setOnClick($("msd-switch-to-server-button"), () => __clickSwitchButton("server")); tools.setOnClick($("msd-disconnect-button"), () => __clickConnectButton(false));
tools.setOnClick($("msd-reset-button"), __clickResetButton); tools.setOnClick($("msd-reset-button"), __clickResetButton);
}; };
@ -80,8 +80,9 @@ export function Msd() {
$("msd-progress-value").style.width = "0%"; $("msd-progress-value").style.width = "0%";
}; };
var __clickSwitchButton = function(to) { var __clickConnectButton = function(connect) {
let http = tools.makeRequest("POST", "/api/msd/connect?to=" + to, function() { let action = (connect ? "connect" : "disconnect");
let http = tools.makeRequest("POST", `/api/msd/${action}`, function() {
if (http.readyState === 4) { if (http.readyState === 4) {
if (http.status !== 200) { if (http.status !== 200) {
wm.error("Switch error:<br>", http.responseText); wm.error("Switch error:<br>", http.responseText);
@ -90,14 +91,14 @@ export function Msd() {
__applyState(); __applyState();
}); });
__applyState(); __applyState();
wm.switchDisabled($(`msd-switch-to-${to}-button`), true); wm.switchDisabled($(`msd-${action}-button`), true);
}; };
var __selectNewImageFile = function() { var __selectNewImageFile = function() {
let el_input = $("msd-select-new-image-file"); let el_input = $("msd-select-new-image-file");
let image_file = (el_input.files.length ? el_input.files[0] : null); let image_file = (el_input.files.length ? el_input.files[0] : null);
if (image_file && image_file.size > __state.info.size) { if (image_file && image_file.size > __state.storage.size) {
wm.error("New image is too big for your Mass Storage Device.<br>Maximum:", __formatSize(__state.info.size)); wm.error("New image is too big for your Mass Storage Device.<br>Maximum:", __formatSize(__state.storage.size));
el_input.value = ""; el_input.value = "";
image_file = null; image_file = null;
} }
@ -131,7 +132,7 @@ export function Msd() {
$("msd-reset-button").classList.add("feature-disabled"); $("msd-reset-button").classList.add("feature-disabled");
} }
if (__state.connected_to === "server") { if (__state.connected) {
$("msd-another-another-user-uploads").style.display = "none"; $("msd-another-another-user-uploads").style.display = "none";
$("msd-led").className = "led-green"; $("msd-led").className = "led-green";
$("msd-status").innerHTML = $("msd-led").title = "Connected to Server"; $("msd-status").innerHTML = $("msd-led").title = "Connected to Server";
@ -145,7 +146,7 @@ export function Msd() {
$("msd-another-another-user-uploads").style.display = "none"; $("msd-another-another-user-uploads").style.display = "none";
$("msd-led").className = "led-gray"; $("msd-led").className = "led-gray";
if (__state.online) { if (__state.online) {
$("msd-status").innerHTML = $("msd-led").title = "Connected to KVM"; $("msd-status").innerHTML = $("msd-led").title = "Disconnected";
} else { } else {
$("msd-status").innerHTML = $("msd-led").title = "Unavailable"; $("msd-status").innerHTML = $("msd-led").title = "Unavailable";
} }
@ -153,18 +154,18 @@ export function Msd() {
$("msd-offline").style.display = (__state.online ? "none" : "block"); $("msd-offline").style.display = (__state.online ? "none" : "block");
$("msd-current-image-broken").style.display = ( $("msd-current-image-broken").style.display = (
__state.online && __state.info.image && __state.online && __state.current &&
!__state.info.image.complete && !__state.uploading ? "block" : "none" !__state.current.complete && !__state.uploading ? "block" : "none"
); );
$("msd-current-image-name").innerHTML = (__state.online && __state.info.image ? __state.info.image.name : "None"); $("msd-current-image-name").innerHTML = (__state.online && __state.current ? __state.current.name : "None");
$("msd-current-image-size").innerHTML = (__state.online && __state.info.image ? __formatSize(__state.info.image.size) : "None"); $("msd-current-image-size").innerHTML = (__state.online && __state.current ? __formatSize(__state.current.size) : "None");
$("msd-storage-size").innerHTML = (__state.online ? __formatSize(__state.info.size) : "Unavailable"); $("msd-storage-size").innerHTML = (__state.online ? __formatSize(__state.storage.size) : "Unavailable");
wm.switchDisabled($("msd-switch-to-kvm-button"), (!__state.online || __state.connected_to === "kvm" || __state.busy)); wm.switchDisabled($("msd-connect-button"), (!__state.online || __state.connected || __state.busy));
wm.switchDisabled($("msd-switch-to-server-button"), (!__state.online || __state.connected_to === "server" || __state.busy)); wm.switchDisabled($("msd-disconnect-button"), (!__state.online || !__state.connected || __state.busy));
wm.switchDisabled($("msd-select-new-image-button"), (!__state.online || __state.connected_to !== "kvm" || __state.busy || __upload_http)); wm.switchDisabled($("msd-select-new-image-button"), (!__state.online || __state.connected || __state.busy || __upload_http));
wm.switchDisabled($("msd-upload-new-image-button"), (!__state.online || __state.connected_to !== "kvm" || __state.busy || !__image_file)); wm.switchDisabled($("msd-upload-new-image-button"), (!__state.online || __state.connected || __state.busy || !__image_file));
wm.switchDisabled($("msd-abort-uploading-button"), (!__state.online || !__upload_http)); wm.switchDisabled($("msd-abort-uploading-button"), (!__state.online || !__upload_http));
wm.switchDisabled($("msd-reset-button"), (!__state.enabled || __state.busy)); wm.switchDisabled($("msd-reset-button"), (!__state.enabled || __state.busy));
@ -185,8 +186,8 @@ export function Msd() {
$("msd-current-image-size").innerHTML = ""; $("msd-current-image-size").innerHTML = "";
$("msd-storage-size").innerHTML = ""; $("msd-storage-size").innerHTML = "";
wm.switchDisabled($("msd-switch-to-kvm-button"), true); wm.switchDisabled($("msd-connect-button"), true);
wm.switchDisabled($("msd-switch-to-server-button"), true); wm.switchDisabled($("msd-disconnect-button"), true);
wm.switchDisabled($("msd-select-new-image-button"), true); wm.switchDisabled($("msd-select-new-image-button"), true);
wm.switchDisabled($("msd-upload-new-image-button"), true); wm.switchDisabled($("msd-upload-new-image-button"), true);
wm.switchDisabled($("msd-abort-uploading-button"), true); wm.switchDisabled($("msd-abort-uploading-button"), true);