using System.IO; using System.Text.Json; namespace VideoDownloader.App.Browser; /// /// 사이트별 줌 배율 영속 저장소(T1-7). WebView2 는 사이트별 줌 영속을 보장하지 않아 /// 앱이 도메인→배율 사전을 SSOT 로 관리한다. LRU 300개, 저장 즉시 원자적 파일 기록. /// public sealed class SiteZoomStore { private sealed record Entry(double Zoom, long LastUsedTicks); private const int MaxEntries = 300; private const double Min = 0.1, Max = 5.0; private readonly string _path; private readonly object _lock = new(); private readonly Dictionary _map = new(StringComparer.OrdinalIgnoreCase); public SiteZoomStore(string path) { _path = path; try { if (File.Exists(path)) { var loaded = JsonSerializer.Deserialize>(File.ReadAllText(path)); if (loaded != null) foreach (var (k, v) in loaded) _map[k] = v; } } catch { // 손상된 JSON → 빈 상태로 시작(기존 BrowserStore 정책과 동일) _map.Clear(); } } public int Count { get { lock (_lock) return _map.Count; } } /// URL 의 호스트에 저장된 배율. 없으면 1.0. 읽기는 LRU 를 갱신한다. public double Get(string? url) { var host = NormalizeHost(url); if (host == null) return 1.0; lock (_lock) { if (_map.TryGetValue(host, out var e)) { _map[host] = e with { LastUsedTicks = DateTime.UtcNow.Ticks }; return e.Zoom; } } return 1.0; } /// 배율 저장(0.1~5.0 클램프). 초과분은 가장 오래된 사용 순으로 제거 후 즉시 영속. public void Set(string? url, double zoom) { var host = NormalizeHost(url); if (host == null) return; zoom = Math.Clamp(zoom, Min, Max); lock (_lock) { _map[host] = new Entry(zoom, DateTime.UtcNow.Ticks); if (_map.Count > MaxEntries) foreach (var k in _map.OrderBy(kv => kv.Value.LastUsedTicks).Take(_map.Count - MaxEntries).Select(kv => kv.Key).ToList()) _map.Remove(k); Save(); } } public void Clear() { lock (_lock) { _map.Clear(); try { if (File.Exists(_path)) File.Delete(_path); } catch { } } } private void Save() { try { var dir = Path.GetDirectoryName(_path); if (!string.IsNullOrEmpty(dir)) Directory.CreateDirectory(dir); var tmp = _path + ".tmp"; File.WriteAllText(tmp, JsonSerializer.Serialize(_map)); File.Move(tmp, _path, overwrite: true); } catch { // 저장 실패는 메모리 상태는 유지(조용한 내구성 저하) } } private static string? NormalizeHost(string? url) { if (string.IsNullOrWhiteSpace(url)) return null; if (!Uri.TryCreate(url, UriKind.Absolute, out var uri) || string.IsNullOrEmpty(uri.Host)) return null; var host = uri.Host; return host.StartsWith("www.", StringComparison.OrdinalIgnoreCase) ? host[4..] : host; } }