Files
Haocode/core/config_paths.py
sorrow404null 5ae660574e chore: re-submit 2026-09-18 changes to verify webhook
- feat(config): add atomic configuration persistence
- fix(ui): repair chat and model settings rendering
- fix(capture): map selections across high-DPI displays
- fix(webview2): scale native bounds for fractional DPI
- chore: remove test scripts from distribution
2026-09-18 16:13:05 +08:00

110 lines
4.2 KiB
Python
Raw Permalink 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 -*-
"""core/config_paths.py —— 统一的配置文件路径解析与容错加载(P0-01 新增)
所有配置读取入口必须经过本模块,不得在别处直接定位 data/config.json
1. 环境变量 HAOCODE_CONFIG_FILE 优先级最高(自动化测试用它指向临时文件);
2. 未设置环境变量时才回落到项目内 data/config.json(锚定 core/ 目录,不受 CWD 影响)。
行为约定(REPAIR_BACKLOG P0-01 硬约束):
· 路径解析与加载过程只在出错时输出可见警告(只含路径与错误类型,不含配置内容);
· 缺失 / 损坏 / 非 JSON 对象 → 返回安全空 dict 继续,不抛异常、不阻断
不需要该配置的源码路径;
· import 本模块时不打印、不复制、不迁移任何配置内容。
"""
import json
import os
import tempfile
ENV_KEY = "HAOCODE_CONFIG_FILE"
_DEFAULT_CONFIG_PATH = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "..", "data", "config.json")
def config_path() -> str:
"""返回当前生效的配置文件路径(环境变量优先,调用时解析)。"""
p = os.environ.get(ENV_KEY, "").strip()
return p if p else _DEFAULT_CONFIG_PATH
def load_config() -> dict:
"""容错加载配置。缺失/损坏/非对象 → 可见警告 + 安全空 dict(绝不抛异常)。"""
p = config_path()
if not os.path.exists(p):
print(f"[config] 配置文件缺失: {p} → 使用安全默认值(providers 为空)继续启动")
return {}
try:
with open(p, "r", encoding="utf-8") as f:
data = json.load(f)
except Exception as e:
print(f"[config] 配置文件读取/解析失败: {p}{type(e).__name__}"
f" → 使用安全默认值继续启动")
return {}
if not isinstance(data, dict):
print(f"[config] 配置文件内容不是 JSON 对象: {p}"
f" → 使用安全默认值继续启动")
return {}
return data
def save_config(data: dict) -> bool:
"""原子保存当前配置;失败时保留原文件并返回 False。"""
if not isinstance(data, dict):
print("[config] 拒绝保存:配置内容不是 JSON 对象")
return False
path = os.path.abspath(config_path())
parent = os.path.dirname(path)
temp_path = ""
try:
os.makedirs(parent, exist_ok=True)
fd, temp_path = tempfile.mkstemp(
prefix=".haocode_config_", suffix=".tmp", dir=parent)
with os.fdopen(fd, "w", encoding="utf-8") as handle:
json.dump(data, handle, ensure_ascii=False, indent=2)
handle.write("\n")
handle.flush()
os.fsync(handle.fileno())
os.replace(temp_path, path)
return True
except Exception as exc:
print(f"[config] 配置保存失败: {path}{type(exc).__name__}")
return False
finally:
if temp_path and os.path.exists(temp_path):
try:
os.remove(temp_path)
except OSError:
pass
# ----------------------------------------------------------------------
# P1-01:渲染窗口配置解析(render_window_mode / render_window_size
# 规则(与 ui/web/render_window.js 的 JS 侧守卫保持一致):
# size:只接受非布尔整数 10..200,缺失/布尔/字符串/小数/零/负数/越界 → 静默回落 40;
# mode:只接受 "auto"/"manual",否则回落 "auto"。
# ----------------------------------------------------------------------
DEFAULT_RENDER_WINDOW_SIZE = 40
ALLOWED_RENDER_WINDOW_SIZES = (10, 40, 200)
def render_window_settings(cfg: dict | None = None) -> dict:
"""解析渲染窗口配置。cfg 缺省时读取当前生效配置。永不抛异常。"""
if cfg is None:
cfg = load_config()
if not isinstance(cfg, dict):
cfg = {}
raw_size = cfg.get("render_window_size", None)
if isinstance(raw_size, bool) or not isinstance(raw_size, int) \
or raw_size < 10 or raw_size > 200:
size = DEFAULT_RENDER_WINDOW_SIZE
else:
size = raw_size
raw_mode = cfg.get("render_window_mode", None)
mode = raw_mode if raw_mode in ("auto", "manual") else "auto"
return {"mode": mode, "size": size}