improved info handler

This commit is contained in:
Devaev Maxim
2020-06-02 20:59:43 +03:00
parent fe7c275d1a
commit cb9597679d
12 changed files with 211 additions and 95 deletions

View File

@@ -214,9 +214,9 @@ def _get_config_scheme() -> Dict:
},
},
"info": {
"meta": Option("/etc/kvmd/meta.yaml", type=valid_abs_file, unpack_as="meta_path"),
"extras": Option("/usr/share/kvmd/extras", type=valid_abs_dir, unpack_as="extras_path"),
"info": { # Accessed via global config, see kvmd/info.py for details
"meta": Option("/etc/kvmd/meta.yaml", type=valid_abs_file),
"extras": Option("/usr/share/kvmd/extras", type=valid_abs_dir),
},
"wol": {

View File

@@ -72,7 +72,7 @@ def main(argv: Optional[List[str]]=None) -> None:
force_internal_users=config.auth.internal.force_users,
enabled=config.auth.enabled,
),
info_manager=InfoManager(global_config, **config.info._unpack()),
info_manager=InfoManager(global_config),
log_reader=LogReader(),
wol=WakeOnLan(**config.wol._unpack()),

View File

@@ -0,0 +1,41 @@
# ========================================================================== #
# #
# 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/>. #
# #
# ========================================================================== #
from aiohttp.web import Request
from aiohttp.web import Response
from ..info import InfoManager
from ..http import exposed_http
from ..http import make_json_response
# =====
class InfoApi:
def __init__(self, info_manager: InfoManager) -> None:
self.__info_manager = info_manager
# =====
@exposed_http("GET", "/info")
async def __state_handler(self, _: Request) -> Response:
return make_json_response(await self.__info_manager.get_state())

View File

@@ -21,9 +21,12 @@
import os
import asyncio
import platform
import contextlib
from typing import Dict
from typing import Optional
import dbus # pylint: disable=import-error
import dbus.exceptions
@@ -34,35 +37,87 @@ from ...yamlconf import Section
from ...yamlconf.loader import load_yaml_file
from ... import aiotools
from ... import aioproc
from ... import __version__
# =====
class InfoManager:
def __init__(
self,
global_config: Section,
meta_path: str,
extras_path: str,
) -> None:
def __init__(self, global_config: Section) -> None:
self.__global_config = global_config
self.__meta_path = meta_path
self.__extras_path = extras_path
async def get_meta(self) -> Dict:
return (await aiotools.run_async(load_yaml_file, self.__meta_path))
async def get_state(self) -> Dict:
(streamer_info, meta_info, extras_info) = await asyncio.gather(
self.__get_streamer_info(),
self.__get_meta_info(),
self.__get_extras_info(),
)
uname_info = platform.uname() # Uname using the internal cache
return {
"system": {
"kvmd": {"version": __version__},
"streamer": streamer_info,
"kernel": {
field: getattr(uname_info, field)
for field in ["system", "release", "version", "machine"]
},
},
"meta": meta_info,
"extras": extras_info,
}
async def get_extras(self) -> Dict:
return (await aiotools.run_async(self.__inner_get_extras))
# =====
def __inner_get_extras(self) -> Dict:
extras: Dict[str, Dict] = {}
for app in os.listdir(self.__extras_path):
if app[0] != "." and os.path.isdir(os.path.join(self.__extras_path, app)):
extras[app] = load_yaml_file(os.path.join(self.__extras_path, app, "manifest.yaml"))
self.__rewrite_app_daemon(extras[app])
self.__rewrite_app_port(extras[app])
return extras
async def __get_streamer_info(self) -> Dict:
version = ""
features: Dict[str, bool] = {}
try:
path = self.__global_config.kvmd.streamer.cmd[0]
((_, version), (_, features_text)) = await asyncio.gather(
aioproc.read_process([path, "--version"], err_to_null=True),
aioproc.read_process([path, "--features"], err_to_null=True),
)
except Exception:
get_logger(0).exception("Can't get streamer info")
else:
try:
for line in features_text.split("\n"):
(status, name) = map(str.strip, line.split(" "))
features[name] = (status == "+")
except Exception:
get_logger(0).exception("Can't parse streamer features")
return {
"app": os.path.basename(path),
"version": version,
"features": features,
}
async def __get_meta_info(self) -> Optional[Dict]:
try:
return ((await aiotools.run_async(load_yaml_file, self.__global_config.kvmd.info.meta)) or {})
except Exception:
get_logger(0).exception("Can't parse meta")
return None
async def __get_extras_info(self) -> Optional[Dict]:
return (await aiotools.run_async(self.__inner_get_extras_info))
# =====
def __inner_get_extras_info(self) -> Optional[Dict]:
try:
extras_path = self.__global_config.kvmd.info.extras
extras: Dict[str, Dict] = {}
for app in os.listdir(extras_path):
if app[0] != "." and os.path.isdir(os.path.join(extras_path, app)):
extras[app] = load_yaml_file(os.path.join(extras_path, app, "manifest.yaml"))
self.__rewrite_app_daemon(extras[app])
self.__rewrite_app_port(extras[app])
return extras
except Exception:
get_logger(0).exception("Can't parse extras")
return None
def __rewrite_app_daemon(self, extras: Dict) -> None:
daemon = extras.get("daemon", "")

View File

@@ -57,8 +57,6 @@ from ...validators.kvm import valid_stream_fps
from ... import aiotools
from ... import __version__
from .auth import AuthManager
from .info import InfoManager
from .logreader import LogReader
@@ -78,6 +76,7 @@ from .http import HttpServer
from .api.auth import AuthApi
from .api.auth import check_request_auth
from .api.info import InfoApi
from .api.log import LogApi
from .api.wol import WolApi
from .api.hid import HidApi
@@ -129,6 +128,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
self.__apis: List[object] = [
self,
AuthApi(auth_manager),
InfoApi(info_manager),
LogApi(log_reader),
WolApi(wol),
HidApi(hid, keymap_path),
@@ -148,24 +148,6 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
self.__reset_streamer = False
self.__new_streamer_params: Dict = {}
async def __make_info(self) -> Dict:
streamer_info = await self.__streamer.get_info()
return {
"version": {
"kvmd": __version__,
"streamer": streamer_info["version"],
},
"streamer": streamer_info["app"],
"meta": await self.__info_manager.get_meta(),
"extras": await self.__info_manager.get_extras(),
}
# ===== SYSTEM
@exposed_http("GET", "/info")
async def __info_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
return make_json_response(await self.__make_info())
# ===== STREAMER CONTROLLER
@exposed_http("POST", "/streamer/set_params")
@@ -198,7 +180,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
await self.__register_socket(ws)
try:
await asyncio.gather(*[
self.__broadcast_event(_Events.INFO_STATE, (await self.__make_info())),
self.__broadcast_event(_Events.INFO_STATE, (await self.__info_manager.get_state())),
self.__broadcast_event(_Events.WOL_STATE, self.__wol.get_state()),
self.__broadcast_event(_Events.HID_STATE, (await self.__hid.get_state())),
self.__broadcast_event(_Events.ATX_STATE, self.__atx.get_state()),

View File

@@ -20,7 +20,6 @@
# ========================================================================== #
import os
import signal
import asyncio
import asyncio.subprocess
@@ -246,13 +245,6 @@ class Streamer: # pylint: disable=too-many-instance-attributes
# =====
async def get_info(self) -> Dict:
version = (await aioproc.read_process([self.__cmd[0], "--version"], err_to_null=True))[1]
return {
"app": os.path.basename(self.__cmd[0]),
"version": version,
}
async def make_snapshot(self, save: bool, load: bool, allow_offline: bool) -> Optional[StreamerSnapshot]:
if load:
return self.__snapshot

View File

@@ -150,13 +150,17 @@ class _Client(RfbClient): # pylint: disable=too-many-instance-attributes
async def __process_ws_event(self, event: Dict) -> None:
if event["event_type"] == "info_state":
host = event["event"]["meta"].get("server", {}).get("host")
if isinstance(host, str):
name = f"Pi-KVM: {host}"
async with self.__lock:
if self._encodings.has_rename:
await self._send_rename(name)
self.__shared_params.name = name
try:
host = event["event"]["meta"]["server"]["host"]
except Exception:
host = None
else:
if isinstance(host, str):
name = f"Pi-KVM: {host}"
async with self.__lock:
if self._encodings.has_rename:
await self._send_rename(name)
self.__shared_params.name = name
elif event["event_type"] == "hid_state":
async with self.__lock: