Compare commits
1 Commits
v1.8.2
...
feature/Im
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed6a7d71b4 |
@@ -103,9 +103,9 @@ namespace Wino.Core.Domain.Interfaces
|
|||||||
/// Creates a draft MailCopy and MimeMessage based on the given options.
|
/// Creates a draft MailCopy and MimeMessage based on the given options.
|
||||||
/// For forward/reply it would include the referenced message.
|
/// For forward/reply it would include the referenced message.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="accountId">AccountId which should have new draft.</param>
|
/// <param name="composerAccount">Account which should have new draft.</param>
|
||||||
/// <param name="draftCreationOptions">Options like new email/forward/draft.</param>
|
/// <param name="draftCreationOptions">Options like new email/forward/draft.</param>
|
||||||
/// <returns>Draft MailCopy and Draft MimeMessage as base64.</returns>
|
/// <returns>Draft MailCopy and Draft MimeMessage as base64.</returns>
|
||||||
Task<(MailCopy draftMailCopy, string draftBase64MimeMessage)> CreateDraftAsync(Guid accountId, DraftCreationOptions draftCreationOptions);
|
Task<(MailCopy draftMailCopy, string draftBase64MimeMessage)> CreateDraftAsync(MailAccount composerAccount, DraftCreationOptions draftCreationOptions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,12 +109,12 @@ namespace Wino.Core.UWP.Services
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ConnectingHandle = new TaskCompletionSource<bool>();
|
ConnectingHandle ??= new TaskCompletionSource<bool>();
|
||||||
|
|
||||||
Status = WinoServerConnectionStatus.Connecting;
|
|
||||||
|
|
||||||
var connectionCancellationToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(ServerConnectionTimeoutMs));
|
var connectionCancellationToken = new CancellationTokenSource(TimeSpan.FromMilliseconds(ServerConnectionTimeoutMs));
|
||||||
|
|
||||||
|
Status = WinoServerConnectionStatus.Connecting;
|
||||||
|
|
||||||
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
|
await FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
|
||||||
|
|
||||||
// Connection establishment handler is in App.xaml.cs OnBackgroundActivated.
|
// Connection establishment handler is in App.xaml.cs OnBackgroundActivated.
|
||||||
@@ -125,21 +125,10 @@ namespace Wino.Core.UWP.Services
|
|||||||
|
|
||||||
Log.Information("Server connection established successfully.");
|
Log.Information("Server connection established successfully.");
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException canceledException)
|
|
||||||
{
|
|
||||||
Log.Error(canceledException, $"Server process did not start in {ServerConnectionTimeoutMs} ms. Operation is canceled.");
|
|
||||||
|
|
||||||
ConnectingHandle?.TrySetException(canceledException);
|
|
||||||
|
|
||||||
Status = WinoServerConnectionStatus.Failed;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log.Error(ex, "Failed to connect to the server.");
|
Log.Error(ex, "Failed to connect to the server.");
|
||||||
|
|
||||||
ConnectingHandle?.TrySetException(ex);
|
|
||||||
|
|
||||||
Status = WinoServerConnectionStatus.Failed;
|
Status = WinoServerConnectionStatus.Failed;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Wino.Core.Services
|
|||||||
{
|
{
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
.MinimumLevel.ControlledBy(_levelSwitch)
|
.MinimumLevel.ControlledBy(_levelSwitch)
|
||||||
.WriteTo.File(fullLogFilePath, retainedFileCountLimit: 3, rollOnFileSizeLimit: true, rollingInterval: RollingInterval.Day)
|
.WriteTo.File(fullLogFilePath, retainedFileCountLimit: 2, rollOnFileSizeLimit: true, rollingInterval: RollingInterval.Day)
|
||||||
.WriteTo.Debug()
|
.WriteTo.Debug()
|
||||||
.Enrich.FromLogContext()
|
.Enrich.FromLogContext()
|
||||||
.Enrich.WithExceptionDetails()
|
.Enrich.WithExceptionDetails()
|
||||||
|
|||||||
@@ -51,9 +51,8 @@ namespace Wino.Core.Services
|
|||||||
_preferencesService = preferencesService;
|
_preferencesService = preferencesService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<(MailCopy draftMailCopy, string draftBase64MimeMessage)> CreateDraftAsync(Guid accountId, DraftCreationOptions draftCreationOptions)
|
public async Task<(MailCopy draftMailCopy, string draftBase64MimeMessage)> CreateDraftAsync(MailAccount composerAccount, DraftCreationOptions draftCreationOptions)
|
||||||
{
|
{
|
||||||
var composerAccount = await _accountService.GetAccountAsync(accountId).ConfigureAwait(false);
|
|
||||||
var createdDraftMimeMessage = await CreateDraftMimeAsync(composerAccount, draftCreationOptions);
|
var createdDraftMimeMessage = await CreateDraftMimeAsync(composerAccount, draftCreationOptions);
|
||||||
|
|
||||||
var draftFolder = await _folderService.GetSpecialFolderByAccountIdAsync(composerAccount.Id, SpecialFolderType.Draft);
|
var draftFolder = await _folderService.GetSpecialFolderByAccountIdAsync(composerAccount.Id, SpecialFolderType.Draft);
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using MailKit;
|
using MailKit;
|
||||||
using MailKit.Net.Imap;
|
using MailKit.Net.Imap;
|
||||||
using MailKit.Search;
|
using MailKit.Search;
|
||||||
@@ -21,13 +23,14 @@ using Wino.Core.Integration.Processors;
|
|||||||
using Wino.Core.Mime;
|
using Wino.Core.Mime;
|
||||||
using Wino.Core.Requests;
|
using Wino.Core.Requests;
|
||||||
using Wino.Core.Requests.Bundles;
|
using Wino.Core.Requests.Bundles;
|
||||||
|
using Wino.Messaging.Server;
|
||||||
|
|
||||||
namespace Wino.Core.Synchronizers
|
namespace Wino.Core.Synchronizers
|
||||||
{
|
{
|
||||||
public class ImapSynchronizer : BaseSynchronizer<ImapRequest, ImapMessageCreationPackage>
|
public class ImapSynchronizer : BaseSynchronizer<ImapRequest, ImapMessageCreationPackage>
|
||||||
{
|
{
|
||||||
private CancellationTokenSource idleDoneToken;
|
private CancellationTokenSource idleDoneToken;
|
||||||
private CancellationTokenSource cancelInboxListeningToken = new CancellationTokenSource();
|
private CancellationTokenSource cancelInboxListeningToken;
|
||||||
|
|
||||||
private IMailFolder inboxFolder;
|
private IMailFolder inboxFolder;
|
||||||
|
|
||||||
@@ -35,6 +38,8 @@ namespace Wino.Core.Synchronizers
|
|||||||
private readonly ImapClientPool _clientPool;
|
private readonly ImapClientPool _clientPool;
|
||||||
private readonly IImapChangeProcessor _imapChangeProcessor;
|
private readonly IImapChangeProcessor _imapChangeProcessor;
|
||||||
|
|
||||||
|
public bool IsChangeListeningActive { get; set; }
|
||||||
|
|
||||||
// Minimum summary items to Fetch for mail synchronization from IMAP.
|
// Minimum summary items to Fetch for mail synchronization from IMAP.
|
||||||
private readonly MessageSummaryItems mailSynchronizationFlags =
|
private readonly MessageSummaryItems mailSynchronizationFlags =
|
||||||
MessageSummaryItems.Flags |
|
MessageSummaryItems.Flags |
|
||||||
@@ -47,12 +52,6 @@ namespace Wino.Core.Synchronizers
|
|||||||
MessageSummaryItems.References |
|
MessageSummaryItems.References |
|
||||||
MessageSummaryItems.ModSeq;
|
MessageSummaryItems.ModSeq;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Timer that keeps the <see cref="InboxClient"/> alive for the lifetime of the pool.
|
|
||||||
/// Sends NOOP command to the server periodically.
|
|
||||||
/// </summary>
|
|
||||||
private Timer _noOpTimer;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ImapClient that keeps the Inbox folder opened all the time for listening notifications.
|
/// ImapClient that keeps the Inbox folder opened all the time for listening notifications.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -65,44 +64,69 @@ namespace Wino.Core.Synchronizers
|
|||||||
{
|
{
|
||||||
_clientPool = new ImapClientPool(Account.ServerInformation);
|
_clientPool = new ImapClientPool(Account.ServerInformation);
|
||||||
_imapChangeProcessor = imapChangeProcessor;
|
_imapChangeProcessor = imapChangeProcessor;
|
||||||
|
|
||||||
idleDoneToken = new CancellationTokenSource();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
|
||||||
// private async void NoOpTimerTriggered(object state) => await AwaitInboxIdleAsync();
|
|
||||||
|
|
||||||
private async Task AwaitInboxIdleAsync()
|
private async Task AwaitInboxIdleAsync()
|
||||||
{
|
{
|
||||||
if (_inboxIdleClient == null)
|
if (_inboxIdleClient == null)
|
||||||
{
|
{
|
||||||
_logger.Warning("InboxClient is null. Cannot send NOOP command.");
|
_logger.Warning("InboxClient is null. Cannot send IDLE command.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inboxFolder == null)
|
||||||
|
{
|
||||||
|
_logger.Warning("Inbox folder is null. Cannot listen for changes.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
idleDoneToken ??= new CancellationTokenSource();
|
||||||
|
cancelInboxListeningToken ??= new CancellationTokenSource();
|
||||||
|
|
||||||
await _clientPool.EnsureConnectedAsync(_inboxIdleClient);
|
await _clientPool.EnsureConnectedAsync(_inboxIdleClient);
|
||||||
await _clientPool.EnsureAuthenticatedAsync(_inboxIdleClient);
|
await _clientPool.EnsureAuthenticatedAsync(_inboxIdleClient);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (inboxFolder == null)
|
if (!inboxFolder.IsOpen)
|
||||||
{
|
{
|
||||||
inboxFolder = _inboxIdleClient.Inbox;
|
|
||||||
await inboxFolder.OpenAsync(FolderAccess.ReadOnly, cancelInboxListeningToken.Token);
|
await inboxFolder.OpenAsync(FolderAccess.ReadOnly, cancelInboxListeningToken.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
idleDoneToken = new CancellationTokenSource();
|
if (!_inboxIdleClient.IsIdle)
|
||||||
|
{
|
||||||
|
idleDoneToken = new CancellationTokenSource();
|
||||||
|
|
||||||
await _inboxIdleClient.IdleAsync(idleDoneToken.Token, cancelInboxListeningToken.Token);
|
IsChangeListeningActive = true;
|
||||||
|
|
||||||
|
await _inboxIdleClient.IdleAsync(idleDoneToken.Token, cancelInboxListeningToken.Token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ImapProtocolException)
|
||||||
|
{
|
||||||
|
await ReconnectAsync();
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
await ReconnectAsync();
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
Logger.Error(exception, "Error occured while listening for Inbox changes.");
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
idleDoneToken.Dispose();
|
IsChangeListeningActive = false;
|
||||||
idleDoneToken = null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task StopInboxListeningAsync()
|
private async Task ReconnectAsync()
|
||||||
|
{
|
||||||
|
await _clientPool.EnsureConnectedAsync(_inboxIdleClient);
|
||||||
|
await _clientPool.EnsureAuthenticatedAsync(_inboxIdleClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task StopInboxListeningAsync()
|
||||||
{
|
{
|
||||||
if (inboxFolder != null)
|
if (inboxFolder != null)
|
||||||
{
|
{
|
||||||
@@ -111,12 +135,6 @@ namespace Wino.Core.Synchronizers
|
|||||||
inboxFolder.MessageFlagsChanged -= InboxFolderMessageFlagsChanged;
|
inboxFolder.MessageFlagsChanged -= InboxFolderMessageFlagsChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_noOpTimer != null)
|
|
||||||
{
|
|
||||||
_noOpTimer.Dispose();
|
|
||||||
_noOpTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idleDoneToken != null)
|
if (idleDoneToken != null)
|
||||||
{
|
{
|
||||||
idleDoneToken.Cancel();
|
idleDoneToken.Cancel();
|
||||||
@@ -124,9 +142,19 @@ namespace Wino.Core.Synchronizers
|
|||||||
idleDoneToken = null;
|
idleDoneToken = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cancelInboxListeningToken != null)
|
||||||
|
{
|
||||||
|
cancelInboxListeningToken.Cancel();
|
||||||
|
cancelInboxListeningToken.Dispose();
|
||||||
|
cancelInboxListeningToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsChangeListeningActive = false;
|
||||||
|
|
||||||
if (_inboxIdleClient != null)
|
if (_inboxIdleClient != null)
|
||||||
{
|
{
|
||||||
await _inboxIdleClient.DisconnectAsync(true);
|
await _inboxIdleClient.DisconnectAsync(true);
|
||||||
|
|
||||||
_inboxIdleClient.Dispose();
|
_inboxIdleClient.Dispose();
|
||||||
_inboxIdleClient = null;
|
_inboxIdleClient = null;
|
||||||
}
|
}
|
||||||
@@ -136,20 +164,14 @@ namespace Wino.Core.Synchronizers
|
|||||||
/// Tries to connect & authenticate with the given credentials.
|
/// Tries to connect & authenticate with the given credentials.
|
||||||
/// Prepares synchronizer for active listening of Inbox folder.
|
/// Prepares synchronizer for active listening of Inbox folder.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public async Task StartInboxListeningAsync()
|
public async Task<bool> StartInboxListeningAsync()
|
||||||
{
|
{
|
||||||
_inboxIdleClient = await _clientPool.GetClientAsync();
|
_inboxIdleClient = await _clientPool.GetClientAsync();
|
||||||
|
|
||||||
// Run it every 8 minutes after 1 minute delay.
|
|
||||||
// _noOpTimer = new Timer(NoOpTimerTriggered, null, 60000, 8 * 60 * 1000);
|
|
||||||
|
|
||||||
await _clientPool.EnsureConnectedAsync(_inboxIdleClient);
|
|
||||||
await _clientPool.EnsureAuthenticatedAsync(_inboxIdleClient);
|
|
||||||
|
|
||||||
if (!_inboxIdleClient.Capabilities.HasFlag(ImapCapabilities.Idle))
|
if (!_inboxIdleClient.Capabilities.HasFlag(ImapCapabilities.Idle))
|
||||||
{
|
{
|
||||||
_logger.Information("Imap server does not support IDLE command. Listening live changes is not supported for {Name}", Account.Name);
|
_logger.Information("Imap server does not support IDLE command. Listening live changes is not supported for {Name}", Account.Name);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
inboxFolder = _inboxIdleClient.Inbox;
|
inboxFolder = _inboxIdleClient.Inbox;
|
||||||
@@ -157,35 +179,62 @@ namespace Wino.Core.Synchronizers
|
|||||||
if (inboxFolder == null)
|
if (inboxFolder == null)
|
||||||
{
|
{
|
||||||
_logger.Information("Inbox folder is null. Cannot listen for changes.");
|
_logger.Information("Inbox folder is null. Cannot listen for changes.");
|
||||||
return;
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
inboxFolder.CountChanged += InboxFolderCountChanged;
|
inboxFolder.CountChanged += InboxFolderCountChanged;
|
||||||
inboxFolder.MessageExpunged += InboxFolderMessageExpunged;
|
inboxFolder.MessageExpunged += InboxFolderMessageExpunged;
|
||||||
inboxFolder.MessageFlagsChanged += InboxFolderMessageFlagsChanged;
|
inboxFolder.MessageFlagsChanged += InboxFolderMessageFlagsChanged;
|
||||||
|
|
||||||
while (!cancelInboxListeningToken.IsCancellationRequested)
|
try
|
||||||
{
|
{
|
||||||
await AwaitInboxIdleAsync();
|
while (true)
|
||||||
|
{
|
||||||
|
await AwaitInboxIdleAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
Log.Information("Listening Inbox changes for IMAP is canceled.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log.Error(ex, "Error occured while listening for Inbox changes.");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
await StopInboxListeningAsync();
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When a change occurs in the Inbox folder, this method is called to force synchronization.
|
||||||
|
/// Quueing new sync is ignored if the synchronizer is already in a synchronization process.
|
||||||
|
/// </summary>
|
||||||
|
private void ForceInboxIdleSynchronization()
|
||||||
|
{
|
||||||
|
// Do not try to synchronize if we're already in a synchronization process.
|
||||||
|
|
||||||
|
if (State == AccountSynchronizerState.Idle)
|
||||||
|
{
|
||||||
|
var options = new SynchronizationOptions()
|
||||||
|
{
|
||||||
|
Type = SynchronizationType.Inbox,
|
||||||
|
AccountId = Account.Id
|
||||||
|
};
|
||||||
|
|
||||||
|
WeakReferenceMessenger.Default.Send(new NewSynchronizationRequested(options, SynchronizationSource.Server));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InboxFolderMessageFlagsChanged(object sender, MessageFlagsChangedEventArgs e)
|
private void InboxFolderMessageFlagsChanged(object sender, MessageFlagsChangedEventArgs e)
|
||||||
{
|
=> ForceInboxIdleSynchronization();
|
||||||
Console.WriteLine("Flags have changed for message #{0} ({1}).", e.Index, e.Flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InboxFolderMessageExpunged(object sender, MessageEventArgs e)
|
private void InboxFolderMessageExpunged(object sender, MessageEventArgs e)
|
||||||
{
|
=> ForceInboxIdleSynchronization();
|
||||||
_logger.Information("Inbox folder message expunged");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InboxFolderCountChanged(object sender, EventArgs e)
|
private void InboxFolderCountChanged(object sender, EventArgs e)
|
||||||
{
|
=> ForceInboxIdleSynchronization();
|
||||||
_logger.Information("Inbox folder count changed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Parses List of string of mail copy ids and return valid uIds.
|
/// Parses List of string of mail copy ids and return valid uIds.
|
||||||
@@ -922,7 +971,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.Ascending));
|
var batchedMissingMailIds = missingMailIds.Batch(50).Select(a => new UniqueIdSet(a, SortOrder.Descending));
|
||||||
|
|
||||||
foreach (var batchMissingMailIds in batchedMissingMailIds)
|
foreach (var batchMissingMailIds in batchedMissingMailIds)
|
||||||
{
|
{
|
||||||
@@ -998,5 +1047,21 @@ namespace Wino.Core.Synchronizers
|
|||||||
/// <param name="remoteFolder">Remote folder</param>
|
/// <param name="remoteFolder">Remote folder</param>
|
||||||
/// <param name="localFolder">Local folder.</param>
|
/// <param name="localFolder">Local folder.</param>
|
||||||
public bool ShouldUpdateFolder(IMailFolder remoteFolder, MailItemFolder localFolder) => remoteFolder.Name != localFolder.FolderName;
|
public bool ShouldUpdateFolder(IMailFolder remoteFolder, MailItemFolder localFolder) => remoteFolder.Name != localFolder.FolderName;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 1. Stops active inbox listening.
|
||||||
|
/// 2. Disconnects client pool with all clients.
|
||||||
|
/// 3. Disposes everything gracefully.
|
||||||
|
///
|
||||||
|
/// Usefull for killing server scenario where nothing is needed to be connected to IMAP server anymore.
|
||||||
|
/// </summary>
|
||||||
|
public async Task KillAsync()
|
||||||
|
{
|
||||||
|
Log.Debug("Killing ImapSynchronizer for {Name}", Account.Name);
|
||||||
|
|
||||||
|
await StopInboxListeningAsync();
|
||||||
|
|
||||||
|
_clientPool.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -777,7 +777,7 @@ namespace Wino.Mail.ViewModels
|
|||||||
MailToUri = _launchProtocolService.MailToUri
|
MailToUri = _launchProtocolService.MailToUri
|
||||||
};
|
};
|
||||||
|
|
||||||
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(account.Id, draftOptions).ConfigureAwait(false);
|
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(account, draftOptions).ConfigureAwait(false);
|
||||||
|
|
||||||
var draftPreparationRequest = new DraftPreparationRequest(account, draftMailCopy, draftBase64MimeMessage);
|
var draftPreparationRequest = new DraftPreparationRequest(account, draftMailCopy, draftBase64MimeMessage);
|
||||||
await _winoRequestDelegator.ExecuteAsync(draftPreparationRequest);
|
await _winoRequestDelegator.ExecuteAsync(draftPreparationRequest);
|
||||||
|
|||||||
@@ -610,9 +610,6 @@ namespace Wino.Mail.ViewModels
|
|||||||
|
|
||||||
if (ActiveFolder == null) return;
|
if (ActiveFolder == null) return;
|
||||||
|
|
||||||
// At least accounts must match.
|
|
||||||
if (ActiveFolder.HandlingFolders.Any(a => a.MailAccountId != addedMail.AssignedAccount.Id)) return;
|
|
||||||
|
|
||||||
// Messages coming to sent or draft folder must be inserted regardless of the filter.
|
// Messages coming to sent or draft folder must be inserted regardless of the filter.
|
||||||
bool shouldPreventIgnoringFilter = addedMail.AssignedFolder.SpecialFolderType == SpecialFolderType.Draft ||
|
bool shouldPreventIgnoringFilter = addedMail.AssignedFolder.SpecialFolderType == SpecialFolderType.Draft ||
|
||||||
addedMail.AssignedFolder.SpecialFolderType == SpecialFolderType.Sent;
|
addedMail.AssignedFolder.SpecialFolderType == SpecialFolderType.Sent;
|
||||||
@@ -620,7 +617,6 @@ namespace Wino.Mail.ViewModels
|
|||||||
// Item does not belong to this folder and doesn't have special type to be inserted.
|
// Item does not belong to this folder and doesn't have special type to be inserted.
|
||||||
if (!shouldPreventIgnoringFilter && !ActiveFolder.HandlingFolders.Any(a => a.Id == addedMail.AssignedFolder.Id)) return;
|
if (!shouldPreventIgnoringFilter && !ActiveFolder.HandlingFolders.Any(a => a.Id == addedMail.AssignedFolder.Id)) return;
|
||||||
|
|
||||||
// Item should be prevented from being added to the list due to filter.
|
|
||||||
if (!shouldPreventIgnoringFilter && ShouldPreventItemAdd(addedMail)) return;
|
if (!shouldPreventIgnoringFilter && ShouldPreventItemAdd(addedMail)) return;
|
||||||
|
|
||||||
await MailCollection.AddAsync(addedMail);
|
await MailCollection.AddAsync(addedMail);
|
||||||
|
|||||||
@@ -271,7 +271,7 @@ namespace Wino.Mail.ViewModels
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(initializedMailItemViewModel.AssignedAccount.Id, draftOptions).ConfigureAwait(false);
|
var (draftMailCopy, draftBase64MimeMessage) = await _mailService.CreateDraftAsync(initializedMailItemViewModel.AssignedAccount, draftOptions).ConfigureAwait(false);
|
||||||
|
|
||||||
var draftPreparationRequest = new DraftPreparationRequest(initializedMailItemViewModel.AssignedAccount, draftMailCopy, draftBase64MimeMessage, initializedMailItemViewModel.MailCopy);
|
var draftPreparationRequest = new DraftPreparationRequest(initializedMailItemViewModel.AssignedAccount, draftMailCopy, draftBase64MimeMessage, initializedMailItemViewModel.MailCopy);
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@
|
|||||||
Description="Mail client designed for Windows 11"
|
Description="Mail client designed for Windows 11"
|
||||||
BackgroundColor="transparent">
|
BackgroundColor="transparent">
|
||||||
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" Square71x71Logo="Assets\SmallTile.png" Square310x310Logo="Assets\LargeTile.png"/>
|
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" Square71x71Logo="Assets\SmallTile.png" Square310x310Logo="Assets\LargeTile.png"/>
|
||||||
<uap:SplashScreen Image="Assets\SplashScreen.png" BackgroundColor="transparent" uap5:Optional="true" />
|
<uap:SplashScreen Image="Assets\SplashScreen.png" BackgroundColor="transparent"/>
|
||||||
<uap:LockScreen BadgeLogo="Assets\BadgeLogo.png" Notification="badgeAndTileText"/>
|
<uap:LockScreen BadgeLogo="Assets\BadgeLogo.png" Notification="badgeAndTileText"/>
|
||||||
</uap:VisualElements>
|
</uap:VisualElements>
|
||||||
<Extensions>
|
<Extensions>
|
||||||
|
|||||||
@@ -8,5 +8,5 @@ namespace Wino.Messaging.Server
|
|||||||
/// Triggers a new synchronization if possible.
|
/// Triggers a new synchronization if possible.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="Options">Options for synchronization.</param>
|
/// <param name="Options">Options for synchronization.</param>
|
||||||
public record NewSynchronizationRequested(SynchronizationOptions Options, SynchronizationSource Source) : IClientMessage;
|
public record NewSynchronizationRequested(SynchronizationOptions Options, SynchronizationSource Source) : IClientMessage, IUIMessage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<Identity
|
<Identity
|
||||||
Name="58272BurakKSE.WinoMailPreview"
|
Name="58272BurakKSE.WinoMailPreview"
|
||||||
Publisher="CN=51FBDAF3-E212-4149-89A2-A2636B3BC911"
|
Publisher="CN=51FBDAF3-E212-4149-89A2-A2636B3BC911"
|
||||||
Version="1.8.1.0" />
|
Version="1.8.0.0" />
|
||||||
|
|
||||||
<Extensions>
|
<Extensions>
|
||||||
<!-- Publisher Cache Folders -->
|
<!-- Publisher Cache Folders -->
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
Square150x150Logo="Images\Square150x150Logo.png"
|
Square150x150Logo="Images\Square150x150Logo.png"
|
||||||
Square44x44Logo="Images\Square44x44Logo.png">
|
Square44x44Logo="Images\Square44x44Logo.png">
|
||||||
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png"/>
|
<uap:DefaultTile Wide310x150Logo="Images\Wide310x150Logo.png" Square71x71Logo="Images\SmallTile.png" Square310x310Logo="Images\LargeTile.png"/>
|
||||||
<uap:SplashScreen Image="Images\SplashScreen.png" uap5:Optional="true" />
|
<uap:SplashScreen Image="Images\SplashScreen.png" />
|
||||||
</uap:VisualElements>
|
</uap:VisualElements>
|
||||||
|
|
||||||
<Extensions>
|
<Extensions>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ using Wino.Core.Domain.Models.Requests;
|
|||||||
using Wino.Core.Domain.Models.Synchronization;
|
using Wino.Core.Domain.Models.Synchronization;
|
||||||
using Wino.Core.Integration.Json;
|
using Wino.Core.Integration.Json;
|
||||||
using Wino.Core.Services;
|
using Wino.Core.Services;
|
||||||
|
using Wino.Core.Synchronizers;
|
||||||
using Wino.Messaging;
|
using Wino.Messaging;
|
||||||
using Wino.Messaging.Client.Authorization;
|
using Wino.Messaging.Client.Authorization;
|
||||||
using Wino.Messaging.Enums;
|
using Wino.Messaging.Enums;
|
||||||
@@ -40,7 +41,8 @@ namespace Wino.Server
|
|||||||
IRecipient<AccountSynchronizerStateChanged>,
|
IRecipient<AccountSynchronizerStateChanged>,
|
||||||
IRecipient<RefreshUnreadCountsMessage>,
|
IRecipient<RefreshUnreadCountsMessage>,
|
||||||
IRecipient<ServerTerminationModeChanged>,
|
IRecipient<ServerTerminationModeChanged>,
|
||||||
IRecipient<AccountSynchronizationProgressUpdatedMessage>
|
IRecipient<AccountSynchronizationProgressUpdatedMessage>,
|
||||||
|
IRecipient<NewSynchronizationRequested>
|
||||||
{
|
{
|
||||||
private readonly System.Timers.Timer _timer;
|
private readonly System.Timers.Timer _timer;
|
||||||
private static object connectionLock = new object();
|
private static object connectionLock = new object();
|
||||||
@@ -57,6 +59,8 @@ namespace Wino.Server
|
|||||||
TypeInfoResolver = new ServerRequestTypeInfoResolver()
|
TypeInfoResolver = new ServerRequestTypeInfoResolver()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private Task imapIdleTask = null;
|
||||||
|
|
||||||
public ServerContext(IDatabaseService databaseService,
|
public ServerContext(IDatabaseService databaseService,
|
||||||
IApplicationConfiguration applicationFolderConfiguration,
|
IApplicationConfiguration applicationFolderConfiguration,
|
||||||
ISynchronizerFactory synchronizerFactory,
|
ISynchronizerFactory synchronizerFactory,
|
||||||
@@ -171,7 +175,11 @@ namespace Wino.Server
|
|||||||
|
|
||||||
AppServiceConnectionStatus status = await connection.OpenAsync();
|
AppServiceConnectionStatus status = await connection.OpenAsync();
|
||||||
|
|
||||||
if (status != AppServiceConnectionStatus.Success)
|
if (status == AppServiceConnectionStatus.Success)
|
||||||
|
{
|
||||||
|
imapIdleTask = RegisterImapSynchronizerChangesAsync();
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Log.Error("Opening server connection failed. Status: {status}", status);
|
Log.Error("Opening server connection failed. Status: {status}", status);
|
||||||
|
|
||||||
@@ -179,6 +187,53 @@ namespace Wino.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task PerformForAllImapSynchronizers(Action<ImapSynchronizer> action)
|
||||||
|
{
|
||||||
|
var allAccounts = await _accountService.GetAccountsAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
|
var imapAccounts = allAccounts.FindAll(a => a.ProviderType == MailProviderType.IMAP4);
|
||||||
|
|
||||||
|
foreach (var account in imapAccounts)
|
||||||
|
{
|
||||||
|
var synchronizer = await _synchronizerFactory.GetAccountSynchronizerAsync(account.Id).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (synchronizer == null) continue;
|
||||||
|
if (synchronizer is not ImapSynchronizer accountImapSynchronizer)
|
||||||
|
{
|
||||||
|
Log.Warning("Account '{Name}' has IMAP4 type but synchronizer is not ImapSynchronizer.", account.Name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
action(accountImapSynchronizer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hooks all ImapSynchronizer instances to listen for changes like new mail, folder rename etc.
|
||||||
|
/// </summary>
|
||||||
|
private Task RegisterImapSynchronizerChangesAsync()
|
||||||
|
=> PerformForAllImapSynchronizers(async accountImapSynchronizer =>
|
||||||
|
{
|
||||||
|
// First make sure that listening is stopped.
|
||||||
|
|
||||||
|
await accountImapSynchronizer.StopInboxListeningAsync();
|
||||||
|
|
||||||
|
var startListeningTask = accountImapSynchronizer.StartInboxListeningAsync();
|
||||||
|
|
||||||
|
await Task.Delay(10000); // Wait for 10 seconds.
|
||||||
|
|
||||||
|
if (startListeningTask.Exception == null)
|
||||||
|
{
|
||||||
|
Log.Information("IMAP change listening started for account '{Name}'.", accountImapSynchronizer.Account.Name);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
public Task DisposeActiveImapConnectionsAsync()
|
||||||
|
=> PerformForAllImapSynchronizers(async accountImapSynchronizer =>
|
||||||
|
{
|
||||||
|
await accountImapSynchronizer.KillAsync();
|
||||||
|
});
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Disposes current connection to UWP app service.
|
/// Disposes current connection to UWP app service.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -219,20 +274,7 @@ namespace Wino.Server
|
|||||||
{ MessageConstants.MessageDataTypeKey, message.GetType().Name }
|
{ MessageConstants.MessageDataTypeKey, message.GetType().Name }
|
||||||
};
|
};
|
||||||
|
|
||||||
try
|
await connection.SendMessageAsync(set);
|
||||||
{
|
|
||||||
await connection.SendMessageAsync(set);
|
|
||||||
}
|
|
||||||
catch (InvalidOperationException)
|
|
||||||
{
|
|
||||||
// Connection might've been disposed during the SendMessageAsync call.
|
|
||||||
// This is a safe way to handle the exception.
|
|
||||||
// We don't lock the connection since this request may take sometime to complete.
|
|
||||||
}
|
|
||||||
catch (Exception exception)
|
|
||||||
{
|
|
||||||
Log.Error(exception, "SendMessageAsync threw an exception");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnConnectionClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
|
private void OnConnectionClosed(AppServiceConnection sender, AppServiceClosedEventArgs args)
|
||||||
@@ -350,5 +392,10 @@ namespace Wino.Server
|
|||||||
|
|
||||||
App.Current.ChangeNotifyIconVisiblity(isServerTrayIconVisible);
|
App.Current.ChangeNotifyIconVisiblity(isServerTrayIconVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async void Receive(NewSynchronizationRequested message)
|
||||||
|
{
|
||||||
|
await ExecuteServerMessageSafeAsync(null, message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ namespace Wino.Server
|
|||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
public Task LaunchWinoAsync()
|
public Task LaunchWinoAsync()
|
||||||
{
|
{
|
||||||
|
// Stop listening active imap synchronizer changes and disconnect gracefully.
|
||||||
|
return Context.DisposeActiveImapConnectionsAsync();
|
||||||
//var opt = new SynchronizationOptions()
|
//var opt = new SynchronizationOptions()
|
||||||
//{
|
//{
|
||||||
// Type = Wino.Core.Domain.Enums.SynchronizationType.Full,
|
// Type = Wino.Core.Domain.Enums.SynchronizationType.Full,
|
||||||
@@ -37,7 +39,7 @@ namespace Wino.Server
|
|||||||
|
|
||||||
// return Task.CompletedTask;
|
// return Task.CompletedTask;
|
||||||
|
|
||||||
return Launcher.LaunchUriAsync(new Uri($"{App.WinoMailLaunchProtocol}:")).AsTask();
|
// return Launcher.LaunchUriAsync(new Uri($"{App.WinoMailLaunchProtocol}:")).AsTask();
|
||||||
//await _notificationBuilder.CreateNotificationsAsync(Guid.Empty, new List<IMailItem>()
|
//await _notificationBuilder.CreateNotificationsAsync(Guid.Empty, new List<IMailItem>()
|
||||||
//{
|
//{
|
||||||
// new MailCopy(){ UniqueId = Guid.Parse("8f25d2a0-4448-4fee-96a9-c9b25a19e866")}
|
// new MailCopy(){ UniqueId = Guid.Parse("8f25d2a0-4448-4fee-96a9-c9b25a19e866")}
|
||||||
@@ -71,6 +73,9 @@ namespace Wino.Server
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop listening active imap synchronizer changes and disconnect gracefully.
|
||||||
|
await Context.DisposeActiveImapConnectionsAsync();
|
||||||
|
|
||||||
Application.Current.Shutdown();
|
Application.Current.Shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user