#pragma once #include #include #include namespace fgc::paths { // Expand a leading "~" (to $HOME) and any "$VAR" / "${VAR}" environment // references in `path`. Returns the expanded path (unchanged if nothing to do). std::string expandUser(const std::string& path); // Absolute directory containing the running executable (via /proc/self/exe). // Returns empty string if it cannot be determined. std::string executableDir(); // The ordered list of locations searched for a config file, given an optional // explicit --config argument. Exposed so callers can report what was tried. std::vector configSearchPaths(const std::string& cliArg = ""); // Resolve the config file path using configSearchPaths(): the first existing, // readable file wins. Returns nullopt if none is found. std::optional resolveConfigPath(const std::string& cliArg = ""); // The ordered list of locations searched for hardware.ini - the assembly-time // description of THIS machine (which board, which sensors are fitted, which // device nodes they are on). Deliberately NOT derived from the config.ini path: // // --hardware explicit, for dev and testing // $FGC_HARDWARE the service environment // /etc/fire_gimbal_control/hardware.ini the normal answer on a tower // /hardware.ini so a dev checkout just works // // /etc is where credentials.env already lives: root-owned, per-device, and // OUTSIDE the deploy tree, so an rsync cannot overwrite or delete it. Keeping the // assembly description there makes "a deploy never touches it" a property of // where the file is, not of an exclude rule someone has to maintain. std::vector hardwareSearchPaths(const std::string& cliArg = "", const std::string& configPath = ""); // Resolve hardware.ini using hardwareSearchPaths(); first existing file wins. // Returns nullopt when there is none - which is not an error: the program then // falls back to reading the assembly keys from config.ini as it always has. std::optional resolveHardwarePath(const std::string& cliArg = "", const std::string& configPath = ""); // Default image output directory when none is configured // ($XDG_DATA_HOME/fire_gimbal_control/images, else ~/.local/share/...). std::string defaultOutputDir(); // Directory for diagnostic/calibration logfiles // ($XDG_DATA_HOME/fire_gimbal_control/logs, else ~/.local/share/...). std::string defaultLogDir(); // "_YYYYMMDD-HHMMSS.log" using the local clock. std::string timestampedLogName(const std::string& prefix); // How many _*.log reports to keep per kind. Generous: these are a few // hundred bytes each and written by hand-run commands, so the cap is about // keeping the directory greppable rather than about disk. inline constexpr int kDefaultReportsKept = 50; // Delete all but the `keep` newest "_*.log" in defaultLogDir(). // // Only files this program GENERATED are eligible: the name must be exactly // "_YYYYMMDD-HHMMSS.log", as timestampedLogName() builds it. A // prefix-only match would also cover `[Test] baseline_file`, which is // configurable and may live in this directory - the pinned reference every // self-test compares against. keep <= 0 prunes nothing. void pruneLogFiles(const std::string& prefix, int keep); // Write `text` to `defaultLogDir()/`, creating the directory if needed. // Returns the full path written, or "" on failure (and logs a warning). // // Then prunes older files of the same kind, the prefix being whatever precedes // the first '_' in `name` - so `test_*.log`, `diag_*.log` and `pitch_setup_*.log` // stay bounded without each caller having to remember. Pass keep = 0 to opt out. std::string writeLogFile(const std::string& name, const std::string& text, int keep = kDefaultReportsKept); } // namespace fgc::paths