using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using AxCopilot.SDK; using AxCopilot.Services; namespace AxCopilot.Handlers; public class RecentFilesHandler : IActionHandler { private static readonly string RecentFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Microsoft\\Windows\\Recent"); private static (DateTime At, List<(string Name, string LinkPath, DateTime Modified)> Files)? _cache; private static readonly TimeSpan CacheTtl = TimeSpan.FromSeconds(10.0); public string? Prefix => "recent"; public PluginMetadata Metadata => new PluginMetadata("RecentFiles", "최근 파일 — recent 뒤에 검색어 입력", "1.0", "AX"); public Task> GetItemsAsync(string query, CancellationToken ct) { string q = query.Trim(); if (string.IsNullOrWhiteSpace(q)) { } List<(string, string, DateTime)> recentFiles = GetRecentFiles(); IEnumerable<(string, string, DateTime)> source = recentFiles; if (!string.IsNullOrWhiteSpace(q)) { source = recentFiles.Where<(string, string, DateTime)>(((string Name, string LinkPath, DateTime Modified) f) => f.Name.Contains(q, StringComparison.OrdinalIgnoreCase)); } List list = (from f in source.Take(20) select new LauncherItem(f.Name, $"{f.Modified:yyyy-MM-dd HH:mm} · Enter로 열기", null, f.LinkPath, null, GetSymbol(f.Name))).ToList(); if (!list.Any()) { list.Add(new LauncherItem(string.IsNullOrWhiteSpace(q) ? "최근 파일 없음" : "검색 결과 없음", string.IsNullOrWhiteSpace(q) ? "Windows Recent 폴더가 비어 있습니다" : ("'" + q + "' 파일을 최근 목록에서 찾을 수 없습니다"), null, null, null, "\ue946")); } return Task.FromResult((IEnumerable)list); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is string text && File.Exists(text)) { try { Process.Start(new ProcessStartInfo(text) { 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; } List<(string, string, DateTime)> list = new List<(string, string, DateTime)>(); try { if (!Directory.Exists(RecentFolder)) { return list; } List<(string, FileInfo)> list2 = (from p in Directory.GetFiles(RecentFolder, "*.lnk") select (Path: p, Info: new FileInfo(p)) into f orderby f.Info.LastWriteTime descending select f).Take(100).ToList(); foreach (var item3 in list2) { string item = item3.Item1; FileInfo item2 = item3.Item2; string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(item2.Name); list.Add((fileNameWithoutExtension, item, item2.LastWriteTime)); } } catch (Exception ex) { LogService.Warn("최근 파일 목록 읽기 실패: " + ex.Message); } _cache = (DateTime.Now, list); return list; } private static string GetSymbol(string name) { string text = Path.GetExtension(name).ToLowerInvariant(); if (1 == 0) { } string result; switch (text) { case ".exe": case ".msi": result = "\uecaa"; break; case ".xlsx": case ".xls": case ".csv": result = "\ue8a5"; break; case ".docx": case ".doc": result = "\ue8a5"; break; case ".pptx": case ".ppt": result = "\ue8a5"; break; case ".pdf": result = "\ue8a5"; break; case ".txt": case ".md": case ".log": result = "\ue8d2"; break; case ".jpg": case ".jpeg": case ".png": case ".gif": case ".webp": case ".bmp": result = "\ueb9f"; break; default: result = "\ue8a5"; break; } if (1 == 0) { } return result; } }