|
@@ -1,8 +1,12 @@
|
|
import asyncio
|
|
import asyncio
|
|
import functools
|
|
import functools
|
|
import inspect
|
|
import inspect
|
|
|
|
+import socket
|
|
|
|
+import threading
|
|
|
|
+import time
|
|
|
|
+import webbrowser
|
|
from contextlib import nullcontext
|
|
from contextlib import nullcontext
|
|
-from typing import TYPE_CHECKING, Any, Awaitable, Callable, Optional, Union
|
|
|
|
|
|
+from typing import TYPE_CHECKING, Any, Awaitable, Callable, Optional, Tuple, Union
|
|
|
|
|
|
from . import background_tasks, globals
|
|
from . import background_tasks, globals
|
|
|
|
|
|
@@ -33,3 +37,46 @@ def safe_invoke(func: Union[Callable, Awaitable], client: Optional['Client'] = N
|
|
background_tasks.create(result_with_client())
|
|
background_tasks.create(result_with_client())
|
|
except Exception as e:
|
|
except Exception as e:
|
|
globals.handle_exception(e)
|
|
globals.handle_exception(e)
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def is_port_open(host: str, port: int) -> bool:
|
|
|
|
+ """Check if the port is open by checking if a TCP connection can be established."""
|
|
|
|
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
+ try:
|
|
|
|
+ sock.connect((host, port))
|
|
|
|
+ except (ConnectionRefusedError, TimeoutError):
|
|
|
|
+ return False
|
|
|
|
+ else:
|
|
|
|
+ return True
|
|
|
|
+ finally:
|
|
|
|
+ sock.close()
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def schedule_browser(host: str, port: int) -> Tuple[threading.Thread, threading.Event]:
|
|
|
|
+ """Wait non-blockingly for the port to be open, then start a webbrowser.
|
|
|
|
+
|
|
|
|
+ This function launches a thread in order to be non-blocking. This thread then uses
|
|
|
|
+ `is_port_open` to check when the port opens. When connectivity is confirmed, the
|
|
|
|
+ webbrowser is launched using `webbrowser.open`
|
|
|
|
+
|
|
|
|
+ The thread is created as a daemon thread, in order to not interfere with Ctrl+C.
|
|
|
|
+
|
|
|
|
+ If you need to stop this thread, you can do this by setting the Event, that gets
|
|
|
|
+ returned. The thread will stop with the next loop without opening the browser.
|
|
|
|
+
|
|
|
|
+ :return: A tuple consisting of the actual thread object and an event for stopping
|
|
|
|
+ the thread.
|
|
|
|
+ """
|
|
|
|
+ cancel = threading.Event()
|
|
|
|
+
|
|
|
|
+ def in_thread(host: str, port: int) -> None:
|
|
|
|
+ while not is_port_open(host, port):
|
|
|
|
+ if cancel.is_set():
|
|
|
|
+ return
|
|
|
|
+ time.sleep(0.1)
|
|
|
|
+ webbrowser.open(f'http://{host}:{port}/')
|
|
|
|
+
|
|
|
|
+ host = host if host != "0.0.0.0" else "127.0.0.1"
|
|
|
|
+ thread = threading.Thread(target=in_thread, args=(host, port), daemon=True)
|
|
|
|
+ thread.start()
|
|
|
|
+ return thread, cancel
|