using SharepointToolbox.Core.Models; using SharepointToolbox.Localization; 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 T = TranslationSource.Instance; var sb = new StringBuilder(); // Header sb.AppendLine($"{T["report.col.library"]},{T["report.col.site"]},{T["report.stat.files"]},{T["report.col.total_size_mb"]},{T["report.col.version_size_mb"]},{T["report.col.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 T = TranslationSource.Instance; var sb = new StringBuilder(); // Library details sb.AppendLine($"{T["report.col.library"]},{T["report.col.site"]},{T["report.stat.files"]},{T["report.col.total_size_mb"]},{T["report.col.version_size_mb"]},{T["report.col.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($"{T["report.col.file_type"]},{T["report.col.size_mb"]},{T["report.col.file_count"]}"); foreach (var m in fileTypeMetrics) { string label = string.IsNullOrEmpty(m.Extension) ? T["report.text.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; } }