From 94ff1810356d757055d0cb3c101bb067e5eecd44 Mon Sep 17 00:00:00 2001 From: Dev Date: Thu, 2 Apr 2026 15:29:45 +0200 Subject: [PATCH] feat(03-03): implement StorageCsvExportService - Replace string.Empty stub with full BuildCsv implementation - UTF-8 BOM header row: Library,Site,Files,Total Size (MB),Version Size (MB),Last Modified - RFC 4180 CSV quoting via Csv() helper - FormatMb() converts bytes to MB with 2 decimal places - Add explicit System.IO using (required in WPF project) --- .../Export/StorageCsvExportService.cs | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/SharepointToolbox/Services/Export/StorageCsvExportService.cs b/SharepointToolbox/Services/Export/StorageCsvExportService.cs index a42b619..9fdc149 100644 --- a/SharepointToolbox/Services/Export/StorageCsvExportService.cs +++ b/SharepointToolbox/Services/Export/StorageCsvExportService.cs @@ -1,14 +1,56 @@ 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) => string.Empty; // implemented in Plan 03-03 + 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 System.IO.File.WriteAllTextAsync(filePath, csv, new System.Text.UTF8Encoding(true), ct); + // UTF-8 with BOM for Excel compatibility + 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; } }