Managing account aliases and profile synchronization for outlook and gmail.
This commit is contained in:
@@ -13,6 +13,7 @@ using Wino.Core.Domain;
|
||||
using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
using Wino.Core.Integration;
|
||||
@@ -71,10 +72,16 @@ namespace Wino.Core.Synchronizers
|
||||
public abstract Task ExecuteNativeRequestsAsync(IEnumerable<IRequestBundle<TBaseRequest>> batchedRequests, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Refreshed remote mail account profile if possible.
|
||||
/// Aliases, profile pictures, mailbox settings will be handled in this step.
|
||||
/// Refreshes remote mail account profile if possible.
|
||||
/// Profile picture, sender name and mailbox settings (todo) will be handled in this step.
|
||||
/// </summary>
|
||||
protected virtual Task SynchronizeProfileInformationAsync() => Task.CompletedTask;
|
||||
public virtual Task<ProfileInformation> SynchronizeProfileInformationAsync() => default;
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the aliases of the account.
|
||||
/// Only available for Gmail right now.
|
||||
/// </summary>
|
||||
protected virtual Task SynchronizeAliasesAsync() => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the base64 encoded profile picture of the account from the given URL.
|
||||
@@ -100,6 +107,33 @@ namespace Wino.Core.Synchronizers
|
||||
/// <returns>Synchronization result that contains summary of the sync.</returns>
|
||||
protected abstract Task<SynchronizationResult> SynchronizeInternalAsync(SynchronizationOptions options, CancellationToken cancellationToken = default);
|
||||
|
||||
/// <summary>
|
||||
/// Safely updates account's profile information.
|
||||
/// Database changes are reflected after this call.
|
||||
/// Null returns mean that the operation failed.
|
||||
/// </summary>
|
||||
private async Task<ProfileInformation> SynchronizeProfileInformationInternalAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var profileInformation = await SynchronizeProfileInformationAsync();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Batches network requests, executes them, and does the needed synchronization after the batch request execution.
|
||||
/// </summary>
|
||||
@@ -139,12 +173,16 @@ namespace Wino.Core.Synchronizers
|
||||
|
||||
await synchronizationSemaphore.WaitAsync(activeSynchronizationCancellationToken);
|
||||
|
||||
if (options.Type == SynchronizationType.Full)
|
||||
if (options.Type == SynchronizationType.UpdateProfile)
|
||||
{
|
||||
// Refresh profile information and mailbox settings on full synchronization.
|
||||
// Refresh profile information on full synchronization.
|
||||
// Exceptions here is not critical. Therefore, they are ignored.
|
||||
|
||||
await SynchronizeProfileInformationAsync();
|
||||
var newprofileInformation = await SynchronizeProfileInformationInternalAsync();
|
||||
|
||||
if (newprofileInformation == null) return SynchronizationResult.Failed;
|
||||
|
||||
return SynchronizationResult.Completed(null, newprofileInformation);
|
||||
}
|
||||
|
||||
// Let servers to finish their job. Sometimes the servers doesn't respond immediately.
|
||||
|
||||
@@ -19,6 +19,7 @@ using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
@@ -68,67 +69,45 @@ namespace Wino.Core.Synchronizers
|
||||
|
||||
public ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args) => _googleHttpClient;
|
||||
|
||||
protected override async Task SynchronizeProfileInformationAsync()
|
||||
public override async Task<ProfileInformation> SynchronizeProfileInformationAsync()
|
||||
{
|
||||
// Gmail profile info synchronizes Sender Name, Alias and Profile Picture.
|
||||
var profileRequest = _peopleService.People.Get("people/me");
|
||||
profileRequest.PersonFields = "names,photos";
|
||||
|
||||
try
|
||||
string senderName = string.Empty, base64ProfilePicture = string.Empty;
|
||||
|
||||
var userProfile = await profileRequest.ExecuteAsync();
|
||||
|
||||
senderName = userProfile.Names?.FirstOrDefault()?.DisplayName ?? Account.SenderName;
|
||||
|
||||
var profilePicture = userProfile.Photos?.FirstOrDefault()?.Url ?? string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty(profilePicture))
|
||||
{
|
||||
var profileRequest = _peopleService.People.Get("people/me");
|
||||
profileRequest.PersonFields = "names,photos";
|
||||
|
||||
string senderName = Account.SenderName, base64ProfilePicture = Account.ProfilePictureBase64;
|
||||
|
||||
var userProfile = await profileRequest.ExecuteAsync();
|
||||
|
||||
senderName = userProfile.Names?.FirstOrDefault()?.DisplayName ?? Account.SenderName;
|
||||
|
||||
var profilePicture = userProfile.Photos?.FirstOrDefault()?.Url ?? string.Empty;
|
||||
|
||||
if (!string.IsNullOrEmpty(profilePicture))
|
||||
{
|
||||
base64ProfilePicture = await GetProfilePictureBase64EncodedAsync(profilePicture).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
bool shouldUpdateAccountProfile = (!string.IsNullOrEmpty(senderName) && Account.SenderName != senderName)
|
||||
|| (!string.IsNullOrEmpty(profilePicture) && Account.ProfilePictureBase64 != base64ProfilePicture);
|
||||
|
||||
if (!string.IsNullOrEmpty(senderName) && Account.SenderName != senderName)
|
||||
{
|
||||
Account.SenderName = senderName;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profilePicture) && Account.ProfilePictureBase64 != base64ProfilePicture)
|
||||
{
|
||||
Account.ProfilePictureBase64 = base64ProfilePicture;
|
||||
}
|
||||
|
||||
// Sync aliases
|
||||
|
||||
var sendAsListRequest = _gmailService.Users.Settings.SendAs.List("me");
|
||||
var sendAsListResponse = await sendAsListRequest.ExecuteAsync();
|
||||
|
||||
var updatedAliases = sendAsListResponse.GetMailAliases(Account);
|
||||
|
||||
bool shouldUpdateAliases =
|
||||
Account.Aliases.Any(a => updatedAliases.Any(b => a.Id == b.Id) == false) ||
|
||||
updatedAliases.Any(a => Account.Aliases.Any(b => a.Id == b.Id) == false);
|
||||
|
||||
if (shouldUpdateAliases)
|
||||
{
|
||||
Account.Aliases = updatedAliases;
|
||||
|
||||
await _gmailChangeProcessor.UpdateAccountAliasesAsync(Account.Id, updatedAliases);
|
||||
}
|
||||
|
||||
if (shouldUpdateAccountProfile)
|
||||
{
|
||||
await _gmailChangeProcessor.UpdateAccountAsync(Account).ConfigureAwait(false);
|
||||
}
|
||||
base64ProfilePicture = await GetProfilePictureBase64EncodedAsync(profilePicture).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
return new ProfileInformation(senderName, base64ProfilePicture);
|
||||
}
|
||||
|
||||
protected override async Task SynchronizeAliasesAsync()
|
||||
{
|
||||
// Sync aliases
|
||||
|
||||
var sendAsListRequest = _gmailService.Users.Settings.SendAs.List("me");
|
||||
var sendAsListResponse = await sendAsListRequest.ExecuteAsync();
|
||||
|
||||
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)
|
||||
{
|
||||
Logger.Error(ex, "Error while synchronizing profile information for {Name}", Account.Name);
|
||||
await _gmailChangeProcessor.UpdateAccountAliasesAsync(Account.Id, updatedAliases);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ using Wino.Core.Domain.Entities;
|
||||
using Wino.Core.Domain.Enums;
|
||||
using Wino.Core.Domain.Exceptions;
|
||||
using Wino.Core.Domain.Interfaces;
|
||||
using Wino.Core.Domain.Models.Accounts;
|
||||
using Wino.Core.Domain.Models.MailItem;
|
||||
using Wino.Core.Domain.Models.Requests;
|
||||
using Wino.Core.Domain.Models.Synchronization;
|
||||
@@ -479,70 +480,28 @@ namespace Wino.Core.Synchronizers
|
||||
/// <returns>Base64 encoded profile picture.</returns>
|
||||
private async Task<string> GetUserProfilePictureAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var photoStream = await _graphClient.Me.Photos["48x48"].Content.GetAsync();
|
||||
var photoStream = await _graphClient.Me.Photos["48x48"].Content.GetAsync();
|
||||
|
||||
using var memoryStream = new MemoryStream();
|
||||
await photoStream.CopyToAsync(memoryStream);
|
||||
var byteArray = memoryStream.ToArray();
|
||||
using var memoryStream = new MemoryStream();
|
||||
await photoStream.CopyToAsync(memoryStream);
|
||||
var byteArray = memoryStream.ToArray();
|
||||
|
||||
return Convert.ToBase64String(byteArray);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error occurred while getting user profile picture.");
|
||||
return string.Empty;
|
||||
}
|
||||
return Convert.ToBase64String(byteArray);
|
||||
}
|
||||
|
||||
private async Task<string> GetSenderNameAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var userInfo = await _graphClient.Users["me"].GetAsync();
|
||||
var userInfo = await _graphClient.Users["me"].GetAsync();
|
||||
|
||||
return userInfo.DisplayName;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to get sender name.");
|
||||
return string.Empty;
|
||||
}
|
||||
return userInfo.DisplayName;
|
||||
}
|
||||
|
||||
protected override async Task SynchronizeProfileInformationAsync()
|
||||
public override async Task<ProfileInformation> SynchronizeProfileInformationAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Outlook profile info synchronizes Sender Name and Profile Picture.
|
||||
string senderName = Account.SenderName, base64ProfilePicture = Account.ProfilePictureBase64;
|
||||
var profilePictureData = await GetUserProfilePictureAsync().ConfigureAwait(false);
|
||||
var senderName = await GetSenderNameAsync().ConfigureAwait(false);
|
||||
|
||||
var profilePictureData = await GetUserProfilePictureAsync().ConfigureAwait(false);
|
||||
senderName = await GetSenderNameAsync().ConfigureAwait(false);
|
||||
|
||||
bool shouldUpdateAccountProfile = (!string.IsNullOrEmpty(senderName) && Account.SenderName != senderName)
|
||||
|| (!string.IsNullOrEmpty(profilePictureData) && Account.ProfilePictureBase64 != base64ProfilePicture);
|
||||
|
||||
if (!string.IsNullOrEmpty(profilePictureData) && Account.ProfilePictureBase64 != profilePictureData)
|
||||
{
|
||||
Account.ProfilePictureBase64 = profilePictureData;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(senderName) && Account.SenderName != senderName)
|
||||
{
|
||||
Account.SenderName = senderName;
|
||||
}
|
||||
|
||||
if (shouldUpdateAccountProfile)
|
||||
{
|
||||
await _outlookChangeProcessor.UpdateAccountAsync(Account).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to synchronize profile information for {Name}", Account.Name);
|
||||
}
|
||||
return new ProfileInformation(senderName, profilePictureData);
|
||||
}
|
||||
|
||||
#region Mail Integration
|
||||
|
||||
Reference in New Issue
Block a user