Import functionality for wino accounts, calendar sync UI, bunch of shell improvements
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using CommunityToolkit.Diagnostics;
|
||||
@@ -642,10 +643,11 @@ public class AccountService : BaseDatabaseService, IAccountService
|
||||
IsExtended = true,
|
||||
RemoteCalendarId = string.Empty,
|
||||
TimeZone = string.Empty,
|
||||
BackgroundColorHex = await GetNextDistinctCalendarColorAsync().ConfigureAwait(false),
|
||||
TextColorHex = "#FFFFFF"
|
||||
BackgroundColorHex = await GetNextDistinctCalendarColorAsync().ConfigureAwait(false)
|
||||
};
|
||||
|
||||
localCalendar.TextColorHex = GetReadableTextColorHex(localCalendar.BackgroundColorHex);
|
||||
|
||||
await Connection.InsertAsync(localCalendar, typeof(AccountCalendar)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -658,6 +660,16 @@ public class AccountService : BaseDatabaseService, IAccountService
|
||||
return CalendarColorPalette.GetDistinctColor(usedColors.Select(a => a.BackgroundColorHex));
|
||||
}
|
||||
|
||||
private static string GetReadableTextColorHex(string backgroundColorHex)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(backgroundColorHex))
|
||||
return "#FFFFFF";
|
||||
|
||||
var color = ColorTranslator.FromHtml(backgroundColorHex);
|
||||
var luminance = ((0.299 * color.R) + (0.587 * color.G) + (0.114 * color.B)) / 255d;
|
||||
return luminance > 0.6 ? "#111111" : "#FFFFFF";
|
||||
}
|
||||
|
||||
public async Task UpdateAccountOrdersAsync(Dictionary<Guid, int> accountIdOrderPair)
|
||||
{
|
||||
foreach (var pair in accountIdOrderPair)
|
||||
|
||||
@@ -29,6 +29,7 @@ public static class ServicesContainerSetup
|
||||
services.AddTransient<IKeyboardShortcutService, KeyboardShortcutService>();
|
||||
services.AddSingleton<IWinoAccountApiClient, WinoAccountApiClient>();
|
||||
services.AddSingleton<IWinoAccountProfileService, WinoAccountProfileService>();
|
||||
services.AddTransient<IWinoAccountDataSyncService, WinoAccountDataSyncService>();
|
||||
services.AddSingleton<IContactPictureFileService, ContactPictureFileService>();
|
||||
|
||||
services.AddTransient<ICalDavClient, CalDavClient>();
|
||||
|
||||
@@ -16,6 +16,7 @@ using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Mail.Api.Contracts.Ai;
|
||||
using Wino.Mail.Api.Contracts.Auth;
|
||||
using Wino.Mail.Api.Contracts.Common;
|
||||
using Wino.Mail.Api.Contracts.Users;
|
||||
|
||||
namespace Wino.Services;
|
||||
|
||||
@@ -160,49 +161,83 @@ public sealed class WinoAccountApiClient : IWinoAccountApiClient, IDisposable
|
||||
|
||||
public async Task<string?> GetSettingsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
using var response = await SendAuthorizedAsync(
|
||||
() => CreateAuthorizedRequestAsync(HttpMethod.Get, "api/v1/users/me/settings"),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
using var response = await SendAuthorizedAsync(
|
||||
() => CreateAuthorizedRequestAsync(HttpMethod.Get, "api/v1/users/me/settings"),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null)
|
||||
return null;
|
||||
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
|
||||
return null;
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
|
||||
return await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
throw new InvalidOperationException("MissingAccessToken");
|
||||
}
|
||||
catch
|
||||
|
||||
if (response.StatusCode == System.Net.HttpStatusCode.NoContent)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
await EnsureSuccessResponseAsync(response, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> SaveSettingsAsync(string settingsJson, CancellationToken cancellationToken = default)
|
||||
public async Task SaveSettingsAsync(string settingsJson, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var response = await SendAuthorizedAsync(
|
||||
() => CreateAuthorizedRequestAsync(
|
||||
HttpMethod.Put,
|
||||
"api/v1/users/me/settings",
|
||||
() => new StringContent(settingsJson, Encoding.UTF8, "application/json")),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
using var response = await SendAuthorizedAsync(
|
||||
() => CreateAuthorizedRequestAsync(
|
||||
HttpMethod.Put,
|
||||
"api/v1/users/me/settings",
|
||||
() => new StringContent(settingsJson, Encoding.UTF8, "application/json")),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null)
|
||||
return false;
|
||||
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
catch
|
||||
if (response == null)
|
||||
{
|
||||
return false;
|
||||
throw new InvalidOperationException("MissingAccessToken");
|
||||
}
|
||||
|
||||
await EnsureSuccessResponseAsync(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<UserMailboxSyncListDto> GetMailboxesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var response = await SendAuthorizedAsync(
|
||||
() => CreateAuthorizedRequestAsync(HttpMethod.Get, "api/v1/users/me/mailboxes"),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
throw new InvalidOperationException("MissingAccessToken");
|
||||
}
|
||||
|
||||
await EnsureSuccessResponseAsync(response, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
var envelope = string.IsNullOrWhiteSpace(payload)
|
||||
? null
|
||||
: JsonSerializer.Deserialize(payload, WinoAccountApiJsonContext.Default.ApiEnvelopeUserMailboxSyncListDto);
|
||||
|
||||
if (envelope?.IsSuccess == true && envelope.Result != null)
|
||||
{
|
||||
return envelope.Result;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(ExtractErrorMessage(payload) ?? envelope?.ErrorCode ?? "Mailbox synchronization request failed.");
|
||||
}
|
||||
|
||||
public async Task ReplaceMailboxesAsync(ReplaceUserMailboxesRequestDto request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var response = await SendAuthorizedAsync(
|
||||
() => CreateAuthorizedRequestAsync(
|
||||
HttpMethod.Put,
|
||||
"api/v1/users/me/mailboxes",
|
||||
() => JsonContent.Create(request, WinoAccountApiJsonContext.Default.ReplaceUserMailboxesRequestDto)),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
throw new InvalidOperationException("MissingAccessToken");
|
||||
}
|
||||
|
||||
await EnsureSuccessResponseAsync(response, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<WinoAccountApiResult<AuthResultDto>> SendAuthRequestAsync<TRequest>(string endpoint, TRequest request, JsonTypeInfo<TRequest> typeInfo, CancellationToken cancellationToken)
|
||||
@@ -321,6 +356,19 @@ public sealed class WinoAccountApiClient : IWinoAccountApiClient, IDisposable
|
||||
return !string.IsNullOrWhiteSpace(value);
|
||||
}
|
||||
|
||||
private static async Task EnsureSuccessResponseAsync(HttpResponseMessage response, CancellationToken cancellationToken)
|
||||
{
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var payload = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
throw new InvalidOperationException(
|
||||
ExtractErrorMessage(payload)
|
||||
?? $"HTTP {(int)response.StatusCode} {response.ReasonPhrase}".Trim());
|
||||
}
|
||||
|
||||
private Task<ApiEnvelope<TResponse>> SendAuthorizedRequestAsync<TResponse>(string endpoint, JsonTypeInfo<ApiEnvelope<TResponse>> typeInfo, CancellationToken cancellationToken)
|
||||
=> SendAuthorizedRequestAsync(HttpMethod.Get, endpoint, typeInfo, cancellationToken);
|
||||
|
||||
@@ -549,7 +597,9 @@ public sealed class WinoAccountApiClient : IWinoAccountApiClient, IDisposable
|
||||
[JsonSerializable(typeof(ApiEnvelope<AiStatusResultDto>))]
|
||||
[JsonSerializable(typeof(ApiEnvelope<AiTextResultDto>))]
|
||||
[JsonSerializable(typeof(ApiEnvelope<WinoStoreCollectionsIdTicketInfo>))]
|
||||
[JsonSerializable(typeof(ApiEnvelope<UserMailboxSyncListDto>))]
|
||||
[JsonSerializable(typeof(ApiEnvelope<JsonElement>))]
|
||||
[JsonSerializable(typeof(ReplaceUserMailboxesRequestDto))]
|
||||
internal sealed partial class WinoAccountApiJsonContext : JsonSerializerContext;
|
||||
|
||||
internal sealed record SyncStoreEntitlementsRequest(string? StoreIdKey, string? PurchaseIdKey);
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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;
|
||||
|
||||
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)
|
||||
{
|
||||
var exportedMailboxCount = 0;
|
||||
|
||||
if (selection.IncludePreferences)
|
||||
{
|
||||
await _profileService.SaveSettingsAsync(_preferencesService.ExportPreferences(), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (selection.IncludeAccounts)
|
||||
{
|
||||
var accounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||
var request = new ReplaceUserMailboxesRequestDto
|
||||
{
|
||||
Mailboxes = accounts
|
||||
.OrderBy(a => a.Order)
|
||||
.Select(MapMailbox)
|
||||
.ToList()
|
||||
};
|
||||
|
||||
await _profileService.ReplaceMailboxesAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
exportedMailboxCount = request.Mailboxes.Count;
|
||||
}
|
||||
|
||||
return new WinoAccountSyncExportResult
|
||||
{
|
||||
IncludedPreferences = selection.IncludePreferences,
|
||||
IncludedAccounts = selection.IncludeAccounts,
|
||||
ExportedMailboxCount = exportedMailboxCount
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<WinoAccountSyncImportResult> ImportAsync(WinoAccountSyncSelection selection, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var result = new WinoAccountSyncImportResult
|
||||
{
|
||||
IncludedPreferences = selection.IncludePreferences,
|
||||
IncludedAccounts = selection.IncludeAccounts
|
||||
};
|
||||
|
||||
if (selection.IncludePreferences)
|
||||
{
|
||||
var settingsJson = await _profileService.GetSettingsAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (!string.IsNullOrWhiteSpace(settingsJson))
|
||||
{
|
||||
var (appliedCount, failedCount) = _preferencesService.ImportPreferences(settingsJson);
|
||||
result = new WinoAccountSyncImportResult
|
||||
{
|
||||
IncludedPreferences = result.IncludedPreferences,
|
||||
IncludedAccounts = result.IncludedAccounts,
|
||||
HadRemotePreferences = true,
|
||||
AppliedPreferenceCount = appliedCount,
|
||||
FailedPreferenceCount = failedCount,
|
||||
ImportedMailboxCount = result.ImportedMailboxCount,
|
||||
SkippedDuplicateMailboxCount = result.SkippedDuplicateMailboxCount,
|
||||
RemoteMailboxCount = result.RemoteMailboxCount
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (selection.IncludeAccounts)
|
||||
{
|
||||
var mailboxes = await _profileService.GetMailboxesAsync(cancellationToken).ConfigureAwait(false);
|
||||
var orderedMailboxes = mailboxes.Mailboxes
|
||||
.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);
|
||||
await _accountService.CreateRootAliasAsync(account.Id, account.Address).ConfigureAwait(false);
|
||||
|
||||
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;
|
||||
|
||||
return new UserMailboxSyncItemDto
|
||||
{
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
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}";
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Mail.Api.Contracts.Ai;
|
||||
using Wino.Mail.Api.Contracts.Auth;
|
||||
using Wino.Mail.Api.Contracts.Common;
|
||||
using Wino.Mail.Api.Contracts.Users;
|
||||
using Wino.Messaging.UI;
|
||||
|
||||
namespace Wino.Services;
|
||||
@@ -285,6 +286,38 @@ public sealed class WinoAccountProfileService : BaseDatabaseService, IWinoAccoun
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<string?> GetSettingsAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
_ = await GetAuthenticatedAccountAsync(cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("MissingAccessToken");
|
||||
|
||||
return await _apiClient.GetSettingsAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task SaveSettingsAsync(string settingsJson, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_ = await GetAuthenticatedAccountAsync(cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("MissingAccessToken");
|
||||
|
||||
await _apiClient.SaveSettingsAsync(settingsJson, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<UserMailboxSyncListDto> GetMailboxesAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
_ = await GetAuthenticatedAccountAsync(cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("MissingAccessToken");
|
||||
|
||||
return await _apiClient.GetMailboxesAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task ReplaceMailboxesAsync(ReplaceUserMailboxesRequestDto request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
_ = await GetAuthenticatedAccountAsync(cancellationToken).ConfigureAwait(false)
|
||||
?? throw new InvalidOperationException("MissingAccessToken");
|
||||
|
||||
await _apiClient.ReplaceMailboxesAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> ProcessBillingCallbackAsync(Uri callbackUri, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _billingCallbackLock.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
Reference in New Issue
Block a user