using System.IO; namespace AxCopilot.Services; /// /// 앱의 저장 공간 사용량을 분석하고 정리 기능을 제공합니다. /// public static class StorageAnalyzer { private static readonly string AppDataDir = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "AxCopilot"); /// 저장 공간 분석 결과를 반환합니다. public static StorageReport Analyze() { var report = new StorageReport(); // 앱 데이터 폴더별 크기 계산 report.Conversations = GetFolderSize(Path.Combine(AppDataDir, "conversations")); report.AuditLogs = GetFolderSize(Path.Combine(AppDataDir, "audit")); report.Logs = GetFolderSize(Path.Combine(AppDataDir, "logs")); report.CodeIndex = GetFolderSize(Path.Combine(AppDataDir, "code_index")); report.EmbeddingDb = GetFolderSize(Path.Combine(AppDataDir, "embeddings")); report.ClipboardHistory = GetFileSize(Path.Combine(AppDataDir, "clipboard_history.dat")); report.Plugins = GetFolderSize(Path.Combine(AppDataDir, "plugins")); report.Skills = GetFolderSize(Path.Combine(AppDataDir, "skills")); report.Settings = GetFileSize(Path.Combine(AppDataDir, "settings.json")); // 앱 실행 파일 위치의 드라이브 여유 공간 var exeDir = Path.GetDirectoryName(Environment.ProcessPath) ?? AppDataDir; try { var drive = new DriveInfo(Path.GetPathRoot(exeDir) ?? "C:"); report.DriveLabel = drive.Name; report.DriveFreeSpace = drive.AvailableFreeSpace; report.DriveTotalSpace = drive.TotalSize; } catch { } return report; } /// 지정된 기간보다 오래된 데이터를 삭제합니다. /// 보관 일수 (7/14/30). 0이면 전체 삭제. /// 삭제된 바이트 수 public static long Cleanup(int retainDays, bool cleanConversations, bool cleanAuditLogs, bool cleanLogs, bool cleanCodeIndex, bool cleanClipboard) { long freed = 0; var cutoff = DateTime.Now.AddDays(-retainDays); if (cleanConversations) freed += CleanFolder(Path.Combine(AppDataDir, "conversations"), cutoff, retainDays == 0); if (cleanAuditLogs) freed += CleanFolder(Path.Combine(AppDataDir, "audit"), cutoff, retainDays == 0); if (cleanLogs) freed += CleanFolder(Path.Combine(AppDataDir, "logs"), cutoff, retainDays == 0); if (cleanCodeIndex) freed += CleanFolder(Path.Combine(AppDataDir, "code_index"), cutoff, true); // 인덱스는 전체 삭제 if (cleanClipboard) { var clipFile = Path.Combine(AppDataDir, "clipboard_history.dat"); if (File.Exists(clipFile)) { freed += new FileInfo(clipFile).Length; File.Delete(clipFile); } } return freed; } private static long GetFolderSize(string path) { if (!Directory.Exists(path)) return 0; try { return Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories) .Sum(f => { try { return new FileInfo(f).Length; } catch { return 0; } }); } catch { return 0; } } private static long GetFileSize(string path) { try { return File.Exists(path) ? new FileInfo(path).Length : 0; } catch { return 0; } } private static long CleanFolder(string path, DateTime cutoff, bool deleteAll) { if (!Directory.Exists(path)) return 0; long freed = 0; foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) { try { var fi = new FileInfo(file); if (deleteAll || fi.LastWriteTime < cutoff) { freed += fi.Length; fi.Delete(); } } catch { } } // 빈 디렉토리 정리 if (deleteAll) { try { Directory.Delete(path, true); Directory.CreateDirectory(path); } catch { } } return freed; } /// 바이트를 읽기 좋은 형태로 변환합니다. public static string FormatSize(long bytes) { if (bytes < 1024) return $"{bytes} B"; if (bytes < 1024 * 1024) return $"{bytes / 1024.0:F1} KB"; if (bytes < 1024L * 1024 * 1024) return $"{bytes / (1024.0 * 1024):F1} MB"; return $"{bytes / (1024.0 * 1024 * 1024):F2} GB"; } } /// 저장 공간 분석 결과. public class StorageReport { public long Conversations { get; set; } public long AuditLogs { get; set; } public long Logs { get; set; } public long CodeIndex { get; set; } public long EmbeddingDb { get; set; } public long ClipboardHistory { get; set; } public long Plugins { get; set; } public long Skills { get; set; } public long Settings { get; set; } public string DriveLabel { get; set; } = ""; public long DriveFreeSpace { get; set; } public long DriveTotalSpace { get; set; } /// 앱 전체 사용량. public long TotalAppUsage => Conversations + AuditLogs + Logs + CodeIndex + EmbeddingDb + ClipboardHistory + Plugins + Skills + Settings; }