using System.IO; using System.Windows; using AxCopilot.SDK; using AxCopilot.Services; using AxCopilot.Themes; namespace AxCopilot.Handlers; /// /// L13-3: 드라이브 정보 핸들러. "drive" 프리픽스로 사용합니다. /// /// 예: drive → 전체 드라이브 목록 + 용량 요약 /// drive C → C 드라이브 상세 정보 /// drive C:\ → 경로 형식도 지원 /// drive large → 사용량 많은 순서로 정렬 /// Enter → 드라이브 정보를 클립보드에 복사. /// public class DriveHandler : IActionHandler { public string? Prefix => "drive"; public PluginMetadata Metadata => new( "Drive", "드라이브 정보 — 용량 · 파일시스템 · 여유공간", "1.0", "AX"); public Task> GetItemsAsync(string query, CancellationToken ct) { var q = query.Trim(); var items = new List(); var drives = GetDrives(); if (string.IsNullOrWhiteSpace(q)) { var totalSize = drives.Sum(d => d.TotalSize); var totalFree = drives.Sum(d => d.AvailableFree); var totalUsed = totalSize - totalFree; items.Add(new LauncherItem( $"드라이브 {drives.Count}개", $"전체 {FormatBytes(totalSize)} · 사용 {FormatBytes(totalUsed)} · 여유 {FormatBytes(totalFree)}", null, null, Symbol: "\uEDA2")); foreach (var d in drives.OrderBy(d => d.Name)) items.Add(MakeDriveSummaryItem(d)); return Task.FromResult>(items); } var sub = q.ToUpperInvariant().TrimEnd(':', '\\', '/'); if (sub == "LARGE") { // 사용량 많은 순 foreach (var d in drives.OrderByDescending(d => d.UsedSpace)) items.Add(MakeDriveSummaryItem(d)); return Task.FromResult>(items); } // 특정 드라이브 상세 var target = drives.FirstOrDefault(d => d.Name.StartsWith(sub, StringComparison.OrdinalIgnoreCase)); if (target == null) { items.Add(new LauncherItem("드라이브 없음", $"'{q}' 드라이브를 찾을 수 없습니다", null, null, Symbol: "\uE783")); return Task.FromResult>(items); } items.AddRange(BuildDetailItems(target)); return Task.FromResult>(items); } public Task ExecuteAsync(LauncherItem item, CancellationToken ct) { if (item.Data is ("copy", string text)) { try { System.Windows.Application.Current.Dispatcher.Invoke( () => Clipboard.SetText(text)); NotificationService.Notify("Drive", "클립보드에 복사했습니다."); } catch { /* 비핵심 */ } } return Task.CompletedTask; } // ── 드라이브 정보 수집 ──────────────────────────────────────────────────── private record DriveInfo2( string Name, string VolumeLabel, string DriveFormat, DriveType DriveType, long TotalSize, long AvailableFree, long UsedSpace, bool IsReady); private static List GetDrives() { return DriveInfo.GetDrives() .Select(d => { if (!d.IsReady) return new DriveInfo2(d.Name, "", "", d.DriveType, 0, 0, 0, false); try { return new DriveInfo2( d.Name, d.VolumeLabel, d.DriveFormat, d.DriveType, d.TotalSize, d.AvailableFreeSpace, d.TotalSize - d.AvailableFreeSpace, true); } catch { return new DriveInfo2(d.Name, "", d.DriveFormat, d.DriveType, 0, 0, 0, false); } }) .ToList(); } private static IEnumerable BuildDetailItems(DriveInfo2 d) { var usagePercent = d.TotalSize > 0 ? (double)d.UsedSpace / d.TotalSize * 100 : 0; var bar = MakeBar(usagePercent, 20); var summary = $""" 드라이브: {d.Name} 볼륨 레이블: {(string.IsNullOrEmpty(d.VolumeLabel) ? "(없음)" : d.VolumeLabel)} 파일 시스템: {d.DriveFormat} 드라이브 종류: {DriveTypeName(d.DriveType)} 전체 용량: {FormatBytes(d.TotalSize)} 사용 중: {FormatBytes(d.UsedSpace)} ({usagePercent:F1}%) 여유 공간: {FormatBytes(d.AvailableFree)} """; yield return new LauncherItem( $"{d.Name} {FormatBytes(d.TotalSize)}", $"사용 {usagePercent:F0}% {bar} 여유 {FormatBytes(d.AvailableFree)}", null, ("copy", summary), Symbol: "\uEDA2"); yield return new LauncherItem("볼륨 레이블", string.IsNullOrEmpty(d.VolumeLabel) ? "(없음)" : d.VolumeLabel, null, null, Symbol: "\uEDA2"); yield return new LauncherItem("파일 시스템", d.DriveFormat, null, null, Symbol: "\uEDA2"); yield return new LauncherItem("드라이브 종류", DriveTypeName(d.DriveType), null, null, Symbol: "\uEDA2"); yield return new LauncherItem("전체 용량", FormatBytes(d.TotalSize), null, ("copy", FormatBytes(d.TotalSize)), Symbol: "\uEDA2"); yield return new LauncherItem("사용 중", $"{FormatBytes(d.UsedSpace)} ({usagePercent:F1}%)", null, ("copy", FormatBytes(d.UsedSpace)), Symbol: "\uEDA2"); yield return new LauncherItem("여유 공간", FormatBytes(d.AvailableFree), null, ("copy", FormatBytes(d.AvailableFree)), Symbol: "\uEDA2"); } private static LauncherItem MakeDriveSummaryItem(DriveInfo2 d) { if (!d.IsReady) return new LauncherItem(d.Name, $"준비 안됨 ({DriveTypeName(d.DriveType)})", null, null, Symbol: "\uEDA2"); var usagePercent = d.TotalSize > 0 ? (double)d.UsedSpace / d.TotalSize * 100 : 0; var bar = MakeBar(usagePercent, 12); var label = string.IsNullOrEmpty(d.VolumeLabel) ? d.Name : $"{d.Name} ({d.VolumeLabel})"; return new LauncherItem( label, $"{bar} {usagePercent:F0}% · 여유 {FormatBytes(d.AvailableFree)} / {FormatBytes(d.TotalSize)}", null, ("copy", $"{d.Name} {FormatBytes(d.TotalSize)} 사용{usagePercent:F0}% 여유{FormatBytes(d.AvailableFree)}"), Symbol: "\uEDA2"); } // ── 유틸 ───────────────────────────────────────────────────────────────── private static string MakeBar(double percent, int width) { var filled = (int)(percent / 100.0 * width); filled = Math.Clamp(filled, 0, width); return "[" + new string('█', filled) + new string('░', width - filled) + "]"; } private static string DriveTypeName(DriveType dt) => dt switch { DriveType.Fixed => "고정 디스크", DriveType.Removable => "이동식 디스크", DriveType.Network => "네트워크 드라이브", DriveType.CDRom => "CD/DVD", DriveType.Ram => "RAM 디스크", DriveType.NoRootDirectory => "루트 없음", _ => "알 수 없음", }; private static string FormatBytes(long bytes) => bytes switch { >= 1024L * 1024 * 1024 * 1024 => $"{bytes / 1024.0 / 1024 / 1024 / 1024:F2} TB", >= 1024L * 1024 * 1024 => $"{bytes / 1024.0 / 1024 / 1024:F1} GB", >= 1024L * 1024 => $"{bytes / 1024.0 / 1024:F1} MB", >= 1024L => $"{bytes / 1024.0:F0} KB", _ => $"{bytes} B", }; }