- Add IBrandingService field and DI constructor parameter to all 5 ViewModels - Add optional IBrandingService? parameter to test constructors (PermissionsViewModel, StorageViewModel, UserAccessAuditViewModel) - Assemble ReportBranding from GetMspLogoAsync + _currentProfile.ClientLogo before each WriteAsync call - Pass branding as last parameter to WriteAsync in all ExportHtmlAsync methods - Guard clause: branding assembly skipped (branding = null) when _brandingService is null (test constructors) - Build: 0 warnings, 0 errors; tests: 254 passed / 0 failed / 26 skipped
188 lines
7.0 KiB
C#
188 lines
7.0 KiB
C#
using System.Collections.ObjectModel;
|
|
using System.Diagnostics;
|
|
using System.Windows;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Win32;
|
|
using SharepointToolbox.Core.Models;
|
|
using SharepointToolbox.Services;
|
|
using SharepointToolbox.Services.Export;
|
|
|
|
namespace SharepointToolbox.ViewModels.Tabs;
|
|
|
|
/// <summary>Flat display row wrapping a DuplicateItem with its group name.</summary>
|
|
public class DuplicateRow
|
|
{
|
|
public string GroupName { get; set; } = string.Empty;
|
|
public string Name { get; set; } = string.Empty;
|
|
public string Path { get; set; } = string.Empty;
|
|
public string Library { get; set; } = string.Empty;
|
|
public long? SizeBytes { get; set; }
|
|
public DateTime? Created { get; set; }
|
|
public DateTime? Modified { get; set; }
|
|
public int? FolderCount { get; set; }
|
|
public int? FileCount { get; set; }
|
|
public int GroupSize { get; set; }
|
|
}
|
|
|
|
public partial class DuplicatesViewModel : FeatureViewModelBase
|
|
{
|
|
private readonly IDuplicatesService _duplicatesService;
|
|
private readonly ISessionManager _sessionManager;
|
|
private readonly DuplicatesHtmlExportService _htmlExportService;
|
|
private readonly IBrandingService _brandingService;
|
|
private readonly ILogger<FeatureViewModelBase> _logger;
|
|
private TenantProfile? _currentProfile;
|
|
private IReadOnlyList<DuplicateGroup> _lastGroups = Array.Empty<DuplicateGroup>();
|
|
|
|
[ObservableProperty] private bool _modeFiles = true;
|
|
[ObservableProperty] private bool _modeFolders;
|
|
[ObservableProperty] private bool _matchSize = true;
|
|
[ObservableProperty] private bool _matchCreated;
|
|
[ObservableProperty] private bool _matchModified;
|
|
[ObservableProperty] private bool _matchSubfolders;
|
|
[ObservableProperty] private bool _matchFileCount;
|
|
[ObservableProperty] private bool _includeSubsites;
|
|
[ObservableProperty] private string _library = string.Empty;
|
|
|
|
private ObservableCollection<DuplicateRow> _results = new();
|
|
public ObservableCollection<DuplicateRow> Results
|
|
{
|
|
get => _results;
|
|
private set
|
|
{
|
|
_results = value;
|
|
OnPropertyChanged();
|
|
ExportHtmlCommand.NotifyCanExecuteChanged();
|
|
}
|
|
}
|
|
|
|
public IAsyncRelayCommand ExportHtmlCommand { get; }
|
|
public TenantProfile? CurrentProfile => _currentProfile;
|
|
|
|
public DuplicatesViewModel(
|
|
IDuplicatesService duplicatesService,
|
|
ISessionManager sessionManager,
|
|
DuplicatesHtmlExportService htmlExportService,
|
|
IBrandingService brandingService,
|
|
ILogger<FeatureViewModelBase> logger)
|
|
: base(logger)
|
|
{
|
|
_duplicatesService = duplicatesService;
|
|
_sessionManager = sessionManager;
|
|
_htmlExportService = htmlExportService;
|
|
_brandingService = brandingService;
|
|
_logger = logger;
|
|
|
|
ExportHtmlCommand = new AsyncRelayCommand(ExportHtmlAsync, CanExport);
|
|
}
|
|
|
|
protected override async Task RunOperationAsync(CancellationToken ct, IProgress<OperationProgress> progress)
|
|
{
|
|
if (_currentProfile == null)
|
|
{
|
|
StatusMessage = "No tenant selected. Please connect to a tenant first.";
|
|
return;
|
|
}
|
|
|
|
var urls = GlobalSites.Select(s => s.Url).Where(u => !string.IsNullOrWhiteSpace(u)).ToList();
|
|
if (urls.Count == 0)
|
|
{
|
|
StatusMessage = "Select at least one site from the toolbar.";
|
|
return;
|
|
}
|
|
|
|
var allGroups = new List<DuplicateGroup>();
|
|
|
|
foreach (var url in urls)
|
|
{
|
|
var siteProfile = new TenantProfile
|
|
{
|
|
TenantUrl = url.TrimEnd('/'),
|
|
ClientId = _currentProfile.ClientId,
|
|
Name = _currentProfile.Name
|
|
};
|
|
var ctx = await _sessionManager.GetOrCreateContextAsync(siteProfile, ct);
|
|
|
|
var opts = new DuplicateScanOptions(
|
|
Mode: ModeFiles ? "Files" : "Folders",
|
|
MatchSize: MatchSize,
|
|
MatchCreated: MatchCreated,
|
|
MatchModified: MatchModified,
|
|
MatchSubfolderCount: MatchSubfolders,
|
|
MatchFileCount: MatchFileCount,
|
|
IncludeSubsites: IncludeSubsites,
|
|
Library: string.IsNullOrWhiteSpace(Library) ? null : Library
|
|
);
|
|
|
|
var groups = await _duplicatesService.ScanDuplicatesAsync(ctx, opts, progress, ct);
|
|
allGroups.AddRange(groups);
|
|
}
|
|
|
|
_lastGroups = allGroups;
|
|
|
|
// Flatten groups to display rows
|
|
var rows = allGroups
|
|
.SelectMany(g => g.Items.Select(item => new DuplicateRow
|
|
{
|
|
GroupName = g.Name,
|
|
Name = item.Name,
|
|
Path = item.Path,
|
|
Library = item.Library,
|
|
SizeBytes = item.SizeBytes,
|
|
Created = item.Created,
|
|
Modified = item.Modified,
|
|
FolderCount = item.FolderCount,
|
|
FileCount = item.FileCount,
|
|
GroupSize = g.Items.Count
|
|
}))
|
|
.ToList();
|
|
|
|
if (Application.Current?.Dispatcher is { } dispatcher)
|
|
await dispatcher.InvokeAsync(() => Results = new ObservableCollection<DuplicateRow>(rows));
|
|
else
|
|
Results = new ObservableCollection<DuplicateRow>(rows);
|
|
}
|
|
|
|
protected override void OnTenantSwitched(Core.Models.TenantProfile profile)
|
|
{
|
|
_currentProfile = profile;
|
|
Results = new ObservableCollection<DuplicateRow>();
|
|
_lastGroups = Array.Empty<DuplicateGroup>();
|
|
OnPropertyChanged(nameof(CurrentProfile));
|
|
ExportHtmlCommand.NotifyCanExecuteChanged();
|
|
}
|
|
|
|
internal void SetCurrentProfile(TenantProfile profile) => _currentProfile = profile;
|
|
|
|
private bool CanExport() => _lastGroups.Count > 0;
|
|
|
|
private async Task ExportHtmlAsync()
|
|
{
|
|
if (_lastGroups.Count == 0) return;
|
|
var dialog = new SaveFileDialog
|
|
{
|
|
Title = "Export duplicates report to HTML",
|
|
Filter = "HTML files (*.html)|*.html|All files (*.*)|*.*",
|
|
DefaultExt = "html",
|
|
FileName = "duplicates_report"
|
|
};
|
|
if (dialog.ShowDialog() != true) return;
|
|
try
|
|
{
|
|
ReportBranding? branding = null;
|
|
if (_brandingService is not null)
|
|
{
|
|
var mspLogo = await _brandingService.GetMspLogoAsync();
|
|
var clientLogo = _currentProfile?.ClientLogo;
|
|
branding = new ReportBranding(mspLogo, clientLogo);
|
|
}
|
|
|
|
await _htmlExportService.WriteAsync(_lastGroups, dialog.FileName, CancellationToken.None, branding);
|
|
Process.Start(new ProcessStartInfo(dialog.FileName) { UseShellExecute = true });
|
|
}
|
|
catch (Exception ex) { StatusMessage = $"Export failed: {ex.Message}"; _logger.LogError(ex, "HTML export failed."); }
|
|
}
|
|
}
|