Initial commit
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
using Microsoft.SharePoint.Client;
|
||||
using Microsoft.SharePoint.Client.Search.Query;
|
||||
using Serilog;
|
||||
using SharepointToolbox.Web.Core.Helpers;
|
||||
using SharepointToolbox.Web.Core.Models;
|
||||
|
||||
namespace SharepointToolbox.Web.Services;
|
||||
|
||||
public class SystemGroupTargetResolver : ISystemGroupTargetResolver
|
||||
{
|
||||
private readonly Dictionary<string, SystemGroupTarget?> _cache = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public async Task<SystemGroupTarget?> ResolveAsync(
|
||||
ClientContext ctx, SystemGroupClassification classification, CancellationToken ct)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var key = BuildCacheKey(ctx.Url, classification);
|
||||
if (key is not null && _cache.TryGetValue(key, out var cached)) return cached;
|
||||
|
||||
SystemGroupTarget? result = null;
|
||||
try
|
||||
{
|
||||
result = classification.Kind switch
|
||||
{
|
||||
SystemGroupKind.LimitedAccessWeb => await ResolveWebAsync(ctx, classification.WebId!.Value, ct),
|
||||
SystemGroupKind.LimitedAccessList => await ResolveListAsync(ctx, classification.ListId!.Value, ct),
|
||||
SystemGroupKind.SharingLink => await ResolveItemAsync(ctx, classification.ItemUniqueId!.Value, classification.LinkType, ct),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
catch (OperationCanceledException) { throw; }
|
||||
catch (Exception ex) { Log.Debug("System group resolve failed for {Kind}: {Error}", classification.Kind, ex.Message); }
|
||||
|
||||
if (key is not null) _cache[key] = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string? BuildCacheKey(string siteUrl, SystemGroupClassification c) => c.Kind switch
|
||||
{
|
||||
SystemGroupKind.LimitedAccessWeb => $"{siteUrl}|web|{c.WebId}",
|
||||
SystemGroupKind.LimitedAccessList => $"{siteUrl}|list|{c.ListId}",
|
||||
SystemGroupKind.SharingLink => $"{siteUrl}|item|{c.ItemUniqueId}",
|
||||
_ => null
|
||||
};
|
||||
|
||||
private static async Task<SystemGroupTarget?> ResolveWebAsync(ClientContext ctx, Guid webId, CancellationToken ct)
|
||||
{
|
||||
var web = ctx.Site.OpenWebById(webId);
|
||||
ctx.Load(web, w => w.Title, w => w.Url);
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, null, ct);
|
||||
return new SystemGroupTarget(SystemGroupKind.LimitedAccessWeb, web.Title, web.Url);
|
||||
}
|
||||
|
||||
private static async Task<SystemGroupTarget?> ResolveListAsync(ClientContext ctx, Guid listId, CancellationToken ct)
|
||||
{
|
||||
var list = ctx.Web.Lists.GetById(listId);
|
||||
ctx.Load(list, l => l.Title, l => l.DefaultViewUrl);
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, null, ct);
|
||||
return new SystemGroupTarget(SystemGroupKind.LimitedAccessList, list.Title, BuildAbsoluteUrl(ctx.Url, list.DefaultViewUrl));
|
||||
}
|
||||
|
||||
private static async Task<SystemGroupTarget?> ResolveItemAsync(ClientContext ctx, Guid itemUniqueId, string? linkType, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = ctx.Web.GetFileById(itemUniqueId);
|
||||
ctx.Load(file, f => f.Name, f => f.ServerRelativeUrl, f => f.Exists);
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, null, ct);
|
||||
if (file.Exists) return new SystemGroupTarget(SystemGroupKind.SharingLink, file.Name, BuildAbsoluteUrl(ctx.Url, file.ServerRelativeUrl), linkType);
|
||||
}
|
||||
catch (ServerException ex) { Log.Debug("File by ID not found: {Error}", ex.Message); }
|
||||
|
||||
try
|
||||
{
|
||||
var folder = ctx.Web.GetFolderById(itemUniqueId);
|
||||
ctx.Load(folder, f => f.Name, f => f.ServerRelativeUrl);
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, null, ct);
|
||||
return new SystemGroupTarget(SystemGroupKind.SharingLink, folder.Name, BuildAbsoluteUrl(ctx.Url, folder.ServerRelativeUrl), linkType);
|
||||
}
|
||||
catch (ServerException ex) { Log.Debug("Folder by ID not found: {Error}", ex.Message); }
|
||||
|
||||
return await TryResolveViaSearchAsync(ctx, itemUniqueId, linkType, ct);
|
||||
}
|
||||
|
||||
private static async Task<SystemGroupTarget?> TryResolveViaSearchAsync(ClientContext ctx, Guid itemUniqueId, string? linkType, CancellationToken ct)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
var kq = new KeywordQuery(ctx) { QueryText = $"UniqueId:{{{itemUniqueId}}}", RowLimit = 1, TrimDuplicates = false };
|
||||
kq.SelectProperties.Add("Path"); kq.SelectProperties.Add("Title");
|
||||
var executor = new SearchExecutor(ctx);
|
||||
var result = executor.ExecuteQuery(kq);
|
||||
await ExecuteQueryRetryHelper.ExecuteQueryRetryAsync(ctx, null, ct);
|
||||
|
||||
var table = result.Value.FirstOrDefault(t => t.TableType == KnownTableTypes.RelevantResults);
|
||||
if (table is null || table.RowCount == 0) return null;
|
||||
|
||||
var row = ToDict(table.ResultRows.First());
|
||||
var path = row.TryGetValue("Path", out var p) ? p?.ToString() : null;
|
||||
var title = row.TryGetValue("Title", out var t) ? t?.ToString() : null;
|
||||
if (string.IsNullOrEmpty(path)) return null;
|
||||
var leaf = !string.IsNullOrWhiteSpace(title) ? title! : Uri.UnescapeDataString(path.TrimEnd('/').Split('/').Last());
|
||||
return new SystemGroupTarget(SystemGroupKind.SharingLink, $"{leaf} (via index)", path, linkType);
|
||||
}
|
||||
catch (OperationCanceledException) { throw; }
|
||||
catch (Exception ex) { Log.Debug("UniqueId search fallback failed: {Error}", ex.Message); return null; }
|
||||
}
|
||||
|
||||
private static IDictionary<string, object> ToDict(object rawRow)
|
||||
{
|
||||
if (rawRow is IDictionary<string, object> generic) return generic;
|
||||
var dict = new Dictionary<string, object>();
|
||||
if (rawRow is System.Collections.IDictionary legacy)
|
||||
foreach (System.Collections.DictionaryEntry e in legacy)
|
||||
dict[e.Key.ToString()!] = e.Value ?? string.Empty;
|
||||
return dict;
|
||||
}
|
||||
|
||||
private static string BuildAbsoluteUrl(string contextUrl, string? serverRelative)
|
||||
{
|
||||
if (string.IsNullOrEmpty(serverRelative)) return contextUrl;
|
||||
if (Uri.TryCreate(serverRelative, UriKind.Absolute, out _)) return serverRelative;
|
||||
var uri = new Uri(contextUrl);
|
||||
return $"{uri.Scheme}://{uri.Host}{serverRelative}";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user