import logging import multiprocessing import os import socket import threading from pathlib import Path import psutil from src.api.api_server import API_VERSION from src.api.api_server import start_api_server from src.api.preview_manager import PreviewManager from src.api.serverproxy_manager import ServerProxyManager from src.distributed_job_manager import DistributedJobManager from src.engines.engine_manager import EngineManager from src.render_queue import RenderQueue from src.utilities.config import Config from src.utilities.misc_helper import (get_gpu_info, current_system_cpu, current_system_os, current_system_os_version, current_system_cpu_brand, check_for_updates) from src.utilities.zeroconf_server import ZeroconfServer from src.version import APP_NAME, APP_VERSION logger = logging.getLogger() class ZordonServer: def __init__(self): # setup logging logging.basicConfig(format='%(asctime)s: %(levelname)s: %(module)s: %(message)s', datefmt='%d-%b-%y %H:%M:%S', level=Config.server_log_level.upper()) logging.getLogger("requests").setLevel(logging.WARNING) # suppress noisy requests/urllib3 logging logging.getLogger("urllib3").setLevel(logging.WARNING) # Load Config YAML Config.setup_config_dir() config_path = Path(Config.config_dir()) / "config.yaml" Config.load_config(config_path) # configure default paths EngineManager.engines_path = Path(Config.upload_folder).expanduser()/ "engines" os.makedirs(EngineManager.engines_path, exist_ok=True) PreviewManager.storage_path = Path(Config.upload_folder).expanduser() / "previews" self.api_server = None self.server_hostname = None def start_server(self): def existing_process(process_name): import psutil current_pid = os.getpid() current_process = psutil.Process(current_pid) for proc in psutil.process_iter(['pid', 'name', 'ppid']): proc_name = proc.info['name'].lower().rstrip('.exe') if proc_name == process_name.lower() and proc.info['pid'] != current_pid: if proc.info['pid'] == current_process.ppid(): continue # parent process elif proc.info['ppid'] == current_pid: continue # child process else: return proc # unrelated process return None # check for existing instance existing_proc = existing_process(APP_NAME) if existing_proc: err_msg = f"Another instance of {APP_NAME} is already running (pid: {existing_proc.pid})" logger.fatal(err_msg) raise ProcessLookupError(err_msg) # main start logger.info(f"Starting {APP_NAME} Render Server ({APP_VERSION})") logger.debug(f"Upload directory: {Path(Config.upload_folder).expanduser()}") logger.debug(f"Thumbs directory: {PreviewManager.storage_path}") logger.debug(f"Engines directory: {EngineManager.engines_path}") # Set up the RenderQueue object RenderQueue.load_state(database_directory=Path(Config.upload_folder).expanduser()) ServerProxyManager.subscribe_to_listener() DistributedJobManager.subscribe_to_listener() # get hostname self.server_hostname = socket.gethostname() # configure and start API server self.api_server = threading.Thread(target=start_api_server, args=(self.server_hostname,)) self.api_server.daemon = True self.api_server.start() # start zeroconf server ZeroconfServer.configure(f"_{APP_NAME.lower()}._tcp.local.", self.server_hostname, Config.port_number) ZeroconfServer.properties = {'system_cpu': current_system_cpu(), 'system_cpu_brand': current_system_cpu_brand(), 'system_cpu_cores': multiprocessing.cpu_count(), 'system_os': current_system_os(), 'system_os_version': current_system_os_version(), 'system_memory': round(psutil.virtual_memory().total / (1024**3)), # in GB 'gpu_info': get_gpu_info(), 'api_version': API_VERSION} ZeroconfServer.start() logger.info(f"{APP_NAME} Render Server started - Hostname: {self.server_hostname}") RenderQueue.start() # Start evaluating the render queue def is_running(self): return self.api_server and self.api_server.is_alive() def stop_server(self): logger.info(f"{APP_NAME} Render Server is preparing to stop") try: ZeroconfServer.stop() RenderQueue.prepare_for_shutdown() except Exception as e: logger.exception(f"Exception during prepare for shutdown: {e}") logger.info(f"{APP_NAME} Render Server has shut down") if __name__ == '__main__': server = ZordonServer() try: server.start_server() server.api_server.join() except KeyboardInterrupt: pass except Exception as e: logger.fatal(f"Unhandled exception: {e}") finally: server.stop_server()