# -*- coding: utf-8 -*- """Project configuration model.""" from util.runtime import * def _empty_config_data(): """无项目时的默认配置结构,避免 get() 报错""" return { "workspace": {"server_root": "", "run_dir": ""}, "database": {"host": "", "user": "", "password": "", "port": 3306}, "server": { "prefix": "ddxq2", "login_server_node": "", "center_server_node": "", "default_server_id": "1", "server_type": "game_server", "server_name": "", "ip": get_local_ip(), "game_host": get_local_ip(), "cookie": "ddxq2-node", }, "erlang": {"r25_path": "", "erl": "erl", "werl": "werl", "escript": "escript"}, "login_db": { "name": "", "server_id": 900, "host": "", "port": 0, "user": "", "password": "", }, "commands": {"rebar": REBAR3_CMD, "svn": "svn"}, "terminal": {"launch_mode": "auto", "exe_path": ""}, } class Config: """项目配置管理类:读写当前打开项目下的 .server_manager/config/tool.config(与项目一起走)。 打开项目时会将软件目录下受管模板同步到 .server_manager/config,并保留项目自己的 tool.config。 支持“无项目”启动:project_root=None 时仅显示欢迎页;通过 open_project(path) 打开项目后加载该项目配置。 """ def __init__(self, project_root: Optional[Path] = None): if getattr(sys, 'frozen', False): self._exe_dir = Path(sys.executable).parent self._script_dir = self._exe_dir else: self._script_dir = SRC_DIR self._exe_dir = self._script_dir.parent.parent self.tool_dir = None self.tool_config_path = None self.config_error = None self.data = _empty_config_data() if project_root is not None: self.open_project(Path(project_root)) else: self.config_error = "未打开项目" def has_project(self) -> bool: """是否已打开有效项目(存在 .server_manager/config)""" if self.tool_dir is None: return False return get_server_manager_config_dir(self.tool_dir).is_dir() def open_project(self, project_root: Path) -> bool: """打开项目目录并同步受管配置模板到 .server_manager/config。成功返回 True。""" project_root = Path(project_root).resolve() cfg_dir, err = ensure_and_get_server_manager_config_dir(project_root) if not cfg_dir: self.config_error = err return False self.tool_dir = project_root self.tool_config_path = cfg_dir / "tool.config" self.config_error = None self.data = self._load_config() return True def _find_server_root(self) -> Path: """查找有效的服务器根目录 查找顺序:优先运行目录(当前工作目录),再考虑 exe/脚本所在位置。 Returns: 有效的服务器根目录路径,如果都找不到返回 None """ candidates = [ Path.cwd(), # 运行目录优先 self._exe_dir, self._script_dir, ] for path in candidates: if (get_server_manager_config_dir(path) / "default.kv").exists(): return path if (path / "config" / "default.kv").exists(): return path return None def _load_tool_config(self) -> dict: """加载 tool.config 文件""" if self.tool_config_path is None or not self.tool_config_path.exists(): return {} config = {} try: with open(self.tool_config_path, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if not line or line.startswith('#'): continue if '=' in line: key, value = line.split('=', 1) config[key.strip()] = value.strip() except Exception: return {} return config def _save_tool_config(self, config: dict): """保存 tool.config 文件""" lines = [ "# Server Manager config", "# 此文件配置工具运行所需的环境路径", "", "# === 工作目录配置 ===", f"server_root={config.get('server_root', '')}", f"run_dir={config.get('run_dir', '')}", "", "# === 启动窗口配置 ===", f"terminal_launch_mode={config.get('terminal_launch_mode', 'auto')}", f"terminal_exe_path={config.get('terminal_exe_path', '')}", "", "# === Erlang 配置 ===", f"r25_path={config.get('r25_path', '')}", "", "# === 数据库配置 ===", f"db_host={config.get('db_host', '')}", f"db_port={config.get('db_port', '3306')}", f"db_user={config.get('db_user', '')}", f"db_pass={config.get('db_pass', '')}", "", "# === 登录服数据库配置(远程连接用)===", f"login_db_name={config.get('login_db_name', '')}", f"login_db_server_id={config.get('login_db_server_id', '900')}", f"login_db_host={config.get('login_db_host', '')}", f"login_db_port={config.get('login_db_port', '0')}", f"login_db_user={config.get('login_db_user', '')}", f"login_db_pass={config.get('login_db_pass', '')}", "", "# === 服务器基础配置 ===", f"prefix={config.get('prefix', 'ddxq2')}", f"default_server_id={config.get('default_server_id', '1')}", f"server_type={config.get('server_type', 'game_server')}", f"server_name={config.get('server_name', '')}", f"login_node={config.get('login_node', '')}", f"center_node={config.get('center_node', '')}", f"cookie={config.get('cookie', 'ddxq2-node')}", ] self.tool_config_path.parent.mkdir(parents=True, exist_ok=True) with open(self.tool_config_path, 'w', encoding='utf-8') as f: f.write('\n'.join(lines)) def _load_default_kv(self, server_root: str) -> dict: """从 default.kv 读取默认配置""" if not server_root: return {} default_kv_path = get_server_manager_config_dir(server_root) / 'default.kv' if not default_kv_path.exists(): return {} config = {} try: with open(default_kv_path, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if not line or line.startswith('#'): continue if '=' in line: key, value = line.split('=', 1) config[key.strip()] = value.strip() except Exception: return {} return config def _load_config(self): """加载配置,从 tool.config 读取,空值从 default.kv 补充。 以当前已打开的项目目录(self.tool_dir)为 server_root 默认值,tool.config 可覆盖。 """ if self.tool_dir is None or self.tool_config_path is None: return self.data tool_cfg = self._load_tool_config() is_first_run = not self.tool_config_path.exists() # 以当前项目目录为默认,tool.config 可覆盖 server_root = tool_cfg.get('server_root', '') or str(self.tool_dir) run_dir = tool_cfg.get('run_dir', '') if not run_dir and server_root: run_dir = str(Path(server_root) / "run") # 首次运行时,创建运行目录 if is_first_run and run_dir: run_path = Path(run_dir) if not run_path.exists(): try: run_path.mkdir(parents=True, exist_ok=True) except Exception: pass # 如果有 server_root,尝试从 default.kv 读取默认值 default_kv = self._load_default_kv(server_root) # 辅助函数:优先使用 tool.config 值,为空则使用 default.kv 值 def get_value(tool_key, default_key, fallback=''): val = tool_cfg.get(tool_key, '') if not val and default_kv: val = default_kv.get(default_key, fallback) # 去掉可能的引号 if isinstance(val, str) and val.startswith("'") and val.endswith("'"): val = val[1:-1] return val if val else fallback db_host = get_value('db_host', 'db_host', '') db_user = get_value('db_user', 'db_user', '') db_pass = get_value('db_pass', 'db_pass', '') db_port = get_value('db_port', 'db_port', '3306') cookie = get_value('cookie', 'cookie', 'ddxq2-node') login_db_name = get_value('login_db_name', 'login_db_name', '') login_db_server_id = get_value('login_db_server_id', 'login_db_server_id', '900') login_db_host = get_value('login_db_host', 'login_db_host', '') login_db_port = get_value('login_db_port', 'login_db_port', '0') login_db_user = get_value('login_db_user', 'login_db_user', '') login_db_pass = get_value('login_db_pass', 'login_db_pass', '') r25_path = get_value('r25_path', 'r25_path', '') terminal_launch_mode = (tool_cfg.get('terminal_launch_mode', '') or 'auto').strip().lower() terminal_exe_path = tool_cfg.get('terminal_exe_path', '') if not terminal_exe_path: legacy_exe = tool_cfg.get('terminal_custom_command', '') legacy_cwd = tool_cfg.get('terminal_custom_cwd', '') if legacy_exe and legacy_cwd and not any(sep in legacy_exe for sep in ('/', '\\')): legacy_path = Path(os.path.expandvars(os.path.expanduser(legacy_cwd))) / legacy_exe if legacy_path.is_file(): terminal_exe_path = str(legacy_path.resolve()) if not terminal_exe_path: terminal_exe_path = legacy_exe # 服务器基础配置 prefix = get_value('prefix', 'prefix', 'ddxq2') default_server_id = get_value('default_server_id', 'server_id', '1') server_type = get_value('server_type', 'server_type', 'game_server') server_name = get_value('server_name', 'server_name', '') local_ip = get_local_ip() login_node = get_value('login_node', 'login_node', '') center_node = get_value('center_node', 'center_node', '') config_data = { "workspace": { "server_root": server_root, "run_dir": run_dir, }, "database": { "host": db_host, "user": db_user, "password": db_pass, "port": int(db_port) if db_port else 3306, }, "server": { "prefix": prefix, "login_server_node": login_node, "center_server_node": center_node, "default_server_id": default_server_id, "server_type": server_type, "server_name": server_name, "ip": local_ip, "game_host": local_ip, "cookie": cookie, }, "erlang": { "r25_path": r25_path, "erl": "erl", "werl": "werl", "escript": "escript" }, "login_db": { "name": login_db_name, "server_id": int(login_db_server_id) if login_db_server_id else 900, "host": login_db_host, "port": int(login_db_port) if login_db_port else 0, "user": login_db_user, "password": login_db_pass, }, "commands": { "rebar": REBAR3_CMD, "svn": "svn" }, "terminal": { "launch_mode": terminal_launch_mode, "exe_path": terminal_exe_path, }, } # 每次启动都检查配置完整性,从 default.kv 补充缺失的值 need_save = is_first_run if default_kv: # 检查是否有从 default.kv 补充的值 fields_to_check = [ ('db_host', db_host), ('db_user', db_user), ('db_pass', db_pass), ('prefix', prefix), ('login_node', login_node), ('center_node', center_node), ('cookie', cookie), ] for field_name, new_value in fields_to_check: old_value = tool_cfg.get(field_name, '') if not old_value and new_value: need_save = True break if need_save: self.data = config_data self.save() return config_data def has_default_kv(self) -> bool: """检查 default.kv 是否存在""" server_root = self.get('workspace', 'server_root', default='') if not server_root: return False default_kv_path = get_server_manager_config_dir(server_root) / 'default.kv' return default_kv_path.exists() def is_config_complete(self) -> bool: """检查工具配置是否完整""" # 检查必要的配置项 db_host = self.get('database', 'host', default='') db_user = self.get('database', 'user', default='') prefix = self.get('server', 'prefix', default='') return bool(db_host and db_user and prefix) def save(self): """保存配置到 tool.config""" if self.tool_config_path is None: return local_ip = get_local_ip() self.set(local_ip, 'server', 'ip') self.set(local_ip, 'server', 'game_host') tool_cfg = { 'server_root': self.get('workspace', 'server_root', default=''), 'run_dir': self.get('workspace', 'run_dir', default=''), 'r25_path': self.get('erlang', 'r25_path', default=''), 'db_host': self.get('database', 'host', default=''), 'db_port': str(self.get('database', 'port', default=3306)), 'db_user': self.get('database', 'user', default=''), 'db_pass': self.get('database', 'password', default=''), 'login_db_name': self.get('login_db', 'name', default=''), 'login_db_server_id': str(self.get('login_db', 'server_id', default=900)), 'login_db_host': self.get('login_db', 'host', default=''), 'login_db_port': str(self.get('login_db', 'port', default=0)), 'login_db_user': self.get('login_db', 'user', default=''), 'login_db_pass': self.get('login_db', 'password', default=''), 'prefix': self.get('server', 'prefix', default='ddxq2'), 'default_server_id': self.get('server', 'default_server_id', default='1'), 'server_type': self.get('server', 'server_type', default='game_server'), 'server_name': self.get('server', 'server_name', default=''), 'login_node': self.get('server', 'login_server_node', default=''), 'center_node': self.get('server', 'center_server_node', default=''), 'cookie': self.get('server', 'cookie', default='ddxq2-node'), 'terminal_launch_mode': self.get('terminal', 'launch_mode', default='auto'), 'terminal_exe_path': self.get('terminal', 'exe_path', default=''), } self._save_tool_config(tool_cfg) def get(self, *keys, default=None): """获取配置项""" result = self.data for key in keys: if isinstance(result, dict) and key in result: result = result[key] else: return default return result def set(self, value, *keys): """设置配置项""" target = self.data for key in keys[:-1]: if key not in target: target[key] = {} target = target[key] target[keys[-1]] = value def is_configured(self) -> bool: """检查必要配置是否已设置""" server_root = self.get('workspace', 'server_root', default='') return bool(server_root and Path(server_root).exists()) def get_missing_config(self) -> list: """获取缺失的必要配置项""" missing = [] server_root = self.get('workspace', 'server_root', default='') if not server_root: missing.append("服务器根目录 (server_root)") elif not Path(server_root).exists(): missing.append(f"服务器根目录不存在: {server_root}") return missing def apply_terminal_launch_config(config: Config) -> None: """Apply project terminal launch settings to the process environment.""" mode = (config.get('terminal', 'launch_mode', default='auto') or 'auto').strip().lower() or 'auto' terminal_exe = (config.get('terminal', 'exe_path', default='') or '').strip() os.environ['SERVER_MANAGER_TERMINAL'] = mode if terminal_exe: os.environ['SERVER_MANAGER_TERMINAL_EXE'] = terminal_exe else: os.environ.pop('SERVER_MANAGER_TERMINAL_EXE', None) os.environ.pop('SERVER_MANAGER_TERMINAL_COMMAND', None) os.environ.pop('SERVER_MANAGER_TERMINAL_CWD', None)