Reworked aliases.
This commit is contained in:
@@ -78,18 +78,20 @@ namespace Wino.Core.Domain.Entities
|
|||||||
[Ignore]
|
[Ignore]
|
||||||
public CustomServerInformation ServerInformation { get; set; }
|
public CustomServerInformation ServerInformation { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
//[Ignore]
|
|
||||||
//public List<MailAccountAlias> Aliases { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Account preferences.
|
/// Account preferences.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Ignore]
|
[Ignore]
|
||||||
public MailAccountPreferences Preferences { get; set; }
|
public MailAccountPreferences Preferences { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the account can perform ProfileInformation sync type.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsProfileInfoSyncSupported => ProviderType == MailProviderType.Outlook || ProviderType == MailProviderType.Office365 || ProviderType == MailProviderType.Gmail;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether the account can perform AliasInformation sync type.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsAliasSyncSupported => ProviderType == MailProviderType.Gmail;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using SQLite;
|
using SQLite;
|
||||||
|
|
||||||
namespace Wino.Core.Domain.Entities
|
namespace Wino.Core.Domain.Entities
|
||||||
{
|
{
|
||||||
public class MailAccountAlias
|
public class RemoteAccountAlias
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Unique Id for the alias.
|
|
||||||
/// </summary>
|
|
||||||
[PrimaryKey]
|
|
||||||
public Guid Id { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Account id that this alias is attached to.
|
|
||||||
/// </summary>
|
|
||||||
public Guid AccountId { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Display address of the alias.
|
/// Display address of the alias.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -32,13 +20,6 @@ namespace Wino.Core.Domain.Entities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsPrimary { get; set; }
|
public bool IsPrimary { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
public bool IsRootAlias { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the alias is verified by the server.
|
/// Whether the alias is verified by the server.
|
||||||
/// Non-verified aliases will show an info tip to users during sending.
|
/// Non-verified aliases will show an info tip to users during sending.
|
||||||
@@ -47,37 +28,30 @@ namespace Wino.Core.Domain.Entities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsVerified { get; set; }
|
public bool IsVerified { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsRootAlias { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MailAccountAlias : RemoteAccountAlias
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Unique Id for the alias.
|
||||||
|
/// </summary>
|
||||||
|
[PrimaryKey]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Account id that this alias is attached to.
|
||||||
|
/// </summary>
|
||||||
|
public Guid AccountId { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Root aliases can't be deleted.
|
/// Root aliases can't be deleted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool CanDelete => !IsRootAlias;
|
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<string>.Default.GetHashCode(AliasAddress);
|
|
||||||
hashCode = hashCode * -1521134295 + EqualityComparer<string>.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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,10 @@
|
|||||||
{
|
{
|
||||||
FoldersOnly, // Only synchronize folder metadata.
|
FoldersOnly, // Only synchronize folder metadata.
|
||||||
ExecuteRequests, // Run the queued requests, and then synchronize if needed.
|
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.
|
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
|
UpdateProfile, // Only update profile information
|
||||||
|
Alias, // Only update alias information
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<MailAccountAlias> GetFinalAliasList(List<MailAccountAlias> localAliases, List<MailAccountAlias> networkAliases)
|
|
||||||
{
|
|
||||||
var finalAliases = new List<MailAccountAlias>();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -139,5 +139,12 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
/// <param name="accountId">Account id.</param>
|
/// <param name="accountId">Account id.</param>
|
||||||
/// <param name="address">Address to create root primary alias from.</param>
|
/// <param name="address">Address to create root primary alias from.</param>
|
||||||
Task CreateRootAliasAsync(Guid accountId, string address);
|
Task CreateRootAliasAsync(Guid accountId, string address);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Will compare local-remote aliases and update the local ones or add/delete new ones.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="remoteAccountAliases">Remotely fetched basic alias info from synchronizer.</param>
|
||||||
|
/// <param name="account">Account to update remote aliases for..</param>
|
||||||
|
Task UpdateRemoteAliasInformationAsync(MailAccount account, List<RemoteAccountAlias> remoteAccountAliases);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
/// Sender name and
|
/// Sender name and
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<ProfileInformation> SynchronizeProfileInformationAsync();
|
Task<ProfileInformation> GetProfileInformationAsync();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Downloads a single MIME message from the server and saves it to disk.
|
/// Downloads a single MIME message from the server and saves it to disk.
|
||||||
|
|||||||
@@ -25,7 +25,12 @@ namespace Wino.Core.Domain.Models.Synchronization
|
|||||||
public static SynchronizationResult Empty => new() { CompletedState = SynchronizationCompletedState.Success };
|
public static SynchronizationResult Empty => new() { CompletedState = SynchronizationCompletedState.Success };
|
||||||
|
|
||||||
public static SynchronizationResult Completed(IEnumerable<IMailItem> downloadedMessages, ProfileInformation profileInformation = null)
|
public static SynchronizationResult Completed(IEnumerable<IMailItem> 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 Canceled => new() { CompletedState = SynchronizationCompletedState.Canceled };
|
||||||
public static SynchronizationResult Failed => new() { CompletedState = SynchronizationCompletedState.Failed };
|
public static SynchronizationResult Failed => new() { CompletedState = SynchronizationCompletedState.Failed };
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
"BasicIMAPSetupDialog_Password": "Password",
|
"BasicIMAPSetupDialog_Password": "Password",
|
||||||
"BasicIMAPSetupDialog_Title": "IMAP Account",
|
"BasicIMAPSetupDialog_Title": "IMAP Account",
|
||||||
"Buttons_AddAccount": "Add Account",
|
"Buttons_AddAccount": "Add Account",
|
||||||
|
"Buttons_AddNewAlias": "Add New Alias",
|
||||||
|
"Buttons_SyncAliases": "Synchronize Aliases",
|
||||||
"Buttons_ApplyTheme": "Apply Theme",
|
"Buttons_ApplyTheme": "Apply Theme",
|
||||||
"Buttons_Browse": "Browse",
|
"Buttons_Browse": "Browse",
|
||||||
"Buttons_Cancel": "Cancel",
|
"Buttons_Cancel": "Cancel",
|
||||||
@@ -128,6 +130,7 @@
|
|||||||
"Exception_CustomThemeMissingName": "You must provide a name.",
|
"Exception_CustomThemeMissingName": "You must provide a name.",
|
||||||
"Exception_CustomThemeMissingWallpaper": "You must provide a custom background image.",
|
"Exception_CustomThemeMissingWallpaper": "You must provide a custom background image.",
|
||||||
"Exception_FailedToSynchronizeFolders": "Failed to synchronize folders",
|
"Exception_FailedToSynchronizeFolders": "Failed to synchronize folders",
|
||||||
|
"Exception_FailedToSynchronizeAliases": "Failed to synchronize aliases",
|
||||||
"Exception_FailedToSynchronizeProfileInformation": "Failed to synchronize profile information",
|
"Exception_FailedToSynchronizeProfileInformation": "Failed to synchronize profile information",
|
||||||
"Exception_GoogleAuthCallbackNull": "Callback uri is null on activation.",
|
"Exception_GoogleAuthCallbackNull": "Callback uri is null on activation.",
|
||||||
"Exception_GoogleAuthCorruptedCode": "Corrupted authorization response.",
|
"Exception_GoogleAuthCorruptedCode": "Corrupted authorization response.",
|
||||||
|
|||||||
15
Wino.Core.Domain/Translator.Designer.cs
generated
15
Wino.Core.Domain/Translator.Designer.cs
generated
@@ -133,6 +133,16 @@ namespace Wino.Core.Domain
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Buttons_AddAccount => Resources.GetTranslatedString(@"Buttons_AddAccount");
|
public static string Buttons_AddAccount => Resources.GetTranslatedString(@"Buttons_AddAccount");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add New Alias
|
||||||
|
/// </summary>
|
||||||
|
public static string Buttons_AddNewAlias => Resources.GetTranslatedString(@"Buttons_AddNewAlias");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Synchronize Aliases
|
||||||
|
/// </summary>
|
||||||
|
public static string Buttons_SyncAliases => Resources.GetTranslatedString(@"Buttons_SyncAliases");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Apply Theme
|
/// Apply Theme
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -663,6 +673,11 @@ namespace Wino.Core.Domain
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static string Exception_FailedToSynchronizeFolders => Resources.GetTranslatedString(@"Exception_FailedToSynchronizeFolders");
|
public static string Exception_FailedToSynchronizeFolders => Resources.GetTranslatedString(@"Exception_FailedToSynchronizeFolders");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Failed to synchronize aliases
|
||||||
|
/// </summary>
|
||||||
|
public static string Exception_FailedToSynchronizeAliases => Resources.GetTranslatedString(@"Exception_FailedToSynchronizeAliases");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Failed to synchronize profile information
|
/// Failed to synchronize profile information
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ using Google.Apis.Gmail.v1.Data;
|
|||||||
using MimeKit;
|
using MimeKit;
|
||||||
using Wino.Core.Domain.Entities;
|
using Wino.Core.Domain.Entities;
|
||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Extensions;
|
|
||||||
|
|
||||||
namespace Wino.Core.Extensions
|
namespace Wino.Core.Extensions
|
||||||
{
|
{
|
||||||
@@ -205,22 +204,16 @@ namespace Wino.Core.Extensions
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<MailAccountAlias> GetMailAliases(this ListSendAsResponse response, List<MailAccountAlias> currentAliases, MailAccount account)
|
public static List<RemoteAccountAlias> GetRemoteAliases(this ListSendAsResponse response)
|
||||||
{
|
{
|
||||||
if (response == null || response.SendAs == null) return currentAliases;
|
return response?.SendAs?.Select(a => new RemoteAccountAlias()
|
||||||
|
|
||||||
var remoteAliases = response.SendAs.Select(a => new MailAccountAlias()
|
|
||||||
{
|
{
|
||||||
AccountId = account.Id,
|
|
||||||
AliasAddress = a.SendAsEmail,
|
AliasAddress = a.SendAsEmail,
|
||||||
|
IsRootAlias = a.IsDefault.GetValueOrDefault(),
|
||||||
IsPrimary = a.IsPrimary.GetValueOrDefault(),
|
IsPrimary = a.IsPrimary.GetValueOrDefault(),
|
||||||
ReplyToAddress = string.IsNullOrEmpty(a.ReplyToAddress) ? account.Address : a.ReplyToAddress,
|
ReplyToAddress = a.ReplyToAddress,
|
||||||
IsVerified = string.IsNullOrEmpty(a.VerificationStatus) ? true : a.VerificationStatus == "accepted",
|
IsVerified = a.VerificationStatus == "accepted" || a.IsDefault.GetValueOrDefault(),
|
||||||
IsRootAlias = account.Address == a.SendAsEmail,
|
|
||||||
Id = Guid.NewGuid()
|
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
return EntityExtensions.GetFinalAliasList(currentAliases, remoteAliases);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ namespace Wino.Core.Integration.Processors
|
|||||||
/// <returns>All folders.</returns>
|
/// <returns>All folders.</returns>
|
||||||
Task<List<MailItemFolder>> GetLocalFoldersAsync(Guid accountId);
|
Task<List<MailItemFolder>> GetLocalFoldersAsync(Guid accountId);
|
||||||
|
|
||||||
Task<List<MailAccountAlias>> GetAccountAliasesAsync(Guid accountId);
|
|
||||||
|
|
||||||
Task<List<MailItemFolder>> GetSynchronizationFoldersAsync(SynchronizationOptions options);
|
Task<List<MailItemFolder>> GetSynchronizationFoldersAsync(SynchronizationOptions options);
|
||||||
|
|
||||||
@@ -48,7 +47,7 @@ namespace Wino.Core.Integration.Processors
|
|||||||
Task UpdateFolderLastSyncDateAsync(Guid folderId);
|
Task UpdateFolderLastSyncDateAsync(Guid folderId);
|
||||||
|
|
||||||
Task<List<MailItemFolder>> GetExistingFoldersAsync(Guid accountId);
|
Task<List<MailItemFolder>> GetExistingFoldersAsync(Guid accountId);
|
||||||
Task UpdateAccountAliasesAsync(Guid accountId, List<MailAccountAlias> aliases);
|
Task UpdateRemoteAliasInformationAsync(MailAccount account, List<RemoteAccountAlias> remoteAccountAliases);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IGmailChangeProcessor : IDefaultChangeProcessor
|
public interface IGmailChangeProcessor : IDefaultChangeProcessor
|
||||||
@@ -180,10 +179,7 @@ namespace Wino.Core.Integration.Processors
|
|||||||
public Task UpdateAccountAsync(MailAccount account)
|
public Task UpdateAccountAsync(MailAccount account)
|
||||||
=> AccountService.UpdateAccountAsync(account);
|
=> AccountService.UpdateAccountAsync(account);
|
||||||
|
|
||||||
public Task UpdateAccountAliasesAsync(Guid accountId, List<MailAccountAlias> aliases)
|
public Task UpdateRemoteAliasInformationAsync(MailAccount account, List<RemoteAccountAlias> remoteAccountAliases)
|
||||||
=> AccountService.UpdateAccountAliasesAsync(accountId, aliases);
|
=> AccountService.UpdateRemoteAliasInformationAsync(account, remoteAccountAliases);
|
||||||
|
|
||||||
public Task<List<MailAccountAlias>> GetAccountAliasesAsync(Guid accountId)
|
|
||||||
=> AccountService.GetAccountAliasesAsync(accountId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -380,6 +380,69 @@ namespace Wino.Core.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task UpdateRemoteAliasInformationAsync(MailAccount account, List<RemoteAccountAlias> 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)
|
public async Task DeleteAccountAliasAsync(Guid aliasId)
|
||||||
{
|
{
|
||||||
// Create query to delete alias.
|
// Create query to delete alias.
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ namespace Wino.Core.Synchronizers
|
|||||||
/// Refreshes remote mail account profile if possible.
|
/// Refreshes remote mail account profile if possible.
|
||||||
/// Profile picture, sender name and mailbox settings (todo) will be handled in this step.
|
/// Profile picture, sender name and mailbox settings (todo) will be handled in this step.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual Task<ProfileInformation> SynchronizeProfileInformationAsync() => default;
|
public virtual Task<ProfileInformation> GetProfileInformationAsync() => default;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Refreshes the aliases of the account.
|
/// Refreshes the aliases of the account.
|
||||||
@@ -110,28 +110,18 @@ namespace Wino.Core.Synchronizers
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Safely updates account's profile information.
|
/// Safely updates account's profile information.
|
||||||
/// Database changes are reflected after this call.
|
/// Database changes are reflected after this call.
|
||||||
/// Null returns mean that the operation failed.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task<ProfileInformation> SynchronizeProfileInformationInternalAsync()
|
private async Task<ProfileInformation> SynchronizeProfileInformationInternalAsync()
|
||||||
{
|
{
|
||||||
try
|
var profileInformation = await GetProfileInformationAsync();
|
||||||
{
|
|
||||||
var profileInformation = await SynchronizeProfileInformationAsync();
|
|
||||||
|
|
||||||
if (profileInformation != null)
|
if (profileInformation != null)
|
||||||
{
|
|
||||||
Account.SenderName = profileInformation.SenderName;
|
|
||||||
Account.Base64ProfilePictureData = profileInformation.Base64ProfilePictureData;
|
|
||||||
}
|
|
||||||
|
|
||||||
return profileInformation;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
{
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -173,16 +163,46 @@ namespace Wino.Core.Synchronizers
|
|||||||
|
|
||||||
await synchronizationSemaphore.WaitAsync(activeSynchronizationCancellationToken);
|
await synchronizationSemaphore.WaitAsync(activeSynchronizationCancellationToken);
|
||||||
|
|
||||||
|
// Handle special synchronization types.
|
||||||
|
|
||||||
|
// Profile information sync.
|
||||||
if (options.Type == SynchronizationType.UpdateProfile)
|
if (options.Type == SynchronizationType.UpdateProfile)
|
||||||
{
|
{
|
||||||
// Refresh profile information on full synchronization.
|
if (!Account.IsProfileInfoSyncSupported) return SynchronizationResult.Empty;
|
||||||
// Exceptions here is not critical. Therefore, they are ignored.
|
|
||||||
|
|
||||||
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.
|
// Let servers to finish their job. Sometimes the servers doesn't respond immediately.
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ namespace Wino.Core.Synchronizers
|
|||||||
|
|
||||||
public ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args) => _googleHttpClient;
|
public ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args) => _googleHttpClient;
|
||||||
|
|
||||||
public override async Task<ProfileInformation> SynchronizeProfileInformationAsync()
|
public override async Task<ProfileInformation> GetProfileInformationAsync()
|
||||||
{
|
{
|
||||||
var profileRequest = _peopleService.People.Get("people/me");
|
var profileRequest = _peopleService.People.Get("people/me");
|
||||||
profileRequest.PersonFields = "names,photos";
|
profileRequest.PersonFields = "names,photos";
|
||||||
@@ -92,23 +92,11 @@ namespace Wino.Core.Synchronizers
|
|||||||
|
|
||||||
protected override async Task SynchronizeAliasesAsync()
|
protected override async Task SynchronizeAliasesAsync()
|
||||||
{
|
{
|
||||||
// Sync aliases
|
|
||||||
|
|
||||||
var sendAsListRequest = _gmailService.Users.Settings.SendAs.List("me");
|
var sendAsListRequest = _gmailService.Users.Settings.SendAs.List("me");
|
||||||
var sendAsListResponse = await sendAsListRequest.ExecuteAsync();
|
var sendAsListResponse = await sendAsListRequest.ExecuteAsync();
|
||||||
|
var remoteAliases = sendAsListResponse.GetRemoteAliases();
|
||||||
|
|
||||||
var localAliases = await _gmailChangeProcessor.GetAccountAliasesAsync(Account.Id).ConfigureAwait(false);
|
await _gmailChangeProcessor.UpdateRemoteAliasInformationAsync(Account, remoteAliases).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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task<SynchronizationResult> SynchronizeInternalAsync(SynchronizationOptions options, CancellationToken cancellationToken = default)
|
protected override async Task<SynchronizationResult> SynchronizeInternalAsync(SynchronizationOptions options, CancellationToken cancellationToken = default)
|
||||||
|
|||||||
@@ -922,7 +922,7 @@ namespace Wino.Core.Synchronizers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// In case of the high input, we'll batch them by 50 to reflect changes quickly.
|
// 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)
|
foreach (var batchMissingMailIds in batchedMissingMailIds)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -496,7 +496,7 @@ namespace Wino.Core.Synchronizers
|
|||||||
return userInfo.DisplayName;
|
return userInfo.DisplayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<ProfileInformation> SynchronizeProfileInformationAsync()
|
public override async Task<ProfileInformation> GetProfileInformationAsync()
|
||||||
{
|
{
|
||||||
var profilePictureData = await GetUserProfilePictureAsync().ConfigureAwait(false);
|
var profilePictureData = await GetUserProfilePictureAsync().ConfigureAwait(false);
|
||||||
var senderName = await GetSenderNameAsync().ConfigureAwait(false);
|
var senderName = await GetSenderNameAsync().ConfigureAwait(false);
|
||||||
|
|||||||
@@ -206,26 +206,29 @@ namespace Wino.Mail.ViewModels
|
|||||||
|
|
||||||
// Local account has been created.
|
// Local account has been created.
|
||||||
|
|
||||||
// Start profile information synchronization.
|
if (createdAccount.ProviderType != MailProviderType.IMAP4)
|
||||||
// Profile info is not updated in the database yet.
|
|
||||||
|
|
||||||
var profileSyncOptions = new SynchronizationOptions()
|
|
||||||
{
|
{
|
||||||
AccountId = createdAccount.Id,
|
// Start profile information synchronization.
|
||||||
Type = SynchronizationType.UpdateProfile
|
// It's only available for Outlook and Gmail synchronizers.
|
||||||
};
|
|
||||||
|
|
||||||
var profileSynchronizationResponse = await _winoServerConnectionManager.GetResponseAsync<SynchronizationResult, NewSynchronizationRequested>(new NewSynchronizationRequested(profileSyncOptions, SynchronizationSource.Client));
|
var profileSyncOptions = new SynchronizationOptions()
|
||||||
|
{
|
||||||
|
AccountId = createdAccount.Id,
|
||||||
|
Type = SynchronizationType.UpdateProfile
|
||||||
|
};
|
||||||
|
|
||||||
var profileSynchronizationResult = profileSynchronizationResponse.Data;
|
var profileSynchronizationResponse = await _winoServerConnectionManager.GetResponseAsync<SynchronizationResult, NewSynchronizationRequested>(new NewSynchronizationRequested(profileSyncOptions, SynchronizationSource.Client));
|
||||||
|
|
||||||
if (profileSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
var profileSynchronizationResult = profileSynchronizationResponse.Data;
|
||||||
throw new Exception(Translator.Exception_FailedToSynchronizeProfileInformation);
|
|
||||||
|
|
||||||
createdAccount.SenderName = profileSynchronizationResult.ProfileInformation.SenderName;
|
if (profileSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
||||||
createdAccount.Base64ProfilePictureData = profileSynchronizationResult.ProfileInformation.Base64ProfilePictureData;
|
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)
|
if (creationDialog is ICustomServerAccountCreationDialog customServerAccountCreationDialog)
|
||||||
customServerAccountCreationDialog.ShowPreparingFolders();
|
customServerAccountCreationDialog.ShowPreparingFolders();
|
||||||
@@ -246,10 +249,29 @@ namespace Wino.Mail.ViewModels
|
|||||||
if (folderSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
if (folderSynchronizationResult.CompletedState != SynchronizationCompletedState.Success)
|
||||||
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
|
throw new Exception(Translator.Exception_FailedToSynchronizeFolders);
|
||||||
|
|
||||||
// Create root primary alias for the account.
|
if (createdAccount.IsAliasSyncSupported)
|
||||||
// This is the first alias for the account and it's primary.
|
{
|
||||||
|
// 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<SynchronizationResult, NewSynchronizationRequested>(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.
|
// 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.
|
// Check if Inbox folder is available for the account after synchronization.
|
||||||
|
|||||||
@@ -10,21 +10,30 @@ using Wino.Core.Domain.Entities;
|
|||||||
using Wino.Core.Domain.Enums;
|
using Wino.Core.Domain.Enums;
|
||||||
using Wino.Core.Domain.Interfaces;
|
using Wino.Core.Domain.Interfaces;
|
||||||
using Wino.Core.Domain.Models.Navigation;
|
using Wino.Core.Domain.Models.Navigation;
|
||||||
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
|
using Wino.Messaging.Server;
|
||||||
|
|
||||||
namespace Wino.Mail.ViewModels
|
namespace Wino.Mail.ViewModels
|
||||||
{
|
{
|
||||||
public partial class AliasManagementPageViewModel : BaseViewModel
|
public partial class AliasManagementPageViewModel : BaseViewModel
|
||||||
{
|
{
|
||||||
private readonly IAccountService _accountService;
|
private readonly IAccountService _accountService;
|
||||||
|
private readonly IWinoServerConnectionManager _winoServerConnectionManager;
|
||||||
public MailAccount Account { get; set; }
|
[ObservableProperty]
|
||||||
|
[NotifyPropertyChangedFor(nameof(CanSynchronizeAliases))]
|
||||||
|
private MailAccount account;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private List<MailAccountAlias> accountAliases = [];
|
private List<MailAccountAlias> 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;
|
_accountService = accountService;
|
||||||
|
_winoServerConnectionManager = winoServerConnectionManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
public override async void OnNavigatedTo(NavigationMode mode, object parameters)
|
||||||
@@ -58,6 +67,25 @@ namespace Wino.Mail.ViewModels
|
|||||||
await LoadAliasesAsync();
|
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<SynchronizationResult, NewSynchronizationRequested>(new NewSynchronizationRequested(aliasSyncOptions, SynchronizationSource.Client));
|
||||||
|
|
||||||
|
if (aliasSyncResponse.IsSuccess)
|
||||||
|
await LoadAliasesAsync();
|
||||||
|
else
|
||||||
|
DialogService.InfoBarMessage(Translator.GeneralTitle_Error, aliasSyncResponse.Message, InfoBarMessageType.Error);
|
||||||
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task AddNewAliasAsync()
|
private async Task AddNewAliasAsync()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ namespace Wino.Mail.ViewModels.Data
|
|||||||
|
|
||||||
public int HoldingAccountCount => 1;
|
public int HoldingAccountCount => 1;
|
||||||
|
|
||||||
|
public bool HasProfilePicture => !string.IsNullOrEmpty(Account.Base64ProfilePictureData);
|
||||||
|
|
||||||
public AccountProviderDetailViewModel(IProviderDetail providerDetail, MailAccount account)
|
public AccountProviderDetailViewModel(IProviderDetail providerDetail, MailAccount account)
|
||||||
{
|
{
|
||||||
ProviderDetail = providerDetail;
|
ProviderDetail = providerDetail;
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
|
|
||||||
<ListView
|
<ListView
|
||||||
Grid.Row="2"
|
Grid.Row="2"
|
||||||
Margin="0,12"
|
|
||||||
Padding="0"
|
Padding="0"
|
||||||
ItemTemplate="{StaticResource NewMailProviderTemplate}"
|
ItemTemplate="{StaticResource NewMailProviderTemplate}"
|
||||||
ItemsSource="{x:Bind Providers}"
|
ItemsSource="{x:Bind Providers}"
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user