changed webserver from addon to feature of tray

This commit is contained in:
Jakub Trllo 2024-07-18 15:50:55 +02:00
parent f6cca927e1
commit f80b82add9
7 changed files with 99 additions and 122 deletions

View file

@ -1,5 +1,7 @@
import time
from ayon_core.addon import AddonsManager, ITrayAddon, ITrayService
from ayon_core.tools.tray.webserver import TrayWebserver
class TrayAddonsManager(AddonsManager):
@ -16,10 +18,15 @@ class TrayAddonsManager(AddonsManager):
super().__init__(initialize=False)
self._tray_manager = tray_manager
self._tray_webserver = None
self.doubleclick_callbacks = {}
self.doubleclick_callback = None
def get_doubleclick_callback(self):
callback_name = self.doubleclick_callback
return self.doubleclick_callbacks.get(callback_name)
def add_doubleclick_callback(self, addon, callback):
"""Register double-click callbacks on tray icon.
@ -68,6 +75,7 @@ class TrayAddonsManager(AddonsManager):
self._tray_manager.restart()
def tray_init(self):
self._tray_webserver = TrayWebserver(self._tray_manager)
report = {}
time_start = time.time()
prev_start_time = time_start
@ -92,6 +100,11 @@ class TrayAddonsManager(AddonsManager):
report[self._report_total_key] = time.time() - time_start
self._report["Tray init"] = report
def connect_addons(self):
enabled_addons = self.get_enabled_addons()
self._tray_webserver.connect_with_addons(enabled_addons)
super().connect_addons()
def tray_menu(self, tray_menu):
ordered_addons = []
enabled_by_name = {
@ -132,6 +145,7 @@ class TrayAddonsManager(AddonsManager):
self._report["Tray menu"] = report
def start_addons(self):
self._tray_webserver.start()
report = {}
time_start = time.time()
prev_start_time = time_start
@ -159,6 +173,7 @@ class TrayAddonsManager(AddonsManager):
self._report["Addons start"] = report
def on_exit(self):
self._tray_webserver.stop()
for addon in self.get_enabled_tray_addons():
if addon.tray_initialized:
try:

View file

@ -1,13 +1,8 @@
from .version import __version__
from .structures import HostMsgAction
from .webserver_module import (
WebServerAddon
)
from .webserver_module import TrayWebserver
__all__ = (
"__version__",
"HostMsgAction",
"WebServerAddon",
"TrayWebserver",
)

View file

@ -1,7 +1,6 @@
"""Helper functions or classes for Webserver module.
These must not be imported in module itself to not break Python 2
applications.
These must not be imported in module itself to not break in-DCC process.
"""
import inspect

View file

@ -22,9 +22,9 @@ class IconType:
class HostListener:
def __init__(self, webserver, module):
def __init__(self, webserver, tray_manager):
self._window_per_id = {}
self.module = module
self._tray_manager = tray_manager
self.webserver = webserver
self._window_per_id = {} # dialogs per host name
self._action_per_id = {} # QAction per host name
@ -32,8 +32,9 @@ class HostListener:
webserver.add_route('*', "/ws/host_listener", self.websocket_handler)
def _host_is_connecting(self, host_name, label):
""" Initialize dialog, adds to submenu. """
services_submenu = self.module._services_submenu
""" Initialize dialog, adds to submenu."""
ITrayService.services_submenu(self._tray_manager)
services_submenu = self._tray_manager.get_services_submenu()
action = QtWidgets.QAction(label, services_submenu)
action.triggered.connect(lambda: self.show_widget(host_name))
@ -73,8 +74,9 @@ class HostListener:
Dialog get initialized when 'host_name' is connecting.
"""
self.module.execute_in_main_thread(
lambda: self._show_widget(host_name))
self._tray_manager.execute_in_main_thread(
self._show_widget, host_name
)
def _show_widget(self, host_name):
widget = self._window_per_id[host_name]
@ -95,21 +97,23 @@ class HostListener:
if action == HostMsgAction.CONNECTING:
self._action_per_id[host_name] = None
# must be sent to main thread, or action wont trigger
self.module.execute_in_main_thread(
lambda: self._host_is_connecting(host_name, text))
self._tray_manager.execute_in_main_thread(
self._host_is_connecting, host_name, text
)
elif action == HostMsgAction.CLOSE:
# clean close
self._close(host_name)
await ws.close()
elif action == HostMsgAction.INITIALIZED:
self.module.execute_in_main_thread(
self._tray_manager.execute_in_main_thread(
# must be queued as _host_is_connecting might not
# be triggered/finished yet
lambda: self._set_host_icon(host_name,
IconType.RUNNING))
self._set_host_icon, host_name, IconType.RUNNING
)
elif action == HostMsgAction.ADD:
self.module.execute_in_main_thread(
lambda: self._add_text(host_name, text))
self._tray_manager.execute_in_main_thread(
self._add_text, host_name, text
)
elif msg.type == aiohttp.WSMsgType.ERROR:
print('ws connection closed with exception %s' %
ws.exception())
@ -131,7 +135,7 @@ class HostListener:
def _close(self, host_name):
""" Clean close - remove from menu, delete widget."""
services_submenu = self.module._services_submenu
services_submenu = self._tray_manager.get_services_submenu()
action = self._action_per_id.pop(host_name)
services_submenu.removeAction(action)
widget = self._window_per_id.pop(host_name)

