mirror of
https://github.com/LifeArchiveProject/WeChatDataAnalysis.git
synced 2026-06-18 15:54:08 +08:00
176 lines
4.7 KiB
Python
176 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
from pathlib import Path
|
|
|
|
|
|
RUNTIME_SETTINGS_FILENAME = "runtime_settings.json"
|
|
BACKEND_PORT_KEY = "backend_port"
|
|
ENV_PORT_KEY = "WECHAT_TOOL_PORT"
|
|
ENV_FILE_KEY = "WECHAT_TOOL_ENV_FILE"
|
|
DEFAULT_ENV_FILENAME = ".env"
|
|
|
|
|
|
def _parse_port(value: object) -> int | None:
|
|
if value is None:
|
|
return None
|
|
try:
|
|
raw = str(value).strip()
|
|
except Exception:
|
|
return None
|
|
if not raw:
|
|
return None
|
|
try:
|
|
port = int(raw, 10)
|
|
except Exception:
|
|
return None
|
|
if port < 1 or port > 65535:
|
|
return None
|
|
return port
|
|
|
|
|
|
def get_runtime_settings_path() -> Path:
|
|
from .app_paths import get_output_dir
|
|
|
|
return get_output_dir() / RUNTIME_SETTINGS_FILENAME
|
|
|
|
|
|
def read_backend_port_setting() -> int | None:
|
|
path = get_runtime_settings_path()
|
|
try:
|
|
if not path.is_file():
|
|
return None
|
|
data = json.loads(path.read_text(encoding="utf-8") or "{}")
|
|
if not isinstance(data, dict):
|
|
return None
|
|
return _parse_port(data.get(BACKEND_PORT_KEY))
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def write_backend_port_setting(port: int | None) -> None:
|
|
path = get_runtime_settings_path()
|
|
safe_port = _parse_port(port)
|
|
try:
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
except Exception:
|
|
return
|
|
|
|
try:
|
|
data: dict = {}
|
|
if path.is_file():
|
|
try:
|
|
existing = json.loads(path.read_text(encoding="utf-8") or "{}")
|
|
if isinstance(existing, dict):
|
|
data = existing
|
|
except Exception:
|
|
data = {}
|
|
|
|
if safe_port is None:
|
|
data.pop(BACKEND_PORT_KEY, None)
|
|
else:
|
|
data[BACKEND_PORT_KEY] = safe_port
|
|
|
|
# Keep the file small and stable; remove if empty.
|
|
if not data:
|
|
try:
|
|
path.unlink(missing_ok=True)
|
|
except Exception:
|
|
pass
|
|
return
|
|
|
|
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
except Exception:
|
|
return
|
|
|
|
|
|
def read_effective_backend_port(default: int) -> tuple[int, str]:
|
|
"""Return (port, source) where source is one of: env | settings | default."""
|
|
|
|
env_raw = str(os.environ.get("WECHAT_TOOL_PORT", "") or "").strip()
|
|
env_port = _parse_port(env_raw)
|
|
if env_port is not None:
|
|
return env_port, "env"
|
|
|
|
settings_port = read_backend_port_setting()
|
|
if settings_port is not None:
|
|
return settings_port, "settings"
|
|
|
|
return int(default), "default"
|
|
|
|
|
|
def get_env_file_path() -> Path | None:
|
|
"""Best-effort env file path for `uv run` (defaults to repo root `.env`)."""
|
|
|
|
v = str(os.environ.get(ENV_FILE_KEY, "") or "").strip()
|
|
if v:
|
|
try:
|
|
return Path(v)
|
|
except Exception:
|
|
return None
|
|
|
|
cwd = Path.cwd()
|
|
# Heuristic: only write `.env` in a project root (avoid polluting random dirs).
|
|
try:
|
|
if (cwd / "pyproject.toml").is_file():
|
|
return cwd / DEFAULT_ENV_FILENAME
|
|
except Exception:
|
|
return None
|
|
|
|
return None
|
|
|
|
|
|
def _set_env_var_in_file(env_file: Path, key: str, value: str | None) -> bool:
|
|
try:
|
|
env_file.parent.mkdir(parents=True, exist_ok=True)
|
|
except Exception:
|
|
return False
|
|
|
|
pattern = re.compile(rf"^\s*(?:export\s+)?{re.escape(key)}\s*=")
|
|
try:
|
|
raw = env_file.read_text(encoding="utf-8") if env_file.is_file() else ""
|
|
except Exception:
|
|
raw = ""
|
|
|
|
lines = raw.splitlines(keepends=True) if raw else []
|
|
out: list[str] = []
|
|
replaced = False
|
|
for line in lines:
|
|
if pattern.match(line):
|
|
if value is None:
|
|
continue
|
|
if not replaced:
|
|
out.append(f"{key}={value}\n")
|
|
replaced = True
|
|
continue
|
|
out.append(line)
|
|
|
|
if value is not None and not replaced:
|
|
if out and not out[-1].endswith("\n"):
|
|
out[-1] = out[-1] + "\n"
|
|
out.append(f"{key}={value}\n")
|
|
|
|
try:
|
|
env_file.write_text("".join(out), encoding="utf-8")
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def write_backend_port_env_file(port: int | None) -> Path | None:
|
|
"""Write `WECHAT_TOOL_PORT` into a `.env` file so `uv run main.py` picks it up on restart.
|
|
|
|
Note: `uv` doesn't override already-set env vars; `.env` only applies when the variable is not
|
|
present in the current shell/session.
|
|
"""
|
|
|
|
env_file = get_env_file_path()
|
|
if not env_file:
|
|
return None
|
|
|
|
safe_port = _parse_port(port)
|
|
ok = _set_env_var_in_file(env_file, ENV_PORT_KEY, str(safe_port) if safe_port is not None else None)
|
|
return env_file if ok else None
|