using System.Security.Cryptography; using System.Text.Json; namespace Everything2Everything.Core; /// 키-값 설정 저장소. API 키·외부 도구 경로 등 민감 정보를 영속화한다. public interface ISettingsStore { string? Get(string key); void Set(string key, string value); void Remove(string key); bool Contains(string key); } /// /// DPAPI(CurrentUser)로 암호화해 %LOCALAPPDATA%\Everything2Everything\settings.dat 에 저장한다. /// 현재 사용자 계정에서만 복호화 가능하므로 API 키 저장에 적합하다. Windows 전용. /// public sealed class DpapiSettingsStore : ISettingsStore { private readonly string _path; private readonly Dictionary _cache; private readonly object _lock = new(); public DpapiSettingsStore(string? filePath = null) { _path = filePath ?? DefaultPath(); _cache = Load(_path); } public static string DefaultPath() { var dir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Everything2Everything"); Directory.CreateDirectory(dir); return Path.Combine(dir, "settings.dat"); } public string? Get(string key) { lock (_lock) return _cache.TryGetValue(key, out var v) ? v : null; } public bool Contains(string key) { lock (_lock) return _cache.ContainsKey(key); } public void Set(string key, string value) { lock (_lock) { _cache[key] = value; Save(); } } public void Remove(string key) { lock (_lock) { if (_cache.Remove(key)) Save(); } } private void Save() { var json = JsonSerializer.SerializeToUtf8Bytes(_cache); var encrypted = ProtectedData.Protect(json, optionalEntropy: null, DataProtectionScope.CurrentUser); var tmp = _path + ".tmp"; File.WriteAllBytes(tmp, encrypted); if (File.Exists(_path)) File.Delete(_path); File.Move(tmp, _path); } private static Dictionary Load(string path) { try { if (!File.Exists(path)) return new(StringComparer.Ordinal); var encrypted = File.ReadAllBytes(path); var json = ProtectedData.Unprotect(encrypted, optionalEntropy: null, DataProtectionScope.CurrentUser); return JsonSerializer.Deserialize>(json) ?? new(StringComparer.Ordinal); } catch { // 손상/복호화 실패 시 빈 저장소로 시작(설정은 비치명적) return new(StringComparer.Ordinal); } } }