Files
Wino-Mail/Wino.Core/Integration/ImapClientPool.cs
Burak Kaan Köse 12d3814626 Initial commit.
2024-04-18 01:44:37 +02:00

180 lines
6.3 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using MailKit.Net.Imap;
using MailKit.Net.Proxy;
using MailKit.Security;
using Serilog;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Exceptions;
namespace Wino.Core.Integration
{
/// <summary>
/// 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
{
// Hardcoded implementation details for ID extension if the server supports.
// Some providers like Chinese 126 require Id to be sent before authentication.
// We don't expose any customer data here. Therefore it's safe for now.
// Later on maybe we can make it configurable and leave it to the user with passing
// real implementation details.
private readonly ImapImplementation _implementation = new ImapImplementation()
{
Version = "1.0",
OS = "Windows",
Vendor = "Wino"
};
private const int MaxPoolSize = 5;
private readonly ConcurrentBag<ImapClient> _clients = [];
private readonly SemaphoreSlim _semaphore = new(MaxPoolSize);
private readonly CustomServerInformation _customServerInformation;
private readonly ILogger _logger = Log.ForContext<ImapClientPool>();
public ImapClientPool(CustomServerInformation customServerInformation)
{
_customServerInformation = customServerInformation;
}
private async Task EnsureConnectivityAsync(ImapClient client, bool isCreatedNew)
{
try
{
await EnsureConnectedAsync(client);
if (isCreatedNew && client.IsConnected)
{
// Activate supported pre-auth capabilities.
if (client.Capabilities.HasFlag(ImapCapabilities.Compress))
await client.CompressAsync();
// Identify if the server supports ID extension.
if (client.Capabilities.HasFlag(ImapCapabilities.Id))
await client.IdentifyAsync(_implementation);
}
await EnsureAuthenticatedAsync(client);
if (isCreatedNew && client.IsAuthenticated)
{
// Activate post-auth capabilities.
if (client.Capabilities.HasFlag(ImapCapabilities.QuickResync))
await client.EnableQuickResyncAsync();
}
}
catch (Exception ex)
{
throw new ImapClientPoolException(ex);
}
finally
{
// Release it even if it fails.
_semaphore.Release();
}
}
public async Task<ImapClient> GetClientAsync()
{
await _semaphore.WaitAsync();
if (_clients.TryTake(out ImapClient item))
{
await EnsureConnectivityAsync(item, false);
return item;
}
var client = CreateNewClient();
await EnsureConnectivityAsync(client, true);
return client;
}
public void Release(ImapClient item)
{
if (item != null)
{
_clients.Add(item);
_semaphore.Release();
}
}
public ImapClient CreateNewClient()
{
var client = new ImapClient();
HttpProxyClient proxyClient = null;
// Add proxy client if exists.
if (!string.IsNullOrEmpty(_customServerInformation.ProxyServer))
{
proxyClient = new HttpProxyClient(_customServerInformation.ProxyServer, int.Parse(_customServerInformation.ProxyServerPort));
}
client.ProxyClient = proxyClient;
_logger.Debug("Created new ImapClient. Current clients: {Count}", _clients.Count);
return client;
}
private SecureSocketOptions GetSocketOptions(ImapConnectionSecurity connectionSecurity)
=> connectionSecurity switch
{
ImapConnectionSecurity.Auto => SecureSocketOptions.Auto,
ImapConnectionSecurity.None => SecureSocketOptions.None,
ImapConnectionSecurity.StartTls => SecureSocketOptions.StartTlsWhenAvailable,
ImapConnectionSecurity.SslTls => SecureSocketOptions.SslOnConnect,
_ => SecureSocketOptions.None
};
public async Task EnsureConnectedAsync(ImapClient client)
{
if (client.IsConnected) return;
await client.ConnectAsync(_customServerInformation.IncomingServer,
int.Parse(_customServerInformation.IncomingServerPort),
GetSocketOptions(_customServerInformation.IncomingServerSocketOption));
}
public async Task EnsureAuthenticatedAsync(ImapClient client)
{
if (client.IsAuthenticated) return;
switch (_customServerInformation.IncomingAuthenticationMethod)
{
case ImapAuthenticationMethod.Auto:
break;
case ImapAuthenticationMethod.None:
break;
case ImapAuthenticationMethod.NormalPassword:
break;
case ImapAuthenticationMethod.EncryptedPassword:
break;
case ImapAuthenticationMethod.Ntlm:
break;
case ImapAuthenticationMethod.CramMd5:
break;
case ImapAuthenticationMethod.DigestMd5:
break;
default:
break;
}
await client.AuthenticateAsync(_customServerInformation.IncomingServerUsername, _customServerInformation.IncomingServerPassword);
}
}
}