using SharepointToolbox.Core.Models; using System.IO; using System.Text; namespace SharepointToolbox.Services.Export; /// /// Exports StorageNode tree to a self-contained HTML file with collapsible subfolder rows. /// Port of PS Export-StorageToHTML (PS lines 1621-1780). /// Uses a toggle(i) JS pattern where each collapsible row has id="sf-{i}". /// public class StorageHtmlExportService { private int _togIdx; public string BuildHtml(IReadOnlyList nodes) { _togIdx = 0; var sb = new StringBuilder(); sb.AppendLine(""" SharePoint Storage Metrics

SharePoint Storage Metrics

"""); sb.AppendLine(""" """); foreach (var node in nodes) { RenderNode(sb, node); } sb.AppendLine("""
Library / Folder Site Files Total Size Version Size Last Modified
"""); sb.AppendLine($"

Generated: {DateTime.Now:yyyy-MM-dd HH:mm}

"); sb.AppendLine(""); return sb.ToString(); } public async Task WriteAsync(IReadOnlyList nodes, string filePath, CancellationToken ct) { var html = BuildHtml(nodes); await File.WriteAllTextAsync(filePath, html, Encoding.UTF8, ct); } // ── Private rendering ──────────────────────────────────────────────────── private void RenderNode(StringBuilder sb, StorageNode node) { bool hasChildren = node.Children.Count > 0; int myIdx = hasChildren ? ++_togIdx : 0; string nameCell = hasChildren ? $"{HtmlEncode(node.Name)}" : $"{HtmlEncode(node.Name)}"; string lastMod = node.LastModified.HasValue ? node.LastModified.Value.ToString("yyyy-MM-dd") : string.Empty; sb.AppendLine($""" {nameCell} {HtmlEncode(node.SiteTitle)} {node.TotalFileCount:N0} {FormatSize(node.TotalSizeBytes)} {FormatSize(node.VersionSizeBytes)} {lastMod} """); if (hasChildren) { sb.AppendLine($""); sb.AppendLine(""); foreach (var child in node.Children) { RenderChildNode(sb, child); } sb.AppendLine("
"); sb.AppendLine(""); } } private void RenderChildNode(StringBuilder sb, StorageNode node) { bool hasChildren = node.Children.Count > 0; int myIdx = hasChildren ? ++_togIdx : 0; string indent = $"margin-left:{(node.IndentLevel + 1) * 16}px"; string nameCell = hasChildren ? $"{HtmlEncode(node.Name)}" : $"{HtmlEncode(node.Name)}"; string lastMod = node.LastModified.HasValue ? node.LastModified.Value.ToString("yyyy-MM-dd") : string.Empty; sb.AppendLine($""" {nameCell} {HtmlEncode(node.SiteTitle)} {node.TotalFileCount:N0} {FormatSize(node.TotalSizeBytes)} {FormatSize(node.VersionSizeBytes)} {lastMod} """); if (hasChildren) { sb.AppendLine($""); sb.AppendLine(""); foreach (var child in node.Children) { RenderChildNode(sb, child); } sb.AppendLine("
"); sb.AppendLine(""); } } private static string FormatSize(long bytes) { if (bytes >= 1_073_741_824L) return $"{bytes / 1_073_741_824.0:F2} GB"; if (bytes >= 1_048_576L) return $"{bytes / 1_048_576.0:F2} MB"; if (bytes >= 1024L) return $"{bytes / 1024.0:F2} KB"; return $"{bytes} B"; } private static string HtmlEncode(string value) => System.Net.WebUtility.HtmlEncode(value ?? string.Empty); }