using System.IO; using System.Text.Json; using CloudPrint.Client.Models; using CloudPrint.Client.Services.Abstractions; namespace CloudPrint.Client.Services; public sealed class JsonSettingsService : ISettingsService { private static readonly JsonSerializerOptions SerializerOptions = new() { WriteIndented = true }; private readonly string _settingsPath; private readonly ISecretProtector _secretProtector; public JsonSettingsService() : this(new DpapiSecretProtector()) { } public JsonSettingsService(ISecretProtector secretProtector) : this(secretProtector, Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "CloudPrint.Client", "settings.json")) { } public JsonSettingsService(ISecretProtector secretProtector, string settingsPath) { if (string.IsNullOrWhiteSpace(settingsPath)) { throw new ArgumentException("设置文件路径不能为空", nameof(settingsPath)); } _secretProtector = secretProtector; _settingsPath = settingsPath; } public ClientSettings Load() { if (!File.Exists(_settingsPath)) { return new ClientSettings(); } try { var json = File.ReadAllText(_settingsPath); var dto = JsonSerializer.Deserialize(json, SerializerOptions); return dto?.ToSettings(_secretProtector) ?? new ClientSettings(); } catch { return new ClientSettings(); } } public void Save(ClientSettings settings) { ArgumentNullException.ThrowIfNull(settings); var directory = Path.GetDirectoryName(_settingsPath); if (!string.IsNullOrWhiteSpace(directory)) { Directory.CreateDirectory(directory); } var json = JsonSerializer.Serialize(SettingsFileDto.FromSettings(settings, _secretProtector), SerializerOptions); File.WriteAllText(_settingsPath, json); } private sealed class SettingsFileDto { public string ServerAddress { get; set; } = "http://localhost:8080"; public string ApiKey { get; set; } = string.Empty; // Legacy plaintext field. public string ProtectedApiKey { get; set; } = string.Empty; public string AdminUsername { get; set; } = string.Empty; public string AdminAccessToken { get; set; } = string.Empty; // Legacy plaintext field. public string ProtectedAdminAccessToken { get; set; } = string.Empty; public string StoreName { get; set; } = "默认打印店"; public bool AllowInsecureGrpc { get; set; } = true; public string LibraryRoot { get; set; } = string.Empty; public string PrintCacheRoot { get; set; } = string.Empty; public int MaxUploadSizeMb { get; set; } = 50; public int HeartbeatIntervalSeconds { get; set; } = 15; public ClientSettings ToSettings(ISecretProtector protector) { return new ClientSettings { ServerAddress = ServerAddress, ApiKey = ReadSecret(ProtectedApiKey, ApiKey, protector), AdminUsername = AdminUsername, AdminAccessToken = ReadSecret(ProtectedAdminAccessToken, AdminAccessToken, protector), StoreName = StoreName, AllowInsecureGrpc = AllowInsecureGrpc, LibraryRoot = LibraryRoot, PrintCacheRoot = PrintCacheRoot, MaxUploadSizeMb = MaxUploadSizeMb, HeartbeatIntervalSeconds = HeartbeatIntervalSeconds }; } public static SettingsFileDto FromSettings(ClientSettings settings, ISecretProtector protector) { return new SettingsFileDto { ServerAddress = settings.ServerAddress, ProtectedApiKey = protector.Protect(settings.ApiKey), AdminUsername = settings.AdminUsername, ProtectedAdminAccessToken = protector.Protect(settings.AdminAccessToken), StoreName = settings.StoreName, AllowInsecureGrpc = settings.AllowInsecureGrpc, LibraryRoot = settings.LibraryRoot, PrintCacheRoot = settings.PrintCacheRoot, MaxUploadSizeMb = settings.MaxUploadSizeMb, HeartbeatIntervalSeconds = settings.HeartbeatIntervalSeconds }; } private static string ReadSecret(string protectedValue, string legacyPlainText, ISecretProtector protector) { if (string.IsNullOrWhiteSpace(protectedValue)) { return legacyPlainText; } try { return protector.Unprotect(protectedValue); } catch { return legacyPlainText; } } } }