using Microsoft.SharePoint.Client; using Serilog; using SharepointToolbox.Web.Core.Helpers; using SharepointToolbox.Web.Core.Models; using SharepointToolbox.Web.Services.Audit; namespace SharepointToolbox.Web.Services; public class FolderStructureService : IFolderStructureService { private readonly IAuditService _audit; public FolderStructureService(IAuditService audit) { _audit = audit; } public async Task> CreateFoldersAsync( ClientContext ctx, string libraryTitle, IReadOnlyList rows, IProgress progress, CancellationToken ct) { var list = ctx.Web.Lists.GetByTitle(libraryTitle); ctx.Load(list, l => l.RootFolder.ServerRelativeUrl); await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, ct); var baseUrl = list.RootFolder.ServerRelativeUrl.TrimEnd('/'); var folderPaths = BuildUniquePaths(rows); var result = await BulkOperationRunner.RunAsync( folderPaths, async (path, idx, token) => { ctx.Web.Folders.Add($"{baseUrl}/{path}"); await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, progress, token); Log.Information("Created folder: {Path}", $"{baseUrl}/{path}"); }, progress, ct); await _audit.LogAsync("CreateFolderStructure", ctx.Url, new[] { ctx.Url }, $"{result.SuccessCount} folders created in '{libraryTitle}', {(result.TotalCount - result.SuccessCount)} failed"); return result; } internal static IReadOnlyList BuildUniquePaths(IReadOnlyList rows) { var paths = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (var row in rows) { var parts = new[] { row.Level1, row.Level2, row.Level3, row.Level4 } .Where(s => !string.IsNullOrWhiteSpace(s)).ToArray(); var current = string.Empty; foreach (var part in parts) { current = string.IsNullOrEmpty(current) ? part.Trim() : $"{current}/{part.Trim()}"; paths.Add(current); } } return paths.OrderBy(p => p.Count(c => c == '/')).ThenBy(p => p, StringComparer.OrdinalIgnoreCase).ToList(); } }