config
1import os 2import sys 3from pathlib import Path 4 5_PROJECT_ROOT = Path(__file__).resolve().parent 6_APP_IDENTIFIER = "com.linxiv.app" # must match src-tauri/tauri.conf.json "identifier" 7 8 9def repo_dir() -> Path: 10 """The source-tree root. For developer/repo artifacts only (e.g. the dev .env). 11 NOT for runtime user data — that is data_dir().""" 12 return _PROJECT_ROOT 13 14 15def _default_data_dir() -> Path: 16 """OS per-user app-data dir for com.linxiv.app, matching Tauri's app_data_dir() 17 so a directly-launched CLI/MCP/API uses the same location as the packaged app. 18 19 Linux: $XDG_DATA_HOME or ~/.local/share, then /com.linxiv.app 20 macOS: ~/Library/Application Support/com.linxiv.app 21 Windows: %APPDATA% (Roaming) or ~/AppData/Roaming, then /com.linxiv.app 22 23 Used only when LINXIV_DATA_DIR is unset (dev / CLI / MCP launched without Tauri). 24 """ 25 if sys.platform == "darwin": 26 base = Path.home() / "Library" / "Application Support" 27 elif sys.platform == "win32": 28 base = Path(os.environ.get("APPDATA") or (Path.home() / "AppData" / "Roaming")) 29 else: 30 base = Path(os.environ.get("XDG_DATA_HOME") or (Path.home() / ".local" / "share")) 31 return base / _APP_IDENTIFIER 32 33 34def data_dir() -> Path: 35 """Runtime data dir: DB, PDFs, user settings, Obsidian vault, arXiv rate-limit file. 36 37 The single source of truth is $LINXIV_DATA_DIR (Tauri sets it on the subprocess). 38 When unset (dev / CLI / MCP launched without Tauri) it falls back to the OS 39 app-data dir — never the repo root. Resolved on every call so it tracks the env 40 var dynamically; call init_data_dir() once at startup to persist and create it. 41 """ 42 d = os.environ.get("LINXIV_DATA_DIR") 43 return Path(d) if d else _default_data_dir() 44 45 46def init_data_dir() -> Path: 47 """Resolve, persist, and create the data dir. Call once per run at startup, before 48 any DB/PDF/vault access. Idempotent. 49 50 Writes the resolved path back to $LINXIV_DATA_DIR so the value is stable for the 51 whole process (and inherited by child processes), then creates the directory. This 52 is what makes "initialize the data dir on any run" hold even when launched directly 53 without Tauri. 54 55 Side effect: once called, $LINXIV_DATA_DIR is pinned for the lifetime of the 56 process. Tests that redirect the data dir must set/restore os.environ 57 (pytest's monkeypatch.setenv does this automatically). 58 """ 59 path = data_dir() 60 os.environ["LINXIV_DATA_DIR"] = str(path) 61 path.mkdir(parents=True, exist_ok=True) 62 return path 63 64 65def resources_dir() -> Path: 66 """Read-only bundled resources: SQL schemas, graph assets, format templates. 67 In a PyInstaller frozen build these are extracted to sys._MEIPASS. 68 In development they live at the project root. 69 """ 70 if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): 71 return Path(sys._MEIPASS) # type: ignore[attr-defined] 72 return _PROJECT_ROOT 73 74 75# .env file location — developer convenience for dev API keys. Lives in the repo (a 76# source artifact), NOT in data_dir(). In production Tauri injects env vars directly; 77# this path just needs to exist. 78ENV_PATH = repo_dir() / ".env"
10def repo_dir() -> Path: 11 """The source-tree root. For developer/repo artifacts only (e.g. the dev .env). 12 NOT for runtime user data — that is data_dir().""" 13 return _PROJECT_ROOT
The source-tree root. For developer/repo artifacts only (e.g. the dev .env). NOT for runtime user data — that is data_dir().
35def data_dir() -> Path: 36 """Runtime data dir: DB, PDFs, user settings, Obsidian vault, arXiv rate-limit file. 37 38 The single source of truth is $LINXIV_DATA_DIR (Tauri sets it on the subprocess). 39 When unset (dev / CLI / MCP launched without Tauri) it falls back to the OS 40 app-data dir — never the repo root. Resolved on every call so it tracks the env 41 var dynamically; call init_data_dir() once at startup to persist and create it. 42 """ 43 d = os.environ.get("LINXIV_DATA_DIR") 44 return Path(d) if d else _default_data_dir()
Runtime data dir: DB, PDFs, user settings, Obsidian vault, arXiv rate-limit file.
The single source of truth is $LINXIV_DATA_DIR (Tauri sets it on the subprocess). When unset (dev / CLI / MCP launched without Tauri) it falls back to the OS app-data dir — never the repo root. Resolved on every call so it tracks the env var dynamically; call init_data_dir() once at startup to persist and create it.
47def init_data_dir() -> Path: 48 """Resolve, persist, and create the data dir. Call once per run at startup, before 49 any DB/PDF/vault access. Idempotent. 50 51 Writes the resolved path back to $LINXIV_DATA_DIR so the value is stable for the 52 whole process (and inherited by child processes), then creates the directory. This 53 is what makes "initialize the data dir on any run" hold even when launched directly 54 without Tauri. 55 56 Side effect: once called, $LINXIV_DATA_DIR is pinned for the lifetime of the 57 process. Tests that redirect the data dir must set/restore os.environ 58 (pytest's monkeypatch.setenv does this automatically). 59 """ 60 path = data_dir() 61 os.environ["LINXIV_DATA_DIR"] = str(path) 62 path.mkdir(parents=True, exist_ok=True) 63 return path
Resolve, persist, and create the data dir. Call once per run at startup, before any DB/PDF/vault access. Idempotent.
Writes the resolved path back to $LINXIV_DATA_DIR so the value is stable for the whole process (and inherited by child processes), then creates the directory. This is what makes "initialize the data dir on any run" hold even when launched directly without Tauri.
Side effect: once called, $LINXIV_DATA_DIR is pinned for the lifetime of the process. Tests that redirect the data dir must set/restore os.environ (pytest's monkeypatch.setenv does this automatically).
66def resources_dir() -> Path: 67 """Read-only bundled resources: SQL schemas, graph assets, format templates. 68 In a PyInstaller frozen build these are extracted to sys._MEIPASS. 69 In development they live at the project root. 70 """ 71 if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"): 72 return Path(sys._MEIPASS) # type: ignore[attr-defined] 73 return _PROJECT_ROOT
Read-only bundled resources: SQL schemas, graph assets, format templates. In a PyInstaller frozen build these are extracted to sys._MEIPASS. In development they live at the project root.