Reworked IMAP setup flow. Implemented easy way to share protocol log on failure if possible.

This commit is contained in:
Burak Kaan Köse
2024-06-17 02:16:06 +02:00
parent a788b1706b
commit 49afed7751
24 changed files with 1000 additions and 331 deletions

View File

@@ -1,7 +1,10 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MailKit;
using MailKit.Net.Imap;
using MailKit.Net.Proxy;
using MailKit.Security;
@@ -20,7 +23,7 @@ namespace Wino.Core.Integration
/// 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
public class ImapClientPool : IDisposable
{
// Hardcoded implementation details for ID extension if the server supports.
// Some providers like Chinese 126 require Id to be sent before authentication.
@@ -40,11 +43,13 @@ namespace Wino.Core.Integration
private readonly ConcurrentBag<ImapClient> _clients = [];
private readonly SemaphoreSlim _semaphore = new(MaxPoolSize);
private readonly CustomServerInformation _customServerInformation;
private readonly Stream _protocolLogStream;
private readonly ILogger _logger = Log.ForContext<ImapClientPool>();
public ImapClientPool(CustomServerInformation customServerInformation)
public ImapClientPool(CustomServerInformation customServerInformation, Stream protocolLogStream = null)
{
_customServerInformation = customServerInformation;
_protocolLogStream = protocolLogStream;
}
private async Task EnsureConnectivityAsync(ImapClient client, bool isCreatedNew)
@@ -75,7 +80,7 @@ namespace Wino.Core.Integration
}
catch (Exception ex)
{
throw new ImapClientPoolException(ex);
throw new ImapClientPoolException(ex, GetProtocolLogContent());
}
finally
{
@@ -84,6 +89,18 @@ namespace Wino.Core.Integration
}
}
public string GetProtocolLogContent()
{
if (_protocolLogStream == null) return default;
// Set the position to the beginning of the stream in case it is not already at the start
if (_protocolLogStream.CanSeek)
_protocolLogStream.Seek(0, SeekOrigin.Begin);
using var reader = new StreamReader(_protocolLogStream, Encoding.UTF8, true, 1024, leaveOpen: true);
return reader.ReadToEnd();
}
public async Task<ImapClient> GetClientAsync()
{
await _semaphore.WaitAsync();
@@ -113,7 +130,17 @@ namespace Wino.Core.Integration
public ImapClient CreateNewClient()
{
var client = new ImapClient();
ImapClient client = null;
// Make sure to create a ImapClient with a protocol logger if enabled.
if (_protocolLogStream != null)
{
client = new ImapClient(new ProtocolLogger(_protocolLogStream));
}
else
{
client = new ImapClient();
}
HttpProxyClient proxyClient = null;
@@ -175,5 +202,20 @@ namespace Wino.Core.Integration
await client.AuthenticateAsync(_customServerInformation.IncomingServerUsername, _customServerInformation.IncomingServerPassword);
}
public void Dispose()
{
foreach (var client in _clients)
{
client.Disconnect(true);
client.Dispose();
}
if (_protocolLogStream != null)
{
_protocolLogStream.Flush();
_protocolLogStream.Dispose();
}
}
}
}

View File

@@ -4,8 +4,9 @@ namespace Wino.Core.Messages.Mails
{
/// <summary>
/// When IMAP setup dialog requestes back breadcrumb navigation.
/// Not providing PageType will go back to previous page by doing back navigation.
/// </summary>
/// <param name="PageType">Type to go back.</param>
/// <param name="Parameter">Back parameters.</param>
public record ImapSetupBackNavigationRequested(Type PageType, object Parameter);
public record ImapSetupBackNavigationRequested(Type PageType = null, object Parameter = null);
}

View File

@@ -1,9 +1,8 @@
using System.IO;
using System.Threading.Tasks;
using MailKit;
using MailKit.Net.Imap;
using Wino.Core.Domain.Entities;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Integration;
namespace Wino.Core.Services
{
@@ -14,39 +13,40 @@ namespace Wino.Core.Services
private readonly IPreferencesService _preferencesService;
private readonly IAppInitializerService _appInitializerService;
private Stream _protocolLogStream;
public ImapTestService(IPreferencesService preferencesService, IAppInitializerService appInitializerService)
{
_preferencesService = preferencesService;
_appInitializerService = appInitializerService;
}
private void EnsureProtocolLogFileExists()
{
// Create new file for protocol logger.
var localAppFolderPath = _appInitializerService.GetApplicationDataFolder();
var logFile = Path.Combine(localAppFolderPath, ProtocolLogFileName);
if (File.Exists(logFile))
File.Delete(logFile);
_protocolLogStream = File.Create(logFile);
}
public async Task TestImapConnectionAsync(CustomServerInformation serverInformation)
{
ImapClient client = null;
EnsureProtocolLogFileExists();
if (_preferencesService.IsMailkitProtocolLoggerEnabled)
var clientPool = new ImapClientPool(serverInformation, _protocolLogStream);
using (clientPool)
{
// Create new file for protocol logger.
// This call will make sure that everything is authenticated + connected successfully.
var client = await clientPool.GetClientAsync();
var localAppFolderPath = _appInitializerService.GetApplicationDataFolder();
var logFile = Path.Combine(localAppFolderPath, ProtocolLogFileName);
if (File.Exists(logFile))
File.Delete(logFile);
var stream = File.Create(logFile);
client = new ImapClient(new ProtocolLogger(stream));
}
else
client = new ImapClient();
using (client)
{
// todo: test connection
// await client.InitializeAsync(serverInformation);
await client.DisconnectAsync(true);
client.Dispose();
}
}
}

View File

@@ -59,14 +59,14 @@ namespace Wino.Core.Synchronizers
private ImapClient _inboxIdleClient;
public override uint BatchModificationSize => 1000;
public override uint InitialMessageDownloadCountPerFolder => 500;
public override uint InitialMessageDownloadCountPerFolder => 250;
public ImapSynchronizer(MailAccount account, IImapChangeProcessor imapChangeProcessor) : base(account)
{
_clientPool = new ImapClientPool(Account.ServerInformation);
_imapChangeProcessor = imapChangeProcessor;
idleDoneToken = new CancellationTokenSource();
_imapChangeProcessor = imapChangeProcessor;
}
// TODO