IMAP Improvements (#558)
* Fixing an issue where scrollviewer overrides a part of template in mail list. Adjusted zoomed out header grid's corner radius. * IDLE implementation, imap synchronization strategies basics and condstore synchronization. * Adding iCloud and Yahoo as special IMAP handling scenario. * iCloud special imap handling. * Support for killing synchronizers. * Update privacy policy url. * Batching condstore downloads into 50, using SORT extension for searches if supported. * Bumping some nugets. More on the imap synchronizers. * Delegating idle synchronizations to server to post-sync operations. * Update mailkit to resolve qresync bug with iCloud. * Fixing remote highest mode seq checks for qresync and condstore synchronizers. * Yahoo custom settings. * Bump google sdk package. * Fixing the build issue.... * NRE on canceled token accounts during setup. * Server crash handlers. * Remove ARM32. Upgrade server to .NET 9. * Fix icons for yahoo and apple. * Fixed an issue where disabled folders causing an exception on forced sync. * Remove smtp encoding constraint. * Remove commented code. * Fixing merge conflict * Addressing double registrations for mailkit remote folder events in synchronizers. * Making sure idle canceled result is not reported. * Fixing custom imap server dialog opening. * Fixing the issue with account creation making the previously selected account as selected as well. * Fixing app close behavior and logging app close.
This commit is contained in:
@@ -25,8 +25,6 @@ namespace Wino.Core.Integration
|
||||
/// Provides a pooling mechanism for ImapClient.
|
||||
/// Makes sure that we don't have too many connections to the server.
|
||||
/// Rents a connected & authenticated client from the pool all the time.
|
||||
/// TODO: Keeps the clients alive by sending NOOP command periodically.
|
||||
/// TODO: Listens to the Inbox folder for new messages.
|
||||
/// </summary>
|
||||
/// <param name="customServerInformation">Connection/Authentication info to be used to configure ImapClient.</param>
|
||||
public class ImapClientPool : IDisposable
|
||||
@@ -48,14 +46,16 @@ namespace Wino.Core.Integration
|
||||
|
||||
public bool ThrowOnSSLHandshakeCallback { get; set; }
|
||||
public ImapClientPoolOptions ImapClientPoolOptions { get; }
|
||||
internal WinoImapClient IdleClient { get; set; }
|
||||
|
||||
private readonly int MinimumPoolSize = 5;
|
||||
|
||||
private readonly ConcurrentStack<ImapClient> _clients = [];
|
||||
private readonly ConcurrentStack<IImapClient> _clients = [];
|
||||
private readonly SemaphoreSlim _semaphore;
|
||||
private readonly CustomServerInformation _customServerInformation;
|
||||
private readonly Stream _protocolLogStream;
|
||||
private readonly ILogger _logger = Log.ForContext<ImapClientPool>();
|
||||
private bool _disposedValue;
|
||||
|
||||
public ImapClientPool(ImapClientPoolOptions imapClientPoolOptions)
|
||||
{
|
||||
@@ -74,7 +74,7 @@ namespace Wino.Core.Integration
|
||||
/// Reconnects and reauthenticates if necessary.
|
||||
/// </summary>
|
||||
/// <param name="isCreatedNew">Whether the client has been newly created.</param>
|
||||
private async Task EnsureCapabilitiesAsync(ImapClient client, bool isCreatedNew)
|
||||
private async Task EnsureCapabilitiesAsync(IImapClient client, bool isCreatedNew)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -84,6 +84,9 @@ namespace Wino.Core.Integration
|
||||
|
||||
if ((isCreatedNew || isReconnected) && client.IsConnected)
|
||||
{
|
||||
if (client.Capabilities.HasFlag(ImapCapabilities.Compress))
|
||||
await client.CompressAsync();
|
||||
|
||||
// Identify if the server supports ID extension.
|
||||
// Some servers require it pre-authentication, some post-authentication.
|
||||
// We'll observe the response here and do it after authentication if needed.
|
||||
@@ -113,10 +116,11 @@ namespace Wino.Core.Integration
|
||||
|
||||
// Activate post-auth capabilities.
|
||||
if (client.Capabilities.HasFlag(ImapCapabilities.QuickResync))
|
||||
await client.EnableQuickResyncAsync();
|
||||
{
|
||||
await client.EnableQuickResyncAsync().ConfigureAwait(false);
|
||||
|
||||
if (client.Capabilities.HasFlag(ImapCapabilities.Compress))
|
||||
await client.CompressAsync();
|
||||
if (client is WinoImapClient winoImapClient) winoImapClient.IsQResyncEnabled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -145,11 +149,11 @@ namespace Wino.Core.Integration
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
|
||||
public async Task<ImapClient> GetClientAsync()
|
||||
public async Task<IImapClient> GetClientAsync()
|
||||
{
|
||||
await _semaphore.WaitAsync();
|
||||
|
||||
if (_clients.TryPop(out ImapClient item))
|
||||
if (_clients.TryPop(out IImapClient item))
|
||||
{
|
||||
await EnsureCapabilitiesAsync(item, false);
|
||||
|
||||
@@ -163,20 +167,24 @@ namespace Wino.Core.Integration
|
||||
return client;
|
||||
}
|
||||
|
||||
public void Release(ImapClient item, bool destroyClient = false)
|
||||
public void Release(IImapClient item, bool destroyClient = false)
|
||||
{
|
||||
if (item != null)
|
||||
{
|
||||
if (destroyClient)
|
||||
{
|
||||
lock (item.SyncRoot)
|
||||
if (item.IsConnected)
|
||||
{
|
||||
item.Disconnect(true);
|
||||
lock (item.SyncRoot)
|
||||
{
|
||||
item.Disconnect(quit: true);
|
||||
}
|
||||
}
|
||||
|
||||
_clients.TryPop(out _);
|
||||
item.Dispose();
|
||||
}
|
||||
else
|
||||
else if (!_disposedValue)
|
||||
{
|
||||
_clients.Push(item);
|
||||
}
|
||||
@@ -185,23 +193,15 @@ namespace Wino.Core.Integration
|
||||
}
|
||||
}
|
||||
|
||||
public void DestroyClient(ImapClient client)
|
||||
private IImapClient CreateNewClient()
|
||||
{
|
||||
if (client == null) return;
|
||||
|
||||
client.Disconnect(true);
|
||||
client.Dispose();
|
||||
}
|
||||
|
||||
private ImapClient CreateNewClient()
|
||||
{
|
||||
ImapClient client = null;
|
||||
WinoImapClient client = null;
|
||||
|
||||
// Make sure to create a ImapClient with a protocol logger if enabled.
|
||||
|
||||
client = _protocolLogStream != null
|
||||
? new ImapClient(new ProtocolLogger(_protocolLogStream))
|
||||
: new ImapClient();
|
||||
? new WinoImapClient(new ProtocolLogger(_protocolLogStream))
|
||||
: new WinoImapClient();
|
||||
|
||||
HttpProxyClient proxyClient = null;
|
||||
|
||||
@@ -213,7 +213,7 @@ namespace Wino.Core.Integration
|
||||
|
||||
client.ProxyClient = proxyClient;
|
||||
|
||||
_logger.Debug("Created new ImapClient. Current clients: {Count}", _clients.Count);
|
||||
_logger.Debug("Creating new ImapClient. Current clients: {Count}", _clients.Count);
|
||||
|
||||
return client;
|
||||
}
|
||||
@@ -229,7 +229,7 @@ namespace Wino.Core.Integration
|
||||
};
|
||||
|
||||
/// <returns>True if the connection is newly established.</returns>
|
||||
public async Task<bool> EnsureConnectedAsync(ImapClient client)
|
||||
public async Task<bool> EnsureConnectedAsync(IImapClient client)
|
||||
{
|
||||
if (client.IsConnected) return false;
|
||||
|
||||
@@ -263,8 +263,20 @@ namespace Wino.Core.Integration
|
||||
{
|
||||
if (_protocolLogStream == null) return;
|
||||
|
||||
var messageBytes = Encoding.UTF8.GetBytes($"W: {message}\n");
|
||||
_protocolLogStream.Write(messageBytes, 0, messageBytes.Length);
|
||||
try
|
||||
{
|
||||
var messageBytes = Encoding.UTF8.GetBytes($"W: {message}\n");
|
||||
_protocolLogStream.Write(messageBytes, 0, messageBytes.Length);
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
Log.Warning($"Protocol log stream is disposed. Cannot write to it.");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
bool MyServerCertificateValidationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
||||
@@ -281,7 +293,7 @@ namespace Wino.Core.Integration
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task EnsureAuthenticatedAsync(ImapClient client)
|
||||
public async Task EnsureAuthenticatedAsync(IImapClient client)
|
||||
{
|
||||
if (client.IsAuthenticated) return;
|
||||
|
||||
@@ -319,27 +331,38 @@ namespace Wino.Core.Integration
|
||||
};
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!_disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_clients.ForEach(client =>
|
||||
{
|
||||
lock (client.SyncRoot)
|
||||
{
|
||||
client.Disconnect(true);
|
||||
}
|
||||
});
|
||||
|
||||
_clients.ForEach(client =>
|
||||
{
|
||||
client.Dispose();
|
||||
});
|
||||
|
||||
_clients.Clear();
|
||||
|
||||
_protocolLogStream?.Dispose();
|
||||
}
|
||||
|
||||
_disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_clients.ForEach(client =>
|
||||
{
|
||||
lock (client.SyncRoot)
|
||||
{
|
||||
client.Disconnect(true);
|
||||
}
|
||||
});
|
||||
|
||||
_clients.ForEach(client =>
|
||||
{
|
||||
client.Dispose();
|
||||
});
|
||||
|
||||
_clients.Clear();
|
||||
|
||||
if (_protocolLogStream != null)
|
||||
{
|
||||
_protocolLogStream.Dispose();
|
||||
}
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user