namespace IBKRTrader.Core.Configuration;
///
/// Die Verzeichnisse, in die die Anwendung schreibt.
///
/// Warum das nötig wurde: Bisher lag alles neben der Binärdatei – Logs/,
/// Backups/, settings.json, master.key. Unter Windows ist das üblich. Auf
/// Linux liegt eine Anwendung typischerweise unter /opt oder /usr/local, und dort
/// hat der Dienstbenutzer keinen Schreibzugriff. Der Dienst wäre beim ersten Logeintrag
/// gescheitert.
///
/// Auflösung, in dieser Reihenfolge:
///
/// - Umgebungsvariable (IBKRTRADER_CONFIG_DIR, _DATA_DIR, _LOG_DIR) –
/// hat immer Vorrang, damit ein Betreiber die Ablage frei bestimmen kann.
/// - Das Verzeichnis der Binärdatei, wenn dort geschrieben werden darf. Das hält das
/// bisherige Verhalten unter Windows und beim Entwickeln unter Linux unverändert.
/// - Sonst plattformabhängig: unter Linux/macOS die FHS-Konvention
/// (/etc/ibkrtrader, /var/lib/ibkrtrader, /var/log/ibkrtrader), unter
/// Windows %ProgramData%\IBKRTrader. Der FHS-Pfad darf hier nicht als
/// plattformübergreifender Rückfall dienen – .NET löst /etc/ibkrtrader unter
/// Windows zu C:\etc\ibkrtrader auf. Genau dieser Fall tritt ein, sobald eine
/// Installation nach Program Files zeigt, wo der Dienst- bzw. Nutzerkonto-Kontext
/// keinen Schreibzugriff neben der Binärdatei hat.
///
///
/// Die Prüfung läuft einmal beim ersten Zugriff; die Verzeichnisse werden dabei angelegt.
///
public static class AppPaths
{
private const string AppFolder = "ibkrtrader";
private static readonly Lazy _config = new(() => Resolve("IBKRTRADER_CONFIG_DIR", $"/etc/{AppFolder}"));
private static readonly Lazy _data = new(() => Resolve("IBKRTRADER_DATA_DIR", $"/var/lib/{AppFolder}", windowsSubDir: "Data"));
private static readonly Lazy _logs = new(() => Resolve("IBKRTRADER_LOG_DIR", $"/var/log/{AppFolder}", "Logs", windowsSubDir: "Logs"));
/// Konfiguration und Schlüsselmaterial: settings.json, master.key, openrouter.key.
public static string Config => _config.Value;
/// Veränderliche Daten: Backups/.
public static string Data => _data.Value;
/// Logdateien (Textlog je Modul und JSONL).
public static string Logs => _logs.Value;
/// Vollständiger Pfad einer Konfigurationsdatei.
public static string ConfigFile(string fileName) => Path.Combine(Config, fileName);
/// Vollständiger Pfad unterhalb des Datenverzeichnisses.
public static string DataPath(string relative) => Path.Combine(Data, relative);
/// Kurzfassung für den Startlog – damit im Betrieb sichtbar ist, wohin geschrieben wird.
public static string Describe() => $"config={Config}, data={Data}, logs={Logs}";
/// Umgebungsvariable, die alles überstimmt.
/// FHS-Pfad unter Linux/macOS, wenn neben der Binärdatei nicht geschrieben werden darf.
///
/// Unterverzeichnis im Binärverzeichnis. Für Logs ist das Logs/; Konfiguration und Daten
/// lagen bisher direkt daneben und bleiben dort, damit bestehende Installationen unverändert
/// weiterlaufen.
///
///
/// Unterverzeichnis unter %ProgramData%\IBKRTrader, wenn unter Windows neben der
/// Binärdatei nicht geschrieben werden darf (z. B. Installation nach Program Files).
/// null = direkt in %ProgramData%\IBKRTrader, wie bei der Konfiguration.
///
private static string Resolve(string envVar, string fhsFallback, string? localSubDir = null, string? windowsSubDir = null)
{
var fromEnv = Environment.GetEnvironmentVariable(envVar);
if (!string.IsNullOrWhiteSpace(fromEnv))
return Ensure(fromEnv.Trim());
var local = localSubDir is null
? AppContext.BaseDirectory
: Path.Combine(AppContext.BaseDirectory, localSubDir);
if (IsWritable(AppContext.BaseDirectory))
return Ensure(local);
// Binärverzeichnis nicht beschreibbar - der FHS-Pfad taugt nur unter Linux/macOS als
// Rückfall. Unter Windows löst .NET "/etc/ibkrtrader" zu "C:\etc\ibkrtrader" auf; der
// richtige Rückfall dort ist das maschinenweite Anwendungsdatenverzeichnis.
if (OperatingSystem.IsWindows())
{
var programData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
var winBase = Path.Combine(programData, "IBKRTrader");
return Ensure(windowsSubDir is null ? winBase : Path.Combine(winBase, windowsSubDir));
}
return Ensure(fhsFallback);
}
///
/// Prüft den Schreibzugriff, indem tatsächlich geschrieben wird. Eine Rechteprüfung über
/// Attribute trägt nicht: unter Linux entscheiden Besitzer, Gruppe und Modus, unter Windows
/// die ACL – ein Schreibversuch ist die einzige verlässliche Antwort.
///
private static bool IsWritable(string dir)
{
try
{
var probe = Path.Combine(dir, $".write-probe-{Guid.NewGuid():N}");
using (File.Create(probe, 1, FileOptions.DeleteOnClose)) { }
return true;
}
catch (UnauthorizedAccessException) { return false; }
catch (IOException) { return false; }
}
private static string Ensure(string dir)
{
Directory.CreateDirectory(dir);
return dir;
}
}