Add local JSON account import and export
This commit is contained in:
@@ -7,5 +7,7 @@ namespace Wino.Core.Domain.Interfaces;
|
|||||||
public interface IWinoAccountDataSyncService
|
public interface IWinoAccountDataSyncService
|
||||||
{
|
{
|
||||||
Task<WinoAccountSyncExportResult> ExportAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default);
|
Task<WinoAccountSyncExportResult> ExportAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default);
|
||||||
|
Task<WinoAccountSyncFileExportResult> ExportToJsonAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default);
|
||||||
Task<WinoAccountSyncImportResult> ImportAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default);
|
Task<WinoAccountSyncImportResult> ImportAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default);
|
||||||
|
Task<WinoAccountSyncImportResult> ImportFromJsonAsync(string jsonContent, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Wino.Core.Domain.Models.Accounts;
|
||||||
|
|
||||||
|
public sealed class WinoAccountSyncFileExportResult
|
||||||
|
{
|
||||||
|
public string JsonContent { get; init; } = string.Empty;
|
||||||
|
public WinoAccountSyncExportResult ExportResult { get; init; } = new();
|
||||||
|
}
|
||||||
@@ -1339,9 +1339,10 @@
|
|||||||
"WelcomeWindow_GetStartedButton": "Get started by adding an account",
|
"WelcomeWindow_GetStartedButton": "Get started by adding an account",
|
||||||
"WelcomeWindow_GetStartedDescription": "Add your Outlook, Gmail, or IMAP account to get started with Wino Mail.",
|
"WelcomeWindow_GetStartedDescription": "Add your Outlook, Gmail, or IMAP account to get started with Wino Mail.",
|
||||||
"WelcomeWindow_ImportFromWinoAccount": "Import from your Wino Account",
|
"WelcomeWindow_ImportFromWinoAccount": "Import from your Wino Account",
|
||||||
"WelcomeWindow_ImportInProgress": "Importing your synchronized preferences and accounts...",
|
"WelcomeWindow_ImportFromJsonFile": "Import from a JSON file",
|
||||||
"WelcomeWindow_ImportNoAccountsFound": "No synced accounts were found in your Wino Account. If preferences were available, they were restored. Use Get started to add an account manually.",
|
"WelcomeWindow_ImportInProgress": "Importing preferences and accounts...",
|
||||||
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} synced accounts are already available on this device. Use Get started to add another account manually if needed.",
|
"WelcomeWindow_ImportNoAccountsFound": "No accounts were found to import. If preferences were available, they were restored. Use Get started to add an account manually.",
|
||||||
|
"WelcomeWindow_ImportDuplicateAccountsSkipped": "{0} imported accounts are already available on this device. Use Get started to add another account manually if needed.",
|
||||||
"WelcomeWindow_SetupTitle": "Set up your account",
|
"WelcomeWindow_SetupTitle": "Set up your account",
|
||||||
"WelcomeWindow_SetupSubtitle": "Choose your email provider to get started",
|
"WelcomeWindow_SetupSubtitle": "Choose your email provider to get started",
|
||||||
"WelcomeWindow_AddAccountButton": "Add account",
|
"WelcomeWindow_AddAccountButton": "Add account",
|
||||||
@@ -1400,13 +1401,13 @@
|
|||||||
"WinoAccount_Management_StatusLabel": "Status: {0}",
|
"WinoAccount_Management_StatusLabel": "Status: {0}",
|
||||||
"WinoAccount_Management_NoRemoteSettings": "There is no synchronized data stored for this account yet.",
|
"WinoAccount_Management_NoRemoteSettings": "There is no synchronized data stored for this account yet.",
|
||||||
"WinoAccount_Management_ExportSucceeded": "Your selected Wino data was exported successfully.",
|
"WinoAccount_Management_ExportSucceeded": "Your selected Wino data was exported successfully.",
|
||||||
"WinoAccount_Management_ExportPreferencesSucceeded": "Your preferences were exported to your Wino Account.",
|
"WinoAccount_Management_ExportPreferencesSucceeded": "Your preferences were exported.",
|
||||||
"WinoAccount_Management_ExportAccountsSucceeded": "Exported {0} account details to your Wino Account.",
|
"WinoAccount_Management_ExportAccountsSucceeded": "Exported {0} account details.",
|
||||||
"WinoAccount_Management_ImportSucceeded": "Imported synchronized data from your Wino Account.",
|
"WinoAccount_Management_ImportSucceeded": "Imported synchronized data from your Wino Account.",
|
||||||
"WinoAccount_Management_ImportPreferencesSucceeded": "Applied {0} synchronized preferences.",
|
"WinoAccount_Management_ImportPreferencesSucceeded": "Applied {0} preferences.",
|
||||||
"WinoAccount_Management_ImportAccountsSucceeded": "Imported {0} accounts.",
|
"WinoAccount_Management_ImportAccountsSucceeded": "Imported {0} accounts.",
|
||||||
"WinoAccount_Management_ImportDuplicateAccountsSkipped": "Skipped {0} accounts that already exist on this device.",
|
"WinoAccount_Management_ImportDuplicateAccountsSkipped": "Skipped {0} accounts that already exist on this device.",
|
||||||
"WinoAccount_Management_ImportPartial": "Applied {0} synchronized preferences. {1} preferences could not be restored.",
|
"WinoAccount_Management_ImportPartial": "Applied {0} preferences. {1} preferences could not be restored.",
|
||||||
"WinoAccount_Management_ImportReloginReminder": "Passwords, tokens, and other sensitive information were not imported. Sign in again for each account on this device before using it.",
|
"WinoAccount_Management_ImportReloginReminder": "Passwords, tokens, and other sensitive information were not imported. Sign in again for each account on this device before using it.",
|
||||||
"WinoAccount_Management_SerializeFailed": "Wino could not serialize your current preferences.",
|
"WinoAccount_Management_SerializeFailed": "Wino could not serialize your current preferences.",
|
||||||
"WinoAccount_Management_EmptyExport": "There are no preference values to export.",
|
"WinoAccount_Management_EmptyExport": "There are no preference values to export.",
|
||||||
@@ -1418,6 +1419,12 @@
|
|||||||
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Passwords, tokens, and other sensitive information are not synced.",
|
"WinoAccount_Management_ExportDialog_AccountsDisclaimer": "Passwords, tokens, and other sensitive information are not synced.",
|
||||||
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Imported accounts on another PC will still need you to sign in again before they can be used.",
|
"WinoAccount_Management_ExportDialog_AccountsRelogin": "Imported accounts on another PC will still need you to sign in again before they can be used.",
|
||||||
"WinoAccount_Management_ExportDialog_InProgress": "Exporting your selected Wino data...",
|
"WinoAccount_Management_ExportDialog_InProgress": "Exporting your selected Wino data...",
|
||||||
|
"WinoAccount_Management_LocalDataSectionTitle": "Transfer with a JSON file",
|
||||||
|
"WinoAccount_Management_LocalDataSectionDescription": "Import from or export to a local JSON file. Passwords, tokens, and other sensitive information are not included.",
|
||||||
|
"WinoAccount_Management_LocalDataImportAction": "Import JSON",
|
||||||
|
"WinoAccount_Management_LocalDataExportAction": "Export JSON",
|
||||||
|
"WinoAccount_Management_LocalDataSaved": "Saved your exported Wino data to {0}.",
|
||||||
|
"WinoAccount_Management_LocalDataInvalidFile": "The selected JSON file doesn't contain a valid Wino export.",
|
||||||
"WinoAccount_Management_LoadFailed": "Wino could not load the latest Wino Account information.",
|
"WinoAccount_Management_LoadFailed": "Wino could not load the latest Wino Account information.",
|
||||||
"WinoAccount_Management_ActionFailed": "The Wino Account request could not be completed.",
|
"WinoAccount_Management_ActionFailed": "The Wino Account request could not be completed.",
|
||||||
"WinoAccount_SettingsSection_Title": "Wino Account",
|
"WinoAccount_SettingsSection_Title": "Wino Account",
|
||||||
|
|||||||
@@ -1,8 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
@@ -26,6 +31,10 @@ namespace Wino.Mail.ViewModels;
|
|||||||
|
|
||||||
public partial class AccountManagementViewModel : AccountManagementPageViewModelBase
|
public partial class AccountManagementViewModel : AccountManagementPageViewModelBase
|
||||||
{
|
{
|
||||||
|
private const string LocalExportFileName = "wino-data-export.json";
|
||||||
|
private static readonly UTF8Encoding Utf8WithoutBom = new(false);
|
||||||
|
|
||||||
|
private readonly IWinoAccountDataSyncService _syncService;
|
||||||
private readonly IWinoLogger _winoLogger;
|
private readonly IWinoLogger _winoLogger;
|
||||||
private readonly ISpecialImapProviderConfigResolver _specialImapProviderConfigResolver;
|
private readonly ISpecialImapProviderConfigResolver _specialImapProviderConfigResolver;
|
||||||
private readonly ICalDavClient _calDavClient;
|
private readonly ICalDavClient _calDavClient;
|
||||||
@@ -38,6 +47,7 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
IProviderService providerService,
|
IProviderService providerService,
|
||||||
IStoreManagementService storeManagementService,
|
IStoreManagementService storeManagementService,
|
||||||
IWinoAccountProfileService winoAccountProfileService,
|
IWinoAccountProfileService winoAccountProfileService,
|
||||||
|
IWinoAccountDataSyncService syncService,
|
||||||
IWinoLogger winoLogger,
|
IWinoLogger winoLogger,
|
||||||
ISpecialImapProviderConfigResolver specialImapProviderConfigResolver,
|
ISpecialImapProviderConfigResolver specialImapProviderConfigResolver,
|
||||||
ICalDavClient calDavClient,
|
ICalDavClient calDavClient,
|
||||||
@@ -45,11 +55,17 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
IPreferencesService preferencesService) : base(dialogService, navigationService, accountService, providerService, storeManagementService, winoAccountProfileService, authenticationProvider, preferencesService)
|
IPreferencesService preferencesService) : base(dialogService, navigationService, accountService, providerService, storeManagementService, winoAccountProfileService, authenticationProvider, preferencesService)
|
||||||
{
|
{
|
||||||
MailDialogService = dialogService;
|
MailDialogService = dialogService;
|
||||||
|
_syncService = syncService;
|
||||||
_winoLogger = winoLogger;
|
_winoLogger = winoLogger;
|
||||||
_specialImapProviderConfigResolver = specialImapProviderConfigResolver;
|
_specialImapProviderConfigResolver = specialImapProviderConfigResolver;
|
||||||
_calDavClient = calDavClient;
|
_calDavClient = calDavClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ObservableProperty]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(ExportLocalDataCommand))]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(ImportLocalDataCommand))]
|
||||||
|
public partial bool IsDataTransferInProgress { get; set; }
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task CreateMergedAccountAsync()
|
private async Task CreateMergedAccountAsync()
|
||||||
{
|
{
|
||||||
@@ -208,6 +224,95 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
[RelayCommand(CanExecute = nameof(CanReorderAccounts))]
|
[RelayCommand(CanExecute = nameof(CanReorderAccounts))]
|
||||||
private Task ReorderAccountsAsync() => MailDialogService.ShowAccountReorderDialogAsync(availableAccounts: Accounts);
|
private Task ReorderAccountsAsync() => MailDialogService.ShowAccountReorderDialogAsync(availableAccounts: Accounts);
|
||||||
|
|
||||||
|
[RelayCommand(CanExecute = nameof(CanTransferLocalData))]
|
||||||
|
private async Task ExportLocalDataAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var exportPath = await ExecuteUIThreadTaskAsync(
|
||||||
|
() => MailDialogService.PickFilePathAsync(LocalExportFileName))
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(exportPath))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ExecuteUIThread(() => IsDataTransferInProgress = true);
|
||||||
|
|
||||||
|
var exportResult = await _syncService.ExportToJsonAsync(new()).ConfigureAwait(false);
|
||||||
|
await File.WriteAllTextAsync(exportPath, exportResult.JsonContent, Utf8WithoutBom).ConfigureAwait(false);
|
||||||
|
|
||||||
|
DialogService.InfoBarMessage(
|
||||||
|
Translator.GeneralTitle_Info,
|
||||||
|
$"{BuildExportSuccessMessage(exportResult.ExportResult)} {string.Format(Translator.WinoAccount_Management_LocalDataSaved, exportPath)}",
|
||||||
|
InfoBarMessageType.Success);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
DialogService.InfoBarMessage(
|
||||||
|
Translator.GeneralTitle_Error,
|
||||||
|
ex.Message,
|
||||||
|
InfoBarMessageType.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await ExecuteUIThread(() => IsDataTransferInProgress = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand(CanExecute = nameof(CanTransferLocalData))]
|
||||||
|
private async Task ImportLocalDataAsync()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fileContent = await ExecuteUIThreadTaskAsync(
|
||||||
|
() => MailDialogService.PickWindowsFileContentAsync(".json"))
|
||||||
|
.ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (fileContent.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ExecuteUIThread(() => IsDataTransferInProgress = true);
|
||||||
|
|
||||||
|
var jsonContent = Encoding.UTF8.GetString(fileContent);
|
||||||
|
var result = await _syncService.ImportFromJsonAsync(jsonContent).ConfigureAwait(false);
|
||||||
|
|
||||||
|
await InitializeAccountsAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var messageType = result.FailedPreferenceCount > 0
|
||||||
|
? InfoBarMessageType.Warning
|
||||||
|
: InfoBarMessageType.Success;
|
||||||
|
|
||||||
|
DialogService.InfoBarMessage(
|
||||||
|
result.FailedPreferenceCount > 0 ? Translator.GeneralTitle_Warning : Translator.GeneralTitle_Info,
|
||||||
|
BuildImportMessage(result),
|
||||||
|
messageType);
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
DialogService.InfoBarMessage(
|
||||||
|
Translator.GeneralTitle_Error,
|
||||||
|
Translator.WinoAccount_Management_LocalDataInvalidFile,
|
||||||
|
InfoBarMessageType.Error);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
DialogService.InfoBarMessage(
|
||||||
|
Translator.GeneralTitle_Error,
|
||||||
|
ex.Message,
|
||||||
|
InfoBarMessageType.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await ExecuteUIThread(() => IsDataTransferInProgress = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanTransferLocalData() => !IsDataTransferInProgress;
|
||||||
|
|
||||||
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
|
public override void OnNavigatedFrom(NavigationMode mode, object parameters)
|
||||||
{
|
{
|
||||||
base.OnNavigatedFrom(mode, parameters);
|
base.OnNavigatedFrom(mode, parameters);
|
||||||
@@ -294,4 +399,60 @@ public partial class AccountManagementViewModel : AccountManagementPageViewModel
|
|||||||
|
|
||||||
await ManageStorePurchasesAsync().ConfigureAwait(false);
|
await ManageStorePurchasesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string BuildExportSuccessMessage(Wino.Core.Domain.Models.Accounts.WinoAccountSyncExportResult result)
|
||||||
|
{
|
||||||
|
var parts = new Collection<string>();
|
||||||
|
|
||||||
|
if (result.IncludedPreferences)
|
||||||
|
{
|
||||||
|
parts.Add(Translator.WinoAccount_Management_ExportPreferencesSucceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.IncludedAccounts)
|
||||||
|
{
|
||||||
|
parts.Add(string.Format(Translator.WinoAccount_Management_ExportAccountsSucceeded, result.ExportedMailboxCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parts.Count == 0)
|
||||||
|
{
|
||||||
|
parts.Add(Translator.WinoAccount_Management_ExportSucceeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(" ", parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string BuildImportMessage(Wino.Core.Domain.Models.Accounts.WinoAccountSyncImportResult result)
|
||||||
|
{
|
||||||
|
var parts = new Collection<string>();
|
||||||
|
|
||||||
|
if (result.HadRemotePreferences)
|
||||||
|
{
|
||||||
|
parts.Add(result.FailedPreferenceCount > 0
|
||||||
|
? string.Format(Translator.WinoAccount_Management_ImportPartial, result.AppliedPreferenceCount, result.FailedPreferenceCount)
|
||||||
|
: string.Format(Translator.WinoAccount_Management_ImportPreferencesSucceeded, result.AppliedPreferenceCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.ImportedMailboxCount > 0)
|
||||||
|
{
|
||||||
|
parts.Add(string.Format(Translator.WinoAccount_Management_ImportAccountsSucceeded, result.ImportedMailboxCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.SkippedDuplicateMailboxCount > 0)
|
||||||
|
{
|
||||||
|
parts.Add(string.Format(Translator.WinoAccount_Management_ImportDuplicateAccountsSkipped, result.SkippedDuplicateMailboxCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parts.Count == 0)
|
||||||
|
{
|
||||||
|
parts.Add(Translator.WinoAccount_Management_ImportEmpty);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.ImportedMailboxCount > 0)
|
||||||
|
{
|
||||||
|
parts.Add(Translator.WinoAccount_Management_ImportReloginReminder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Join(" ", parts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
@@ -27,6 +30,7 @@ public partial class WelcomePageV2ViewModel : MailBaseViewModel
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[NotifyCanExecuteChangedFor(nameof(GetStartedCommand))]
|
[NotifyCanExecuteChangedFor(nameof(GetStartedCommand))]
|
||||||
[NotifyCanExecuteChangedFor(nameof(ImportFromWinoAccountCommand))]
|
[NotifyCanExecuteChangedFor(nameof(ImportFromWinoAccountCommand))]
|
||||||
|
[NotifyCanExecuteChangedFor(nameof(ImportFromJsonCommand))]
|
||||||
public partial bool IsImportInProgress { get; set; }
|
public partial bool IsImportInProgress { get; set; }
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
@@ -101,6 +105,49 @@ public partial class WelcomePageV2ViewModel : MailBaseViewModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[RelayCommand(CanExecute = nameof(CanOpenWelcomeActions))]
|
||||||
|
private async Task ImportFromJsonAsync()
|
||||||
|
{
|
||||||
|
await ExecuteUIThread(() => ImportStatusMessage = string.Empty);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var fileContent = await _dialogService.PickWindowsFileContentAsync(".json");
|
||||||
|
if (fileContent.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ExecuteUIThread(() => IsImportInProgress = true);
|
||||||
|
|
||||||
|
var jsonContent = Encoding.UTF8.GetString(fileContent);
|
||||||
|
var result = await _syncService.ImportFromJsonAsync(jsonContent);
|
||||||
|
if (result.ImportedMailboxCount > 0)
|
||||||
|
{
|
||||||
|
ReportUIChange(new WelcomeImportCompletedMessage(result.ImportedMailboxCount));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ExecuteUIThread(() => ImportStatusMessage = BuildInlineImportMessage(result));
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
Debug.WriteLine(ex.Message);
|
||||||
|
await _dialogService.ShowMessageAsync(
|
||||||
|
Translator.WinoAccount_Management_LocalDataInvalidFile,
|
||||||
|
Translator.GeneralTitle_Error,
|
||||||
|
WinoCustomMessageDialogIcon.Error);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
await _dialogService.ShowMessageAsync(ex.Message, Translator.GeneralTitle_Error, WinoCustomMessageDialogIcon.Error);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
await ExecuteUIThread(() => IsImportInProgress = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool CanOpenWelcomeActions() => !IsImportInProgress;
|
private bool CanOpenWelcomeActions() => !IsImportInProgress;
|
||||||
|
|
||||||
private static string BuildInlineImportMessage(WinoAccountSyncImportResult result)
|
private static string BuildInlineImportMessage(WinoAccountSyncImportResult result)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<Identity
|
<Identity
|
||||||
Name="58272BurakKSE.WinoMailPreview"
|
Name="58272BurakKSE.WinoMailPreview"
|
||||||
Publisher="CN=bkaan"
|
Publisher="CN=bkaan"
|
||||||
Version="2.0.3.0" />
|
Version="2.0.4.0" />
|
||||||
|
|
||||||
<mp:PhoneIdentity PhoneProductId="3879fcfb-a561-4599-9103-e0c9b35a271f" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
<mp:PhoneIdentity PhoneProductId="3879fcfb-a561-4599-9103-e0c9b35a271f" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Wino.Core.ViewModels;
|
using Wino.Mail.ViewModels;
|
||||||
|
|
||||||
namespace Wino.Mail.WinUI.Views.Abstract;
|
namespace Wino.Mail.WinUI.Views.Abstract;
|
||||||
|
|
||||||
public abstract class ManageAccountsPageAbstract : BasePage<ManageAccountsPagePageViewModel>
|
public abstract class ManageAccountsPageAbstract : BasePage<AccountManagementViewModel>
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,6 +220,21 @@
|
|||||||
<SymbolIcon Symbol="Account" />
|
<SymbolIcon Symbol="Account" />
|
||||||
</winuiControls:SettingsCard.HeaderIcon>
|
</winuiControls:SettingsCard.HeaderIcon>
|
||||||
</winuiControls:SettingsCard>
|
</winuiControls:SettingsCard>
|
||||||
|
<winuiControls:SettingsCard
|
||||||
|
Description="{x:Bind domain:Translator.WinoAccount_Management_LocalDataSectionDescription}"
|
||||||
|
Header="{x:Bind domain:Translator.WinoAccount_Management_LocalDataSectionTitle}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||||
|
<Button
|
||||||
|
Command="{x:Bind ViewModel.ImportLocalDataCommand}"
|
||||||
|
Content="{x:Bind domain:Translator.WinoAccount_Management_LocalDataImportAction}" />
|
||||||
|
<Button
|
||||||
|
Command="{x:Bind ViewModel.ExportLocalDataCommand}"
|
||||||
|
Content="{x:Bind domain:Translator.WinoAccount_Management_LocalDataExportAction}" />
|
||||||
|
</StackPanel>
|
||||||
|
<winuiControls:SettingsCard.HeaderIcon>
|
||||||
|
<SymbolIcon Symbol="Sync" />
|
||||||
|
</winuiControls:SettingsCard.HeaderIcon>
|
||||||
|
</winuiControls:SettingsCard>
|
||||||
<winuiControls:SettingsCard
|
<winuiControls:SettingsCard
|
||||||
Command="{x:Bind ViewModel.CreateMergedAccountCommand}"
|
Command="{x:Bind ViewModel.CreateMergedAccountCommand}"
|
||||||
Description="{x:Bind domain:Translator.SettingsLinkAccounts_Description}"
|
Description="{x:Bind domain:Translator.SettingsLinkAccounts_Description}"
|
||||||
|
|||||||
@@ -162,12 +162,12 @@
|
|||||||
</AppBarButton>
|
</AppBarButton>
|
||||||
<AppBarToggleButton
|
<AppBarToggleButton
|
||||||
x:Name="ComposeAiActionsToggleButton"
|
x:Name="ComposeAiActionsToggleButton"
|
||||||
Checked="ComposeAiActionsToggleButton_Checked"
|
|
||||||
MinWidth="40"
|
MinWidth="40"
|
||||||
HorizontalContentAlignment="Center"
|
HorizontalContentAlignment="Center"
|
||||||
|
Checked="ComposeAiActionsToggleButton_Checked"
|
||||||
LabelPosition="Collapsed"
|
LabelPosition="Collapsed"
|
||||||
Visibility="{x:Bind GetAiActionsToggleVisibility(ViewModel.PreferencesService.IsAiActionsPanelHidden), Mode=OneWay}"
|
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_AiActions}"
|
||||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_AiActions}">
|
Visibility="{x:Bind GetAiActionsToggleVisibility(ViewModel.PreferencesService.IsAiActionsPanelHidden), Mode=OneWay}">
|
||||||
<AppBarToggleButton.Icon>
|
<AppBarToggleButton.Icon>
|
||||||
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
<FontIcon FontFamily="{StaticResource SymbolThemeFontFamily}" Glyph="" />
|
||||||
</AppBarToggleButton.Icon>
|
</AppBarToggleButton.Icon>
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:abstract="using:Wino.Mail.WinUI.Views.Abstract"
|
xmlns:abstract="using:Wino.Mail.WinUI.Views.Abstract"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:domain="using:Wino.Core.Domain"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:winuiControls="using:CommunityToolkit.WinUI.Controls"
|
||||||
Style="{StaticResource PageStyle}"
|
Style="{StaticResource PageStyle}"
|
||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
@@ -12,6 +14,22 @@
|
|||||||
<Grid
|
<Grid
|
||||||
MaxWidth="900"
|
MaxWidth="900"
|
||||||
Padding="20"
|
Padding="20"
|
||||||
HorizontalAlignment="Stretch" />
|
HorizontalAlignment="Stretch">
|
||||||
|
<winuiControls:SettingsCard
|
||||||
|
Description="{x:Bind domain:Translator.WinoAccount_Management_LocalDataSectionDescription}"
|
||||||
|
Header="{x:Bind domain:Translator.WinoAccount_Management_LocalDataSectionTitle}">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||||
|
<Button
|
||||||
|
Command="{x:Bind ViewModel.ImportLocalDataCommand}"
|
||||||
|
Content="{x:Bind domain:Translator.WinoAccount_Management_LocalDataImportAction}" />
|
||||||
|
<Button
|
||||||
|
Command="{x:Bind ViewModel.ExportLocalDataCommand}"
|
||||||
|
Content="{x:Bind domain:Translator.WinoAccount_Management_LocalDataExportAction}" />
|
||||||
|
</StackPanel>
|
||||||
|
<winuiControls:SettingsCard.HeaderIcon>
|
||||||
|
<SymbolIcon Symbol="Sync" />
|
||||||
|
</winuiControls:SettingsCard.HeaderIcon>
|
||||||
|
</winuiControls:SettingsCard>
|
||||||
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
</abstract:ManageAccountsPageAbstract>
|
</abstract:ManageAccountsPageAbstract>
|
||||||
|
|||||||
@@ -126,13 +126,17 @@
|
|||||||
<StackPanel
|
<StackPanel
|
||||||
Grid.Row="3"
|
Grid.Row="3"
|
||||||
MaxWidth="600"
|
MaxWidth="600"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center">
|
||||||
Spacing="8">
|
|
||||||
<HyperlinkButton
|
<HyperlinkButton
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Command="{x:Bind ViewModel.ImportFromWinoAccountCommand}"
|
Command="{x:Bind ViewModel.ImportFromWinoAccountCommand}"
|
||||||
Content="{x:Bind domain:Translator.WelcomeWindow_ImportFromWinoAccount}" />
|
Content="{x:Bind domain:Translator.WelcomeWindow_ImportFromWinoAccount}" />
|
||||||
|
|
||||||
|
<HyperlinkButton
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Command="{x:Bind ViewModel.ImportFromJsonCommand}"
|
||||||
|
Content="{x:Bind domain:Translator.WelcomeWindow_ImportFromJsonFile}" />
|
||||||
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
x:Name="ImportProgressPanel"
|
x:Name="ImportProgressPanel"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
@@ -157,6 +161,7 @@
|
|||||||
Visibility="{x:Bind ViewModel.HasImportStatus, Mode=OneWay}" />
|
Visibility="{x:Bind ViewModel.HasImportStatus, Mode=OneWay}" />
|
||||||
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
|
Margin="0,4"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
Style="{StaticResource BodyTextBlockStyle}"
|
Style="{StaticResource BodyTextBlockStyle}"
|
||||||
@@ -164,6 +169,7 @@
|
|||||||
|
|
||||||
<Button
|
<Button
|
||||||
MinWidth="240"
|
MinWidth="240"
|
||||||
|
Margin="0,12,0,0"
|
||||||
Padding="12,10"
|
Padding="12,10"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
Command="{x:Bind ViewModel.GetStartedCommand}"
|
Command="{x:Bind ViewModel.GetStartedCommand}"
|
||||||
|
|||||||
@@ -378,7 +378,6 @@
|
|||||||
<AppxBundle>Always</AppxBundle>
|
<AppxBundle>Always</AppxBundle>
|
||||||
<AppxBundlePlatforms>x86|x64|arm64</AppxBundlePlatforms>
|
<AppxBundlePlatforms>x86|x64|arm64</AppxBundlePlatforms>
|
||||||
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
|
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
|
||||||
<PackageCertificateThumbprint>58CE57FA3E5BF2393C86FC3B3161A925679FB3D4</PackageCertificateThumbprint>
|
|
||||||
<PackageCertificateKeyFile>Wino.Mail.WinUI_TemporaryKey.pfx</PackageCertificateKeyFile>
|
<PackageCertificateKeyFile>Wino.Mail.WinUI_TemporaryKey.pfx</PackageCertificateKeyFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
@@ -600,6 +601,7 @@ public sealed class WinoAccountApiClient : IWinoAccountApiClient, IDisposable
|
|||||||
[JsonSerializable(typeof(ApiEnvelope<UserMailboxSyncListDto>))]
|
[JsonSerializable(typeof(ApiEnvelope<UserMailboxSyncListDto>))]
|
||||||
[JsonSerializable(typeof(ApiEnvelope<JsonElement>))]
|
[JsonSerializable(typeof(ApiEnvelope<JsonElement>))]
|
||||||
[JsonSerializable(typeof(ReplaceUserMailboxesRequestDto))]
|
[JsonSerializable(typeof(ReplaceUserMailboxesRequestDto))]
|
||||||
|
[JsonSerializable(typeof(List<UserMailboxSyncItemDto>))]
|
||||||
internal sealed partial class WinoAccountApiJsonContext : JsonSerializerContext;
|
internal sealed partial class WinoAccountApiJsonContext : JsonSerializerContext;
|
||||||
|
|
||||||
internal sealed record SyncStoreEntitlementsRequest(string? StoreIdKey, string? PurchaseIdKey);
|
internal sealed record SyncStoreEntitlementsRequest(string? StoreIdKey, string? PurchaseIdKey);
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
@@ -18,6 +21,7 @@ namespace Wino.Services;
|
|||||||
public sealed class WinoAccountDataSyncService : IWinoAccountDataSyncService
|
public sealed class WinoAccountDataSyncService : IWinoAccountDataSyncService
|
||||||
{
|
{
|
||||||
private const int DefaultMaxConcurrentClients = 5;
|
private const int DefaultMaxConcurrentClients = 5;
|
||||||
|
private const int LocalExportVersion = 1;
|
||||||
|
|
||||||
private readonly IWinoAccountProfileService _profileService;
|
private readonly IWinoAccountProfileService _profileService;
|
||||||
private readonly IPreferencesService _preferencesService;
|
private readonly IPreferencesService _preferencesService;
|
||||||
@@ -35,37 +39,159 @@ public sealed class WinoAccountDataSyncService : IWinoAccountDataSyncService
|
|||||||
|
|
||||||
public async Task<WinoAccountSyncExportResult> ExportAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default)
|
public async Task<WinoAccountSyncExportResult> ExportAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var exportedMailboxCount = 0;
|
var preparedExport = await PrepareExportAsync(selection).ConfigureAwait(false);
|
||||||
|
|
||||||
if (selection.IncludePreferences)
|
if (selection.IncludePreferences && preparedExport.PreferencesJson != null)
|
||||||
{
|
{
|
||||||
await _profileService.SaveSettingsAsync(_preferencesService.ExportPreferences(), cancellationToken).ConfigureAwait(false);
|
await _profileService.SaveSettingsAsync(preparedExport.PreferencesJson, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selection.IncludeAccounts)
|
if (selection.IncludeAccounts)
|
||||||
{
|
{
|
||||||
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
|
||||||
var request = new ReplaceUserMailboxesRequestDto
|
var request = new ReplaceUserMailboxesRequestDto
|
||||||
{
|
{
|
||||||
Mailboxes = accounts
|
Mailboxes = preparedExport.Mailboxes
|
||||||
.OrderBy(a => a.Order)
|
|
||||||
.Select(MapMailbox)
|
|
||||||
.ToList()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await _profileService.ReplaceMailboxesAsync(request, cancellationToken).ConfigureAwait(false);
|
await _profileService.ReplaceMailboxesAsync(request, cancellationToken).ConfigureAwait(false);
|
||||||
exportedMailboxCount = request.Mailboxes.Count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new WinoAccountSyncExportResult
|
return preparedExport.ExportResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<WinoAccountSyncFileExportResult> ExportToJsonAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var preparedExport = await PrepareExportAsync(selection).ConfigureAwait(false);
|
||||||
|
|
||||||
|
using var stream = new MemoryStream();
|
||||||
|
using (var writer = new Utf8JsonWriter(stream, new JsonWriterOptions { Indented = true }))
|
||||||
{
|
{
|
||||||
IncludedPreferences = selection.IncludePreferences,
|
writer.WriteStartObject();
|
||||||
IncludedAccounts = selection.IncludeAccounts,
|
writer.WriteNumber("version", LocalExportVersion);
|
||||||
ExportedMailboxCount = exportedMailboxCount
|
writer.WriteString("exportedAtUtc", DateTime.UtcNow);
|
||||||
|
writer.WriteBoolean("includesPreferences", preparedExport.ExportResult.IncludedPreferences);
|
||||||
|
writer.WriteBoolean("includesAccounts", preparedExport.ExportResult.IncludedAccounts);
|
||||||
|
|
||||||
|
writer.WritePropertyName("preferences");
|
||||||
|
if (!string.IsNullOrWhiteSpace(preparedExport.PreferencesJson))
|
||||||
|
{
|
||||||
|
using var preferencesDocument = JsonDocument.Parse(preparedExport.PreferencesJson);
|
||||||
|
preferencesDocument.RootElement.WriteTo(writer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteNullValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.WritePropertyName("mailboxes");
|
||||||
|
JsonSerializer.Serialize(writer, preparedExport.Mailboxes, WinoAccountApiJsonContext.Default.ListUserMailboxSyncItemDto);
|
||||||
|
writer.WriteEndObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WinoAccountSyncFileExportResult
|
||||||
|
{
|
||||||
|
JsonContent = Encoding.UTF8.GetString(stream.ToArray()),
|
||||||
|
ExportResult = preparedExport.ExportResult
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<WinoAccountSyncImportResult> ImportAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default)
|
public async Task<WinoAccountSyncImportResult> ImportAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
string? settingsJson = null;
|
||||||
|
List<UserMailboxSyncItemDto> orderedMailboxes = [];
|
||||||
|
|
||||||
|
if (selection.IncludePreferences)
|
||||||
|
{
|
||||||
|
settingsJson = await _profileService.GetSettingsAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selection.IncludeAccounts)
|
||||||
|
{
|
||||||
|
var mailboxes = await _profileService.GetMailboxesAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
orderedMailboxes = mailboxes.Mailboxes
|
||||||
|
.OrderBy(a => a.SortOrder)
|
||||||
|
.ThenBy(a => a.Address, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return await ImportDataAsync(selection, settingsJson, orderedMailboxes, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<WinoAccountSyncImportResult> ImportFromJsonAsync(string jsonContent, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
jsonContent = TrimUtf8Bom(jsonContent);
|
||||||
|
|
||||||
|
using var document = JsonDocument.Parse(jsonContent);
|
||||||
|
if (document.RootElement.ValueKind != JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
throw new JsonException("Invalid root element.");
|
||||||
|
}
|
||||||
|
|
||||||
|
string? settingsJson = null;
|
||||||
|
if (document.RootElement.TryGetProperty("preferences", out var preferencesElement))
|
||||||
|
{
|
||||||
|
settingsJson = preferencesElement.ValueKind switch
|
||||||
|
{
|
||||||
|
JsonValueKind.Object => preferencesElement.GetRawText(),
|
||||||
|
JsonValueKind.String => preferencesElement.GetString(),
|
||||||
|
JsonValueKind.Null or JsonValueKind.Undefined => null,
|
||||||
|
_ => throw new JsonException("Invalid preferences payload.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var mailboxes = new List<UserMailboxSyncItemDto>();
|
||||||
|
if (document.RootElement.TryGetProperty("mailboxes", out var mailboxesElement))
|
||||||
|
{
|
||||||
|
if (mailboxesElement.ValueKind is not (JsonValueKind.Array or JsonValueKind.Null or JsonValueKind.Undefined))
|
||||||
|
{
|
||||||
|
throw new JsonException("Invalid mailboxes payload.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mailboxesElement.ValueKind == JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
mailboxes = JsonSerializer.Deserialize(mailboxesElement.GetRawText(), WinoAccountApiJsonContext.Default.ListUserMailboxSyncItemDto) ?? [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var selection = new WinoAccountSyncSelection(
|
||||||
|
IncludePreferences: !string.IsNullOrWhiteSpace(settingsJson),
|
||||||
|
IncludeAccounts: mailboxes.Count > 0);
|
||||||
|
|
||||||
|
return await ImportDataAsync(selection, settingsJson, mailboxes, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<PreparedSyncExport> PrepareExportAsync(WinoAccountSyncSelection selection)
|
||||||
|
{
|
||||||
|
var preferencesJson = selection.IncludePreferences
|
||||||
|
? _preferencesService.ExportPreferences()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
var mailboxes = selection.IncludeAccounts
|
||||||
|
? (await _accountService.GetAccountsAsync().ConfigureAwait(false))
|
||||||
|
.OrderBy(a => a.Order)
|
||||||
|
.Select(MapMailbox)
|
||||||
|
.ToList()
|
||||||
|
: [];
|
||||||
|
|
||||||
|
return new PreparedSyncExport(
|
||||||
|
preferencesJson,
|
||||||
|
mailboxes,
|
||||||
|
new WinoAccountSyncExportResult
|
||||||
|
{
|
||||||
|
IncludedPreferences = selection.IncludePreferences,
|
||||||
|
IncludedAccounts = selection.IncludeAccounts,
|
||||||
|
ExportedMailboxCount = mailboxes.Count
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<WinoAccountSyncImportResult> ImportDataAsync(
|
||||||
|
WinoAccountSyncSelection selection,
|
||||||
|
string? settingsJson,
|
||||||
|
List<UserMailboxSyncItemDto> mailboxes,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var result = new WinoAccountSyncImportResult
|
var result = new WinoAccountSyncImportResult
|
||||||
{
|
{
|
||||||
@@ -73,30 +199,25 @@ public sealed class WinoAccountDataSyncService : IWinoAccountDataSyncService
|
|||||||
IncludedAccounts = selection.IncludeAccounts
|
IncludedAccounts = selection.IncludeAccounts
|
||||||
};
|
};
|
||||||
|
|
||||||
if (selection.IncludePreferences)
|
if (selection.IncludePreferences && !string.IsNullOrWhiteSpace(settingsJson))
|
||||||
{
|
{
|
||||||
var settingsJson = await _profileService.GetSettingsAsync(cancellationToken).ConfigureAwait(false);
|
var (appliedCount, failedCount) = _preferencesService.ImportPreferences(settingsJson);
|
||||||
if (!string.IsNullOrWhiteSpace(settingsJson))
|
result = new WinoAccountSyncImportResult
|
||||||
{
|
{
|
||||||
var (appliedCount, failedCount) = _preferencesService.ImportPreferences(settingsJson);
|
IncludedPreferences = result.IncludedPreferences,
|
||||||
result = new WinoAccountSyncImportResult
|
IncludedAccounts = result.IncludedAccounts,
|
||||||
{
|
HadRemotePreferences = true,
|
||||||
IncludedPreferences = result.IncludedPreferences,
|
AppliedPreferenceCount = appliedCount,
|
||||||
IncludedAccounts = result.IncludedAccounts,
|
FailedPreferenceCount = failedCount,
|
||||||
HadRemotePreferences = true,
|
ImportedMailboxCount = result.ImportedMailboxCount,
|
||||||
AppliedPreferenceCount = appliedCount,
|
SkippedDuplicateMailboxCount = result.SkippedDuplicateMailboxCount,
|
||||||
FailedPreferenceCount = failedCount,
|
RemoteMailboxCount = result.RemoteMailboxCount
|
||||||
ImportedMailboxCount = result.ImportedMailboxCount,
|
};
|
||||||
SkippedDuplicateMailboxCount = result.SkippedDuplicateMailboxCount,
|
|
||||||
RemoteMailboxCount = result.RemoteMailboxCount
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selection.IncludeAccounts)
|
if (selection.IncludeAccounts)
|
||||||
{
|
{
|
||||||
var mailboxes = await _profileService.GetMailboxesAsync(cancellationToken).ConfigureAwait(false);
|
var orderedMailboxes = mailboxes
|
||||||
var orderedMailboxes = mailboxes.Mailboxes
|
|
||||||
.OrderBy(a => a.SortOrder)
|
.OrderBy(a => a.SortOrder)
|
||||||
.ThenBy(a => a.Address, StringComparer.OrdinalIgnoreCase)
|
.ThenBy(a => a.Address, StringComparer.OrdinalIgnoreCase)
|
||||||
.ToList();
|
.ToList();
|
||||||
@@ -288,4 +409,14 @@ public sealed class WinoAccountDataSyncService : IWinoAccountDataSyncService
|
|||||||
|
|
||||||
private static string CreateMailboxKey(string? address, int providerType)
|
private static string CreateMailboxKey(string? address, int providerType)
|
||||||
=> $"{address?.Trim().ToLowerInvariant()}|{providerType}";
|
=> $"{address?.Trim().ToLowerInvariant()}|{providerType}";
|
||||||
|
|
||||||
|
private static string TrimUtf8Bom(string jsonContent)
|
||||||
|
=> !string.IsNullOrEmpty(jsonContent) && jsonContent[0] == '\uFEFF'
|
||||||
|
? jsonContent[1..]
|
||||||
|
: jsonContent;
|
||||||
|
|
||||||
|
private sealed record PreparedSyncExport(
|
||||||
|
string? PreferencesJson,
|
||||||
|
List<UserMailboxSyncItemDto> Mailboxes,
|
||||||
|
WinoAccountSyncExportResult ExportResult);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user