120 lines
4.1 KiB
Python
120 lines
4.1 KiB
Python
"""SonicForge DAW — Desktop engine launcher (PyInstaller entry point).
|
|
|
|
Chay FastAPI backend duoi dang tien trinh nen (sidecar) cho ban cai Standalone
|
|
Windows (Tauri v2 shell). Tu chon port 8000-8010 (fallback neu bi chiem), tu
|
|
thoat khi tien trinh cha (Tauri) ket thuc — Graceful Shutdown.
|
|
"""
|
|
import logging
|
|
import os
|
|
import socket
|
|
import sys
|
|
import threading
|
|
import time
|
|
|
|
PORT_RANGE = (8000, 8010)
|
|
APP_DATA_DIR_NAME = "SonicForgeDAW"
|
|
|
|
|
|
def _pick_port():
|
|
for port in range(PORT_RANGE[0], PORT_RANGE[1] + 1):
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
# KHONG dung SO_REUSEADDR: tren Windows no CHO PHEP bind trung port
|
|
# (2 engine cung 8000 -> request roi vao engine ngau nhien -> UI loi).
|
|
# Listen socket khong can SO_REUSEADDR (TIME_WAIT chi ap dung cho
|
|
# connection socket, khong phai listen socket).
|
|
try:
|
|
s.bind(("127.0.0.1", port))
|
|
return port
|
|
except OSError:
|
|
continue
|
|
return PORT_RANGE[0]
|
|
|
|
|
|
def _parent_alive(pid):
|
|
# Windows: os.kill(pid, 0) KHONG kiem tra ton tai — no goi
|
|
# TerminateProcess (giai thich: moi sig khac CTRL_C/BREAK deu terminate).
|
|
# => watchdog se GIET LUON tien trinh cha (Tauri) sau 1s dau tien.
|
|
# Dung OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION): tra ve NULL khi
|
|
# pid khong con ton tai.
|
|
if os.name == "nt":
|
|
import ctypes
|
|
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
|
h = ctypes.windll.kernel32.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, pid)
|
|
if not h:
|
|
return False # tien trinh da chet
|
|
ctypes.windll.kernel32.CloseHandle(h)
|
|
return True
|
|
try:
|
|
os.kill(pid, 0)
|
|
return True
|
|
except OSError:
|
|
return False
|
|
|
|
|
|
def main():
|
|
os.environ.setdefault("SF_DESKTOP", "1")
|
|
|
|
# Log dir tao SOM de stderr co the tro vao file (xem duoi).
|
|
log_dir = os.path.join(
|
|
os.environ.get("APPDATA") or os.path.expanduser("~"),
|
|
APP_DATA_DIR_NAME, "logs",
|
|
)
|
|
try:
|
|
os.makedirs(log_dir, exist_ok=True)
|
|
except OSError:
|
|
log_dir = os.path.expanduser("~")
|
|
|
|
# Windowed app (engine.spec console=False): Windows khong tao console ->
|
|
# sys.stdout/sys.stderr = None -> uvicorn logging crash
|
|
# ('NoneType' object has no attribute 'isatty'). Tro stdout/stderr vao
|
|
# FILE (KHONG phai devnull) — neu devnull thi moi exception bi nuot im
|
|
# lang (trieu chung 'stuck khong logs, khong loi').
|
|
if sys.stdout is None:
|
|
sys.stdout = open(os.path.join(log_dir, "stdout.log"), "w", encoding="utf-8")
|
|
if sys.stderr is None:
|
|
sys.stderr = open(os.path.join(log_dir, "stderr.log"), "w", encoding="utf-8")
|
|
|
|
port = _pick_port()
|
|
os.environ["SF_PORT"] = str(port)
|
|
|
|
# Log ra file — khi dong goi console=False, stdout khong nhin thay duoc.
|
|
try:
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
|
|
handlers=[
|
|
logging.FileHandler(os.path.join(log_dir, "engine.log"), encoding="utf-8"),
|
|
logging.StreamHandler(),
|
|
],
|
|
)
|
|
except Exception:
|
|
logging.basicConfig(level=logging.INFO)
|
|
|
|
import uvicorn
|
|
import app.main # noqa: F401 — import tuong minh de PyInstaller bundle du package app
|
|
|
|
config = uvicorn.Config(app.main.app, host="127.0.0.1", port=port,
|
|
log_level="info", access_log=False)
|
|
server = uvicorn.Server(config)
|
|
|
|
parent_pid = os.environ.get("SF_PARENT_PID")
|
|
if parent_pid:
|
|
def _watchdog():
|
|
pid = int(parent_pid)
|
|
while True:
|
|
time.sleep(1.0)
|
|
if not _parent_alive(pid):
|
|
logging.getLogger("desktop_engine").info(
|
|
"Parent process exited — shutting down engine")
|
|
server.should_exit = True
|
|
return
|
|
threading.Thread(target=_watchdog, daemon=True).start()
|
|
|
|
logging.getLogger("desktop_engine").info(
|
|
"SonicForge engine listening on 127.0.0.1:%d", port)
|
|
server.run()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|