Merge branch 'main' of https://git.azuze.fr/kawa/Sharepoint-Toolbox
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
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.Localization;
|
||||
using SharepointToolbox.Services;
|
||||
|
||||
namespace SharepointToolbox.ViewModels.Tabs;
|
||||
|
||||
public partial class VersionCleanupViewModel : FeatureViewModelBase
|
||||
{
|
||||
private readonly IVersionCleanupService _versionService;
|
||||
private readonly ISessionManager _sessionManager;
|
||||
private readonly ILogger<FeatureViewModelBase> _logger;
|
||||
private TenantProfile? _currentProfile;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _keepLast = 5;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _keepFirstVersion;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _confirmDelete = true;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _selectedLibrariesLabel = string.Empty;
|
||||
|
||||
public ObservableCollection<string> SelectedLibraries { get; } = new();
|
||||
|
||||
public ObservableCollection<VersionCleanupResult> Results { get; } = new();
|
||||
|
||||
public long TotalBytesFreed => Results.Sum(r => r.BytesFreed);
|
||||
public int TotalVersionsDeleted => Results.Sum(r => r.VersionsDeleted);
|
||||
public int TotalFilesAffected => Results.Count(r => r.VersionsDeleted > 0);
|
||||
public bool HasResults => Results.Count > 0;
|
||||
|
||||
public TenantProfile? CurrentProfile => _currentProfile;
|
||||
|
||||
/// <summary>Set by the view to invoke <see cref="LibraryPickerDialog"/> against the current site.</summary>
|
||||
public Func<string, IReadOnlyCollection<string>, Task<IReadOnlyList<string>?>>? PickLibrariesAsync { get; set; }
|
||||
|
||||
/// <summary>Set by the view to display a confirm dialog before destructive run.</summary>
|
||||
public Func<string, bool>? ConfirmAction { get; set; }
|
||||
|
||||
public IAsyncRelayCommand SelectLibrariesCommand { get; }
|
||||
public IRelayCommand ClearLibrariesCommand { get; }
|
||||
public IAsyncRelayCommand ExportCsvCommand { get; }
|
||||
|
||||
public VersionCleanupViewModel(
|
||||
IVersionCleanupService versionService,
|
||||
ISessionManager sessionManager,
|
||||
ILogger<FeatureViewModelBase> logger)
|
||||
: base(logger)
|
||||
{
|
||||
_versionService = versionService;
|
||||
_sessionManager = sessionManager;
|
||||
_logger = logger;
|
||||
|
||||
SelectLibrariesCommand = new AsyncRelayCommand(SelectLibrariesAsync, CanPickLibraries);
|
||||
ClearLibrariesCommand = new RelayCommand(ClearLibraries);
|
||||
ExportCsvCommand = new AsyncRelayCommand(ExportCsvAsync, () => Results.Count > 0);
|
||||
|
||||
SelectedLibraries.CollectionChanged += (_, _) => UpdateSelectedLibrariesLabel();
|
||||
Results.CollectionChanged += (_, _) =>
|
||||
{
|
||||
OnPropertyChanged(nameof(HasResults));
|
||||
OnPropertyChanged(nameof(TotalBytesFreed));
|
||||
OnPropertyChanged(nameof(TotalVersionsDeleted));
|
||||
OnPropertyChanged(nameof(TotalFilesAffected));
|
||||
ExportCsvCommand.NotifyCanExecuteChanged();
|
||||
};
|
||||
UpdateSelectedLibrariesLabel();
|
||||
}
|
||||
|
||||
protected override void OnTenantSwitched(TenantProfile profile)
|
||||
{
|
||||
_currentProfile = profile;
|
||||
SelectedLibraries.Clear();
|
||||
Results.Clear();
|
||||
OnPropertyChanged(nameof(CurrentProfile));
|
||||
SelectLibrariesCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
protected override void OnGlobalSitesChanged(IReadOnlyList<SiteInfo> sites)
|
||||
{
|
||||
// Site changes invalidate library list — clear so user re-picks.
|
||||
SelectedLibraries.Clear();
|
||||
SelectLibrariesCommand.NotifyCanExecuteChanged();
|
||||
}
|
||||
|
||||
private bool CanPickLibraries() => _currentProfile != null && GlobalSites.Count > 0;
|
||||
|
||||
private async Task SelectLibrariesAsync()
|
||||
{
|
||||
if (PickLibrariesAsync == null || _currentProfile == null) return;
|
||||
var first = GlobalSites.FirstOrDefault();
|
||||
if (first == null)
|
||||
{
|
||||
StatusMessage = TranslationSource.Instance["err.no_sites_selected"];
|
||||
return;
|
||||
}
|
||||
|
||||
var picked = await PickLibrariesAsync(first.Url, SelectedLibraries.ToArray());
|
||||
if (picked == null) return;
|
||||
SelectedLibraries.Clear();
|
||||
foreach (var t in picked) SelectedLibraries.Add(t);
|
||||
}
|
||||
|
||||
private void ClearLibraries() => SelectedLibraries.Clear();
|
||||
|
||||
private void UpdateSelectedLibrariesLabel()
|
||||
{
|
||||
SelectedLibrariesLabel = SelectedLibraries.Count == 0
|
||||
? TranslationSource.Instance["versions.libs.all"]
|
||||
: string.Format(TranslationSource.Instance["versions.libs.count"], SelectedLibraries.Count);
|
||||
}
|
||||
|
||||
protected override async Task RunOperationAsync(CancellationToken ct, IProgress<OperationProgress> progress)
|
||||
{
|
||||
if (_currentProfile == null)
|
||||
{
|
||||
StatusMessage = TranslationSource.Instance["err.no_tenant_connected"];
|
||||
return;
|
||||
}
|
||||
|
||||
var urls = GlobalSites.Select(s => s.Url).Where(u => !string.IsNullOrWhiteSpace(u)).ToList();
|
||||
if (urls.Count == 0)
|
||||
{
|
||||
StatusMessage = TranslationSource.Instance["err.no_sites_selected"];
|
||||
return;
|
||||
}
|
||||
|
||||
if (KeepLast < 0)
|
||||
{
|
||||
StatusMessage = TranslationSource.Instance["versions.err.keepLast"];
|
||||
return;
|
||||
}
|
||||
|
||||
if (ConfirmDelete && ConfirmAction != null)
|
||||
{
|
||||
var msg = string.Format(
|
||||
TranslationSource.Instance["versions.confirm"],
|
||||
KeepLast,
|
||||
KeepFirstVersion ? TranslationSource.Instance["versions.confirm.keepFirst"] : string.Empty);
|
||||
if (!ConfirmAction(msg)) return;
|
||||
}
|
||||
|
||||
var options = new VersionCleanupOptions(
|
||||
SelectedLibraries.ToList(), KeepLast, KeepFirstVersion);
|
||||
|
||||
Results.Clear();
|
||||
int siteIdx = 0;
|
||||
foreach (var url in urls)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
siteIdx++;
|
||||
progress.Report(new OperationProgress(siteIdx, urls.Count, $"Cleaning {url}..."));
|
||||
|
||||
var siteProfile = new TenantProfile
|
||||
{
|
||||
TenantUrl = url.TrimEnd('/'),
|
||||
ClientId = _currentProfile.ClientId,
|
||||
Name = _currentProfile.Name,
|
||||
};
|
||||
|
||||
var ctx = await _sessionManager.GetOrCreateContextAsync(siteProfile, ct);
|
||||
var siteResults = await _versionService.DeleteOldVersionsAsync(ctx, options, progress, ct);
|
||||
|
||||
if (Application.Current?.Dispatcher is { } dispatcher)
|
||||
{
|
||||
await dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
foreach (var r in siteResults) Results.Add(r);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var r in siteResults) Results.Add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ExportCsvAsync()
|
||||
{
|
||||
if (Results.Count == 0) return;
|
||||
var dialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Export version cleanup results",
|
||||
Filter = "CSV files (*.csv)|*.csv",
|
||||
DefaultExt = "csv",
|
||||
FileName = "version_cleanup",
|
||||
};
|
||||
if (dialog.ShowDialog() != true) return;
|
||||
try
|
||||
{
|
||||
using var w = new System.IO.StreamWriter(dialog.FileName);
|
||||
await w.WriteLineAsync("Site,Library,File,Versions Before,Versions Deleted,Versions Remaining,Bytes Freed,Error");
|
||||
foreach (var r in Results)
|
||||
{
|
||||
await w.WriteLineAsync(string.Join(",",
|
||||
Csv(r.SiteUrl),
|
||||
Csv(r.Library),
|
||||
Csv(r.FileServerRelativeUrl),
|
||||
r.VersionsBefore,
|
||||
r.VersionsDeleted,
|
||||
r.VersionsRemaining,
|
||||
r.BytesFreed,
|
||||
Csv(r.Error ?? string.Empty)));
|
||||
}
|
||||
try { Process.Start(new ProcessStartInfo(dialog.FileName) { UseShellExecute = true }); }
|
||||
catch { }
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StatusMessage = $"Export failed: {ex.Message}";
|
||||
_logger.LogError(ex, "Version cleanup CSV export failed.");
|
||||
}
|
||||
}
|
||||
|
||||
private static string Csv(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value)) return string.Empty;
|
||||
if (value.Contains(',') || value.Contains('"') || value.Contains('\n'))
|
||||
return "\"" + value.Replace("\"", "\"\"") + "\"";
|
||||
return value;
|
||||
}
|
||||
|
||||
partial void OnKeepLastChanged(int value)
|
||||
{
|
||||
if (value < 0) KeepLast = 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user