View file

@ -1,6 +1,7 @@
import re
import threading
import asyncio
from typing import Callable, Optional
from aiohttp import web
@ -11,7 +12,9 @@ from .cors_middleware import cors_middleware
class WebServerManager:
"""Manger that care about web server thread."""
def __init__(self, port=None, host=None):
def __init__(
self, port: Optional[int] = None, host: Optional[str] = None
):
self._log = None
self.port = port or 8079
@ -40,14 +43,14 @@ class WebServerManager:
return self._log
@property
def url(self):
return "http://{}:{}".format(self.host, self.port)
def url(self) -> str:
return f"http://{self.host}:{self.port}"
def add_route(self, *args, **kwargs):
self.app.router.add_route(*args, **kwargs)
def add_route(self, request_method: str, path: str, handler: Callable):
self.app.router.add_route(request_method, path, handler)
def add_static(self, *args, **kwargs):
self.app.router.add_static(*args, **kwargs)
def add_static(self, prefix: str, path: str):
self.app.router.add_static(prefix, path)
def start_server(self):
if self.webserver_thread and not self.webserver_thread.is_alive():
@ -68,7 +71,7 @@ class WebServerManager:
)
@property
def is_running(self):
def is_running(self) -> bool:
if not self.webserver_thread:
return False
return self.webserver_thread.is_running

View file

@ -1 +0,0 @@
__version__ = "1.0.0"

View file

