using System.IO; using System.Text.Json; namespace AxCopilot.Services; /// /// 런처 항목 실행 횟수를 추적하여 퍼지 검색 결과 정렬에 활용합니다. /// 저장 위치: %APPDATA%\AxCopilot\usage.json /// 형식: { "키": 횟수 } — 키는 IndexEntry.Path (파일/폴더/앱 경로) /// internal static class UsageRankingService { private static readonly string _dataFile = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "usage.json"); private static Dictionary _counts = new(StringComparer.OrdinalIgnoreCase); private static bool _loaded = false; private static readonly object _lock = new(); /// /// 항목 실행 시 호출하여 카운트를 증가시킵니다. /// public static void RecordExecution(string key) { if (string.IsNullOrWhiteSpace(key)) return; EnsureLoaded(); lock (_lock) { _counts.TryGetValue(key, out var current); _counts[key] = current + 1; } _ = SaveAsync(); } /// /// 주어진 키의 실행 횟수를 반환합니다. 없으면 0. /// public static int GetScore(string key) { if (string.IsNullOrWhiteSpace(key)) return 0; EnsureLoaded(); lock (_lock) { return _counts.TryGetValue(key, out var count) ? count : 0; } } /// /// 실행 횟수 기준으로 내림차순 정렬하는 컴파러를 반환합니다. /// 동점이면 원래 순서 유지 (stable sort). /// public static IEnumerable SortByUsage(IEnumerable items, Func keySelector) { EnsureLoaded(); return items .Select((item, idx) => (item, idx, score: GetScore(keySelector(item) ?? ""))) .OrderByDescending(x => x.score) .ThenBy(x => x.idx) .Select(x => x.item); } /// /// 실행 횟수 상위 항목을 반환합니다. /// 빠른 실행 칩처럼 경로 자체 목록이 필요한 화면에서 사용합니다. /// public static IReadOnlyList> GetTopItems(int maxCount) { EnsureLoaded(); lock (_lock) { return _counts .OrderByDescending(x => x.Value) .ThenBy(x => x.Key, StringComparer.OrdinalIgnoreCase) .Take(Math.Max(0, maxCount)) .ToList(); } } // ─── 내부 ────────────────────────────────────────────────────────────────── private static void EnsureLoaded() { if (_loaded) return; lock (_lock) { if (_loaded) return; try { if (File.Exists(_dataFile)) { var json = File.ReadAllText(_dataFile); var data = JsonSerializer.Deserialize>(json); if (data != null) _counts = new Dictionary(data, StringComparer.OrdinalIgnoreCase); } } catch (Exception ex) { LogService.Warn($"usage.json 로드 실패: {ex.Message}"); } _loaded = true; } } private static async Task SaveAsync() { try { Dictionary snapshot; lock (_lock) { snapshot = new Dictionary(_counts); } Directory.CreateDirectory(Path.GetDirectoryName(_dataFile)!); var json = JsonSerializer.Serialize(snapshot, new JsonSerializerOptions { WriteIndented = false }); await File.WriteAllTextAsync(_dataFile, json); } catch (Exception ex) { LogService.Warn($"usage.json 저장 실패: {ex.Message}"); } } }