Files
server_manager/src/models/config.py
T
2026-05-22 00:16:08 +08:00

415 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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)