@ -1,47 +1,62 @@
"""WebServerAddon spawns aiohttp server in asyncio loop.
"""TrayWebserver spawns aiohttp server in asyncio loop.
Main usage of the module is in AYON tray where make sense to add ability
of other modules to add theirs routes. Module which would want use that
option must have implemented method `webserver_initialization` which must
expect `WebServerManager` object where is possible to add routes or paths
with handlers.
Usage is to add ability to register routes from addons, or for inner calls
of tray. Addon which would want use that option must have implemented method
webserver_initialization` which must expect `WebServerManager` object where
is possible to add routes or paths with handlers.
WebServerManager is by default created only in tray.
It is possible to create server manager without using module logic at all
using `create_new_server_manager`. That can be handy for standalone scripts
with predefined host and port and separated routes and logic.
Running multiple servers in one process is not recommended and probably won't
work as expected. It is because of few limitations connected to asyncio module.
When module's `create_server_manager` is called it is also set environment
variable "AYON_WEBSERVER_URL". Which should lead to root access point
of server.
"""
import os
import socket
from ayon_core import resources
from ayon_core.addon import AYONAddon, ITrayService
from ayon_core.lib import Logger
from .version import __version__
from .server import WebServerManager
from .host_console_listener import HostListener
class WebServerAddon(AYONAddon, ITrayService):
name = "webserver"
version = __version__
label = "WebServer"
class TrayWebserver:
webserver_url_env = "AYON_WEBSERVER_URL"
def initialize(self, settings):
self._server_manager = None
self._host_listener = None
def __init__(self, tray_manager):
self._log = None
self._tray_manager = tray_manager
self._port = self.find_free_port()
self._webserver_url = None
self._server_manager = WebServerManager(self._port, None)
webserver_url = self._server_manager.url
self._webserver_url = webserver_url
self._host_listener = HostListener(self, self._tray_manager)
static_prefix = "/res"
self._server_manager.add_static(static_prefix, resources.RESOURCES_DIR)
statisc_url = "{}{}".format(
self._webserver_url, static_prefix
)
os.environ[self.webserver_url_env] = str(webserver_url)
os.environ["AYON_STATICS_SERVER"] = statisc_url
# Deprecated
os.environ["OPENPYPE_WEBSERVER_URL"] = str(webserver_url)
os.environ["OPENPYPE_STATICS_SERVER"] = statisc_url
@property
def log(self):
if self._log is None:
self._log = Logger.get_logger("TrayWebserver")
return self._log
def add_route(self, *args, **kwargs):
self._server_manager.add_route(*args, **kwargs)
@property
def server_manager(self):
@ -73,72 +88,36 @@ class WebServerAddon(AYONAddon, ITrayService):
"""
return self._webserver_url
def connect_with_addons(self, enabled_modules):
def connect_with_addons(self, enabled_addons):
if not self._server_manager:
return
for module in enabled_modules:
if not hasattr(module, "webserver_initialization"):
for addon in enabled_addons:
if not hasattr(addon, "webserver_initialization"):
continue
try:
module.webserver_initialization(self._server_manager)
addon.webserver_initialization(self._server_manager)
except Exception:
self.log.warning(
(
"Failed to connect module \"{}\" to webserver."
).format(module.name),
f"Failed to connect addon \"{addon.name}\" to webserver.",
exc_info=True
)
def tray_init(self):
self.create_server_manager()
self._add_resources_statics()
self._add_listeners()
def start(self):
self._start_server()
def tray_start(self):
self.start_server()
def stop(self):
self._stop_server()
def tray_exit(self):
self.stop_server()
def start_server(self):
def _start_server(self):
if self._server_manager is not None:
self._server_manager.start_server()
def stop_server(self):
def _stop_server(self):
if self._server_manager is not None:
self._server_manager.stop_server()
@staticmethod
def create_new_server_manager(port=None, host=None):
"""Create webserver manager for passed port and host.
Args:
port(int): Port on which wil webserver listen.
host(str): Host name or IP address. Default is 'localhost'.
Returns:
WebServerManager: Prepared manager.
"""
from .server import WebServerManager
return WebServerManager(port, host)
def create_server_manager(self):
if self._server_manager is not None:
return
self._server_manager = self.create_new_server_manager(self._port)
self._server_manager.on_stop_callbacks.append(
self.set_service_failed_icon
)
webserver_url = self._server_manager.url
os.environ["OPENPYPE_WEBSERVER_URL"] = str(webserver_url)
os.environ[self.webserver_url_env] = str(webserver_url)
self._webserver_url = webserver_url
@staticmethod
def find_free_port(
port_from=None, port_to=None, exclude_ports=None, host=None
@ -193,20 +172,3 @@ class WebServerAddon(AYONAddon, ITrayService):
break
return found_port
def _add_resources_statics(self):
static_prefix = "/res"
self._server_manager.add_static(static_prefix, resources.RESOURCES_DIR)
statisc_url = "{}{}".format(
self._webserver_url, static_prefix
)
os.environ["AYON_STATICS_SERVER"] = statisc_url
os.environ["OPENPYPE_STATICS_SERVER"] = statisc_url
def _add_listeners(self):
from . import host_console_listener
self._host_listener = host_console_listener.HostListener(
self._server_manager, self
)