diff --git a/Wino.Core.Domain/Entities/MailAccount.cs b/Wino.Core.Domain/Entities/MailAccount.cs
index c1bc43a1..e668a6a3 100644
--- a/Wino.Core.Domain/Entities/MailAccount.cs
+++ b/Wino.Core.Domain/Entities/MailAccount.cs
@@ -78,18 +78,20 @@ namespace Wino.Core.Domain.Entities
[Ignore]
public CustomServerInformation ServerInformation { get; set; }
- ///
- /// Gets or sets the aliases of the account.
- /// It's only synchronized for Gmail right now.
- /// Other provider types are manually added by users and not verified.
- ///
- //[Ignore]
- //public List Aliases { get; set; }
-
///
/// Account preferences.
///
[Ignore]
public MailAccountPreferences Preferences { get; set; }
+
+ ///
+ /// Gets whether the account can perform ProfileInformation sync type.
+ ///
+ public bool IsProfileInfoSyncSupported => ProviderType == MailProviderType.Outlook || ProviderType == MailProviderType.Office365 || ProviderType == MailProviderType.Gmail;
+
+ ///
+ /// Gets whether the account can perform AliasInformation sync type.
+ ///
+ public bool IsAliasSyncSupported => ProviderType == MailProviderType.Gmail;
}
}
diff --git a/Wino.Core.Domain/Entities/MailAccountAlias.cs b/Wino.Core.Domain/Entities/MailAccountAlias.cs
index 8d103744..fa60b42b 100644
--- a/Wino.Core.Domain/Entities/MailAccountAlias.cs
+++ b/Wino.Core.Domain/Entities/MailAccountAlias.cs
@@ -1,22 +1,10 @@
using System;
-using System.Collections.Generic;
using SQLite;
namespace Wino.Core.Domain.Entities
{
- public class MailAccountAlias
+ public class RemoteAccountAlias
{
- ///
- /// Unique Id for the alias.
- ///
- [PrimaryKey]
- public Guid Id { get; set; }
-
- ///
- /// Account id that this alias is attached to.
- ///
- public Guid AccountId { get; set; }
-
///
/// Display address of the alias.
///
@@ -32,13 +20,6 @@ namespace Wino.Core.Domain.Entities
///
public bool IsPrimary { get; set; }
- ///
- /// Whether this alias is the root alias for the account.
- /// Root alias means the first alias that was created for the account.
- /// It can't be deleted or changed.
- ///
- public bool IsRootAlias { get; set; }
-
///
/// Whether the alias is verified by the server.
/// Non-verified aliases will show an info tip to users during sending.
@@ -47,37 +28,30 @@ namespace Wino.Core.Domain.Entities
///
public bool IsVerified { get; set; }
+ ///
+ /// Whether this alias is the root alias for the account.
+ /// Root alias means the first alias that was created for the account.
+ /// It can't be deleted or changed.
+ ///
+ public bool IsRootAlias { get; set; }
+ }
+
+ public class MailAccountAlias : RemoteAccountAlias
+ {
+ ///
+ /// Unique Id for the alias.
+ ///
+ [PrimaryKey]
+ public Guid Id { get; set; }
+
+ ///
+ /// Account id that this alias is attached to.
+ ///
+ public Guid AccountId { get; set; }
+
///
/// Root aliases can't be deleted.
///
public bool CanDelete => !IsRootAlias;
-
- public override bool Equals(object obj)
- {
- if (obj == null || GetType() != obj.GetType())
- return false;
-
- var other = (MailAccountAlias)obj;
- return other != null &&
- AccountId == other.AccountId &&
- AliasAddress == other.AliasAddress &&
- ReplyToAddress == other.ReplyToAddress &&
- IsPrimary == other.IsPrimary &&
- IsVerified == other.IsVerified &&
- IsRootAlias == other.IsRootAlias;
- }
-
- public override int GetHashCode()
- {
- int hashCode = 59052167;
- hashCode = hashCode * -1521134295 + AccountId.GetHashCode();
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(AliasAddress);
- hashCode = hashCode * -1521134295 + EqualityComparer.Default.GetHashCode(ReplyToAddress);
- hashCode = hashCode * -1521134295 + IsPrimary.GetHashCode();
- hashCode = hashCode * -1521134295 + IsRootAlias.GetHashCode();
- hashCode = hashCode * -1521134295 + IsVerified.GetHashCode();
- hashCode = hashCode * -1521134295 + CanDelete.GetHashCode();
- return hashCode;
- }
}
}
diff --git a/Wino.Core.Domain/Enums/SynchronizationType.cs b/Wino.Core.Domain/Enums/SynchronizationType.cs
index 927016d6..a8cfba1d 100644
--- a/Wino.Core.Domain/Enums/SynchronizationType.cs
+++ b/Wino.Core.Domain/Enums/SynchronizationType.cs
@@ -4,9 +4,10 @@
{
FoldersOnly, // Only synchronize folder metadata.
ExecuteRequests, // Run the queued requests, and then synchronize if needed.
- Inbox, // Only Inbox
+ Inbox, // Only Inbox, Sent and Draft folders.
Custom, // Only sync folders that are specified in the options.
- Full, // Synchronize everything
+ Full, // Synchronize all folders. This won't update profile or alias information.
UpdateProfile, // Only update profile information
+ Alias, // Only update alias information
}
}
diff --git a/Wino.Core.Domain/Extensions/EntityExtensions.cs b/Wino.Core.Domain/Extensions/EntityExtensions.cs
deleted file mode 100644
index b078ad99..00000000
--- a/Wino.Core.Domain/Extensions/EntityExtensions.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using Wino.Core.Domain.Entities;
-
-namespace Wino.Core.Domain.Extensions
-{
- public static class EntityExtensions
- {
- public static List GetFinalAliasList(List localAliases, List networkAliases)
- {
- var finalAliases = new List();
-
- var networkAliasDict = networkAliases.ToDictionary(a => a, a => a);
-
- // Handle updating and retaining existing aliases
- foreach (var localAlias in localAliases)
- {
- if (networkAliasDict.TryGetValue(localAlias, out var networkAlias))
- {
- // If alias exists in both lists, update it with the network alias (preserving Id from local)
- networkAlias.Id = localAlias.Id; // Preserve the local Id
- finalAliases.Add(networkAlias);
- networkAliasDict.Remove(localAlias); // Remove from dictionary to track what's been handled
- }
- // If the alias isn't in the network list, it's considered deleted and not added to finalAliases
- }
-
- // Add new aliases that were not in the local list
- finalAliases.AddRange(networkAliasDict.Values);
-
- return finalAliases;
- }
- }
-}
diff --git a/Wino.Core.Domain/Interfaces/IAccountService.cs b/Wino.Core.Domain/Interfaces/IAccountService.cs
index e7196653..8c800220 100644
--- a/Wino.Core.Domain/Interfaces/IAccountService.cs
+++ b/Wino.Core.Domain/Interfaces/IAccountService.cs
@@ -139,5 +139,12 @@ namespace Wino.Core.Domain.Interfaces
/// Account id.
/// Address to create root primary alias from.
Task CreateRootAliasAsync(Guid accountId, string address);
+
+ ///
+ /// Will compare local-remote aliases and update the local ones or add/delete new ones.
+ ///
+ /// Remotely fetched basic alias info from synchronizer.
+ /// Account to update remote aliases for..
+ Task UpdateRemoteAliasInformationAsync(MailAccount account, List remoteAccountAliases);
}
}
diff --git a/Wino.Core.Domain/Interfaces/IBaseSynchronizer.cs b/Wino.Core.Domain/Interfaces/IBaseSynchronizer.cs
index e6ccd2a1..00b1ec6c 100644
--- a/Wino.Core.Domain/Interfaces/IBaseSynchronizer.cs
+++ b/Wino.Core.Domain/Interfaces/IBaseSynchronizer.cs
@@ -49,7 +49,7 @@ namespace Wino.Core.Domain.Interfaces
/// Sender name and
///
///
- Task SynchronizeProfileInformationAsync();
+ Task GetProfileInformationAsync();
///
/// Downloads a single MIME message from the server and saves it to disk.
diff --git a/Wino.Core.Domain/Models/Synchronization/SynchronizationResult.cs b/Wino.Core.Domain/Models/Synchronization/SynchronizationResult.cs
index 885a4b9b..0e98844a 100644
--- a/Wino.Core.Domain/Models/Synchronization/SynchronizationResult.cs
+++ b/Wino.Core.Domain/Models/Synchronization/SynchronizationResult.cs
@@ -25,7 +25,12 @@ namespace Wino.Core.Domain.Models.Synchronization
public static SynchronizationResult Empty => new() { CompletedState = SynchronizationCompletedState.Success };
public static SynchronizationResult Completed(IEnumerable downloadedMessages, ProfileInformation profileInformation = null)
- => new() { DownloadedMessages = downloadedMessages, ProfileInformation = profileInformation, CompletedState = SynchronizationCompletedState.Success };
+ => new()
+ {
+ DownloadedMessages = downloadedMessages,
+ ProfileInformation = profileInformation,
+ CompletedState = SynchronizationCompletedState.Success
+ };
public static SynchronizationResult Canceled => new() { CompletedState = SynchronizationCompletedState.Canceled };
public static SynchronizationResult Failed => new() { CompletedState = SynchronizationCompletedState.Failed };
diff --git a/Wino.Core.Domain/Translations/en_US/resources.json b/Wino.Core.Domain/Translations/en_US/resources.json
index b77392dc..696f41ef 100644
--- a/Wino.Core.Domain/Translations/en_US/resources.json
+++ b/Wino.Core.Domain/Translations/en_US/resources.json
@@ -22,6 +22,8 @@
"BasicIMAPSetupDialog_Password": "Password",
"BasicIMAPSetupDialog_Title": "IMAP Account",
"Buttons_AddAccount": "Add Account",
+ "Buttons_AddNewAlias": "Add New Alias",
+ "Buttons_SyncAliases": "Synchronize Aliases",
"Buttons_ApplyTheme": "Apply Theme",
"Buttons_Browse": "Browse",
"Buttons_Cancel": "Cancel",
@@ -128,6 +130,7 @@
"Exception_CustomThemeMissingName": "You must provide a name.",
"Exception_CustomThemeMissingWallpaper": "You must provide a custom background image.",
"Exception_FailedToSynchronizeFolders": "Failed to synchronize folders",
+ "Exception_FailedToSynchronizeAliases": "Failed to synchronize aliases",
"Exception_FailedToSynchronizeProfileInformation": "Failed to synchronize profile information",
"Exception_GoogleAuthCallbackNull": "Callback uri is null on activation.",
"Exception_GoogleAuthCorruptedCode": "Corrupted authorization response.",
diff --git a/Wino.Core.Domain/Translator.Designer.cs b/Wino.Core.Domain/Translator.Designer.cs
index a20c9711..e37aedff 100644
--- a/Wino.Core.Domain/Translator.Designer.cs
+++ b/Wino.Core.Domain/Translator.Designer.cs
@@ -133,6 +133,16 @@ namespace Wino.Core.Domain
///
public static string Buttons_AddAccount => Resources.GetTranslatedString(@"Buttons_AddAccount");
+ ///
+ /// Add New Alias
+ ///
+ public static string Buttons_AddNewAlias => Resources.GetTranslatedString(@"Buttons_AddNewAlias");
+
+ ///
+ /// Synchronize Aliases
+ ///
+ public static string Buttons_SyncAliases => Resources.GetTranslatedString(@"Buttons_SyncAliases");
+
///
/// Apply Theme
///
@@ -663,6 +673,11 @@ namespace Wino.Core.Domain
///
public static string Exception_FailedToSynchronizeFolders => Resources.GetTranslatedString(@"Exception_FailedToSynchronizeFolders");
+ ///
+ /// Failed to synchronize aliases
+ ///
+ public static string Exception_FailedToSynchronizeAliases => Resources.GetTranslatedString(@"Exception_FailedToSynchronizeAliases");
+
///
/// Failed to synchronize profile information
///
diff --git a/Wino.Core/Extensions/GoogleIntegratorExtensions.cs b/Wino.Core/Extensions/GoogleIntegratorExtensions.cs
index ed290d13..1aa319e3 100644
--- a/Wino.Core/Extensions/GoogleIntegratorExtensions.cs
+++ b/Wino.Core/Extensions/GoogleIntegratorExtensions.cs
@@ -6,7 +6,6 @@ using Google.Apis.Gmail.v1.Data;
using MimeKit;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
-using Wino.Core.Domain.Extensions;
namespace Wino.Core.Extensions
{
@@ -205,22 +204,16 @@ namespace Wino.Core.Extensions
};
}
- public static List GetMailAliases(this ListSendAsResponse response, List currentAliases, MailAccount account)
+ public static List GetRemoteAliases(this ListSendAsResponse response)
{
- if (response == null || response.SendAs == null) return currentAliases;
-
- var remoteAliases = response.SendAs.Select(a => new MailAccountAlias()
+ return response?.SendAs?.Select(a => new RemoteAccountAlias()
{
- AccountId = account.Id,
AliasAddress = a.SendAsEmail,
+ IsRootAlias = a.IsDefault.GetValueOrDefault(),
IsPrimary = a.IsPrimary.GetValueOrDefault(),
- ReplyToAddress = string.IsNullOrEmpty(a.ReplyToAddress) ? account.Address : a.ReplyToAddress,
- IsVerified = string.IsNullOrEmpty(a.VerificationStatus) ? true : a.VerificationStatus == "accepted",
- IsRootAlias = account.Address == a.SendAsEmail,
- Id = Guid.NewGuid()
+ ReplyToAddress = a.ReplyToAddress,
+ IsVerified = a.VerificationStatus == "accepted" || a.IsDefault.GetValueOrDefault(),
}).ToList();
-
- return EntityExtensions.GetFinalAliasList(currentAliases, remoteAliases);
}
}
}
diff --git a/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs b/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs
index 46116d72..7c39fcb5 100644
--- a/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs
+++ b/Wino.Core/Integration/Processors/DefaultChangeProcessor.cs
@@ -40,7 +40,6 @@ namespace Wino.Core.Integration.Processors
/// All folders.
Task> GetLocalFoldersAsync(Guid accountId);
- Task> GetAccountAliasesAsync(Guid accountId);
Task> GetSynchronizationFoldersAsync(SynchronizationOptions options);
@@ -48,7 +47,7 @@ namespace Wino.Core.Integration.Processors
Task UpdateFolderLastSyncDateAsync(Guid folderId);
Task> GetExistingFoldersAsync(Guid accountId);
- Task UpdateAccountAliasesAsync(Guid accountId, List aliases);
+ Task UpdateRemoteAliasInformationAsync(MailAccount account, List remoteAccountAliases);
}
public interface IGmailChangeProcessor : IDefaultChangeProcessor
@@ -180,10 +179,7 @@ namespace Wino.Core.Integration.Processors
public Task UpdateAccountAsync(MailAccount account)
=> AccountService.UpdateAccountAsync(account);
- public Task UpdateAccountAliasesAsync(Guid accountId, List aliases)
- => AccountService.UpdateAccountAliasesAsync(accountId, aliases);
-
- public Task> GetAccountAliasesAsync(Guid accountId)
- => AccountService.GetAccountAliasesAsync(accountId);
+ public Task UpdateRemoteAliasInformationAsync(MailAccount account, List remoteAccountAliases)
+ => AccountService.UpdateRemoteAliasInformationAsync(account, remoteAccountAliases);
}
}
diff --git a/Wino.Core/Services/AccountService.cs b/Wino.Core/Services/AccountService.cs
index e029e6bf..163c099e 100644
--- a/Wino.Core/Services/AccountService.cs
+++ b/Wino.Core/Services/AccountService.cs
@@ -380,6 +380,69 @@ namespace Wino.Core.Services
}
}
+ public async Task UpdateRemoteAliasInformationAsync(MailAccount account, List remoteAccountAliases)
+ {
+ var localAliases = await GetAccountAliasesAsync(account.Id).ConfigureAwait(false);
+ var rootAlias = localAliases.Find(a => a.IsRootAlias);
+
+ foreach (var remoteAlias in remoteAccountAliases)
+ {
+ var existingAlias = localAliases.Find(a => a.AccountId == account.Id && a.AliasAddress == remoteAlias.AliasAddress);
+
+ if (existingAlias == null)
+ {
+ // Create new alias.
+ var newAlias = new MailAccountAlias()
+ {
+ AccountId = account.Id,
+ AliasAddress = remoteAlias.AliasAddress,
+ IsPrimary = remoteAlias.IsPrimary,
+ IsVerified = remoteAlias.IsVerified,
+ ReplyToAddress = remoteAlias.ReplyToAddress,
+ Id = Guid.NewGuid(),
+ IsRootAlias = remoteAlias.IsRootAlias
+ };
+
+ await Connection.InsertAsync(newAlias);
+ localAliases.Add(newAlias);
+ }
+ else
+ {
+ // Update existing alias.
+ existingAlias.IsPrimary = remoteAlias.IsPrimary;
+ existingAlias.IsVerified = remoteAlias.IsVerified;
+ existingAlias.ReplyToAddress = remoteAlias.ReplyToAddress;
+
+ await Connection.UpdateAsync(existingAlias);
+ }
+ }
+
+ // Make sure there is only 1 root alias and 1 primary alias selected.
+
+ bool shouldUpdatePrimary = localAliases.Count(a => a.IsPrimary) != 1;
+ bool shouldUpdateRoot = localAliases.Count(a => a.IsRootAlias) != 1;
+
+ if (shouldUpdatePrimary)
+ {
+ localAliases.ForEach(a => a.IsPrimary = false);
+
+ var idealPrimaryAlias = localAliases.Find(a => a.AliasAddress == account.Address) ?? localAliases.First();
+
+ idealPrimaryAlias.IsPrimary = true;
+ await Connection.UpdateAsync(idealPrimaryAlias).ConfigureAwait(false);
+ }
+
+ if (shouldUpdateRoot)
+ {
+ localAliases.ForEach(a => a.IsRootAlias = false);
+
+ var idealRootAlias = localAliases.Find(a => a.AliasAddress == account.Address) ?? localAliases.First();
+
+ idealRootAlias.IsRootAlias = true;
+ await Connection.UpdateAsync(idealRootAlias).ConfigureAwait(false);
+ }
+ }
+
public async Task DeleteAccountAliasAsync(Guid aliasId)
{
// Create query to delete alias.
diff --git a/Wino.Core/Synchronizers/BaseSynchronizer.cs b/Wino.Core/Synchronizers/BaseSynchronizer.cs
index 11c107c6..39d53f66 100644
--- a/Wino.Core/Synchronizers/BaseSynchronizer.cs
+++ b/Wino.Core/Synchronizers/BaseSynchronizer.cs
@@ -75,7 +75,7 @@ namespace Wino.Core.Synchronizers
/// Refreshes remote mail account profile if possible.
/// Profile picture, sender name and mailbox settings (todo) will be handled in this step.
///
- public virtual Task SynchronizeProfileInformationAsync() => default;
+ public virtual Task GetProfileInformationAsync() => default;
///
/// Refreshes the aliases of the account.
@@ -110,28 +110,18 @@ namespace Wino.Core.Synchronizers
///
/// Safely updates account's profile information.
/// Database changes are reflected after this call.
- /// Null returns mean that the operation failed.
///
private async Task SynchronizeProfileInformationInternalAsync()
{
- try
- {
- var profileInformation = await SynchronizeProfileInformationAsync();
+ var profileInformation = await GetProfileInformationAsync();
- if (profileInformation != null)
- {
- Account.SenderName = profileInformation.SenderName;
- Account.Base64ProfilePictureData = profileInformation.Base64ProfilePictureData;
- }
-
- return profileInformation;
- }
- catch (Exception ex)
+ if (profileInformation != null)
{
- Log.Error(ex, "Failed to update profile information for account '{Name}'", Account.Name);
+ Account.SenderName = profileInformation.SenderName;
+ Account.Base64ProfilePictureData = profileInformation.Base64ProfilePictureData;
}
- return null;
+ return profileInformation;
}
///
@@ -173,16 +163,46 @@ namespace Wino.Core.Synchronizers
await synchronizationSemaphore.WaitAsync(activeSynchronizationCancellationToken);
+ // Handle special synchronization types.
+
+ // Profile information sync.
if (options.Type == SynchronizationType.UpdateProfile)
{
- // Refresh profile information on full synchronization.
- // Exceptions here is not critical. Therefore, they are ignored.
+ if (!Account.IsProfileInfoSyncSupported) return SynchronizationResult.Empty;
- var newprofileInformation = await SynchronizeProfileInformationInternalAsync();
+ ProfileInformation newProfileInformation = null;
- if (newprofileInformation == null) return SynchronizationResult.Failed;
+ try
+ {
+ newProfileInformation = await SynchronizeProfileInformationInternalAsync();
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Failed to update profile information for {Name}", Account.Name);
- return SynchronizationResult.Completed(null, newprofileInformation);
+ return SynchronizationResult.Failed;
+ }
+
+ return SynchronizationResult.Completed(null, newProfileInformation);
+ }
+
+ // Alias sync.
+ if (options.Type == SynchronizationType.Alias)
+ {
+ if (!Account.IsAliasSyncSupported) return SynchronizationResult.Empty;
+
+ try
+ {
+ await SynchronizeAliasesAsync();
+
+ return SynchronizationResult.Empty;
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Failed to update aliases for {Name}", Account.Name);
+
+ return SynchronizationResult.Failed;
+ }
}
// Let servers to finish their job. Sometimes the servers doesn't respond immediately.
diff --git a/Wino.Core/Synchronizers/GmailSynchronizer.cs b/Wino.Core/Synchronizers/GmailSynchronizer.cs
index 8dd48eab..d0cf7b2f 100644
--- a/Wino.Core/Synchronizers/GmailSynchronizer.cs
+++ b/Wino.Core/Synchronizers/GmailSynchronizer.cs
@@ -69,7 +69,7 @@ namespace Wino.Core.Synchronizers
public ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args) => _googleHttpClient;
- public override async Task SynchronizeProfileInformationAsync()
+ public override async Task GetProfileInformationAsync()
{
var profileRequest = _peopleService.People.Get("people/me");
profileRequest.PersonFields = "names,photos";
@@ -92,23 +92,11 @@ namespace Wino.Core.Synchronizers
protected override async Task SynchronizeAliasesAsync()
{
- // Sync aliases
-
var sendAsListRequest = _gmailService.Users.Settings.SendAs.List("me");
var sendAsListResponse = await sendAsListRequest.ExecuteAsync();
+ var remoteAliases = sendAsListResponse.GetRemoteAliases();
- var localAliases = await _gmailChangeProcessor.GetAccountAliasesAsync(Account.Id).ConfigureAwait(false);
-
- var updatedAliases = sendAsListResponse.GetMailAliases(localAliases, Account);
-
- bool shouldUpdateAliases =
- localAliases.Any(a => updatedAliases.Any(b => a.Id == b.Id) == false) ||
- updatedAliases.Any(a => localAliases.Any(b => a.Id == b.Id) == false);
-
- if (shouldUpdateAliases)
- {
- await _gmailChangeProcessor.UpdateAccountAliasesAsync(Account.Id, updatedAliases);
- }
+ await _gmailChangeProcessor.UpdateRemoteAliasInformationAsync(Account, remoteAliases).ConfigureAwait(false);
}
protected override async Task SynchronizeInternalAsync(SynchronizationOptions options, CancellationToken cancellationToken = default)
diff --git a/Wino.Core/Synchronizers/ImapSynchronizer.cs b/Wino.Core/Synchronizers/ImapSynchronizer.cs
index 5624edf4..e0ef6810 100644
--- a/Wino.Core/Synchronizers/ImapSynchronizer.cs
+++ b/Wino.Core/Synchronizers/ImapSynchronizer.cs
@@ -922,7 +922,7 @@ namespace Wino.Core.Synchronizers
}
// In case of the high input, we'll batch them by 50 to reflect changes quickly.
- var batchedMissingMailIds = missingMailIds.Batch(50).Select(a => new UniqueIdSet(a, SortOrder.Descending));
+ var batchedMissingMailIds = missingMailIds.Batch(50).Select(a => new UniqueIdSet(a, SortOrder.Ascending));
foreach (var batchMissingMailIds in batchedMissingMailIds)
{
diff --git a/Wino.Core/Synchronizers/OutlookSynchronizer.cs b/Wino.Core/Synchronizers/OutlookSynchronizer.cs
index 90bc66f5..964d5c5b 100644
--- a/Wino.Core/Synchronizers/OutlookSynchronizer.cs
+++ b/Wino.Core/Synchronizers/OutlookSynchronizer.cs
@@ -496,7 +496,7 @@ namespace Wino.Core.Synchronizers
return userInfo.DisplayName;
}
- public override async Task SynchronizeProfileInformationAsync()
+ public override async Task GetProfileInformationAsync()
{
var profilePictureData = await GetUserProfilePictureAsync().ConfigureAwait(false);
var senderName = await GetSenderNameAsync().ConfigureAwait(false);
diff --git a/Wino.Mail.ViewModels/AccountManagementViewModel.cs b/Wino.Mail.ViewModels/AccountManagementViewModel.cs
index 367ca141..a1dee843 100644
--- a/Wino.Mail.ViewModels/AccountManagementViewModel.cs
+++ b/Wino.Mail.ViewModels/AccountManagementViewModel.cs
@@ -206,26 +206,29 @@ namespace Wino.Mail.ViewModels
// Local account has been created.
- // Start profile information synchronization.
- // Profile info is not updated in the database yet.
-
- var profileSyncOptions = new SynchronizationOptions()
+ if (createdAccount.ProviderType != MailProviderType.IMAP4)
{
- AccountId = createdAccount.Id,
- Type = SynchronizationType.UpdateProfile
- };
+ // Start profile information synchronization.
+ // It's only available for Outlook and Gmail synchronizers.
- var profileSynchronizationResponse = await _winoServerConnectionManager.GetResponseAsync(new NewSynchronizationRequested(profileSyncOptions, SynchronizationSource.Client));
+ var profileSyncOptions = new SynchronizationOptions()
+ {
+ AccountId = createdAccount.Id,
+ Type = SynchronizationType.UpdateProfile
+ };
- var profileSynchronizationResult = profileSynchronizationResponse.Data;
+ var profileSynchronizationResponse = await _winoServerConnectionManager.GetResponseAsync(new NewSynchronizationRequested(profileSyncOptions, SynchronizationSource.Client));
- if (profileSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
- throw new Exception(Translator.Exception_FailedToSynchronizeProfileInformation);
+ var profileSynchronizationResult = profileSynchronizationResponse.Data;
- createdAccount.SenderName = profileSynchronizationResult.ProfileInformation.SenderName;
- createdAccount.Base64ProfilePictureData = profileSynchronizationResult.ProfileInformation.Base64ProfilePictureData;
+ if (profileSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
+ throw new Exception(Translator.Exception_FailedToSynchronizeProfileInformation);
- await _accountService.UpdateProfileInformationAsync(createdAccount.Id, profileSynchronizationResult.ProfileInformation);
+ createdAccount.SenderName = profileSynchronizationResult.ProfileInformation.SenderName;
+ createdAccount.Base64ProfilePictureData = profileSynchronizationResult.ProfileInformation.Base64ProfilePictureData;
+
+ await _accountService.UpdateProfileInformationAsync(createdAccount.Id, profileSynchronizationResult.ProfileInformation);
+ }
if (creationDialog is ICustomServerAccountCreationDialog customServerAccountCreationDialog)
customServerAccountCreationDialog.ShowPreparingFolders();
@@ -246,10 +249,29 @@ namespace Wino.Mail.ViewModels
if (folderSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
- // Create root primary alias for the account.
- // This is the first alias for the account and it's primary.
+ if (createdAccount.IsAliasSyncSupported)
+ {
+ // Try to synchronize aliases for the account.
- await _accountService.CreateRootAliasAsync(createdAccount.Id, createdAccount.Address);
+ var aliasSyncOptions = new SynchronizationOptions()
+ {
+ AccountId = createdAccount.Id,
+ Type = SynchronizationType.Alias
+ };
+
+ var aliasSyncResponse = await _winoServerConnectionManager.GetResponseAsync(new NewSynchronizationRequested(aliasSyncOptions, SynchronizationSource.Client));
+ var aliasSynchronizationResult = folderSynchronizationResponse.Data;
+
+ if (aliasSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
+ throw new Exception(Translator.Exception_FailedToSynchronizeAliases);
+ }
+ else
+ {
+ // Create root primary alias for the account.
+ // This is only available for accounts that do not support alias synchronization.
+
+ await _accountService.CreateRootAliasAsync(createdAccount.Id, createdAccount.Address);
+ }
// TODO: Temporary disabled. Is this even needed? Users can configure special folders manually later on if discovery fails.
// Check if Inbox folder is available for the account after synchronization.
diff --git a/Wino.Mail.ViewModels/AliasManagementPageViewModel.cs b/Wino.Mail.ViewModels/AliasManagementPageViewModel.cs
index 234cf49f..3da9e8d0 100644
--- a/Wino.Mail.ViewModels/AliasManagementPageViewModel.cs
+++ b/Wino.Mail.ViewModels/AliasManagementPageViewModel.cs
@@ -10,21 +10,30 @@ using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Navigation;
+using Wino.Core.Domain.Models.Synchronization;
+using Wino.Messaging.Server;
namespace Wino.Mail.ViewModels
{
public partial class AliasManagementPageViewModel : BaseViewModel
{
private readonly IAccountService _accountService;
-
- public MailAccount Account { get; set; }
+ private readonly IWinoServerConnectionManager _winoServerConnectionManager;
+ [ObservableProperty]
+ [NotifyPropertyChangedFor(nameof(CanSynchronizeAliases))]
+ private MailAccount account;
[ObservableProperty]
private List accountAliases = [];
- public AliasManagementPageViewModel(IDialogService dialogService, IAccountService accountService) : base(dialogService)
+ public bool CanSynchronizeAliases => Account?.IsAliasSyncSupported ?? false;
+
+ public AliasManagementPageViewModel(IDialogService dialogService,
+ IAccountService accountService,
+ IWinoServerConnectionManager winoServerConnectionManager) : base(dialogService)
{
_accountService = accountService;
+ _winoServerConnectionManager = winoServerConnectionManager;
}
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
@@ -58,6 +67,25 @@ namespace Wino.Mail.ViewModels
await LoadAliasesAsync();
}
+ [RelayCommand]
+ private async Task SyncAliasesAsync()
+ {
+ if (!CanSynchronizeAliases) return;
+
+ var aliasSyncOptions = new SynchronizationOptions()
+ {
+ AccountId = Account.Id,
+ Type = SynchronizationType.Alias
+ };
+
+ var aliasSyncResponse = await _winoServerConnectionManager.GetResponseAsync(new NewSynchronizationRequested(aliasSyncOptions, SynchronizationSource.Client));
+
+ if (aliasSyncResponse.IsSuccess)
+ await LoadAliasesAsync();
+ else
+ DialogService.InfoBarMessage(Translator.GeneralTitle_Error, aliasSyncResponse.Message, InfoBarMessageType.Error);
+ }
+
[RelayCommand]
private async Task AddNewAliasAsync()
{
diff --git a/Wino.Mail.ViewModels/Data/AccountProviderDetailViewModel.cs b/Wino.Mail.ViewModels/Data/AccountProviderDetailViewModel.cs
index 8cdacde7..3d1c5e3a 100644
--- a/Wino.Mail.ViewModels/Data/AccountProviderDetailViewModel.cs
+++ b/Wino.Mail.ViewModels/Data/AccountProviderDetailViewModel.cs
@@ -23,6 +23,8 @@ namespace Wino.Mail.ViewModels.Data
public int HoldingAccountCount => 1;
+ public bool HasProfilePicture => !string.IsNullOrEmpty(Account.Base64ProfilePictureData);
+
public AccountProviderDetailViewModel(IProviderDetail providerDetail, MailAccount account)
{
ProviderDetail = providerDetail;
diff --git a/Wino.Mail/Dialogs/NewAccountDialog.xaml b/Wino.Mail/Dialogs/NewAccountDialog.xaml
index 0cf082e2..7d3d1d02 100644
--- a/Wino.Mail/Dialogs/NewAccountDialog.xaml
+++ b/Wino.Mail/Dialogs/NewAccountDialog.xaml
@@ -71,7 +71,7 @@
+
-
+
@@ -36,29 +34,26 @@
-
+
-
-
+
+
@@ -67,18 +62,16 @@
-
+
-
+
@@ -90,12 +83,11 @@
-
-
+
-
+
-
+
-
+
-
+
@@ -80,84 +74,91 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
-
-
-
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
-
+
+
+
-
-
+
+
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+