using System; using System.IO; using System.Text; using Deploymentcenter.Client.Models; namespace Deploymentcenter.Client { /// /// Loest die Zielpfade einer setup.json plattformrichtig auf. /// /// Ohne das schrieb der Installer ausschliesslich ins /// Installationsverzeichnis. Eine Anwendung, die sich unter Linux richtig /// verhaelt, liest ihre Konfiguration aber aus $XDG_CONFIG_HOME und unter /// Windows aus %APPDATA% - schon deshalb, weil /opt/<app> fuer den /// Dienstbenutzer typischerweise nicht schreibbar ist. Der Installer legte /// die Datei also dorthin, wo die Anwendung nie nachsieht. /// public static class SetupPaths { /// /// Ermittelt den vollstaendigen Pfad eines Ziels. /// /// Ziel aus der setup.json. /// Installationsverzeichnis der Anwendung. public static string Resolve(SetupTarget target, string installDir) { string file = Expand(target?.File ?? string.Empty); // Ein absoluter Pfad ist bereits die Antwort - etwa nachdem // ${XDG_CONFIG_HOME} ersetzt wurde. if (Path.IsPathRooted(file)) { return Path.GetFullPath(file); } string root = BaseDirectory(target?.Location, installDir); return Path.GetFullPath(Path.Combine(root, file.Replace('/', Path.DirectorySeparatorChar))); } /// /// Wurzelverzeichnis fuer eine Ortsangabe. /// /// install (Vorgabe) - das Installationsverzeichnis /// config - %APPDATA% bzw. $XDG_CONFIG_HOME (~/.config) /// data - %LOCALAPPDATA% bzw. $XDG_DATA_HOME (~/.local/share) /// home - Benutzerverzeichnis /// public static string BaseDirectory(string? location, string installDir) { switch ((location ?? string.Empty).Trim().ToLowerInvariant()) { case "config": return ConfigDirectory(); case "data": return DataDirectory(); case "home": return HomeDirectory(); case "": case "install": return installDir; default: // Eine unbekannte Angabe ins Installationsverzeichnis // umzubiegen ist die harmlosere Auslegung: dort landet die // Datei sichtbar statt an einem geratenen Ort. return installDir; } } public static string ConfigDirectory() { if (OperatingSystemHelpers.IsWindows()) { string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); return string.IsNullOrWhiteSpace(appData) ? Path.Combine(HomeDirectory(), "AppData", "Roaming") : appData; } string xdg = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME") ?? string.Empty; return string.IsNullOrWhiteSpace(xdg) ? Path.Combine(HomeDirectory(), ".config") : xdg; } public static string DataDirectory() { if (OperatingSystemHelpers.IsWindows()) { string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); return string.IsNullOrWhiteSpace(localAppData) ? Path.Combine(HomeDirectory(), "AppData", "Local") : localAppData; } string xdg = Environment.GetEnvironmentVariable("XDG_DATA_HOME") ?? string.Empty; return string.IsNullOrWhiteSpace(xdg) ? Path.Combine(HomeDirectory(), ".local", "share") : xdg; } public static string HomeDirectory() { string home = Environment.GetEnvironmentVariable("HOME") ?? string.Empty; if (string.IsNullOrWhiteSpace(home)) { home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); } return string.IsNullOrWhiteSpace(home) ? Directory.GetCurrentDirectory() : home; } /// /// Ersetzt Umgebungsvariablen in beiden gebraeuchlichen Schreibweisen: /// ${NAME} und %NAME%. Unbekannte Namen bleiben stehen - /// eine leere Ersetzung wuerde stillschweigend einen falschen Pfad /// ergeben, ein stehengebliebener Platzhalter faellt dagegen auf. /// public static string Expand(string value) { if (string.IsNullOrEmpty(value)) return string.Empty; var builder = new StringBuilder(value.Length); for (int i = 0; i < value.Length; i++) { // ${NAME} if (value[i] == '$' && i + 1 < value.Length && value[i + 1] == '{') { int close = value.IndexOf('}', i + 2); if (close > i + 2) { string name = value.Substring(i + 2, close - i - 2); string? resolved = Lookup(name); if (resolved != null) { builder.Append(resolved); i = close; continue; } } } // %NAME% if (value[i] == '%') { int close = value.IndexOf('%', i + 1); if (close > i + 1) { string name = value.Substring(i + 1, close - i - 1); string? resolved = Lookup(name); if (resolved != null) { builder.Append(resolved); i = close; continue; } } } builder.Append(value[i]); } return builder.ToString(); } /// /// Schlaegt einen Namen nach. Neben echten Umgebungsvariablen werden /// die XDG-Namen auch dann beantwortet, wenn sie nicht gesetzt sind - /// sonst muesste jede setup.json den Rueckfall auf ~/.config selbst /// ausformulieren. /// private static string? Lookup(string name) { string value = Environment.GetEnvironmentVariable(name) ?? string.Empty; if (!string.IsNullOrWhiteSpace(value)) return value; switch (name.ToUpperInvariant()) { case "XDG_CONFIG_HOME": return ConfigDirectory(); case "XDG_DATA_HOME": return DataDirectory(); case "HOME": case "USERPROFILE": return HomeDirectory(); case "APPDATA": return ConfigDirectory(); case "LOCALAPPDATA": return DataDirectory(); default: return null; } } } }