2026-06-11 10:47:26 +02:00
|
|
|
using PalladiumWallet.Core.Chain;
|
|
|
|
|
|
|
|
|
|
namespace PalladiumWallet.Core.Storage;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2026-06-12 09:25:00 +02:00
|
|
|
/// Percorsi dati per piattaforma (blueprint §8). La radice dati può essere:
|
|
|
|
|
/// 1. <b>portable</b>: cartella "palladium-data" accanto all'eseguibile;
|
|
|
|
|
/// 2. <b>personalizzata</b>: scelta dall'utente al primo avvio e memorizzata in
|
|
|
|
|
/// un piccolo file "puntatore" in una posizione di bootstrap fissa;
|
|
|
|
|
/// 3. <b>legacy</b>: vecchia posizione (%APPDATA%/PalladiumWallet) se contiene già dati;
|
|
|
|
|
/// 4. <b>default</b>: ~/.PalladiumWallet (Linux/macOS) o %ProgramFiles%\PalladiumWallet (Windows).
|
|
|
|
|
/// Sotto la radice c'è una sottocartella per rete (config, wallet, header, certificati).
|
2026-06-11 10:47:26 +02:00
|
|
|
/// </summary>
|
|
|
|
|
public static class AppPaths
|
|
|
|
|
{
|
|
|
|
|
public const string PortableDirName = "palladium-data";
|
|
|
|
|
|
2026-06-12 09:25:00 +02:00
|
|
|
/// <summary>Nome cartella applicazione, usato nei vari percorsi.</summary>
|
|
|
|
|
public const string AppDirName = "PalladiumWallet";
|
|
|
|
|
|
|
|
|
|
/// <summary>Override esplicito della radice dati (es. CLI --data-dir). Ha priorità su tutto.</summary>
|
|
|
|
|
public static string? OverrideDataRoot { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Radice dati predefinita: cartella nascosta nella home dell'utente
|
|
|
|
|
/// (%USERPROFILE%\.PalladiumWallet su Windows, ~/.PalladiumWallet su Linux/macOS).
|
|
|
|
|
/// Per-utente e sempre scrivibile, senza privilegi di amministratore.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static string DefaultDataRoot()
|
|
|
|
|
{
|
|
|
|
|
var home = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
|
|
|
|
return Path.Combine(home, "." + AppDirName);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Vecchia radice (%APPDATA%/PalladiumWallet, ~/.config su Linux): mantenuta
|
|
|
|
|
/// per non orfanizzare i dati di installazioni precedenti.</summary>
|
|
|
|
|
private static string LegacyDataRoot() =>
|
|
|
|
|
Path.Combine(
|
|
|
|
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
|
|
|
|
AppDirName);
|
|
|
|
|
|
|
|
|
|
/// <summary>File puntatore alla radice dati scelta dall'utente. Vive in una
|
|
|
|
|
/// posizione di bootstrap sempre scrivibile e indipendente dalla radice dati.</summary>
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
/// <summary>Radice dati effettiva, secondo l'ordine di precedenza documentato in classe.</summary>
|
2026-06-11 10:47:26 +02:00
|
|
|
public static string DataRoot()
|
|
|
|
|
{
|
2026-06-12 09:25:00 +02:00
|
|
|
if (!string.IsNullOrEmpty(OverrideDataRoot))
|
|
|
|
|
return OverrideDataRoot;
|
|
|
|
|
|
|
|
|
|
var portable = PortableRoot();
|
2026-06-11 10:47:26 +02:00
|
|
|
if (Directory.Exists(portable))
|
|
|
|
|
return portable;
|
|
|
|
|
|
2026-06-12 09:25:00 +02:00
|
|
|
if (ReadPointer() is { } custom)
|
|
|
|
|
return custom;
|
|
|
|
|
|
|
|
|
|
var legacy = LegacyDataRoot();
|
|
|
|
|
if (HasData(legacy))
|
|
|
|
|
return legacy;
|
|
|
|
|
|
|
|
|
|
return DefaultDataRoot();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 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 legacy o nella default.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public static bool IsDataLocationConfigured() =>
|
|
|
|
|
!string.IsNullOrEmpty(OverrideDataRoot)
|
|
|
|
|
|| Directory.Exists(PortableRoot())
|
|
|
|
|
|| ReadPointer() is not null
|
|
|
|
|
|| HasData(LegacyDataRoot())
|
|
|
|
|
|| HasData(DefaultDataRoot());
|
|
|
|
|
|
|
|
|
|
/// <summary>Memorizza la radice dati scelta dall'utente e la crea su disco.</summary>
|
|
|
|
|
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;
|
2026-06-11 10:47:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>Cartella dati della rete (config, wallet, header, certificati).</summary>
|
|
|
|
|
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");
|
|
|
|
|
|
|
|
|
|
public static string CertificatePinsPath(NetKind net) =>
|
|
|
|
|
Path.Combine(ForNetwork(net), "server-certs.json");
|
|
|
|
|
|
2026-06-11 11:27:14 +02:00
|
|
|
public static string ServersPath(NetKind net) =>
|
|
|
|
|
Path.Combine(ForNetwork(net), "servers.json");
|
|
|
|
|
|
2026-06-11 10:47:26 +02:00
|
|
|
public static string ConfigPath() =>
|
|
|
|
|
Path.Combine(DataRoot(), "config.json");
|
|
|
|
|
}
|