Migration plan v1
This commit is contained in:
@@ -17,6 +17,7 @@ using Wino.Core.Domain.Entities.Shared;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Core.Domain.Models.Calendar;
|
||||
using Wino.Core.Domain.Models.Navigation;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
@@ -35,6 +36,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
||||
private static readonly UTF8Encoding Utf8WithoutBom = new(false);
|
||||
|
||||
private readonly IWinoAccountDataSyncService _syncService;
|
||||
private readonly ILegacyLocalMigrationService _legacyLocalMigrationService;
|
||||
private readonly IWinoLogger _winoLogger;
|
||||
private readonly ISpecialImapProviderConfigResolver _specialImapProviderConfigResolver;
|
||||
private readonly ICalDavClient _calDavClient;
|
||||
@@ -48,6 +50,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
||||
IStoreManagementService storeManagementService,
|
||||
IWinoAccountProfileService winoAccountProfileService,
|
||||
IWinoAccountDataSyncService syncService,
|
||||
ILegacyLocalMigrationService legacyLocalMigrationService,
|
||||
IWinoLogger winoLogger,
|
||||
ISpecialImapProviderConfigResolver specialImapProviderConfigResolver,
|
||||
ICalDavClient calDavClient,
|
||||
@@ -56,6 +59,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
||||
{
|
||||
MailDialogService = dialogService;
|
||||
_syncService = syncService;
|
||||
_legacyLocalMigrationService = legacyLocalMigrationService;
|
||||
_winoLogger = winoLogger;
|
||||
_specialImapProviderConfigResolver = specialImapProviderConfigResolver;
|
||||
_calDavClient = calDavClient;
|
||||
@@ -64,8 +68,26 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
||||
[ObservableProperty]
|
||||
[NotifyCanExecuteChangedFor(nameof(ExportLocalDataCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ImportLocalDataCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ImportLegacyDatabaseCommand))]
|
||||
public partial bool IsDataTransferInProgress { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(HasLegacyImportAvailable))]
|
||||
[NotifyPropertyChangedFor(nameof(HasLegacyImportWarnings))]
|
||||
[NotifyPropertyChangedFor(nameof(LegacyMigrationSummary))]
|
||||
[NotifyPropertyChangedFor(nameof(LegacyMigrationWarningSummary))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ImportLegacyDatabaseCommand))]
|
||||
public partial LegacyLocalMigrationPreview LegacyMigrationPreview { get; set; }
|
||||
|
||||
public bool HasLegacyImportAvailable => LegacyMigrationPreview?.HasImportableData == true;
|
||||
public bool HasLegacyImportWarnings => !string.IsNullOrWhiteSpace(LegacyMigrationWarningSummary);
|
||||
public string LegacyMigrationSummary => HasLegacyImportAvailable
|
||||
? LegacyLocalMigrationFormatter.BuildPreviewSummary(LegacyMigrationPreview)
|
||||
: string.Empty;
|
||||
public string LegacyMigrationWarningSummary => HasLegacyImportAvailable
|
||||
? LegacyLocalMigrationFormatter.BuildWarningSummary(LegacyMigrationPreview)
|
||||
: string.Empty;
|
||||
|
||||
[RelayCommand]
|
||||
private async Task CreateMergedAccountAsync()
|
||||
{
|
||||
@@ -314,7 +336,42 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanImportLegacyDatabase))]
|
||||
private async Task ImportLegacyDatabaseAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
await ExecuteUIThread(() => IsDataTransferInProgress = true);
|
||||
|
||||
var result = await _legacyLocalMigrationService.ImportAsync().ConfigureAwait(false);
|
||||
|
||||
await InitializeAccountsAsync().ConfigureAwait(false);
|
||||
await RefreshLegacyMigrationPreviewAsync().ConfigureAwait(false);
|
||||
|
||||
var messageType = result.FailedAccountCount > 0
|
||||
? InfoBarMessageType.Warning
|
||||
: InfoBarMessageType.Success;
|
||||
|
||||
DialogService.InfoBarMessage(
|
||||
result.FailedAccountCount > 0 ? Translator.GeneralTitle_Warning : Translator.GeneralTitle_Info,
|
||||
LegacyLocalMigrationFormatter.BuildImportMessage(result),
|
||||
messageType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
DialogService.InfoBarMessage(
|
||||
Translator.GeneralTitle_Error,
|
||||
ex.Message,
|
||||
InfoBarMessageType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
await ExecuteUIThread(() => IsDataTransferInProgress = false);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanTransferLocalData() => !IsDataTransferInProgress;
|
||||
private bool CanImportLegacyDatabase() => !IsDataTransferInProgress && HasLegacyImportAvailable;
|
||||
|
||||
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
|
||||
{
|
||||
@@ -350,6 +407,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
||||
Accounts.CollectionChanged += AccountCollectionChanged;
|
||||
|
||||
await InitializeAccountsAsync();
|
||||
await RefreshLegacyMigrationPreviewAsync();
|
||||
|
||||
PropertyChanged -= PagePropertyChanged;
|
||||
PropertyChanged += PagePropertyChanged;
|
||||
@@ -403,6 +461,19 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
||||
await ManageStorePurchasesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task RefreshLegacyMigrationPreviewAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var preview = await _legacyLocalMigrationService.DetectAsync().ConfigureAwait(false);
|
||||
await ExecuteUIThread(() => LegacyMigrationPreview = preview);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
await ExecuteUIThread(() => LegacyMigrationPreview = null);
|
||||
}
|
||||
}
|
||||
|
||||
private static string BuildExportSuccessMessage(Wino.Core.Domain.Models.Accounts.WinoAccountSyncExportResult result)
|
||||
{
|
||||
var parts = new Collection<string>();
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
|
||||
namespace Wino.Mail.ViewModels.Data;
|
||||
|
||||
internal static class LegacyLocalMigrationFormatter
|
||||
{
|
||||
public static string BuildPreviewSummary(LegacyLocalMigrationPreview preview)
|
||||
{
|
||||
if (!preview.HasImportableData)
|
||||
{
|
||||
return Translator.LegacyLocalMigration_ImportEmpty;
|
||||
}
|
||||
|
||||
var providerSummary = string.Join(", ", preview.ProviderCounts
|
||||
.Where(a => a.ImportableAccountCount > 0)
|
||||
.Select(a => $"{a.ImportableAccountCount} {GetProviderName(a.ProviderType)}"));
|
||||
|
||||
var parts = new List<string>
|
||||
{
|
||||
string.Format(Translator.LegacyLocalMigration_PreviewSummary, preview.ImportableAccountCount, providerSummary)
|
||||
};
|
||||
|
||||
if (preview.DuplicateAccountCount > 0)
|
||||
{
|
||||
parts.Add(string.Format(Translator.LegacyLocalMigration_PreviewDuplicateSummary, preview.DuplicateAccountCount));
|
||||
}
|
||||
|
||||
if (preview.ImportableMergedInboxCount > 0)
|
||||
{
|
||||
parts.Add(string.Format(Translator.LegacyLocalMigration_PreviewMergedSummary, preview.ImportableMergedInboxCount));
|
||||
}
|
||||
|
||||
return string.Join(" ", parts.Where(a => !string.IsNullOrWhiteSpace(a)));
|
||||
}
|
||||
|
||||
public static string BuildWarningSummary(LegacyLocalMigrationPreview preview)
|
||||
=> string.Join(Environment.NewLine, preview.Warnings.Where(a => !string.IsNullOrWhiteSpace(a)).Distinct(StringComparer.Ordinal));
|
||||
|
||||
public static string BuildImportMessage(LegacyLocalMigrationResult result)
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if (result.ImportedAccountCount > 0)
|
||||
{
|
||||
parts.Add(string.Format(Translator.LegacyLocalMigration_ImportAccountsSucceeded, result.ImportedAccountCount));
|
||||
}
|
||||
|
||||
if (result.SkippedDuplicateAccountCount > 0)
|
||||
{
|
||||
parts.Add(string.Format(Translator.WinoAccount_Management_ImportDuplicateAccountsSkipped, result.SkippedDuplicateAccountCount));
|
||||
}
|
||||
|
||||
if (result.ImportedMergedInboxCount > 0)
|
||||
{
|
||||
parts.Add(string.Format(Translator.LegacyLocalMigration_ImportMergedInboxesSucceeded, result.ImportedMergedInboxCount));
|
||||
}
|
||||
|
||||
if (result.SkippedMergedInboxCount > 0)
|
||||
{
|
||||
parts.Add(string.Format(Translator.LegacyLocalMigration_ImportMergedInboxesSkipped, result.SkippedMergedInboxCount));
|
||||
}
|
||||
|
||||
if (result.FailedAccountCount > 0)
|
||||
{
|
||||
parts.Add(string.Format(Translator.LegacyLocalMigration_ImportFailedAccounts, result.FailedAccountCount));
|
||||
}
|
||||
|
||||
if (parts.Count == 0)
|
||||
{
|
||||
parts.Add(Translator.LegacyLocalMigration_ImportEmpty);
|
||||
}
|
||||
|
||||
if (result.ImportedAccountCount > 0)
|
||||
{
|
||||
parts.Add(Translator.WinoAccount_Management_ImportReloginReminder);
|
||||
}
|
||||
|
||||
return string.Join(" ", parts);
|
||||
}
|
||||
|
||||
public static string BuildPromptMessage(LegacyLocalMigrationPreview preview)
|
||||
{
|
||||
var summary = BuildPreviewSummary(preview);
|
||||
var warnings = BuildWarningSummary(preview);
|
||||
|
||||
return string.IsNullOrWhiteSpace(warnings)
|
||||
? summary
|
||||
: $"{summary}{Environment.NewLine}{Environment.NewLine}{warnings}";
|
||||
}
|
||||
|
||||
private static string GetProviderName(MailProviderType providerType)
|
||||
{
|
||||
return providerType switch
|
||||
{
|
||||
MailProviderType.Outlook => Translator.LegacyLocalMigration_Provider_Outlook,
|
||||
MailProviderType.Gmail => Translator.LegacyLocalMigration_Provider_Gmail,
|
||||
MailProviderType.IMAP4 => Translator.LegacyLocalMigration_Provider_Imap,
|
||||
_ => providerType.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -90,10 +90,12 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
private readonly IWebView2RuntimeValidatorService _webView2RuntimeValidatorService;
|
||||
private readonly IStoreUpdateService _storeUpdateService;
|
||||
private readonly IShareActivationService _shareActivationService;
|
||||
private readonly ILegacyLocalMigrationService _legacyLocalMigrationService;
|
||||
|
||||
private readonly INativeAppService _nativeAppService;
|
||||
private readonly IMailService _mailService;
|
||||
private bool _hasRegisteredPersistentRecipients;
|
||||
private bool _hasHandledLegacyMigrationPrompt;
|
||||
private readonly SemaphoreSlim _menuRefreshSemaphore = new(1, 1);
|
||||
|
||||
private readonly SemaphoreSlim accountInitFolderUpdateSlim = new SemaphoreSlim(1);
|
||||
@@ -117,7 +119,8 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
IStartupBehaviorService startupBehaviorService,
|
||||
IWebView2RuntimeValidatorService webView2RuntimeValidatorService,
|
||||
IStoreUpdateService storeUpdateService,
|
||||
IShareActivationService shareActivationService)
|
||||
IShareActivationService shareActivationService,
|
||||
ILegacyLocalMigrationService legacyLocalMigrationService)
|
||||
{
|
||||
StatePersistenceService = statePersistanceService;
|
||||
|
||||
@@ -141,6 +144,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
_webView2RuntimeValidatorService = webView2RuntimeValidatorService;
|
||||
_storeUpdateService = storeUpdateService;
|
||||
_shareActivationService = shareActivationService;
|
||||
_legacyLocalMigrationService = legacyLocalMigrationService;
|
||||
}
|
||||
|
||||
protected override void OnDispatcherAssigned()
|
||||
@@ -286,6 +290,7 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
await ProcessLaunchOptionsAsync();
|
||||
await HandlePendingShareRequestAsync();
|
||||
await ValidateWebView2RuntimeAsync();
|
||||
await PromptLegacyMigrationIfNeededAsync(shouldRunStartupFlows);
|
||||
|
||||
if (shouldRunStartupFlows && !Debugger.IsAttached)
|
||||
{
|
||||
@@ -298,6 +303,53 @@ public partial class MailAppShellViewModel : MailBaseViewModel,
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PromptLegacyMigrationIfNeededAsync(bool shouldRunStartupFlows)
|
||||
{
|
||||
if (!shouldRunStartupFlows || _hasHandledLegacyMigrationPrompt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_hasHandledLegacyMigrationPrompt = true;
|
||||
|
||||
var currentAccounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||
if (!currentAccounts.Any())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var preview = await _legacyLocalMigrationService.DetectAsync().ConfigureAwait(false);
|
||||
if (!preview.ShouldPrompt || !preview.HasImportableData)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var shouldImport = await _dialogService.ShowConfirmationDialogAsync(
|
||||
LegacyLocalMigrationFormatter.BuildPromptMessage(preview),
|
||||
Translator.LegacyLocalMigration_PromptTitle,
|
||||
Translator.LegacyLocalMigration_ImportAction);
|
||||
|
||||
if (!shouldImport)
|
||||
{
|
||||
_legacyLocalMigrationService.MarkPromptDeferred();
|
||||
return;
|
||||
}
|
||||
|
||||
var result = await _legacyLocalMigrationService.ImportAsync().ConfigureAwait(false);
|
||||
|
||||
await RecreateMenuItemsAsync().ConfigureAwait(false);
|
||||
await RestoreSelectedAccountAfterMenuRefreshAsync(false).ConfigureAwait(false);
|
||||
|
||||
var messageType = result.FailedAccountCount > 0
|
||||
? InfoBarMessageType.Warning
|
||||
: InfoBarMessageType.Success;
|
||||
|
||||
_dialogService.InfoBarMessage(
|
||||
result.FailedAccountCount > 0 ? Translator.GeneralTitle_Warning : Translator.GeneralTitle_Info,
|
||||
LegacyLocalMigrationFormatter.BuildImportMessage(result),
|
||||
messageType);
|
||||
}
|
||||
|
||||
private async Task ValidateWebView2RuntimeAsync()
|
||||
{
|
||||
var isRuntimeAvailable = await _webView2RuntimeValidatorService.IsRuntimeAvailableAsync();
|
||||
|
||||
@@ -24,6 +24,7 @@ public partial class WelcomePageV2ViewModel : MailBaseViewModel
|
||||
private readonly IUpdateManager _updateManager;
|
||||
private readonly IMailDialogService _dialogService;
|
||||
private readonly IWinoAccountDataSyncService _syncService;
|
||||
private readonly ILegacyLocalMigrationService _legacyLocalMigrationService;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial List<UpdateNoteSection> UpdateSections { get; set; } = [];
|
||||
@@ -32,21 +33,40 @@ public partial class WelcomePageV2ViewModel : MailBaseViewModel
|
||||
[NotifyCanExecuteChangedFor(nameof(GetStartedCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ImportFromWinoAccountCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ImportFromJsonCommand))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ImportLegacyDatabaseCommand))]
|
||||
public partial bool IsImportInProgress { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(HasImportStatus))]
|
||||
public partial string ImportStatusMessage { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedFor(nameof(HasLegacyImportPreview))]
|
||||
[NotifyPropertyChangedFor(nameof(HasLegacyImportWarnings))]
|
||||
[NotifyPropertyChangedFor(nameof(LegacyImportSummary))]
|
||||
[NotifyPropertyChangedFor(nameof(LegacyImportWarnings))]
|
||||
[NotifyCanExecuteChangedFor(nameof(ImportLegacyDatabaseCommand))]
|
||||
public partial LegacyLocalMigrationPreview LegacyMigrationPreview { get; set; }
|
||||
|
||||
public bool HasImportStatus => !string.IsNullOrWhiteSpace(ImportStatusMessage);
|
||||
public bool HasLegacyImportPreview => LegacyMigrationPreview?.HasImportableData == true;
|
||||
public bool HasLegacyImportWarnings => !string.IsNullOrWhiteSpace(LegacyImportWarnings);
|
||||
public string LegacyImportSummary => HasLegacyImportPreview
|
||||
? LegacyLocalMigrationFormatter.BuildPreviewSummary(LegacyMigrationPreview)
|
||||
: string.Empty;
|
||||
public string LegacyImportWarnings => HasLegacyImportPreview
|
||||
? LegacyLocalMigrationFormatter.BuildWarningSummary(LegacyMigrationPreview)
|
||||
: string.Empty;
|
||||
|
||||
public WelcomePageV2ViewModel(IUpdateManager updateManager,
|
||||
IMailDialogService dialogService,
|
||||
IWinoAccountDataSyncService syncService)
|
||||
IWinoAccountDataSyncService syncService,
|
||||
ILegacyLocalMigrationService legacyLocalMigrationService)
|
||||
{
|
||||
_updateManager = updateManager;
|
||||
_dialogService = dialogService;
|
||||
_syncService = syncService;
|
||||
_legacyLocalMigrationService = legacyLocalMigrationService;
|
||||
}
|
||||
|
||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||
@@ -62,6 +82,15 @@ public partial class WelcomePageV2ViewModel : MailBaseViewModel
|
||||
{
|
||||
UpdateSections = [];
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
LegacyMigrationPreview = await _legacyLocalMigrationService.DetectAsync();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
LegacyMigrationPreview = null;
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanOpenWelcomeActions))]
|
||||
@@ -150,7 +179,52 @@ public partial class WelcomePageV2ViewModel : MailBaseViewModel
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand(CanExecute = nameof(CanImportLegacyDatabase))]
|
||||
private async Task ImportLegacyDatabaseAsync()
|
||||
{
|
||||
await ExecuteUIThread(() => ImportStatusMessage = string.Empty);
|
||||
|
||||
try
|
||||
{
|
||||
await ExecuteUIThread(() => IsImportInProgress = true);
|
||||
|
||||
var result = await _legacyLocalMigrationService.ImportAsync().ConfigureAwait(false);
|
||||
if (result.ImportedAccountCount > 0)
|
||||
{
|
||||
ReportUIChange(new WelcomeImportCompletedMessage(
|
||||
result.ImportedAccountCount,
|
||||
LegacyLocalMigrationFormatter.BuildImportMessage(result)));
|
||||
return;
|
||||
}
|
||||
|
||||
await ExecuteUIThread(() =>
|
||||
{
|
||||
ImportStatusMessage = LegacyLocalMigrationFormatter.BuildImportMessage(result);
|
||||
LegacyMigrationPreview = result.Preview;
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _dialogService.ShowMessageAsync(ex.Message, Translator.GeneralTitle_Error, WinoCustomMessageDialogIcon.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
var preview = await _legacyLocalMigrationService.DetectAsync().ConfigureAwait(false);
|
||||
await ExecuteUIThread(() => LegacyMigrationPreview = preview);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Keep the current preview if detection fails after import.
|
||||
}
|
||||
|
||||
await ExecuteUIThread(() => IsImportInProgress = false);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanOpenWelcomeActions() => !IsImportInProgress;
|
||||
private bool CanImportLegacyDatabase() => !IsImportInProgress && HasLegacyImportPreview;
|
||||
|
||||
private static string BuildInlineImportMessage(WinoAccountSyncImportResult result)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user