Skip to content

Commit

Permalink
Fixed MoveToAsync for #68
Browse files Browse the repository at this point in the history
  • Loading branch information
danzuep committed Jun 22, 2024
1 parent 9eac411 commit 8e803f7
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 55 deletions.
11 changes: 7 additions & 4 deletions samples/WorkerServiceExample/Worker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public Worker(IServiceScopeFactory serviceScopeFactory, ILoggerFactory loggerFac
protected override async Task ExecuteAsync(CancellationToken cancellationToken = default)
{
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
await ReceiveAsync(cancellationToken);
//await TemplateSendAsync(1, cancellationToken);
//await SendAttachmentAsync(500);
//await ReceiveAsync(cancellationToken);
Expand All @@ -43,7 +44,8 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken =
//await GetMessageSummaryRepliesAsync(cancellationToken);
//await GetMimeMessageRepliesAsync(cancellationToken);
//await AddFlagsToNewestMessageSummaryAsync(cancellationToken);
await GetMailFolderCacheAsync();
//await GetMailFolderCacheAsync();
//await CreateFolderAndMoveTopOneAsync();
}

private static ImapReceiver CreateExchangeOAuth2ImapClientExample(SaslMechanismOAuth2 oauth2)
Expand Down Expand Up @@ -132,11 +134,12 @@ private async Task MoveTopOneToFolderAsync(IMailFolderClient mailFolderClient, s
_logger.LogInformation($"Added mime message to {_imapReceiver} {destinationFolderFullName} folder as #{uniqueId}.");
}

private async Task CreateFolderAndMoveTopOneAsync(string destinationFolderFullName = "INBOX/Processed", CancellationToken cancellationToken = default)
private async Task CreateFolderAndMoveTopOneAsync(string mailFolderFullName = "Processed", CancellationToken cancellationToken = default)
{
//var mailFolderNames = await _imapReceiver.GetMailFolderNamesAsync(cancellationToken);
using var mailFolderClient = _serviceScope.ServiceProvider.GetRequiredService<IMailFolderClient>();
var folder = await mailFolderClient.GetOrCreateFolderAsync(destinationFolderFullName, cancellationToken);
await MoveTopOneToFolderAsync(mailFolderClient, destinationFolderFullName, cancellationToken);
var mailFolder = await mailFolderClient.GetOrCreateFolderAsync(mailFolderFullName, cancellationToken);
await MoveTopOneToFolderAsync(mailFolderClient, mailFolderFullName, cancellationToken);
}

private async Task GetMailFolderCacheAsync(string destinationFolderFullName = "INBOX", CancellationToken cancellationToken = default)
Expand Down
2 changes: 0 additions & 2 deletions samples/WorkerServiceExample/appsettings.Development.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@
"Mailbox": {
"EmailReceivers": [
{
"MailFolderName": "INBOX",
"ImapHost": "localhost"
}
],
"FolderMonitors": [
{
"EmailReceiver": {
"MailFolderName": "INBOX",
"ImapHost": "localhost"
},
"IgnoreExistingMailOnConnect": false
Expand Down
3 changes: 2 additions & 1 deletion samples/WorkerServiceExample/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"ProtocolLog": "Logs\\SmtpClient.txt"
},
"EmailReceiver": {
"MailFolderName": "INBOX",
"MailFolderNames": [],
"MailFolderAccess": "ReadOnly",
"ImapHost": "imap.example.com",
"ImapPort": 993,
"ImapCredential": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ public interface IMailFolderClient : IAsyncDisposable, IDisposable
/// <summary>
/// Get or create a mail folder in the user namespace.
/// </summary>
/// <param name="folderName">Folder name to search for.</param>
/// <param name="mailFolderFullName">Folder name to search for.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Mail folder with a matching name.</returns>
Task<IMailFolder> GetOrCreateFolderAsync(string folderName, CancellationToken cancellationToken = default);
Task<IMailFolder> GetOrCreateFolderAsync(string mailFolderFullName, CancellationToken cancellationToken = default);

/// <summary>
/// Add flags with checks to make sure the folder is open and writeable.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public string MailFolderName
set => MailFolderNames = new List<string> { value };
}

public IList<string> MailFolderNames { get; set; } = new List<string> { _inbox };
public IList<string> MailFolderNames { get; set; } = new List<string> { string.Empty };

public FolderAccess MailFolderAccess { get; set; } = FolderAccess.None;

Expand Down
16 changes: 14 additions & 2 deletions source/MailKitSimplified.Receiver/Services/ImapReceiver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,12 +291,24 @@ public async ValueTask<IMailFolder> ConnectMailFolderAsync(CancellationToken can
{
_ = await ConnectAuthenticatedImapClientAsync(cancellationToken).ConfigureAwait(false);
_logger.LogTrace($"Connecting to mail folder: '{_receiverOptions.MailFolderName}'.");
var mailFolder = string.IsNullOrWhiteSpace(_receiverOptions.MailFolderName) || _receiverOptions.MailFolderName.Equals("INBOX", StringComparison.OrdinalIgnoreCase) ?
_imapClient.Inbox : await _imapClient.GetFolderAsync(_receiverOptions.MailFolderName, cancellationToken).ConfigureAwait(false);
IMailFolder mailFolder;
if (string.IsNullOrWhiteSpace(_receiverOptions.MailFolderName))
{
var namespaceFolder = _imapClient.PersonalNamespaces.FirstOrDefault()
?? _imapClient.SharedNamespaces.FirstOrDefault()
?? _imapClient.OtherNamespaces.FirstOrDefault();
mailFolder = !string.IsNullOrWhiteSpace(namespaceFolder?.Path) ? _imapClient.GetFolder(namespaceFolder) : _imapClient.Inbox;
_receiverOptions.MailFolderName = mailFolder.FullName;
}
else if (_receiverOptions.MailFolderName.Equals("INBOX", StringComparison.OrdinalIgnoreCase))
mailFolder = _imapClient.Inbox;
else
mailFolder = await _imapClient.GetFolderAsync(_receiverOptions.MailFolderName, cancellationToken).ConfigureAwait(false);
if (_receiverOptions.MailFolderAccess != FolderAccess.None)
{
_ = await mailFolder.OpenAsync(_receiverOptions.MailFolderAccess, cancellationToken).ConfigureAwait(false);
_logger.LogTrace($"{this} mail folder opened with {_receiverOptions.MailFolderAccess} access.");
//_receiverOptions.MailFolderAccess = FolderAccess.None;
}
return mailFolder;
}
Expand Down
8 changes: 3 additions & 5 deletions source/MailKitSimplified.Receiver/Services/MailFolderCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
using MailKitSimplified.Receiver.Models;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using Org.BouncyCastle.Asn1.X509;

namespace MailKitSimplified.Receiver.Services
{
Expand Down Expand Up @@ -92,18 +91,18 @@ private void CacheMailFolder(string key, IMailFolder mailFolder)
}

#if NET5_0_OR_GREATER
private async Task<int> CacheAllMailFoldersAsync(IImapReceiver imapReceiver, IImapClient imapClient)
private async Task<int> AsyncCacheAllMailFoldersAsync(IImapReceiver imapReceiver, IImapClient imapClient)
{
int folderCount = 0;
await foreach (var mailFolder in imapClient.GetAllSubfoldersAsync(_cancellationTokenSource.Token).ConfigureAwait(false))
await foreach (var mailFolder in imapClient.AsyncGetAllSubfolders(_cancellationTokenSource.Token).ConfigureAwait(false))
{
var key = GetKey(imapReceiver, mailFolder.FullName);
CacheMailFolder(key, mailFolder);
folderCount++;
}
return folderCount;
}
#else
#endif
private async Task<int> CacheAllMailFoldersAsync(IImapReceiver imapReceiver, IImapClient imapClient)
{
int folderCount = 0;
Expand All @@ -115,7 +114,6 @@ private async Task<int> CacheAllMailFoldersAsync(IImapReceiver imapReceiver, IIm
}
return folderCount;
}
#endif

// Let the Garbage Collector dispose of the injected MemoryCache.
}
Expand Down
61 changes: 25 additions & 36 deletions source/MailKitSimplified.Receiver/Services/MailFolderClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,11 @@ private async ValueTask<IMailFolder> ConnectMailFolderAsync(IMailFolder mailFold
{
var folderAccess = enableWrite ? FolderAccess.ReadWrite : FolderAccess.ReadOnly;
_ = await mailFolder.OpenAsync(folderAccess, cancellationToken).ConfigureAwait(false);
_logger.LogTrace($"{this} mail folder opened with {folderAccess} access.");
_logger.LogTrace($"{this} | {mailFolder} mail folder opened with {folderAccess} access.");
}
else if (enableWrite && mailFolder.Access != FolderAccess.ReadWrite)
{
_logger.LogTrace($"{this} mail folder SyncRoot changed for ReadWrite access.");
_logger.LogTrace($"{this} | {mailFolder} mail folder SyncRoot changed for ReadWrite access.");
await mailFolder.OpenAsync(FolderAccess.ReadWrite, cancellationToken).ConfigureAwait(false);
}
return mailFolder;
Expand Down Expand Up @@ -186,41 +186,28 @@ private IEnumerable<string> GetFolderNames(SpecialFolder specialFolder)

public IMailFolder TrashFolder => GetFolder(SpecialFolder.Trash).Value;

public async Task<IMailFolder> GetOrCreateFolderAsync(string mailFolderName, CancellationToken cancellationToken = default)
public async Task<IMailFolder> GetOrCreateFolderAsync(string mailFolderFullName, CancellationToken cancellationToken = default)
{
IMailFolder folder = null;
var imapClient = await _imapReceiver.ConnectAuthenticatedImapClientAsync(cancellationToken).ConfigureAwait(false);
IMailFolder mailFolder = null;
_semaphoreSlim.Wait();
try
{
var imapClient = await _imapReceiver.ConnectAuthenticatedImapClientAsync().ConfigureAwait(false);
mailFolder = await imapClient.GetFolderAsync(mailFolderFullName, cancellationToken).ConfigureAwait(false);
}
catch (FolderNotFoundException)
{
var namespaceFolder = imapClient.PersonalNamespaces.FirstOrDefault()
?? imapClient.SharedNamespaces.FirstOrDefault()
?? imapClient.OtherNamespaces.FirstOrDefault();
var baseFolder = string.IsNullOrEmpty(namespaceFolder?.Path) ?
imapClient.Inbox : imapClient.GetFolder(namespaceFolder);
folder = baseFolder.GetSubfolders(false, CancellationToken.None).FirstOrDefault(x =>
mailFolderName.Equals(x.Name, StringComparison.OrdinalIgnoreCase));
if (folder == null)
{
try
{
folder = await imapClient.GetFolderAsync(mailFolderName, cancellationToken).ConfigureAwait(false);
}
catch (FolderNotFoundException)
{
bool peekFolder = !folder?.IsOpen ?? true;
_ = await ConnectAsync(true, cancellationToken).ConfigureAwait(false);
folder = await baseFolder.CreateAsync(mailFolderName, isMessageFolder: true, cancellationToken);
if (peekFolder)
await folder.CloseAsync(expunge: false, cancellationToken).ConfigureAwait(false);
}
}
var baseFolder = namespaceFolder != null ? imapClient.GetFolder(namespaceFolder) : imapClient.Inbox;
mailFolder = await baseFolder.CreateAsync(mailFolderFullName, isMessageFolder: true, cancellationToken).ConfigureAwait(false);
}
finally
{
_semaphoreSlim.Release();
}
return folder;
return mailFolder;
}

/// <inheritdoc />
Expand Down Expand Up @@ -312,7 +299,9 @@ public async Task<int> DeleteMessagesAsync(TimeSpan relativeOffset, SearchQuery
{
UniqueId? result;
if (_mailFolderCache == null)
{
result = await MoveOrCopyAsync(messageSummary.UniqueId, destinationFolderFullName, move: true, cancellationToken).ConfigureAwait(false);
}
else
{
var sourceFolder = await _mailFolderCache.GetMailFolderAsync(_imapReceiver, messageSummary.Folder.FullName, createIfMissing: false, cancellationToken).ConfigureAwait(false);
Expand All @@ -335,15 +324,13 @@ public async Task<int> DeleteMessagesAsync(TimeSpan relativeOffset, SearchQuery
if (destination == null)
throw new ArgumentNullException(nameof(destination));
bool peekSourceFolder = !source.IsOpen;
bool peekDestinationFolder = !destination.IsOpen;
_ = await ConnectMailFolderAsync(source, enableWrite: false, cancellationToken).ConfigureAwait(false);
_ = await ConnectMailFolderAsync(destination, enableWrite: true, cancellationToken).ConfigureAwait(false);
// Beware, source must be opened after destination to keep it open
_ = await ConnectMailFolderAsync(source, enableWrite: move, cancellationToken).ConfigureAwait(false);
resultUid = await source.MoveToAsync(messageUid, destination, cancellationToken).ConfigureAwait(false);
_logger.LogTrace("{0} {1} {2} to {3} in {4}.", _imapReceiver, messageUid, verb, resultUid, destination.FullName);
if (peekSourceFolder)
if (peekSourceFolder && source.IsOpen)
await source.CloseAsync(expunge: false, cancellationToken).ConfigureAwait(false);
if (peekDestinationFolder)
await destination.CloseAsync(expunge: false, cancellationToken).ConfigureAwait(false);
// destination folder is already closed at this point
}
catch (Exception ex)
{
Expand All @@ -352,21 +339,23 @@ public async Task<int> DeleteMessagesAsync(TimeSpan relativeOffset, SearchQuery
return resultUid;
}

private async Task<UniqueId?> MoveOrCopyAsync(UniqueId messageUid, string destinationFolder, bool move = true, CancellationToken cancellationToken = default)
private async Task<UniqueId?> MoveOrCopyAsync(UniqueId messageUid, string destinationFolderFullName, bool move = true, CancellationToken cancellationToken = default)
{
UniqueId? resultUid = null;
if (messageUid.IsValid && !string.IsNullOrWhiteSpace(destinationFolder))
if (!messageUid.IsValid)
_logger.LogInformation("IMessageSummary UniqueId is invalid.");
else if (!string.IsNullOrWhiteSpace(destinationFolderFullName))
{
try
{
var destination = await _imapReceiver.ImapClient.GetFolderAsync(destinationFolder, cancellationToken).ConfigureAwait(false);
_ = await ConnectAsync(true, cancellationToken).ConfigureAwait(false);
var destination = await _imapReceiver.ImapClient.GetFolderAsync(destinationFolderFullName, cancellationToken).ConfigureAwait(false);
resultUid = await MoveOrCopyAsync(messageUid, _mailFolder, destination, move, cancellationToken).ConfigureAwait(false);
await destination.CloseAsync(expunge: false, cancellationToken).ConfigureAwait(false);
}
catch (FolderNotFoundException ex)
{
string verb = move ? "moved" : "copied";
_logger.LogWarning(ex, "{DestinationFolder} folder not found, {MessageUid} not {Verb} from {ImapReceiverFolder}.", destinationFolder, messageUid, verb, _imapReceiver);
_logger.LogWarning(ex, "{DestinationFolder} folder not found, {MessageUid} not {Verb} from {ImapReceiverFolder}.", destinationFolderFullName, messageUid, verb, _imapReceiver);
}
}
return resultUid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public sealed class MailFolderReader : IMailFolderReader
private static readonly int _queryAmount = 250;
private SearchQuery _searchQuery = _queryAll;
private static readonly SearchQuery _queryAll = SearchQuery.All;
private MessageSummaryItems _messageSummaryItems = MessageSummaryItems.Envelope;
private MessageSummaryItems _messageSummaryItems = MessageSummaryItems.UniqueId | MessageSummaryItems.Envelope;
private readonly ILogger _logger;
private readonly IImapReceiver _imapReceiver;

Expand Down Expand Up @@ -110,7 +110,7 @@ public IMailReader Query(SearchQuery searchQuery)

public IMailReader Items(MessageSummaryItems messageSummaryItems)
{
_messageSummaryItems = messageSummaryItems | MessageSummaryItems.UniqueId;
_messageSummaryItems = MessageSummaryItems.UniqueId | messageSummaryItems;
return this;
}

Expand Down

0 comments on commit 8e803f7

Please sign in to comment.