using PalladiumWallet.Core.Chain; namespace PalladiumWallet.Core.Storage; /// /// Percorsi dati per piattaforma (blueprint §8). La radice dati può essere: /// 1. portable: cartella "palladium-data" accanto all'eseguibile; /// 2. personalizzata: scelta dall'utente al primo avvio e memorizzata in /// un piccolo file "puntatore" in una posizione di bootstrap fissa; /// 3. legacy: vecchia posizione (%APPDATA%/PalladiumWallet) se contiene già dati; /// 4. default: ~/.PalladiumWallet (Linux/macOS) o %ProgramFiles%\PalladiumWallet (Windows). /// Sotto la radice c'è una sottocartella per rete (config, wallet, header, certificati). /// public static class AppPaths { public const string PortableDirName = "palladium-data"; /// Nome cartella applicazione, usato nei vari percorsi. public const string AppDirName = "PalladiumWallet"; /// Override esplicito della radice dati (es. CLI --data-dir). Ha priorità su tutto. public static string? OverrideDataRoot { get; set; } /// /// Radice dati predefinita, secondo la convenzione di ogni piattaforma: /// Windows → %APPDATA%\PalladiumWallet (PascalCase, come Electrum/Bitcoin); /// Linux/macOS → ~/.palladium-wallet (dotfolder minuscolo, come ~/.bitcoin). /// Per-utente e sempre scrivibile, senza privilegi di amministratore. /// public static string DefaultDataRoot() { if (OperatingSystem.IsWindows()) return Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppDirName); var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); return Path.Combine(home, ".palladium-wallet"); } /// File puntatore alla radice dati scelta dall'utente. Vive in una /// posizione di bootstrap sempre scrivibile e indipendente dalla radice dati. private static string LocationPointerPath() => Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), AppDirName, "data-location"); private static string PortableRoot() => Path.Combine(AppContext.BaseDirectory, PortableDirName); private static bool HasData(string root) => Directory.Exists(root) && Directory.EnumerateFileSystemEntries(root).Any(); /// Radice dati effettiva, secondo l'ordine di precedenza documentato in classe. public static string DataRoot() { if (!string.IsNullOrEmpty(OverrideDataRoot)) return OverrideDataRoot; var portable = PortableRoot(); if (Directory.Exists(portable)) return portable; if (ReadPointer() is { } custom) return custom; return DefaultDataRoot(); } /// /// true se la posizione dei dati è già determinata e non serve chiederla /// all'utente: modalità portable, override, puntatore già scritto, oppure /// dati già presenti nella posizione predefinita. /// public static bool IsDataLocationConfigured() => !string.IsNullOrEmpty(OverrideDataRoot) || Directory.Exists(PortableRoot()) || ReadPointer() is not null || HasData(DefaultDataRoot()); /// Memorizza la radice dati scelta dall'utente e la crea su disco. public static void ConfigureDataLocation(string root) { root = Path.GetFullPath(root.Trim()); Directory.CreateDirectory(root); var pointer = LocationPointerPath(); Directory.CreateDirectory(Path.GetDirectoryName(pointer)!); File.WriteAllText(pointer, root); } private static string? ReadPointer() { var pointer = LocationPointerPath(); if (!File.Exists(pointer)) return null; var path = File.ReadAllText(pointer).Trim(); return string.IsNullOrEmpty(path) ? null : path; } /// Cartella dati della rete (config, wallet, header, certificati). public static string ForNetwork(NetKind net) { var dir = Path.Combine(DataRoot(), ChainProfiles.For(net).NetName); Directory.CreateDirectory(dir); return dir; } public static string WalletsDir(NetKind net) { var dir = Path.Combine(ForNetwork(net), "wallets"); Directory.CreateDirectory(dir); return dir; } public static string DefaultWalletPath(NetKind net) => Path.Combine(WalletsDir(net), "default.wallet.json"); /// Tutti i file wallet della rete, ordinati per nome (multi-wallet §8). public static IReadOnlyList WalletFiles(NetKind net) { var dir = WalletsDir(net); return Directory.EnumerateFiles(dir, "*.wallet.json") .OrderBy(p => p, StringComparer.OrdinalIgnoreCase) .ToList(); } public static string CertificatePinsPath(NetKind net) => Path.Combine(ForNetwork(net), "server-certs.json"); public static string ServersPath(NetKind net) => Path.Combine(ForNetwork(net), "servers.json"); public static string ConfigPath() => Path.Combine(DataRoot(), "config.json"); }