using System.IO; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// 최근 파일 핸들러. "recent" 프리픽스로 사용합니다. /// Windows Recent 폴더(%APPDATA%\Microsoft\Windows\Recent)의 .lnk 파일을 /// 최근 수정 순으로 나열합니다. /// 예: recent → 최근 20개 파일 목록 /// recent 보고서 → 이름에 "보고서" 포함 파일 필터 /// public class RecentFilesHandler : IActionHandler { public string? Prefix => "recent"; public PluginMetadata Metadata => new( "RecentFiles", "최근 파일 — recent 뒤에 검색어 입력", "1.0", "AX"); private static readonly string RecentFolder = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), @"Microsoft\Windows\Recent"); // 간단한 캐시: 10초간 유효 private static (DateTime At, List<(string Name, string LinkPath, DateTime Modified)> Files)? _cache; private static readonly TimeSpan CacheTtl = TimeSpan.FromSeconds(10); public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim(); if (string.IsNullOrWhiteSpace(q)) { // 힌트만 표시 } var files = GetRecentFiles(); IEnumerable<(string Name, string LinkPath, DateTime Modified)> filtered = files; if (!string.IsNullOrWhiteSpace(q)) { filtered = files.Where(f => f.Name.Contains(q, StringComparison.OrdinalIgnoreCase)); } var items = filtered.Take(20).Select(f => new LauncherItem( f.Name, $"{f.Modified:yyyy-MM-dd HH:mm} · Enter로 열기", null, f.LinkPath, Symbol: GetSymbol(f.Name))).ToList(); if (!items.Any()) { items.Add(new LauncherItem( string.IsNullOrWhiteSpace(q) ? "최근 파일 없음" : "검색 결과 없음", string.IsNullOrWhiteSpace(q) ? "Windows Recent 폴더가 비어 있습니다" : $"'{q}' 파일을 최근 목록에서 찾을 수 없습니다", null, null, Symbol: Symbols.Info)); } return Task.FromResult>(items); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is string linkPath && File.Exists(linkPath)) { try { System.Diagnostics.Process.Start( new System.Diagnostics.ProcessStartInfo(linkPath) { UseShellExecute = true }); } catch (Exception ex) { LogService.Warn($"최근 파일 열기 실패: {ex.Message}"); } } return Task.CompletedTask; } // ─── 내부 ───────────────────────────────────────────────────────────────── private static List<(string Name, string LinkPath, DateTime Modified)> GetRecentFiles() { // 캐시 유효 확인 if (_cache.HasValue && (DateTime.Now - _cache.Value.At) < CacheTtl) return _cache.Value.Files; var result = new List<(string, string, DateTime)>(); try { if (!Directory.Exists(RecentFolder)) return result; var lnkFiles = Directory .GetFiles(RecentFolder, "*.lnk") .Select(p => (Path: p, Info: new FileInfo(p))) .OrderByDescending(f => f.Info.LastWriteTime) .Take(100) .ToList(); foreach (var (path, info) in lnkFiles) { var name = Path.GetFileNameWithoutExtension(info.Name); result.Add((name, path, info.LastWriteTime)); } } catch (Exception ex) { LogService.Warn($"최근 파일 목록 읽기 실패: {ex.Message}"); } _cache = (DateTime.Now, result); return result; } private static string GetSymbol(string name) { var ext = Path.GetExtension(name).ToLowerInvariant(); return ext switch { ".exe" or ".msi" => Symbols.App, ".xlsx" or ".xls" or ".csv" => Symbols.File, ".docx" or ".doc" => Symbols.File, ".pptx" or ".ppt" => Symbols.File, ".pdf" => Symbols.File, ".txt" or ".md" or ".log" => Symbols.Text, ".jpg" or ".jpeg" or ".png" or ".gif" or ".webp" or ".bmp" => Symbols.Picture, _ => Symbols.File }; } }