using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Threading; using AxCopilot.SDK; using AxCopilot.Services; namespace AxCopilot.Handlers; public class FavoriteHandler : IActionHandler { private class FavEntry { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("path")] public string Path { get; set; } = ""; } private static readonly string FavFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "favorites.json"); private static readonly JsonSerializerOptions JsonOpts = new JsonSerializerOptions { WriteIndented = true, PropertyNameCaseInsensitive = true }; private List _cache = new List(); private bool _loaded; public string? Prefix => "fav"; public PluginMetadata Metadata => new PluginMetadata("Favorite", "즐겨찾기 관리 — fav", "1.0", "AX"); public Task> GetItemsAsync(string query, CancellationToken ct) { EnsureLoaded(); string q = query.Trim(); if (q.StartsWith("add ", StringComparison.OrdinalIgnoreCase)) { string text = q; string text2 = text.Substring(4, text.Length - 4).Trim(); int num = text2.IndexOf(' '); if (num > 0) { string text3 = text2.Substring(0, num).Trim(); text = text2; int num2 = num + 1; string text4 = text.Substring(num2, text.Length - num2).Trim(); return Task.FromResult((IEnumerable)new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("즐겨찾기 추가: " + text3, "경로: " + text4 + " · Enter로 추가", null, ValueTuple.Create("__ADD__", text3, text4), null, "\ue74e"))); } return Task.FromResult((IEnumerable)new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("사용법: fav add [이름] [경로]", "예: fav add 보고서 C:\\work\\report.xlsx", null, null, null, "\ue946"))); } if (q.StartsWith("del ", StringComparison.OrdinalIgnoreCase)) { string text = q; string name = text.Substring(4, text.Length - 4).Trim(); FavEntry favEntry = _cache.FirstOrDefault((FavEntry b) => b.Name.Contains(name, StringComparison.OrdinalIgnoreCase)); if (favEntry != null) { return Task.FromResult((IEnumerable)new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("즐겨찾기 삭제: " + favEntry.Name, "경로: " + favEntry.Path + " · Enter로 삭제", null, ValueTuple.Create("__DEL__", favEntry.Name), null, "\ue74d"))); } return Task.FromResult((IEnumerable)new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem("'" + name + "'에 해당하는 즐겨찾기 없음", "fav으로 전체 목록 확인", null, null, null, "\ue7ba"))); } List source = (string.IsNullOrWhiteSpace(q) ? _cache : _cache.Where((FavEntry b) => b.Name.Contains(q, StringComparison.OrdinalIgnoreCase) || b.Path.Contains(q, StringComparison.OrdinalIgnoreCase)).ToList()); if (!source.Any()) { return Task.FromResult((IEnumerable)new _003C_003Ez__ReadOnlySingleElementList(new LauncherItem((_cache.Count == 0) ? "즐겨찾기가 없습니다" : ("'" + q + "'에 해당하는 즐겨찾기 없음"), "fav add [이름] [경로]로 추가하세요", null, null, null, "\ue946"))); } List result = source.Select(delegate(FavEntry b) { bool flag = Directory.Exists(b.Path); bool flag2 = File.Exists(b.Path); string symbol = (flag ? "\ue8b7" : (flag2 ? "\ue8a5" : "\ue7ba")); string text5 = (flag ? "폴더 열기" : (flag2 ? "파일 열기" : "경로를 찾을 수 없음")); return new LauncherItem(b.Name, b.Path + " · " + text5, null, b.Path, null, symbol); }).ToList(); return Task.FromResult((IEnumerable)result); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is (string, string, string) { Item1: "__ADD__" } tuple) { AddFav(tuple.Item2, tuple.Item3); NotificationService.Notify("AX Copilot", "즐겨찾기 추가: " + tuple.Item2); return Task.CompletedTask; } if (item.Data is (string, string) { Item1: "__DEL__" } tuple2) { RemoveFav(tuple2.Item2); NotificationService.Notify("AX Copilot", "즐겨찾기 삭제: " + tuple2.Item2); return Task.CompletedTask; } object data = item.Data; string path = data as string; if (path != null) { if (Directory.Exists(path) || File.Exists(path)) { try { Process.Start(new ProcessStartInfo(path) { UseShellExecute = true }); } catch (Exception ex) { LogService.Warn("즐겨찾기 열기 실패: " + ex.Message); } } else { try { Application current = Application.Current; if (current != null) { ((DispatcherObject)current).Dispatcher.Invoke((Action)delegate { Clipboard.SetText(path); }); } } catch { } } } return Task.CompletedTask; } private void EnsureLoaded() { if (_loaded) { return; } _loaded = true; try { if (File.Exists(FavFile)) { _cache = JsonSerializer.Deserialize>(File.ReadAllText(FavFile), JsonOpts) ?? new List(); } } catch (Exception ex) { LogService.Warn("즐겨찾기 로드 실패: " + ex.Message); } } private void AddFav(string name, string path) { EnsureLoaded(); _cache.RemoveAll((FavEntry b) => b.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); _cache.Insert(0, new FavEntry { Name = name, Path = path }); Save(); } private void RemoveFav(string name) { EnsureLoaded(); _cache.RemoveAll((FavEntry b) => b.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); Save(); } private void Save() { try { Directory.CreateDirectory(Path.GetDirectoryName(FavFile)); File.WriteAllText(FavFile, JsonSerializer.Serialize(_cache, JsonOpts)); } catch (Exception ex) { LogService.Warn("즐겨찾기 저장 실패: " + ex.Message); } } }