mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
tray is somewhat capable of hangling single tray running
This commit is contained in:
parent
996998d53c
commit
5498bccf85
9 changed files with 254 additions and 33 deletions
|
|
@ -12,10 +12,7 @@ class Commands:
|
|||
"""
|
||||
@staticmethod
|
||||
def launch_tray():
|
||||
from ayon_core.lib import Logger
|
||||
from ayon_core.tools.tray.ui import main
|
||||
|
||||
Logger.set_process_name("Tray")
|
||||
from ayon_core.tools.tray import main
|
||||
|
||||
main()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
from .addons_manager import TrayAddonsManager
|
||||
from .lib import (
|
||||
is_tray_running,
|
||||
main,
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"TrayAddonsManager",
|
||||
"main",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -146,6 +146,7 @@ class TrayAddonsManager(AddonsManager):
|
|||
|
||||
def start_addons(self):
|
||||
self._tray_webserver.start()
|
||||
|
||||
report = {}
|
||||
time_start = time.time()
|
||||
prev_start_time = time_start
|
||||
|
|
@ -185,3 +186,6 @@ class TrayAddonsManager(AddonsManager):
|
|||
),
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
def get_tray_webserver(self):
|
||||
return self._tray_webserver
|
||||
|
|
|
|||
|
|
@ -1,8 +1,27 @@
|
|||
@@ -1,49 +0,0 @@
|
||||
import os
|
||||
from typing import Optional, Dict, Any
|
||||
import json
|
||||
import hashlib
|
||||
import subprocess
|
||||
import csv
|
||||
import time
|
||||
import signal
|
||||
from typing import Optional, Dict, Tuple, Any
|
||||
|
||||
import ayon_api
|
||||
import requests
|
||||
|
||||
from ayon_core.lib import Logger
|
||||
from ayon_core.lib.local_settings import get_ayon_appdirs
|
||||
|
||||
|
||||
class TrayState:
|
||||
NOT_RUNNING = 0
|
||||
STARTING = 1
|
||||
RUNNING = 2
|
||||
|
||||
|
||||
class TrayIsRunningError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _get_default_server_url() -> str:
|
||||
|
|
@ -13,38 +32,170 @@ def _get_default_variant() -> str:
|
|||
return ayon_api.get_default_settings_variant()
|
||||
|
||||
|
||||
def get_tray_store_dir() -> str:
|
||||
pass
|
||||
def _get_server_and_variant(
|
||||
server_url: Optional[str] = None,
|
||||
variant: Optional[str] = None
|
||||
) -> Tuple[str, str]:
|
||||
if not server_url:
|
||||
server_url = _get_default_server_url()
|
||||
if not variant:
|
||||
variant = _get_default_variant()
|
||||
return server_url, variant
|
||||
|
||||
|
||||
def get_tray_information(
|
||||
sever_url: str, variant: str
|
||||
def _windows_pid_is_running(pid: int) -> bool:
|
||||
args = ["tasklist.exe", "/fo", "csv", "/fi", f"PID eq {pid}"]
|
||||
output = subprocess.check_output(args)
|
||||
csv_content = csv.DictReader(output.decode("utf-8").splitlines())
|
||||
# if "PID" not in csv_content.fieldnames:
|
||||
# return False
|
||||
for _ in csv_content:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _create_tray_hash(server_url: str, variant: str) -> str:
|
||||
data = f"{server_url}|{variant}"
|
||||
return hashlib.sha256(data.encode()).hexdigest()
|
||||
|
||||
|
||||
def get_tray_storage_dir() -> str:
|
||||
return get_ayon_appdirs("tray")
|
||||
|
||||
|
||||
def _get_tray_information(tray_url: str) -> Optional[Dict[str, Any]]:
|
||||
# TODO implement server side information
|
||||
response = requests.get(f"{tray_url}/tray")
|
||||
try:
|
||||
response.raise_for_status()
|
||||
except requests.HTTPError:
|
||||
return None
|
||||
return response.json()
|
||||
|
||||
|
||||
def _get_tray_info_filepath(
|
||||
server_url: Optional[str] = None,
|
||||
variant: Optional[str] = None
|
||||
) -> str:
|
||||
hash_dir = get_tray_storage_dir()
|
||||
server_url, variant = _get_server_and_variant(server_url, variant)
|
||||
filename = _create_tray_hash(server_url, variant)
|
||||
return os.path.join(hash_dir, filename)
|
||||
|
||||
|
||||
def get_tray_file_info(
|
||||
server_url: Optional[str] = None,
|
||||
variant: Optional[str] = None
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
pass
|
||||
|
||||
|
||||
def validate_tray_server(server_url: str) -> bool:
|
||||
tray_info = get_tray_information(server_url)
|
||||
if tray_info is None:
|
||||
return False
|
||||
return True
|
||||
filepath = _get_tray_info_filepath(server_url, variant)
|
||||
if not os.path.exists(filepath):
|
||||
return None
|
||||
try:
|
||||
with open(filepath, "r") as stream:
|
||||
data = json.load(stream)
|
||||
except Exception:
|
||||
return None
|
||||
return data
|
||||
|
||||
|
||||
def get_tray_server_url(
|
||||
server_url: Optional[str] = None,
|
||||
variant: Optional[str] = None
|
||||
) -> Optional[str]:
|
||||
if not server_url:
|
||||
server_url = _get_default_server_url()
|
||||
if not variant:
|
||||
variant = _get_default_variant()
|
||||
data = get_tray_file_info(server_url, variant)
|
||||
if data is None:
|
||||
return None
|
||||
return data.get("url")
|
||||
|
||||
|
||||
def set_tray_server_url(tray_url: str, started: bool):
|
||||
filepath = _get_tray_info_filepath()
|
||||
if os.path.exists(filepath):
|
||||
info = get_tray_file_info()
|
||||
if info.get("pid") != os.getpid():
|
||||
raise TrayIsRunningError("Tray is already running.")
|
||||
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
||||
data = {
|
||||
"url": tray_url,
|
||||
"pid": os.getpid(),
|
||||
"started": started
|
||||
}
|
||||
with open(filepath, "w") as stream:
|
||||
json.dump(data, stream)
|
||||
|
||||
|
||||
def remove_tray_server_url():
|
||||
filepath = _get_tray_info_filepath()
|
||||
if not os.path.exists(filepath):
|
||||
return
|
||||
with open(filepath, "r") as stream:
|
||||
data = json.load(stream)
|
||||
if data.get("pid") != os.getpid():
|
||||
return
|
||||
os.remove(filepath)
|
||||
|
||||
|
||||
def get_tray_information(
|
||||
server_url: Optional[str] = None,
|
||||
variant: Optional[str] = None
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
tray_url = get_tray_server_url(server_url, variant)
|
||||
return _get_tray_information(tray_url)
|
||||
|
||||
|
||||
def get_tray_state(
|
||||
server_url: Optional[str] = None,
|
||||
variant: Optional[str] = None
|
||||
):
|
||||
file_info = get_tray_file_info(server_url, variant)
|
||||
if file_info is None:
|
||||
return TrayState.NOT_RUNNING
|
||||
|
||||
if file_info.get("started") is False:
|
||||
return TrayState.STARTING
|
||||
|
||||
tray_url = file_info.get("url")
|
||||
info = _get_tray_information(tray_url)
|
||||
if not info:
|
||||
# Remove the information as the tray is not running
|
||||
remove_tray_server_url()
|
||||
return TrayState.NOT_RUNNING
|
||||
return TrayState.RUNNING
|
||||
|
||||
|
||||
def is_tray_running(
|
||||
server_url: Optional[str] = None,
|
||||
variant: Optional[str] = None
|
||||
) -> bool:
|
||||
server_url = get_tray_server_url(server_url, variant)
|
||||
if server_url and validate_tray_server(server_url):
|
||||
return True
|
||||
return False
|
||||
state = get_tray_state(server_url, variant)
|
||||
return state != TrayState.NOT_RUNNING
|
||||
|
||||
|
||||
def main():
|
||||
from ayon_core.tools.tray.ui import main
|
||||
|
||||
Logger.set_process_name("Tray")
|
||||
|
||||
state = get_tray_state()
|
||||
if state == TrayState.RUNNING:
|
||||
# TODO send some information to tray?
|
||||
print("Tray is already running.")
|
||||
return
|
||||
|
||||
if state == TrayState.STARTING:
|
||||
print("Tray is starting.")
|
||||
return
|
||||
# TODO try to handle stuck tray?
|
||||
time.sleep(5)
|
||||
state = get_tray_state()
|
||||
if state == TrayState.RUNNING:
|
||||
return
|
||||
if state == TrayState.STARTING:
|
||||
file_info = get_tray_file_info() or {}
|
||||
pid = file_info.get("pid")
|
||||
if pid is not None:
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
remove_tray_server_url()
|
||||
|
||||
main()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
import os
|
||||
import sys
|
||||
import time
|
||||
import collections
|
||||
import atexit
|
||||
|
||||
import json
|
||||
import platform
|
||||
|
||||
from aiohttp.web_response import Response
|
||||
import ayon_api
|
||||
from qtpy import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
|
@ -27,6 +29,11 @@ from ayon_core.tools.utils import (
|
|||
get_ayon_qt_app,
|
||||
)
|
||||
from ayon_core.tools.tray import TrayAddonsManager
|
||||
from ayon_core.tools.tray.lib import (
|
||||
set_tray_server_url,
|
||||
remove_tray_server_url,
|
||||
TrayIsRunningError,
|
||||
)
|
||||
|
||||
from .info_widget import InfoWidget
|
||||
from .dialogs import (
|
||||
|
|
@ -68,6 +75,7 @@ class TrayManager:
|
|||
self._execution_in_progress = None
|
||||
self._closing = False
|
||||
self._services_submenu = None
|
||||
self._start_time = time.time()
|
||||
|
||||
@property
|
||||
def doubleclick_callback(self):
|
||||
|
|
@ -105,6 +113,15 @@ class TrayManager:
|
|||
|
||||
tray_menu = self.tray_widget.menu
|
||||
self._addons_manager.initialize(tray_menu)
|
||||
webserver = self._addons_manager.get_tray_webserver()
|
||||
try:
|
||||
set_tray_server_url(webserver.webserver_url, False)
|
||||
except TrayIsRunningError:
|
||||
self.log.error("Tray is already running.")
|
||||
self.exit()
|
||||
return
|
||||
|
||||
webserver.add_route("GET", "/tray", self._get_web_tray_info)
|
||||
|
||||
admin_submenu = ITrayAction.admin_submenu(tray_menu)
|
||||
tray_menu.addMenu(admin_submenu)
|
||||
|
|
@ -125,7 +142,15 @@ class TrayManager:
|
|||
tray_menu.addAction(exit_action)
|
||||
|
||||
# Tell each addon which addons were imported
|
||||
self._addons_manager.start_addons()
|
||||
# TODO Capture only webserver issues (the only thing that can crash).
|
||||
try:
|
||||
self._addons_manager.start_addons()
|
||||
except Exception:
|
||||
self.log.error(
|
||||
"Failed to start addons.",
|
||||
exc_info=True
|
||||
)
|
||||
return self.exit()
|
||||
|
||||
# Print time report
|
||||
self._addons_manager.print_report()
|
||||
|
|
@ -147,6 +172,8 @@ class TrayManager:
|
|||
|
||||
self.execute_in_main_thread(self._startup_validations)
|
||||
|
||||
set_tray_server_url(webserver.webserver_url, True)
|
||||
|
||||
def get_services_submenu(self):
|
||||
return self._services_submenu
|
||||
|
||||
|
|
@ -213,6 +240,7 @@ class TrayManager:
|
|||
self.tray_widget.exit()
|
||||
|
||||
def on_exit(self):
|
||||
remove_tray_server_url()
|
||||
self._addons_manager.on_exit()
|
||||
|
||||
def execute_in_main_thread(self, callback, *args, **kwargs):
|
||||
|
|
@ -225,6 +253,19 @@ class TrayManager:
|
|||
|
||||
return item
|
||||
|
||||
async def _get_web_tray_info(self, request):
|
||||
return Response(text=json.dumps({
|
||||
"bundle": os.getenv("AYON_BUNDLE_NAME"),
|
||||
"dev_mode": is_dev_mode_enabled(),
|
||||
"staging_mode": is_staging_enabled(),
|
||||
"addons": {
|
||||
addon.name: addon.version
|
||||
for addon in self._addons_manager.get_enabled_addons()
|
||||
},
|
||||
"installer_version": os.getenv("AYON_VERSION"),
|
||||
"running_time": time.time() - self._start_time,
|
||||
}))
|
||||
|
||||
def _on_update_check_timer(self):
|
||||
try:
|
||||
bundles = ayon_api.get_bundles()
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
from .structures import HostMsgAction
|
||||
from .base_routes import RestApiEndpoint
|
||||
from .webserver import TrayWebserver
|
||||
|
||||
|
||||
__all__ = (
|
||||
"HostMsgAction",
|
||||
"RestApiEndpoint",
|
||||
"TrayWebserver",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ class IconType:
|
|||
|
||||
class HostListener:
|
||||
def __init__(self, webserver, tray_manager):
|
||||
self._window_per_id = {}
|
||||
self._tray_manager = tray_manager
|
||||
self.webserver = webserver
|
||||
self._window_per_id = {} # dialogs per host name
|
||||
self._action_per_id = {} # QAction per host name
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,25 @@ class WebServerManager:
|
|||
def add_static(self, prefix: str, path: str):
|
||||
self.app.router.add_static(prefix, path)
|
||||
|
||||
def add_addon_route(
|
||||
self,
|
||||
addon_name: str,
|
||||
path: str,
|
||||
request_method: str,
|
||||
handler: Callable
|
||||
) -> str:
|
||||
path = path.lstrip("/")
|
||||
full_path = f"/addons/{addon_name}/{path}"
|
||||
self.app.router.add_route(request_method, full_path, handler)
|
||||
return full_path
|
||||
|
||||
def add_addon_static(
|
||||
self, addon_name: str, prefix: str, path: str
|
||||
) -> str:
|
||||
full_path = f"/addons/{addon_name}/{prefix}"
|
||||
self.app.router.add_static(full_path, path)
|
||||
return full_path
|
||||
|
||||
def start_server(self):
|
||||
if self.webserver_thread and not self.webserver_thread.is_alive():
|
||||
self.webserver_thread.start()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ work as expected. It is because of few limitations connected to asyncio module.
|
|||
|
||||
import os
|
||||
import socket
|
||||
from typing import Callable
|
||||
|
||||
from ayon_core import resources
|
||||
from ayon_core.lib import Logger
|
||||
|
|
@ -39,7 +40,7 @@ class TrayWebserver:
|
|||
static_prefix = "/res"
|
||||
self._server_manager.add_static(static_prefix, resources.RESOURCES_DIR)
|
||||
statisc_url = "{}{}".format(
|
||||
self._webserver_url, static_prefix
|
||||
webserver_url, static_prefix
|
||||
)
|
||||
|
||||
os.environ[self.webserver_url_env] = str(webserver_url)
|
||||
|
|
@ -55,8 +56,11 @@ class TrayWebserver:
|
|||
self._log = Logger.get_logger("TrayWebserver")
|
||||
return self._log
|
||||
|
||||
def add_route(self, *args, **kwargs):
|
||||
self._server_manager.add_route(*args, **kwargs)
|
||||
def add_route(self, request_method: str, path: str, handler: Callable):
|
||||
self._server_manager.add_route(request_method, path, handler)
|
||||
|
||||
def add_static(self, prefix: str, path: str):
|
||||
self._server_manager.add_static(prefix, path)
|
||||
|
||||
@property
|
||||
def server_manager(self):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue