using System.Diagnostics; using System.IO; using System.Text.Json; using System.Text.Json.Serialization; using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// 즐겨찾기(파일/폴더/경로) 핸들러. "fav" 프리픽스로 사용합니다. /// 자주 사용하는 로컬 파일·폴더 경로를 등록하고 빠르게 실행합니다. /// 예: fav → 전체 즐겨찾기 목록 /// fav 보고서 → "보고서" 포함 항목 필터 /// fav add 보고서 C:\work\report.xlsx → 즐겨찾기 추가 /// fav del 보고서 → 즐겨찾기 삭제 /// Enter → 파일/폴더 열기. /// public class FavoriteHandler : IActionHandler { public string? Prefix => "fav"; public PluginMetadata Metadata => new( "Favorite", "즐겨찾기 관리 — fav", "1.0", "AX"); private static readonly string FavFile = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot", "favorites.json"); private static readonly JsonSerializerOptions JsonOpts = new() { WriteIndented = true, PropertyNameCaseInsensitive = true }; private List _cache = new(); private bool _loaded; public Task> GetItemsAsync(string query, CancellationToken ct) { EnsureLoaded(); var q = query.Trim(); // add 명령 if (q.StartsWith("add ", StringComparison.OrdinalIgnoreCase)) { var rest = q[4..].Trim(); var spaceIdx = rest.IndexOf(' '); if (spaceIdx > 0) { var name = rest[..spaceIdx].Trim(); var path = rest[(spaceIdx + 1)..].Trim(); return Task.FromResult>( [ new LauncherItem( $"즐겨찾기 추가: {name}", $"경로: {path} · Enter로 추가", null, ValueTuple.Create("__ADD__", name, path), Symbol: Symbols.Save) ]); } return Task.FromResult>( [ new LauncherItem( "사용법: fav add [이름] [경로]", "예: fav add 보고서 C:\\work\\report.xlsx", null, null, Symbol: Symbols.Info) ]); } // del 명령 if (q.StartsWith("del ", StringComparison.OrdinalIgnoreCase)) { var name = q[4..].Trim(); var found = _cache.FirstOrDefault(b => b.Name.Contains(name, StringComparison.OrdinalIgnoreCase)); if (found != null) { return Task.FromResult>( [ new LauncherItem( $"즐겨찾기 삭제: {found.Name}", $"경로: {found.Path} · Enter로 삭제", null, ValueTuple.Create("__DEL__", found.Name), Symbol: Symbols.Delete) ]); } return Task.FromResult>( [ new LauncherItem($"'{name}'에 해당하는 즐겨찾기 없음", "fav으로 전체 목록 확인", null, null, Symbol: Symbols.Warning) ]); } // 목록/검색 var filtered = string.IsNullOrWhiteSpace(q) ? _cache : _cache.Where(b => b.Name.Contains(q, StringComparison.OrdinalIgnoreCase) || b.Path.Contains(q, StringComparison.OrdinalIgnoreCase)).ToList(); if (!filtered.Any()) { return Task.FromResult>( [ new LauncherItem( _cache.Count == 0 ? "즐겨찾기가 없습니다" : $"'{q}'에 해당하는 즐겨찾기 없음", "fav add [이름] [경로]로 추가하세요", null, null, Symbol: Symbols.Info) ]); } var items = filtered.Select(b => { var isDir = Directory.Exists(b.Path); var isFile = File.Exists(b.Path); var symbol = isDir ? Symbols.Folder : isFile ? Symbols.File : Symbols.Warning; var hint = isDir ? "폴더 열기" : isFile ? "파일 열기" : "경로를 찾을 수 없음"; return new LauncherItem(b.Name, $"{b.Path} · {hint}", null, b.Path, Symbol: symbol); }).ToList(); return Task.FromResult>(items); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is ValueTuple add && add.Item1 == "__ADD__") { AddFav(add.Item2, add.Item3); NotificationService.Notify("AX Copilot", $"즐겨찾기 추가: {add.Item2}"); return Task.CompletedTask; } if (item.Data is ValueTuple del && del.Item1 == "__DEL__") { RemoveFav(del.Item2); NotificationService.Notify("AX Copilot", $"즐겨찾기 삭제: {del.Item2}"); return Task.CompletedTask; } if (item.Data is string path) { 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?.Dispatcher.Invoke(() => Clipboard.SetText(path)); } catch (Exception) { } } } return Task.CompletedTask; } private void EnsureLoaded() { if (_loaded) return; _loaded = true; try { if (!File.Exists(FavFile)) return; _cache = JsonSerializer.Deserialize>(File.ReadAllText(FavFile), JsonOpts) ?? new(); } catch (Exception ex) { LogService.Warn($"즐겨찾기 로드 실패: {ex.Message}"); } } private void AddFav(string name, string path) { EnsureLoaded(); _cache.RemoveAll(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(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}"); } } private class FavEntry { [JsonPropertyName("name")] public string Name { get; set; } = ""; [JsonPropertyName("path")] public string Path { get; set; } = ""; } }