2026-04-04 20:23:20 +02:00
|
|
|
#nullable enable
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2026-04-20 19:38:30 +02:00
|
|
|
using System.Diagnostics.CodeAnalysis;
|
2026-04-18 15:55:15 +02:00
|
|
|
using System.IO;
|
2026-04-04 20:23:20 +02:00
|
|
|
using System.Linq;
|
2026-04-18 15:55:15 +02:00
|
|
|
using System.Text;
|
|
|
|
|
using System.Text.Json;
|
2026-04-04 20:23:20 +02:00
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using CommunityToolkit.Mvvm.Messaging;
|
|
|
|
|
using Wino.Core.Domain.Entities.Shared;
|
|
|
|
|
using Wino.Core.Domain.Enums;
|
|
|
|
|
using Wino.Core.Domain.Interfaces;
|
|
|
|
|
using Wino.Core.Domain.Models.Accounts;
|
|
|
|
|
using Wino.Mail.Api.Contracts.Users;
|
|
|
|
|
using Wino.Messaging.Client.Accounts;
|
|
|
|
|
|
|
|
|
|
namespace Wino.Services;
|
|
|
|
|
|
|
|
|
|
public sealed class WinoAccountDataSyncService : IWinoAccountDataSyncService
|
|
|
|
|
{
|
|
|
|
|
private const int DefaultMaxConcurrentClients = 5;
|
2026-04-18 15:55:15 +02:00
|
|
|
private const int LocalExportVersion = 1;
|
2026-04-04 20:23:20 +02:00
|
|
|
|
|
|
|
|
private readonly IWinoAccountProfileService _profileService;
|
|
|
|
|
private readonly IPreferencesService _preferencesService;
|
|
|
|
|
private readonly IAccountService _accountService;
|
|
|
|
|
|
|
|
|
|
public WinoAccountDataSyncService(
|
|
|
|
|
IWinoAccountProfileService profileService,
|
|
|
|
|
IPreferencesService preferencesService,
|
|
|
|
|
IAccountService accountService)
|
|
|
|
|
{
|
|
|
|
|
_profileService = profileService;
|
|
|
|
|
_preferencesService = preferencesService;
|
|
|
|
|
_accountService = accountService;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<WinoAccountSyncExportResult> ExportAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default)
|
|
|
|
|
{
|
2026-04-18 15:55:15 +02:00
|
|
|
var preparedExport = await PrepareExportAsync(selection).ConfigureAwait(false);
|
2026-04-04 20:23:20 +02:00
|
|
|
|
2026-04-18 15:55:15 +02:00
|
|
|
if (selection.IncludePreferences && preparedExport.PreferencesJson != null)
|
2026-04-04 20:23:20 +02:00
|
|
|
{
|
2026-04-18 15:55:15 +02:00
|
|
|
await _profileService.SaveSettingsAsync(preparedExport.PreferencesJson, cancellationToken).ConfigureAwait(false);
|
2026-04-04 20:23:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (selection.IncludeAccounts)
|
|
|
|
|
{
|
|
|
|
|
var request = new ReplaceUserMailboxesRequestDto
|
|
|
|
|
{
|
2026-04-18 15:55:15 +02:00
|
|
|
Mailboxes = preparedExport.Mailboxes
|
2026-04-04 20:23:20 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await _profileService.ReplaceMailboxesAsync(request, cancellationToken).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 15:55:15 +02:00
|
|
|
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 }))
|
2026-04-04 20:23:20 +02:00
|
|
|
{
|
2026-04-18 15:55:15 +02:00
|
|
|
writer.WriteStartObject();
|
|
|
|
|
writer.WriteNumber("version", LocalExportVersion);
|
|
|
|
|
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
|
2026-04-04 20:23:20 +02:00
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async Task<WinoAccountSyncImportResult> ImportAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default)
|
2026-04-18 15:55:15 +02:00
|
|
|
{
|
|
|
|
|
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)
|
2026-04-04 20:23:20 +02:00
|
|
|
{
|
|
|
|
|
var result = new WinoAccountSyncImportResult
|
|
|
|
|
{
|
|
|
|
|
IncludedPreferences = selection.IncludePreferences,
|
|
|
|
|
IncludedAccounts = selection.IncludeAccounts
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-18 15:55:15 +02:00
|
|
|
if (selection.IncludePreferences && !string.IsNullOrWhiteSpace(settingsJson))
|
2026-04-04 20:23:20 +02:00
|
|
|
{
|
2026-04-18 15:55:15 +02:00
|
|
|
var (appliedCount, failedCount) = _preferencesService.ImportPreferences(settingsJson);
|
|
|
|
|
result = new WinoAccountSyncImportResult
|
2026-04-04 20:23:20 +02:00
|
|
|
{
|
2026-04-18 15:55:15 +02:00
|
|
|
IncludedPreferences = result.IncludedPreferences,
|
|
|
|
|
IncludedAccounts = result.IncludedAccounts,
|
|
|
|
|
HadRemotePreferences = true,
|
|
|
|
|
AppliedPreferenceCount = appliedCount,
|
|
|
|
|
FailedPreferenceCount = failedCount,
|
|
|
|
|
ImportedMailboxCount = result.ImportedMailboxCount,
|
|
|
|
|
SkippedDuplicateMailboxCount = result.SkippedDuplicateMailboxCount,
|
|
|
|
|
RemoteMailboxCount = result.RemoteMailboxCount
|
|
|
|
|
};
|
2026-04-04 20:23:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (selection.IncludeAccounts)
|
|
|
|
|
{
|
2026-04-18 15:55:15 +02:00
|
|
|
var orderedMailboxes = mailboxes
|
2026-04-04 20:23:20 +02:00
|
|
|
.OrderBy(a => a.SortOrder)
|
|
|
|
|
.ThenBy(a => a.Address, StringComparer.OrdinalIgnoreCase)
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
var localAccounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
|
|
|
|
var existingKeys = localAccounts
|
|
|
|
|
.Select(CreateMailboxKey)
|
|
|
|
|
.ToHashSet(StringComparer.Ordinal);
|
|
|
|
|
|
|
|
|
|
var importedMailboxCount = 0;
|
|
|
|
|
var skippedDuplicateMailboxCount = 0;
|
|
|
|
|
|
|
|
|
|
foreach (var mailbox in orderedMailboxes)
|
|
|
|
|
{
|
|
|
|
|
cancellationToken.ThrowIfCancellationRequested();
|
|
|
|
|
|
|
|
|
|
var mailboxKey = CreateMailboxKey(mailbox.Address, mailbox.ProviderType);
|
|
|
|
|
if (!existingKeys.Add(mailboxKey))
|
|
|
|
|
{
|
|
|
|
|
skippedDuplicateMailboxCount++;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var account = CreateImportedAccount(mailbox);
|
|
|
|
|
var serverInformation = CreateImportedServerInformation(mailbox, account.Id);
|
|
|
|
|
|
|
|
|
|
await _accountService.CreateAccountAsync(account, serverInformation).ConfigureAwait(false);
|
2026-04-20 19:38:30 +02:00
|
|
|
|
|
|
|
|
if (account.IsMailAccessGranted)
|
|
|
|
|
{
|
|
|
|
|
await _accountService.CreateRootAliasAsync(account.Id, account.Address).ConfigureAwait(false);
|
|
|
|
|
}
|
2026-04-04 20:23:20 +02:00
|
|
|
|
|
|
|
|
if (account.ProviderType == MailProviderType.IMAP4)
|
|
|
|
|
{
|
|
|
|
|
var persistedAccount = await _accountService.GetAccountAsync(account.Id).ConfigureAwait(false);
|
|
|
|
|
if (persistedAccount != null && persistedAccount.AttentionReason != AccountAttentionReason.InvalidCredentials)
|
|
|
|
|
{
|
|
|
|
|
persistedAccount.AttentionReason = AccountAttentionReason.InvalidCredentials;
|
|
|
|
|
await _accountService.UpdateAccountAsync(persistedAccount).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
importedMailboxCount++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (importedMailboxCount > 0)
|
|
|
|
|
{
|
|
|
|
|
WeakReferenceMessenger.Default.Send(new AccountsMenuRefreshRequested(false));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result = new WinoAccountSyncImportResult
|
|
|
|
|
{
|
|
|
|
|
IncludedPreferences = result.IncludedPreferences,
|
|
|
|
|
IncludedAccounts = result.IncludedAccounts,
|
|
|
|
|
HadRemotePreferences = result.HadRemotePreferences,
|
|
|
|
|
AppliedPreferenceCount = result.AppliedPreferenceCount,
|
|
|
|
|
FailedPreferenceCount = result.FailedPreferenceCount,
|
|
|
|
|
ImportedMailboxCount = importedMailboxCount,
|
|
|
|
|
SkippedDuplicateMailboxCount = skippedDuplicateMailboxCount,
|
|
|
|
|
RemoteMailboxCount = orderedMailboxes.Count
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await RepairStartupEntityAsync().ConfigureAwait(false);
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static UserMailboxSyncItemDto MapMailbox(MailAccount account)
|
|
|
|
|
{
|
|
|
|
|
var serverInformation = account.ProviderType == MailProviderType.IMAP4
|
|
|
|
|
? account.ServerInformation
|
|
|
|
|
: null;
|
|
|
|
|
|
2026-04-20 19:38:30 +02:00
|
|
|
var mailbox = new UserMailboxSyncItemDto
|
2026-04-04 20:23:20 +02:00
|
|
|
{
|
|
|
|
|
Address = account.Address ?? string.Empty,
|
|
|
|
|
ProviderType = (int)account.ProviderType,
|
|
|
|
|
SpecialImapProvider = (int)account.SpecialImapProvider,
|
|
|
|
|
AccountName = account.Name,
|
|
|
|
|
SenderName = account.SenderName,
|
|
|
|
|
AccountColorHex = account.AccountColorHex,
|
|
|
|
|
SortOrder = account.Order,
|
|
|
|
|
IsCalendarAccessGranted = account.IsCalendarAccessGranted,
|
|
|
|
|
CalendarSupportMode = serverInformation != null ? (int)serverInformation.CalendarSupportMode : 0,
|
|
|
|
|
IncomingServer = serverInformation?.IncomingServer,
|
|
|
|
|
IncomingServerPort = serverInformation?.IncomingServerPort,
|
|
|
|
|
IncomingServerUsername = serverInformation?.IncomingServerUsername,
|
|
|
|
|
IncomingServerSocketOption = serverInformation != null ? (int?)serverInformation.IncomingServerSocketOption : null,
|
|
|
|
|
IncomingAuthenticationMethod = serverInformation != null ? (int?)serverInformation.IncomingAuthenticationMethod : null,
|
|
|
|
|
OutgoingServer = serverInformation?.OutgoingServer,
|
|
|
|
|
OutgoingServerPort = serverInformation?.OutgoingServerPort,
|
|
|
|
|
OutgoingServerUsername = serverInformation?.OutgoingServerUsername,
|
|
|
|
|
OutgoingServerSocketOption = serverInformation != null ? (int?)serverInformation.OutgoingServerSocketOption : null,
|
|
|
|
|
OutgoingAuthenticationMethod = serverInformation != null ? (int?)serverInformation.OutgoingAuthenticationMethod : null,
|
|
|
|
|
CalDavServiceUrl = serverInformation?.CalDavServiceUrl,
|
|
|
|
|
CalDavUsername = serverInformation?.CalDavUsername,
|
|
|
|
|
ProxyServer = serverInformation?.ProxyServer,
|
|
|
|
|
ProxyServerPort = serverInformation?.ProxyServerPort,
|
|
|
|
|
MaxConcurrentClients = serverInformation?.MaxConcurrentClients
|
|
|
|
|
};
|
2026-04-20 19:38:30 +02:00
|
|
|
|
|
|
|
|
SetOptionalBooleanProperty(mailbox, "IsMailAccessGranted", account.IsMailAccessGranted);
|
|
|
|
|
|
|
|
|
|
return mailbox;
|
2026-04-04 20:23:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static MailAccount CreateImportedAccount(UserMailboxSyncItemDto mailbox)
|
|
|
|
|
{
|
|
|
|
|
var providerType = (MailProviderType)mailbox.ProviderType;
|
|
|
|
|
|
|
|
|
|
return new MailAccount
|
|
|
|
|
{
|
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
Address = mailbox.Address.Trim(),
|
|
|
|
|
Name = string.IsNullOrWhiteSpace(mailbox.AccountName) ? mailbox.Address.Trim() : mailbox.AccountName.Trim(),
|
|
|
|
|
SenderName = string.IsNullOrWhiteSpace(mailbox.SenderName) ? mailbox.Address.Trim() : mailbox.SenderName.Trim(),
|
|
|
|
|
ProviderType = providerType,
|
|
|
|
|
SpecialImapProvider = (SpecialImapProvider)mailbox.SpecialImapProvider,
|
|
|
|
|
AccountColorHex = mailbox.AccountColorHex?.Trim(),
|
|
|
|
|
Base64ProfilePictureData = string.Empty,
|
2026-04-14 00:03:48 +02:00
|
|
|
CreatedAt = DateTime.UtcNow,
|
|
|
|
|
InitialSynchronizationRange = InitialSynchronizationRange.SixMonths,
|
2026-04-20 19:38:30 +02:00
|
|
|
IsMailAccessGranted = GetOptionalBooleanProperty(mailbox, "IsMailAccessGranted", defaultValue: true),
|
2026-04-04 20:23:20 +02:00
|
|
|
IsCalendarAccessGranted = mailbox.IsCalendarAccessGranted,
|
|
|
|
|
SynchronizationDeltaIdentifier = string.Empty,
|
|
|
|
|
CalendarSynchronizationDeltaIdentifier = string.Empty,
|
|
|
|
|
AttentionReason = AccountAttentionReason.InvalidCredentials
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static CustomServerInformation? CreateImportedServerInformation(UserMailboxSyncItemDto mailbox, Guid accountId)
|
|
|
|
|
{
|
|
|
|
|
var providerType = (MailProviderType)mailbox.ProviderType;
|
|
|
|
|
if (providerType != MailProviderType.IMAP4)
|
|
|
|
|
{
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new CustomServerInformation
|
|
|
|
|
{
|
|
|
|
|
Id = Guid.NewGuid(),
|
|
|
|
|
AccountId = accountId,
|
|
|
|
|
Address = mailbox.Address.Trim(),
|
|
|
|
|
IncomingServer = mailbox.IncomingServer?.Trim(),
|
|
|
|
|
IncomingServerPort = mailbox.IncomingServerPort?.Trim(),
|
|
|
|
|
IncomingServerUsername = mailbox.IncomingServerUsername?.Trim(),
|
|
|
|
|
IncomingServerPassword = string.Empty,
|
|
|
|
|
IncomingServerSocketOption = mailbox.IncomingServerSocketOption is int incomingSocketOption
|
|
|
|
|
? (ImapConnectionSecurity)incomingSocketOption
|
|
|
|
|
: ImapConnectionSecurity.Auto,
|
|
|
|
|
IncomingAuthenticationMethod = mailbox.IncomingAuthenticationMethod is int incomingAuthMethod
|
|
|
|
|
? (ImapAuthenticationMethod)incomingAuthMethod
|
|
|
|
|
: ImapAuthenticationMethod.Auto,
|
|
|
|
|
OutgoingServer = mailbox.OutgoingServer?.Trim(),
|
|
|
|
|
OutgoingServerPort = mailbox.OutgoingServerPort?.Trim(),
|
|
|
|
|
OutgoingServerUsername = mailbox.OutgoingServerUsername?.Trim(),
|
|
|
|
|
OutgoingServerPassword = string.Empty,
|
|
|
|
|
OutgoingServerSocketOption = mailbox.OutgoingServerSocketOption is int outgoingSocketOption
|
|
|
|
|
? (ImapConnectionSecurity)outgoingSocketOption
|
|
|
|
|
: ImapConnectionSecurity.Auto,
|
|
|
|
|
OutgoingAuthenticationMethod = mailbox.OutgoingAuthenticationMethod is int outgoingAuthMethod
|
|
|
|
|
? (ImapAuthenticationMethod)outgoingAuthMethod
|
|
|
|
|
: ImapAuthenticationMethod.Auto,
|
|
|
|
|
CalDavServiceUrl = mailbox.CalDavServiceUrl?.Trim(),
|
|
|
|
|
CalDavUsername = mailbox.CalDavUsername?.Trim(),
|
|
|
|
|
CalDavPassword = string.Empty,
|
|
|
|
|
CalendarSupportMode = (ImapCalendarSupportMode)mailbox.CalendarSupportMode,
|
|
|
|
|
ProxyServer = mailbox.ProxyServer?.Trim(),
|
|
|
|
|
ProxyServerPort = mailbox.ProxyServerPort?.Trim(),
|
|
|
|
|
MaxConcurrentClients = mailbox.MaxConcurrentClients.GetValueOrDefault(DefaultMaxConcurrentClients)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async Task RepairStartupEntityAsync()
|
|
|
|
|
{
|
|
|
|
|
if (!_preferencesService.StartupEntityId.HasValue)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var startupEntityId = _preferencesService.StartupEntityId.Value;
|
|
|
|
|
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
|
|
|
|
var accountIds = accounts.Select(a => a.Id);
|
|
|
|
|
var mergedInboxIds = accounts.Where(a => a.MergedInboxId.HasValue).Select(a => a.MergedInboxId!.Value);
|
|
|
|
|
|
|
|
|
|
if (accountIds.Concat(mergedInboxIds).Contains(startupEntityId))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_preferencesService.StartupEntityId = accounts.FirstOrDefault()?.Id;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static string CreateMailboxKey(MailAccount account)
|
|
|
|
|
=> CreateMailboxKey(account.Address, (int)account.ProviderType);
|
|
|
|
|
|
|
|
|
|
private static string CreateMailboxKey(string? address, int providerType)
|
|
|
|
|
=> $"{address?.Trim().ToLowerInvariant()}|{providerType}";
|
2026-04-18 15:55:15 +02:00
|
|
|
|
2026-04-20 19:38:30 +02:00
|
|
|
private static bool GetOptionalBooleanProperty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
|
|
|
|
|
T instance,
|
|
|
|
|
string propertyName,
|
|
|
|
|
bool defaultValue)
|
|
|
|
|
{
|
|
|
|
|
if (instance == null)
|
|
|
|
|
return defaultValue;
|
|
|
|
|
|
|
|
|
|
var property = typeof(T).GetProperty(propertyName);
|
|
|
|
|
if (property?.PropertyType != typeof(bool) || !property.CanRead)
|
|
|
|
|
return defaultValue;
|
|
|
|
|
|
|
|
|
|
return property.GetValue(instance) is bool boolValue
|
|
|
|
|
? boolValue
|
|
|
|
|
: defaultValue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void SetOptionalBooleanProperty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(
|
|
|
|
|
T instance,
|
|
|
|
|
string propertyName,
|
|
|
|
|
bool value)
|
|
|
|
|
{
|
|
|
|
|
if (instance == null)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
var property = typeof(T).GetProperty(propertyName);
|
|
|
|
|
if (property?.PropertyType == typeof(bool) && property.CanWrite)
|
|
|
|
|
{
|
|
|
|
|
property.SetValue(instance, value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-18 15:55:15 +02:00
|
|
|
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);
|
2026-04-04 20:23:20 +02:00
|
|
|
}
|