using SharepointToolbox.Core.Models; using System.IO; using System.Text; namespace SharepointToolbox.Services.Export; /// /// Exports a flat list of StorageNode objects to a UTF-8 BOM CSV. /// Compatible with Microsoft Excel (BOM signals UTF-8 encoding). /// public class StorageCsvExportService { public string BuildCsv(IReadOnlyList nodes) { var sb = new StringBuilder(); // Header sb.AppendLine("Library,Site,Files,Total Size (MB),Version Size (MB),Last Modified"); foreach (var node in nodes) { sb.AppendLine(string.Join(",", Csv(node.Name), Csv(node.SiteTitle), node.TotalFileCount.ToString(), FormatMb(node.TotalSizeBytes), FormatMb(node.VersionSizeBytes), node.LastModified.HasValue ? Csv(node.LastModified.Value.ToString("yyyy-MM-dd")) : string.Empty)); } return sb.ToString(); } public async Task WriteAsync(IReadOnlyList nodes, string filePath, CancellationToken ct) { var csv = BuildCsv(nodes); await File.WriteAllTextAsync(filePath, csv, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true), ct); } /// /// Builds a CSV with library details followed by a file-type breakdown section. /// public string BuildCsv(IReadOnlyList nodes, IReadOnlyList fileTypeMetrics) { var sb = new StringBuilder(); // Library details sb.AppendLine("Library,Site,Files,Total Size (MB),Version Size (MB),Last Modified"); foreach (var node in nodes) { sb.AppendLine(string.Join(",", Csv(node.Name), Csv(node.SiteTitle), node.TotalFileCount.ToString(), FormatMb(node.TotalSizeBytes), FormatMb(node.VersionSizeBytes), node.LastModified.HasValue ? Csv(node.LastModified.Value.ToString("yyyy-MM-dd")) : string.Empty)); } // File type breakdown if (fileTypeMetrics.Count > 0) { sb.AppendLine(); sb.AppendLine("File Type,Size (MB),File Count"); foreach (var m in fileTypeMetrics) { string label = string.IsNullOrEmpty(m.Extension) ? "(no extension)" : m.Extension; sb.AppendLine(string.Join(",", Csv(label), FormatMb(m.TotalSizeBytes), m.FileCount.ToString())); } } return sb.ToString(); } public async Task WriteAsync(IReadOnlyList nodes, IReadOnlyList fileTypeMetrics, string filePath, CancellationToken ct) { var csv = BuildCsv(nodes, fileTypeMetrics); await File.WriteAllTextAsync(filePath, csv, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true), ct); } // ── Helpers ─────────────────────────────────────────────────────────────── private static string FormatMb(long bytes) => (bytes / (1024.0 * 1024.0)).ToString("F2"); /// RFC 4180 CSV field quoting. private static string Csv(string value) { if (string.IsNullOrEmpty(value)) return string.Empty; if (value.Contains(',') || value.Contains('"') || value.Contains('\n')) return $"\"{value.Replace("\"", "\"\"")}\""; return value; } }