@page "/duplicates" @attribute [Authorize] @inject IUserSessionService Session @inject ISessionManager SessionMgr @inject IElevationCoordinator Elevation @inject IDuplicatesService DupSvc @inject DuplicatesCsvExportService CsvExport @inject DuplicatesHtmlExportService HtmlExport @inject WebExportService WebExport @rendermode InteractiveServer

Duplicate Detection

@if (!Session.HasProfile) { return; }
@if (_mode == "Folders") { }
@if (_running) { }
@if (!string.IsNullOrEmpty(_error)) {
@_error
} @if (_results.Count > 0) {
Duplicate Groups @_results.Count
@foreach (var g in _results.Take(100)) {
@g.Name @g.Items.Count copies
@foreach (var item in g.Items) {
@item.Library › @item.Path @if (item.SizeBytes.HasValue) { (@((item.SizeBytes.Value/1024.0).ToString("F1")) KB) }
}
} @if (_results.Count > 100) {
Showing first 100 groups. Export for all.
}
} @code { private List _sites = new(); private string _library = string.Empty, _mode = "Files"; private bool _matchSize = true, _matchCreated, _matchModified, _matchFolderCount, _matchFileCount; private bool _running; private string _status = string.Empty, _error = string.Empty; private int _current, _total; private List _results = new(); private List<(string Label, IReadOnlyList Results)> _bySite = new(); private ReportMergeMode _mergeMode = ReportMergeMode.SingleMerged; private CancellationTokenSource? _cts; private async Task RunScan() { _error = string.Empty; _results = new(); _bySite = new(); _running = true; _cts = new CancellationTokenSource(); if (_sites.Count == 0) { _error = "Please select at least one site."; _running = false; return; } var progress = new Progress(p => { _status = p.Message; _current = p.Current; _total = p.Total; InvokeAsync(StateHasChanged); }); try { var opts = new DuplicateScanOptions(_mode, _matchSize, _matchCreated, _matchModified, _matchFolderCount, _matchFileCount, Library: _library.TrimOrNull()); var bySite = new List<(string, IReadOnlyList)>(); var flat = new List(); int i = 0; foreach (var site in _sites) { _cts.Token.ThrowIfCancellationRequested(); _status = $"Scanning {site.Title} ({++i}/{_sites.Count})…"; await InvokeAsync(StateHasChanged); var groups = await Elevation.RunAsync(async c => { var ctx = await SessionMgr.GetOrCreateContextAsync(site.Url, Session.CurrentProfile!, c); return await DupSvc.ScanDuplicatesAsync(ctx, opts, progress, c); }, _cts.Token); bySite.Add((site.Title, groups)); flat.AddRange(groups); } _bySite = bySite; _results = flat; _status = $"Found {_results.Count} duplicate groups across {_sites.Count} site(s)."; } catch (OperationCanceledException) { _status = "Cancelled."; } catch (Exception ex) { _error = ex.Message; } finally { _running = false; await InvokeAsync(StateHasChanged); } } private void Cancel() => _cts?.Cancel(); private async Task ExportCsv() { var ts = DateTime.Now.ToString("yyyyMMdd_HHmmss"); var output = ReportMergeHelper.Build(_bySite, _mergeMode, "duplicates", ts, ReportFormat.Csv, rs => CsvExport.BuildCsv(rs)); await WebExport.DownloadBytesAsync(output.Bytes, output.FileName, output.Mime); } private async Task ExportHtml() { var ts = DateTime.Now.ToString("yyyyMMdd_HHmmss"); var output = ReportMergeHelper.Build(_bySite, _mergeMode, "duplicates", ts, ReportFormat.Html, rs => HtmlExport.BuildHtml(rs, Session.CurrentBranding)); await WebExport.DownloadBytesAsync(output.Bytes, output.FileName, output.Mime); } }