238 lines
8.4 KiB
C#
238 lines
8.4 KiB
C#
|
|
using System;
|
||
|
|
using System.Collections.ObjectModel;
|
||
|
|
using System.Linq;
|
||
|
|
using System.Threading.Tasks;
|
||
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||
|
|
using CommunityToolkit.Mvvm.Input;
|
||
|
|
using Serilog;
|
||
|
|
using Wino.Core.Domain;
|
||
|
|
using Wino.Core.Domain.Interfaces;
|
||
|
|
using Wino.Core.Domain.Models.Navigation;
|
||
|
|
using Wino.Core.Extensions;
|
||
|
|
using Wino.Mail.ViewModels.Data;
|
||
|
|
|
||
|
|
namespace Wino.Mail.ViewModels;
|
||
|
|
|
||
|
|
public partial class StoragePageViewModel(
|
||
|
|
IAccountService accountService,
|
||
|
|
IMimeStorageService mimeStorageService,
|
||
|
|
IMailDialogService dialogService) : MailBaseViewModel
|
||
|
|
{
|
||
|
|
private readonly ILogger _logger = Log.ForContext<StoragePageViewModel>();
|
||
|
|
private readonly IAccountService _accountService = accountService;
|
||
|
|
private readonly IMimeStorageService _mimeStorageService = mimeStorageService;
|
||
|
|
private readonly IMailDialogService _dialogService = dialogService;
|
||
|
|
|
||
|
|
public ObservableCollection<AccountStorageItemViewModel> AccountStorageItems { get; } = [];
|
||
|
|
|
||
|
|
[ObservableProperty]
|
||
|
|
[NotifyPropertyChangedFor(nameof(IsBusy))]
|
||
|
|
public partial bool IsLoading { get; set; }
|
||
|
|
|
||
|
|
[ObservableProperty]
|
||
|
|
[NotifyPropertyChangedFor(nameof(IsBusy))]
|
||
|
|
public partial bool IsCleaning { get; set; }
|
||
|
|
|
||
|
|
[ObservableProperty]
|
||
|
|
public partial string MimeRootPath { get; set; } = string.Empty;
|
||
|
|
|
||
|
|
[ObservableProperty]
|
||
|
|
public partial string SummaryText { get; set; } = "";
|
||
|
|
|
||
|
|
public bool IsBusy => IsLoading || IsCleaning;
|
||
|
|
|
||
|
|
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||
|
|
{
|
||
|
|
base.OnNavigatedTo(mode, parameters);
|
||
|
|
await ExecuteUIThread(() => { SummaryText = Translator.SettingsStorage_NoLocalMimeDataFound; });
|
||
|
|
|
||
|
|
await RefreshStorageAsync();
|
||
|
|
}
|
||
|
|
|
||
|
|
partial void OnIsLoadingChanged(bool value)
|
||
|
|
{
|
||
|
|
UpdateAccountBusyState();
|
||
|
|
}
|
||
|
|
|
||
|
|
partial void OnIsCleaningChanged(bool value)
|
||
|
|
{
|
||
|
|
UpdateAccountBusyState();
|
||
|
|
}
|
||
|
|
|
||
|
|
private void UpdateAccountBusyState()
|
||
|
|
{
|
||
|
|
Dispatcher.ExecuteOnUIThread(() =>
|
||
|
|
{
|
||
|
|
foreach (var item in AccountStorageItems)
|
||
|
|
{
|
||
|
|
item.IsBusy = IsBusy;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
[RelayCommand]
|
||
|
|
private async Task RefreshStorageAsync()
|
||
|
|
{
|
||
|
|
if (IsBusy) return;
|
||
|
|
|
||
|
|
await ExecuteUIThread(() => { IsLoading = true; });
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var mimeRootPath = await _mimeStorageService.GetMimeRootPathAsync().ConfigureAwait(false);
|
||
|
|
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||
|
|
var sizeMap = await _mimeStorageService.GetAccountsMimeStorageSizesAsync(accounts.Select(a => a.Id)).ConfigureAwait(false);
|
||
|
|
|
||
|
|
var storageItems = accounts
|
||
|
|
.Select(account =>
|
||
|
|
{
|
||
|
|
sizeMap.TryGetValue(account.Id, out var accountSize);
|
||
|
|
var viewModel = new AccountStorageItemViewModel(account, accountSize, DeleteAllCommand, DeleteOlderThanOneMonthCommand, DeleteOlderThanThreeMonthsCommand, DeleteOlderThanSixMonthsCommand, DeleteOlderThanOneYearCommand);
|
||
|
|
viewModel.SizeDescription = string.Format(Translator.SettingsStorage_AccountUsageDescription, viewModel.SizeText);
|
||
|
|
return viewModel;
|
||
|
|
})
|
||
|
|
.OrderByDescending(a => a.SizeBytes)
|
||
|
|
.ToList();
|
||
|
|
|
||
|
|
await ExecuteUIThread(() =>
|
||
|
|
{
|
||
|
|
MimeRootPath = mimeRootPath;
|
||
|
|
AccountStorageItems.Clear();
|
||
|
|
|
||
|
|
foreach (var item in storageItems)
|
||
|
|
{
|
||
|
|
AccountStorageItems.Add(item);
|
||
|
|
}
|
||
|
|
|
||
|
|
var total = storageItems.Sum(a => a.SizeBytes);
|
||
|
|
SummaryText = storageItems.Count == 0
|
||
|
|
? Translator.SettingsStorage_NoAccountsFound
|
||
|
|
: string.Format(Translator.SettingsStorage_TotalUsage, total.GetBytesReadable());
|
||
|
|
});
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
_logger.Error(ex, "Failed to refresh storage data.");
|
||
|
|
await ExecuteUIThread(() =>
|
||
|
|
{
|
||
|
|
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, ex.Message, Core.Domain.Enums.InfoBarMessageType.Error);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
await ExecuteUIThread(() => { IsLoading = false; });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
[RelayCommand]
|
||
|
|
private async Task DeleteAllAsync(AccountStorageItemViewModel accountItem)
|
||
|
|
{
|
||
|
|
if (accountItem == null || IsBusy) return;
|
||
|
|
|
||
|
|
bool approved = await _dialogService.ShowConfirmationDialogAsync(
|
||
|
|
string.Format(Translator.SettingsStorage_DeleteAll_Confirm_Message, accountItem.AccountName),
|
||
|
|
Translator.SettingsStorage_DeleteAll_Confirm_Title,
|
||
|
|
Translator.Buttons_Delete);
|
||
|
|
|
||
|
|
if (!approved) return;
|
||
|
|
|
||
|
|
await ExecuteUIThread(() => { IsCleaning = true; });
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
await _mimeStorageService.DeleteAccountMimeStorageAsync(accountItem.Account.Id).ConfigureAwait(false);
|
||
|
|
await ExecuteUIThread(() =>
|
||
|
|
{
|
||
|
|
_dialogService.InfoBarMessage(Translator.GeneralTitle_Info, Translator.SettingsStorage_DeleteAll_Success, Core.Domain.Enums.InfoBarMessageType.Success);
|
||
|
|
});
|
||
|
|
await RefreshStorageAsync();
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
_logger.Error(ex, "Failed to delete all MIME content for account {AccountId}", accountItem.Account.Id);
|
||
|
|
await ExecuteUIThread(() =>
|
||
|
|
{
|
||
|
|
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, ex.Message, Core.Domain.Enums.InfoBarMessageType.Error);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
await ExecuteUIThread(() => { IsCleaning = false; });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
[RelayCommand]
|
||
|
|
private Task DeleteOlderThanOneMonthAsync(AccountStorageItemViewModel accountItem)
|
||
|
|
=> DeleteOlderThanAsync(accountItem, 1);
|
||
|
|
|
||
|
|
[RelayCommand]
|
||
|
|
private Task DeleteOlderThanThreeMonthsAsync(AccountStorageItemViewModel accountItem)
|
||
|
|
=> DeleteOlderThanAsync(accountItem, 3);
|
||
|
|
|
||
|
|
[RelayCommand]
|
||
|
|
private Task DeleteOlderThanSixMonthsAsync(AccountStorageItemViewModel accountItem)
|
||
|
|
=> DeleteOlderThanAsync(accountItem, 6);
|
||
|
|
|
||
|
|
[RelayCommand]
|
||
|
|
private Task DeleteOlderThanOneYearAsync(AccountStorageItemViewModel accountItem)
|
||
|
|
=> DeleteOlderThanAsync(accountItem, 12);
|
||
|
|
|
||
|
|
private async Task DeleteOlderThanAsync(AccountStorageItemViewModel accountItem, int months)
|
||
|
|
{
|
||
|
|
if (accountItem == null || IsBusy) return;
|
||
|
|
|
||
|
|
string rangeText = GetRangeText(months);
|
||
|
|
|
||
|
|
bool approved = await _dialogService.ShowConfirmationDialogAsync(
|
||
|
|
string.Format(Translator.SettingsStorage_DeleteOld_Confirm_Message, rangeText, accountItem.AccountName),
|
||
|
|
Translator.SettingsStorage_DeleteOld_Confirm_Title,
|
||
|
|
Translator.Buttons_Delete);
|
||
|
|
|
||
|
|
if (!approved) return;
|
||
|
|
|
||
|
|
await ExecuteUIThread(() => { IsCleaning = true; });
|
||
|
|
|
||
|
|
try
|
||
|
|
{
|
||
|
|
var cutoffDateUtc = DateTime.UtcNow.AddMonths(-months);
|
||
|
|
var deletedDirectoryCount = await _mimeStorageService
|
||
|
|
.DeleteAccountMimeStorageOlderThanAsync(accountItem.Account.Id, cutoffDateUtc)
|
||
|
|
.ConfigureAwait(false);
|
||
|
|
|
||
|
|
await ExecuteUIThread(() =>
|
||
|
|
{
|
||
|
|
_dialogService.InfoBarMessage(
|
||
|
|
Translator.GeneralTitle_Info,
|
||
|
|
string.Format(Translator.SettingsStorage_DeleteOld_Success, deletedDirectoryCount, rangeText),
|
||
|
|
Core.Domain.Enums.InfoBarMessageType.Success);
|
||
|
|
});
|
||
|
|
|
||
|
|
await RefreshStorageAsync();
|
||
|
|
}
|
||
|
|
catch (Exception ex)
|
||
|
|
{
|
||
|
|
_logger.Error(ex, "Failed to delete MIME content by cutoff for account {AccountId}", accountItem.Account.Id);
|
||
|
|
await ExecuteUIThread(() =>
|
||
|
|
{
|
||
|
|
_dialogService.InfoBarMessage(Translator.GeneralTitle_Error, ex.Message, Core.Domain.Enums.InfoBarMessageType.Error);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
await ExecuteUIThread(() => { IsCleaning = false; });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static string GetRangeText(int months)
|
||
|
|
{
|
||
|
|
return months switch
|
||
|
|
{
|
||
|
|
1 => Translator.SettingsStorage_1Month,
|
||
|
|
3 => Translator.SettingsStorage_3Months,
|
||
|
|
6 => Translator.SettingsStorage_6Months,
|
||
|
|
12 => Translator.SettingsStorage_1Year,
|
||
|
|
_ => string.Format(Translator.SettingsStorage_Months, months)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
}
|