From 0611e608136527df7dca3aee16d9d6cdfa2f85fc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 4 Feb 2022 19:13:14 +0100 Subject: [PATCH] OP-2414 - separated logging and launching logic --- openpype/hosts/harmony/api/__init__.py | 2 +- openpype/hosts/harmony/api/lib.py | 35 ++++--- openpype/tools/stdout_broker/app.py | 138 +++++-------------------- 3 files changed, 47 insertions(+), 128 deletions(-) diff --git a/openpype/hosts/harmony/api/__init__.py b/openpype/hosts/harmony/api/__init__.py index d78e04e83f..e1cdc2aa23 100644 --- a/openpype/hosts/harmony/api/__init__.py +++ b/openpype/hosts/harmony/api/__init__.py @@ -79,7 +79,7 @@ __all__ = [ "find_node_by_name", "signature", "select_nodes", - "get_scene_data" + "get_scene_data", # Workfiles API "open_file", diff --git a/openpype/hosts/harmony/api/lib.py b/openpype/hosts/harmony/api/lib.py index 05f2b55d88..088a4e3d3a 100644 --- a/openpype/hosts/harmony/api/lib.py +++ b/openpype/hosts/harmony/api/lib.py @@ -15,12 +15,14 @@ import signal import time from uuid import uuid4 from Qt import QtWidgets +import queue from .server import Server from openpype.tools.stdout_broker.app import StdOutBroker from openpype.tools.utils import host_tools +# TODO refactor self = sys.modules[__name__] self.server = None self.pid = None @@ -28,14 +30,20 @@ self.application_path = None self.callback_queue = None self.workfile_path = None self.port = None +self.stdout_broker = None # Setup logging. self.log = logging.getLogger(__name__) self.log.setLevel(logging.DEBUG) -def execute_in_main_thread(func, *args, **kwargs): - StdOutBroker.instance().execute_in_main_thread(func, *args, **kwargs) +def execute_in_main_thread(func_to_call_from_main_thread): + self.callback_queue.put(func_to_call_from_main_thread) + + +def main_thread_listen(): + callback = self.callback_queue.get() + callback() def signature(postfix="func") -> str: @@ -63,18 +71,14 @@ class _ZipFile(zipfile.ZipFile): def main(*subprocess_args): - def is_host_connected(): - # Harmony always connected, not waiting - return True - # coloring in StdOutBroker os.environ["OPENPYPE_LOG_NO_COLORS"] = "False" app = QtWidgets.QApplication([]) app.setQuitOnLastWindowClosed(False) - instance = StdOutBroker('harmony', launch, - subprocess_args, is_host_connected) - StdOutBroker._instance = instance + self.stdout_broker = StdOutBroker('harmony') + + launch(*subprocess_args) sys.exit(app.exec_()) @@ -191,6 +195,10 @@ def launch(application_path, *args): launch_zip_file(zip_file) + self.callback_queue = queue.Queue() + while True: + main_thread_listen() + def get_local_harmony_path(filepath): """From the provided path get the equivalent local Harmony path.""" @@ -280,13 +288,14 @@ def launch_zip_file(filepath): return print("Launching {}".format(scene_path)) - StdOutBroker.instance().websocket_server = self.server - StdOutBroker.instance().process = subprocess.Popen( + + process = subprocess.Popen( [self.application_path, scene_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) - self.pid = StdOutBroker.instance().process.pid + self.pid = process.pid + self.stdout_broker.host_connected() def on_file_changed(path, threaded=True): @@ -351,7 +360,7 @@ def show(module_name): if tool_name == "loader": kwargs["use_context"] = True - StdOutBroker.instance().execute_in_main_thread( + execute_in_main_thread( lambda: host_tools.show_tool_by_name(tool_name, **kwargs) ) diff --git a/openpype/tools/stdout_broker/app.py b/openpype/tools/stdout_broker/app.py index 6632ae6664..cfc05b0cc3 100644 --- a/openpype/tools/stdout_broker/app.py +++ b/openpype/tools/stdout_broker/app.py @@ -2,13 +2,13 @@ import os import sys import re import collections -import queue import websocket import json from datetime import datetime from avalon import style -from openpype_modules.webserver import host_console_listener +from openpype_modules.webserver.host_console_listener import MsgAction + from openpype.api import Logger from Qt import QtWidgets, QtCore @@ -65,43 +65,20 @@ class StdOutBroker: ' ERROR ' } - def __init__(self, host, launch_method, subprocess_args, is_host_connected, - parent=None): - self.host = host + def __init__(self, host_name): + self.host_name = host_name self.websocket_server = None - self.launch_method = launch_method - self.subprocess_args = subprocess_args - self.is_host_connected = is_host_connected - self.process = None self.original_stdout_write = None self.original_stderr_write = None self.log_queue = collections.deque() - start_process_timer = QtCore.QTimer() - start_process_timer.setInterval(200) - start_process_timer.timeout.connect(self._on_start_process_timer) - start_process_timer.start() - - self.start_process_timer = start_process_timer - - loop_timer = QtCore.QTimer() - loop_timer.setInterval(200) - self.loop_timer = loop_timer - - start_process_timer.timeout.connect(self._on_start_process_timer) - loop_timer.timeout.connect(self._on_loop_timer) + date_str = datetime.now().strftime("%d%m%Y%H%M%S") + self.host_id = "{}_{}".format(self.host_name, date_str) self._std_available = False - self.catch_std_outputs() - date_str = datetime.now().strftime("%d%m%Y%H%M%S") - self.host_id = "{}_{}".format(self.host, date_str) - - @classmethod - def instance(cls): - if not cls._instance: - raise RuntimeError("Not initialized yet") - return cls._instance + self._catch_std_outputs() + self._connect_to_tray() @property def websocket_server_is_running(self): @@ -109,70 +86,15 @@ class StdOutBroker: return self.websocket_server.is_running return False - @property - def is_process_running(self): - if self.process is not None: - return self.process.poll() is None - return False - @property def send_to_tray(self): """Checks if connected to tray and have access to logs.""" return self.webserver_client and self._std_available - def _on_start_process_timer(self): - """Called periodically to initialize and run function on main thread""" - # Start application process - if self.process is None: - self._start_process() - log.info("Waiting for host to connect") - return - - host_connected = self.is_host_connected() - if host_connected is None: # not yetkeep trying - return - - try: - self._connect_to_tray() - except Exception: - log.info("Connection to Tray app failed." - "Log messages won't be pushed through.", exc_info=True) - - if host_connected: - self._host_connected() - else: # app cannot connect - text = "{} process is not alive. Exiting".format(self.host) - print(text) - self.log_queue.append(text) - self._process_queue() - self.exit() - - def _on_loop_timer(self): - """Regular processing of queue""" - if self.callback_queue and \ - not self.callback_queue.empty(): - try: - callback = self.callback_queue.get(block=False) - callback() - except queue.Empty: - pass - elif self.process.poll() is not None: - self.exit() - - self._process_queue() - - def _start_process(self): - if self.process is not None: - return - log.info("Starting host process") - try: - self.launch_method(*self.subprocess_args) - except Exception as exp: - log.info("Exception when app started", exc_info=True) - self.exit() - def _connect_to_tray(self): """ Connect to Tray webserver to pass console output. """ + if not self._std_available: # not content to log + return ws = websocket.WebSocket() webserver_url = os.environ.get("OPENPYPE_WEBSERVER_URL") @@ -186,47 +108,40 @@ class StdOutBroker: payload = { "host": self.host_id, - "action": host_console_listener.MsgAction.CONNECTING, - "text": "Integration with {}".format(str.capitalize(self.host)) + "action": MsgAction.CONNECTING, + "text": "Integration with {}".format( + str.capitalize(self.host_name)) } self._send(payload) def _disconnect_from_tray(self): """ Send to Tray that host is closing - remove from Services. """ - print("Host {} closing".format(self.host)) + print("Host {} closing".format(self.host_name)) if not self.webserver_client: return payload = { "host": self.host_id, - "action": host_console_listener.MsgAction.CLOSE, - "text": "Integration with {}".format(str.capitalize(self.host)) + "action": MsgAction.CLOSE, + "text": "Integration with {}".format( + str.capitalize(self.host_name)) } self._send(payload) self.webserver_client.close() - def _host_connected(self): + def host_connected(self): """ Send to Tray console that host is ready - icon change. """ - log.info("Host {} connected".format(self.host)) - self.start_process_timer.stop() - self.loop_timer.start() - - self.callback_queue = queue.Queue() + log.info("Host {} connected".format(self.host_id)) payload = { "host": self.host_id, - "action": host_console_listener.MsgAction.INITIALIZED, - "text": "Integration with {}".format(str.capitalize(self.host)) + "action": MsgAction.INITIALIZED, + "text": "Integration with {}".format( + str.capitalize(self.host_name)) } self._send(payload) - def execute_in_main_thread(self, func_to_call_from_main_thread): - """Put function to the queue to be picked by 'on_timer'""" - if not self.callback_queue: - self.callback_queue = queue.Queue() - self.callback_queue.put(func_to_call_from_main_thread) - def restart_server(self): if self.websocket_server: self.websocket_server.stop_server(restart=True) @@ -237,14 +152,9 @@ class StdOutBroker: if self.websocket_server: self.websocket_server.stop() - if self.process: - self.process.kill() - self.process.wait() - if self.loop_timer: - self.loop_timer.stop() QtCore.QCoreApplication.exit() - def catch_std_outputs(self): + def _catch_std_outputs(self): """Redirects standard out and error to own functions""" if sys.stdout: self.original_stdout_write = sys.stdout.write @@ -280,7 +190,7 @@ class StdOutBroker: if lines: payload = { "host": self.host_id, - "action": host_console_listener.MsgAction.ADD, + "action": MsgAction.ADD, "text": "\n".join(lines) }