"""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()