feat: get_data_dir() three-tier fallback (env -> ~/.cmbot -> dev root)

Packaged builds now resolve the data root to ~/.cmbot (%USERPROFILE%\.cmbot)
instead of the program dir, so user config/templates/output stay writable and
per-user regardless of where the program is installed or whether it is launched
via Launcher.exe (docs/10-lan-update.md §5). CMBOT_DATA_DIR still overrides.

- tests/test_file_service.py: 5 tests covering env override, frozen->~/.cmbot,
  dev->project root, blank-env handling

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 10:39:52 +08:00
co-authored by Claude Opus 4.8
parent 0c9415fbcd
commit 3d01bed98c
3 changed files with 69 additions and 10 deletions
+12 -9
View File
@@ -22,9 +22,9 @@ def is_supported_image(path):
def get_app_dir():
"""Return the program root directory (read-only program files) as a Path.
PyInstaller onedir: directory that contains the .exe. In the versioned
install layout (docs/10-lan-update.md) this is versions/<x.y.z>/, which is
replaced wholesale on update.
PyInstaller onedir: directory that contains the .exe. In the launcher
layout (docs/10-lan-update.md) this is the app\\ folder, replaced wholesale
on update.
Development: project root (three levels above this file:
src/services/file_service.py -> src/services -> src -> project root).
@@ -39,17 +39,20 @@ def get_data_dir():
"""Return the writable data root (config, templates, logs, output) as a Path.
Kept separate from the program root so that replacing the program on update
never touches user data (docs/10-lan-update.md §5).
never touches user data (docs/10-lan-update.md §5). Resolution order:
Resolution order:
1. CMBOT_DATA_DIR environment variable, when set. The launcher points this
at the install-wide data/ folder in the versioned layout.
2. Fallback to get_app_dir() — the flat layout used in development and in
the current onedir release, where data sits next to the program.
1. CMBOT_DATA_DIR environment variable, when set — explicit override for
tests or special deployments.
2. Packaged (sys.frozen) → ~/.cmbot (%USERPROFILE%\\.cmbot): always writable,
per-user, independent of where the program is installed, so it works even
when launched directly rather than through Launcher.exe.
3. Development → project root, so dev runs don't pollute the home directory.
"""
env = os.environ.get("CMBOT_DATA_DIR", "").strip()
if env:
return Path(env)
if getattr(sys, "frozen", False):
return Path.home() / ".cmbot"
return get_app_dir()