From 393f055959800ffd6c7d90d09e0a8af80c6cb016 Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Tue, 26 Dec 2023 14:00:56 -0500 Subject: [PATCH] Refactored the rest of ImapFolder.cs methods to split sync/async Part of an ongoing effort to fix issue #1335 --- MailKit/Net/Imap/ImapFolder.cs | 875 ++++++++++++++++++++------------- 1 file changed, 527 insertions(+), 348 deletions(-) diff --git a/MailKit/Net/Imap/ImapFolder.cs b/MailKit/Net/Imap/ImapFolder.cs index 97d8add77f..b05e494b53 100644 --- a/MailKit/Net/Imap/ImapFolder.cs +++ b/MailKit/Net/Imap/ImapFolder.cs @@ -4102,14 +4102,15 @@ public override async Task SetQuotaAsync (uint? messageLimit, uint? return ProcessSetQuotaResponse (ic); } - async Task ExpungeAsync (bool doAsync, CancellationToken cancellationToken) + ImapCommand QueueExpunge (CancellationToken cancellationToken) { CheckState (true, true); - var ic = Engine.QueueCommand (cancellationToken, this, "EXPUNGE\r\n"); - - await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); + return Engine.QueueCommand (cancellationToken, this, "EXPUNGE\r\n"); + } + void ProcessExpungeResponse (ImapCommand ic) + { ProcessResponseCodes (ic, null); if (ic.Response != ImapCommandResponse.Ok) @@ -4157,7 +4158,11 @@ async Task ExpungeAsync (bool doAsync, CancellationToken cancellationToken) /// public override void Expunge (CancellationToken cancellationToken = default) { - ExpungeAsync (false, cancellationToken).GetAwaiter ().GetResult (); + var ic = QueueExpunge (cancellationToken); + + Engine.Run (ic); + + ProcessExpungeResponse (ic); } /// @@ -4200,55 +4205,13 @@ public override void Expunge (CancellationToken cancellationToken = default) /// /// The server replied with a NO or BAD response. /// - public override Task ExpungeAsync (CancellationToken cancellationToken = default) + public override async Task ExpungeAsync (CancellationToken cancellationToken = default) { - return ExpungeAsync (true, cancellationToken); - } - - async Task ExpungeAsync (IList uids, bool doAsync, CancellationToken cancellationToken) - { - if (uids == null) - throw new ArgumentNullException (nameof (uids)); - - CheckState (true, true); - - if (uids.Count == 0) - return; - - if ((Engine.Capabilities & ImapCapabilities.UidPlus) == 0) { - // get the list of messages marked for deletion that should not be expunged - var query = SearchQuery.Deleted.And (SearchQuery.Not (SearchQuery.Uids (uids))); - SearchResults unmark; - - if (doAsync) - unmark = await SearchAsync (SearchOptions.None, query, cancellationToken).ConfigureAwait (false); - else - unmark = Search (SearchOptions.None, query, cancellationToken); + var ic = QueueExpunge (cancellationToken); - if (unmark.Count > 0) { - // clear the \Deleted flag on all messages except the ones that are to be expunged - await StoreAsync (unmark.UniqueIds, RemoveDeletedFlag, doAsync, cancellationToken).ConfigureAwait (false); - } - - // expunge the folder - await ExpungeAsync (doAsync, cancellationToken).ConfigureAwait (false); - - if (unmark.Count > 0) { - // restore the \Deleted flags - await StoreAsync (unmark.UniqueIds, AddDeletedFlag, doAsync, cancellationToken).ConfigureAwait (false); - } - - return; - } - - foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID EXPUNGE %s\r\n", uids)) { - await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); - - ProcessResponseCodes (ic, null); + await Engine.RunAsync (ic).ConfigureAwait (false); - if (ic.Response != ImapCommandResponse.Ok) - throw ImapCommandException.Create ("EXPUNGE", ic); - } + ProcessExpungeResponse (ic); } /// @@ -4308,7 +4271,40 @@ async Task ExpungeAsync (IList uids, bool doAsync, CancellationToken c /// public override void Expunge (IList uids, CancellationToken cancellationToken = default) { - ExpungeAsync (uids, false, cancellationToken).GetAwaiter ().GetResult (); + if (uids == null) + throw new ArgumentNullException (nameof (uids)); + + CheckState (true, true); + + if (uids.Count == 0) + return; + + if ((Engine.Capabilities & ImapCapabilities.UidPlus) == 0) { + // get the list of messages marked for deletion that should not be expunged + var query = SearchQuery.Deleted.And (SearchQuery.Not (SearchQuery.Uids (uids))); + var unmark = Search (SearchOptions.None, query, cancellationToken); + + if (unmark.Count > 0) { + // clear the \Deleted flag on all messages except the ones that are to be expunged + Store (unmark.UniqueIds, RemoveDeletedFlag, cancellationToken); + } + + // expunge the folder + Expunge (cancellationToken); + + if (unmark.Count > 0) { + // restore the \Deleted flags + Store (unmark.UniqueIds, AddDeletedFlag, cancellationToken); + } + + return; + } + + foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID EXPUNGE %s\r\n", uids)) { + Engine.Run (ic); + + ProcessExpungeResponse (ic); + } } /// @@ -4367,13 +4363,77 @@ public override void Expunge (IList uids, CancellationToken cancellati /// /// The server replied with a NO or BAD response. /// - public override Task ExpungeAsync (IList uids, CancellationToken cancellationToken = default) + public override async Task ExpungeAsync (IList uids, CancellationToken cancellationToken = default) + { + if (uids == null) + throw new ArgumentNullException (nameof (uids)); + + CheckState (true, true); + + if (uids.Count == 0) + return; + + if ((Engine.Capabilities & ImapCapabilities.UidPlus) == 0) { + // get the list of messages marked for deletion that should not be expunged + var query = SearchQuery.Deleted.And (SearchQuery.Not (SearchQuery.Uids (uids))); + var unmark = await SearchAsync (SearchOptions.None, query, cancellationToken).ConfigureAwait (false); + + if (unmark.Count > 0) { + // clear the \Deleted flag on all messages except the ones that are to be expunged + await StoreAsync (unmark.UniqueIds, RemoveDeletedFlag, cancellationToken).ConfigureAwait (false); + } + + // expunge the folder + await ExpungeAsync (cancellationToken).ConfigureAwait (false); + + if (unmark.Count > 0) { + // restore the \Deleted flags + await StoreAsync (unmark.UniqueIds, AddDeletedFlag, cancellationToken).ConfigureAwait (false); + } + + return; + } + + foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID EXPUNGE %s\r\n", uids)) { + await Engine.RunAsync (ic).ConfigureAwait (false); + + ProcessExpungeResponse (ic); + } + } + + FormatOptions CreateAppendOptions (FormatOptions options) { - return ExpungeAsync (uids, true, cancellationToken); + if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0) + throw new NotSupportedException ("The IMAP server does not support the UTF8 extension."); + + var format = options.Clone (); + format.NewLineFormat = NewLineFormat.Dos; + format.EnsureNewLine = true; + + if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only) + format.International = true; + + if (format.International && !Engine.UTF8Enabled) + throw new InvalidOperationException ("The UTF8 extension has not been enabled."); + + return format; } ImapCommand QueueAppend (FormatOptions options, IAppendRequest request, CancellationToken cancellationToken) { + if (options == null) + throw new ArgumentNullException (nameof (options)); + + if (request == null) + throw new ArgumentNullException (nameof (request)); + + CheckState (false, false); + + var format = CreateAppendOptions (options); + + if (request.Annotations != null && request.Annotations.Count > 0 && (Engine.Capabilities & ImapCapabilities.Annotate) == 0) + throw new NotSupportedException ("The IMAP server does not support annotations."); + int numKeywords = request.Keywords != null ? request.Keywords.Count : 0; var builder = new StringBuilder ("APPEND %F "); var list = new List { @@ -4409,7 +4469,7 @@ ImapCommand QueueAppend (FormatOptions options, IAppendRequest request, Cancella var command = builder.ToString (); var args = list.ToArray (); - var ic = new ImapCommand (Engine, cancellationToken, null, options, command, args) { + var ic = new ImapCommand (Engine, cancellationToken, null, format, command, args) { Progress = request.TransferProgress }; @@ -4418,36 +4478,8 @@ ImapCommand QueueAppend (FormatOptions options, IAppendRequest request, Cancella return ic; } - async Task AppendAsync (FormatOptions options, IAppendRequest request, bool doAsync, CancellationToken cancellationToken) + UniqueId? ProcessAppendResponse (ImapCommand ic) { - if (options == null) - throw new ArgumentNullException (nameof (options)); - - if (request == null) - throw new ArgumentNullException (nameof (request)); - - CheckState (false, false); - - if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0) - throw new NotSupportedException ("The IMAP server does not support the UTF8 extension."); - - if (request.Annotations != null && request.Annotations.Count > 0 && (Engine.Capabilities & ImapCapabilities.Annotate) == 0) - throw new NotSupportedException ("The IMAP server does not support annotations."); - - var format = options.Clone (); - format.NewLineFormat = NewLineFormat.Dos; - format.EnsureNewLine = true; - - if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only) - format.International = true; - - if (format.International && !Engine.UTF8Enabled) - throw new InvalidOperationException ("The UTF8 extension has not been enabled."); - - var ic = QueueAppend (format, request, cancellationToken); - - await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); - ProcessResponseCodes (ic, this); if (ic.Response != ImapCommandResponse.Ok) @@ -4510,7 +4542,11 @@ ImapCommand QueueAppend (FormatOptions options, IAppendRequest request, Cancella /// public override UniqueId? Append (FormatOptions options, IAppendRequest request, CancellationToken cancellationToken = default) { - return AppendAsync (options, request, false, cancellationToken).GetAwaiter ().GetResult (); + var ic = QueueAppend (options, request, cancellationToken); + + Engine.Run (ic); + + return ProcessAppendResponse (ic); } /// @@ -4560,13 +4596,37 @@ ImapCommand QueueAppend (FormatOptions options, IAppendRequest request, Cancella /// /// The server replied with a NO or BAD response. /// - public override Task AppendAsync (FormatOptions options, IAppendRequest request, CancellationToken cancellationToken = default) + public override async Task AppendAsync (FormatOptions options, IAppendRequest request, CancellationToken cancellationToken = default) { - return AppendAsync (options, request, true, cancellationToken); + var ic = QueueAppend (options, request, cancellationToken); + + await Engine.RunAsync (ic).ConfigureAwait (false); + + return ProcessAppendResponse (ic); + } + + void ValidateArguments (FormatOptions options, IList requests) + { + if (options == null) + throw new ArgumentNullException (nameof (options)); + + if (requests == null) + throw new ArgumentNullException (nameof (requests)); + + for (int i = 0; i < requests.Count; i++) { + if (requests[i] == null) + throw new ArgumentException ("One or more of the requests is null."); + + if (requests[i].Annotations != null && requests[i].Annotations.Count > 0 && (Engine.Capabilities & ImapCapabilities.Annotate) == 0) + throw new NotSupportedException ("One ore more requests included annotations but the IMAP server does not support annotations."); + } + + CheckState (false, false); } ImapCommand QueueMultiAppend (FormatOptions options, IList requests, CancellationToken cancellationToken) { + var format = CreateAppendOptions (options); var builder = new StringBuilder ("APPEND %F"); var list = new List { this @@ -4609,7 +4669,7 @@ ImapCommand QueueMultiAppend (FormatOptions options, IList reque var command = builder.ToString (); var args = list.ToArray (); - var ic = new ImapCommand (Engine, cancellationToken, null, options, command, args) { + var ic = new ImapCommand (Engine, cancellationToken, null, format, command, args) { Progress = requests[0].TransferProgress }; @@ -4618,73 +4678,19 @@ ImapCommand QueueMultiAppend (FormatOptions options, IList reque return ic; } - async Task> AppendAsync (FormatOptions options, IList requests, bool doAsync, CancellationToken cancellationToken) + IList ProcessMultiAppendResponse (ImapCommand ic) { - if (options == null) - throw new ArgumentNullException (nameof (options)); - - if (requests == null) - throw new ArgumentNullException (nameof (requests)); - - for (int i = 0; i < requests.Count; i++) { - if (requests[i] == null) - throw new ArgumentException ("One or more of the requests is null."); - - if (requests[i].Annotations != null && requests[i].Annotations.Count > 0 && (Engine.Capabilities & ImapCapabilities.Annotate) == 0) - throw new NotSupportedException ("One ore more requests included annotations but the IMAP server does not support annotations."); - } - - CheckState (false, false); - - if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0) - throw new NotSupportedException ("The IMAP server does not support the UTF8 extension."); - - var format = options.Clone (); - format.NewLineFormat = NewLineFormat.Dos; - format.EnsureNewLine = true; - - if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only) - format.International = true; - - if (format.International && !Engine.UTF8Enabled) - throw new InvalidOperationException ("The UTF8 extension has not been enabled."); - - if (requests.Count == 0) - return Array.Empty (); - - if ((Engine.Capabilities & ImapCapabilities.MultiAppend) != 0) { - var ic = QueueMultiAppend (format, requests, cancellationToken); - - await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); - - ProcessResponseCodes (ic, this); - - if (ic.Response != ImapCommandResponse.Ok) - throw ImapCommandException.Create ("APPEND", ic); - - var append = (AppendUidResponseCode) ic.GetResponseCode (ImapResponseCodeType.AppendUid); - - if (append != null) - return append.UidSet; - - return Array.Empty (); - } + ProcessResponseCodes (ic, this); - // FIXME: use an aggregate progress reporter - var uids = new List (); + if (ic.Response != ImapCommandResponse.Ok) + throw ImapCommandException.Create ("APPEND", ic); - for (int i = 0; i < requests.Count; i++) { - var uid = await AppendAsync (format, requests[i], doAsync, cancellationToken).ConfigureAwait (false); - if (uids != null && uid.HasValue) - uids.Add (uid.Value); - else - uids = null; - } + var append = (AppendUidResponseCode) ic.GetResponseCode (ImapResponseCodeType.AppendUid); - if (uids == null) - return Array.Empty (); + if (append != null) + return append.UidSet; - return uids; + return Array.Empty (); } /// @@ -4739,7 +4745,34 @@ async Task> AppendAsync (FormatOptions options, IList public override IList Append (FormatOptions options, IList requests, CancellationToken cancellationToken = default) { - return AppendAsync (options, requests, false, cancellationToken).GetAwaiter ().GetResult (); + ValidateArguments (options, requests); + + if (requests.Count == 0) + return Array.Empty (); + + if ((Engine.Capabilities & ImapCapabilities.MultiAppend) != 0) { + var ic = QueueMultiAppend (options, requests, cancellationToken); + + Engine.Run (ic); + + return ProcessMultiAppendResponse (ic); + } + + // FIXME: use an aggregate progress reporter + var uids = new List (); + + for (int i = 0; i < requests.Count; i++) { + var uid = Append (options, requests[i], cancellationToken); + if (uids != null && uid.HasValue) + uids.Add (uid.Value); + else + uids = null; + } + + if (uids == null) + return Array.Empty (); + + return uids; } /// @@ -4792,13 +4825,61 @@ public override IList Append (FormatOptions options, IList /// The server replied with a NO or BAD response. /// - public override Task> AppendAsync (FormatOptions options, IList requests, CancellationToken cancellationToken = default) + public override async Task> AppendAsync (FormatOptions options, IList requests, CancellationToken cancellationToken = default) { - return AppendAsync (options, requests, true, cancellationToken); + ValidateArguments (options, requests); + + if (requests.Count == 0) + return Array.Empty (); + + if ((Engine.Capabilities & ImapCapabilities.MultiAppend) != 0) { + var ic = QueueMultiAppend (options, requests, cancellationToken); + + await Engine.RunAsync (ic).ConfigureAwait (false); + + return ProcessMultiAppendResponse (ic); + } + + // FIXME: use an aggregate progress reporter + var uids = new List (); + + for (int i = 0; i < requests.Count; i++) { + var uid = await AppendAsync (options, requests[i], cancellationToken).ConfigureAwait (false); + if (uids != null && uid.HasValue) + uids.Add (uid.Value); + else + uids = null; + } + + if (uids == null) + return Array.Empty (); + + return uids; + } + + void ValidateArguments (FormatOptions options, UniqueId uid, IReplaceRequest request) + { + if (options == null) + throw new ArgumentNullException (nameof (options)); + + if (!uid.IsValid) + throw new ArgumentException ("The uid is invalid.", nameof (uid)); + + if (request == null) + throw new ArgumentNullException (nameof (request)); + + if (request.Destination != null && !(request.Destination is ImapFolder target && target.Engine == Engine)) + throw new ArgumentException ("The destination folder does not belong to this ImapClient.", nameof (request)); + + if (request.Annotations != null && request.Annotations.Count > 0 && (Engine.Capabilities & ImapCapabilities.Annotate) == 0) + throw new NotSupportedException ("The IMAP server does not support annotations."); + + CheckState (true, true); } ImapCommand QueueReplace (FormatOptions options, UniqueId uid, IReplaceRequest request, CancellationToken cancellationToken) { + var format = CreateAppendOptions (options); int numKeywords = request.Keywords != null ? request.Keywords.Count : 0; var builder = new StringBuilder ($"UID REPLACE {uid} %F "); var list = new List { @@ -4834,7 +4915,7 @@ ImapCommand QueueReplace (FormatOptions options, UniqueId uid, IReplaceRequest r var command = builder.ToString (); var args = list.ToArray (); - var ic = new ImapCommand (Engine, cancellationToken, null, options, command, args) { + var ic = new ImapCommand (Engine, cancellationToken, null, format, command, args) { Progress = request.TransferProgress }; @@ -4843,51 +4924,8 @@ ImapCommand QueueReplace (FormatOptions options, UniqueId uid, IReplaceRequest r return ic; } - async Task ReplaceAsync (FormatOptions options, UniqueId uid, IReplaceRequest request, bool doAsync, CancellationToken cancellationToken) + UniqueId? ProcessReplaceResponse (ImapCommand ic) { - if (options == null) - throw new ArgumentNullException (nameof (options)); - - if (!uid.IsValid) - throw new ArgumentException ("The uid is invalid.", nameof (uid)); - - if (request == null) - throw new ArgumentNullException (nameof (request)); - - if (request.Destination != null && !(request.Destination is ImapFolder target && target.Engine == Engine)) - throw new ArgumentException ("The destination folder does not belong to this ImapClient.", nameof (request)); - - if (request.Annotations != null && request.Annotations.Count > 0 && (Engine.Capabilities & ImapCapabilities.Annotate) == 0) - throw new NotSupportedException ("The IMAP server does not support annotations."); - - CheckState (true, true); - - if ((Engine.Capabilities & ImapCapabilities.Replace) == 0) { - var destination = request.Destination as ImapFolder ?? this; - var appended = await destination.AppendAsync (options, request, doAsync, cancellationToken).ConfigureAwait (false); - await StoreAsync (new[] { uid }, AddDeletedFlag, doAsync, cancellationToken).ConfigureAwait (false); - if ((Engine.Capabilities & ImapCapabilities.UidPlus) != 0) - await ExpungeAsync (new[] { uid }, doAsync, cancellationToken).ConfigureAwait (false); - return appended; - } - - if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0) - throw new NotSupportedException ("The IMAP server does not support the UTF8 extension."); - - var format = options.Clone (); - format.NewLineFormat = NewLineFormat.Dos; - format.EnsureNewLine = true; - - if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only) - format.International = true; - - if (format.International && !Engine.UTF8Enabled) - throw new InvalidOperationException ("The UTF8 extension has not been enabled."); - - var ic = QueueReplace (format, uid, request, cancellationToken); - - await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); - ProcessResponseCodes (ic, this); if (ic.Response != ImapCommandResponse.Ok) @@ -4957,7 +4995,22 @@ ImapCommand QueueReplace (FormatOptions options, UniqueId uid, IReplaceRequest r /// public override UniqueId? Replace (FormatOptions options, UniqueId uid, IReplaceRequest request, CancellationToken cancellationToken = default) { - return ReplaceAsync (options, uid, request, false, cancellationToken).GetAwaiter ().GetResult (); + ValidateArguments (options, uid, request); + + if ((Engine.Capabilities & ImapCapabilities.Replace) == 0) { + var destination = request.Destination as ImapFolder ?? this; + var appended = destination.Append (options, request, cancellationToken); + Store (new[] { uid }, AddDeletedFlag, cancellationToken); + if ((Engine.Capabilities & ImapCapabilities.UidPlus) != 0) + Expunge (new[] { uid }, cancellationToken); + return appended; + } + + var ic = QueueReplace (options, uid, request, cancellationToken); + + Engine.Run (ic); + + return ProcessReplaceResponse (ic); } /// @@ -5014,13 +5067,29 @@ ImapCommand QueueReplace (FormatOptions options, UniqueId uid, IReplaceRequest r /// /// The server replied with a NO or BAD response. /// - public override Task ReplaceAsync (FormatOptions options, UniqueId uid, IReplaceRequest request, CancellationToken cancellationToken = default) + public override async Task ReplaceAsync (FormatOptions options, UniqueId uid, IReplaceRequest request, CancellationToken cancellationToken = default) { - return ReplaceAsync (options, uid, request, true, cancellationToken); + ValidateArguments (options, uid, request); + + if ((Engine.Capabilities & ImapCapabilities.Replace) == 0) { + var destination = request.Destination as ImapFolder ?? this; + var appended = await destination.AppendAsync (options, request, cancellationToken).ConfigureAwait (false); + await StoreAsync (new[] { uid }, AddDeletedFlag, cancellationToken).ConfigureAwait (false); + if ((Engine.Capabilities & ImapCapabilities.UidPlus) != 0) + await ExpungeAsync (new[] { uid }, cancellationToken).ConfigureAwait (false); + return appended; + } + + var ic = QueueReplace (options, uid, request, cancellationToken); + + await Engine.RunAsync (ic).ConfigureAwait (false); + + return ProcessReplaceResponse (ic); } ImapCommand QueueReplace (FormatOptions options, int index, IReplaceRequest request, CancellationToken cancellationToken) { + var format = CreateAppendOptions (options); int numKeywords = request.Keywords != null ? request.Keywords.Count : 0; var builder = new StringBuilder ($"REPLACE %d %F "); var list = new List { @@ -5057,7 +5126,7 @@ ImapCommand QueueReplace (FormatOptions options, int index, IReplaceRequest requ var command = builder.ToString (); var args = list.ToArray (); - var ic = new ImapCommand (Engine, cancellationToken, null, options, command, args) { + var ic = new ImapCommand (Engine, cancellationToken, null, format, command, args) { Progress = request.TransferProgress }; @@ -5066,7 +5135,7 @@ ImapCommand QueueReplace (FormatOptions options, int index, IReplaceRequest requ return ic; } - async Task ReplaceAsync (FormatOptions options, int index, IReplaceRequest request, bool doAsync, CancellationToken cancellationToken) + void ValidateArguments (FormatOptions options, int index, IReplaceRequest request) { if (options == null) throw new ArgumentNullException (nameof (options)); @@ -5084,42 +5153,6 @@ ImapCommand QueueReplace (FormatOptions options, int index, IReplaceRequest requ throw new NotSupportedException ("The IMAP server does not support annotations."); CheckState (true, true); - - if ((Engine.Capabilities & ImapCapabilities.Replace) == 0) { - var destination = request.Destination as ImapFolder ?? this; - var uid = await destination.AppendAsync (options, request, doAsync, cancellationToken).ConfigureAwait (false); - await StoreAsync (new[] { index }, AddDeletedFlag, doAsync, cancellationToken).ConfigureAwait (false); - return uid; - } - - if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0) - throw new NotSupportedException ("The IMAP server does not support the UTF8 extension."); - - var format = options.Clone (); - format.NewLineFormat = NewLineFormat.Dos; - format.EnsureNewLine = true; - - if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only) - format.International = true; - - if (format.International && !Engine.UTF8Enabled) - throw new InvalidOperationException ("The UTF8 extension has not been enabled."); - - var ic = QueueReplace (format, index, request, cancellationToken); - - await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); - - ProcessResponseCodes (ic, this); - - if (ic.Response != ImapCommandResponse.Ok) - throw ImapCommandException.Create ("REPLACE", ic); - - var append = (AppendUidResponseCode) ic.GetResponseCode (ImapResponseCodeType.AppendUid); - - if (append != null) - return append.UidSet[0]; - - return null; } /// @@ -5179,7 +5212,20 @@ ImapCommand QueueReplace (FormatOptions options, int index, IReplaceRequest requ /// public override UniqueId? Replace (FormatOptions options, int index, IReplaceRequest request, CancellationToken cancellationToken = default) { - return ReplaceAsync (options, index, request, false, cancellationToken).GetAwaiter ().GetResult (); + ValidateArguments (options, index, request); + + if ((Engine.Capabilities & ImapCapabilities.Replace) == 0) { + var destination = request.Destination as ImapFolder ?? this; + var uid = destination.Append (options, request, cancellationToken); + Store (new[] { index }, AddDeletedFlag, cancellationToken); + return uid; + } + + var ic = QueueReplace (options, index, request, cancellationToken); + + Engine.Run (ic); + + return ProcessReplaceResponse (ic); } /// @@ -5237,26 +5283,43 @@ ImapCommand QueueReplace (FormatOptions options, int index, IReplaceRequest requ /// /// The server replied with a NO or BAD response. /// - public override Task ReplaceAsync (FormatOptions options, int index, IReplaceRequest request, CancellationToken cancellationToken = default) + public override async Task ReplaceAsync (FormatOptions options, int index, IReplaceRequest request, CancellationToken cancellationToken = default) { - return ReplaceAsync (options, index, request, true, cancellationToken); + ValidateArguments (options, index, request); + + if ((Engine.Capabilities & ImapCapabilities.Replace) == 0) { + var destination = request.Destination as ImapFolder ?? this; + var uid = await destination.AppendAsync (options, request, cancellationToken).ConfigureAwait (false); + await StoreAsync (new[] { index }, AddDeletedFlag, cancellationToken).ConfigureAwait (false); + return uid; + } + + var ic = QueueReplace (options, index, request, cancellationToken); + + await Engine.RunAsync (ic).ConfigureAwait (false); + + return ProcessReplaceResponse (ic); } - async Task> GetIndexesAsync (IList uids, bool doAsync, CancellationToken cancellationToken) + ImapCommand QueueGetIndexes (IList uids, CancellationToken cancellationToken) { var command = string.Format ("SEARCH UID {0}\r\n", UniqueIdSet.ToString (uids)); var ic = new ImapCommand (Engine, cancellationToken, this, command); - var results = new SearchResults (SortOrder.Ascending); if ((Engine.Capabilities & ImapCapabilities.ESearch) != 0) ic.RegisterUntaggedHandler ("ESEARCH", UntaggedESearchHandler); ic.RegisterUntaggedHandler ("SEARCH", UntaggedSearchHandler); - ic.UserData = results; + ic.UserData = new SearchResults (SortOrder.Ascending); Engine.QueueCommand (ic); - await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); + return ic; + } + + IList ProcessGetIndexesResponse (ImapCommand ic) + { + var results = (SearchResults) ic.UserData; ProcessResponseCodes (ic, null); @@ -5270,52 +5333,50 @@ async Task> GetIndexesAsync (IList uids, bool doAsync, Canc return indexes; } - async Task CopyToAsync (IList uids, IMailFolder destination, bool doAsync, CancellationToken cancellationToken) + IList GetIndexes (IList uids, CancellationToken cancellationToken) { - if (uids == null) - throw new ArgumentNullException (nameof (uids)); + var ic = QueueGetIndexes (uids, cancellationToken); - CheckValidDestination (destination); + Engine.Run (ic); - CheckState (true, false); + return ProcessGetIndexesResponse (ic); + } - if (uids.Count == 0) - return UniqueIdMap.Empty; + async Task> GetIndexesAsync (IList uids, CancellationToken cancellationToken) + { + var ic = QueueGetIndexes (uids, cancellationToken); - if ((Engine.Capabilities & ImapCapabilities.UidPlus) == 0) { - var indexes = await GetIndexesAsync (uids, doAsync, cancellationToken).ConfigureAwait (false); - await CopyToAsync (indexes, destination, doAsync, cancellationToken).ConfigureAwait (false); - return UniqueIdMap.Empty; - } + await Engine.RunAsync (ic).ConfigureAwait (false); - UniqueIdSet dest = null; - UniqueIdSet src = null; + return ProcessGetIndexesResponse (ic); + } - foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID COPY %s %F\r\n", uids, destination)) { - await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); + void ValidateArguments (IList uids, IMailFolder destination) + { + if (uids == null) + throw new ArgumentNullException (nameof (uids)); - ProcessResponseCodes (ic, destination); + CheckValidDestination (destination); + } + + void ProcessCopyToResponse (ImapCommand ic, IMailFolder destination, ref UniqueIdSet src, ref UniqueIdSet dest) + { + ProcessResponseCodes (ic, destination); - if (ic.Response != ImapCommandResponse.Ok) - throw ImapCommandException.Create ("COPY", ic); + if (ic.Response != ImapCommandResponse.Ok) + throw ImapCommandException.Create ("COPY", ic); - var copy = (CopyUidResponseCode) ic.GetResponseCode (ImapResponseCodeType.CopyUid); + var copy = (CopyUidResponseCode) ic.GetResponseCode (ImapResponseCodeType.CopyUid); - if (copy != null) { - if (dest == null) { - dest = copy.DestUidSet; - src = copy.SrcUidSet; - } else { - dest.AddRange (copy.DestUidSet); - src.AddRange (copy.SrcUidSet); - } + if (copy != null) { + if (dest == null) { + dest = copy.DestUidSet; + src = copy.SrcUidSet; + } else { + dest.AddRange (copy.DestUidSet); + src.AddRange (copy.SrcUidSet); } } - - if (dest == null) - return UniqueIdMap.Empty; - - return new UniqueIdMap (src, dest); } /// @@ -5370,7 +5431,32 @@ async Task CopyToAsync (IList uids, IMailFolder destinati /// public override UniqueIdMap CopyTo (IList uids, IMailFolder destination, CancellationToken cancellationToken = default) { - return CopyToAsync (uids, destination, false, cancellationToken).GetAwaiter ().GetResult (); + ValidateArguments (uids, destination); + + CheckState (true, false); + + if (uids.Count == 0) + return UniqueIdMap.Empty; + + if ((Engine.Capabilities & ImapCapabilities.UidPlus) == 0) { + var indexes = GetIndexes (uids, cancellationToken); + CopyTo (indexes, destination, cancellationToken); + return UniqueIdMap.Empty; + } + + UniqueIdSet dest = null; + UniqueIdSet src = null; + + foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID COPY %s %F\r\n", uids, destination)) { + Engine.Run (ic); + + ProcessCopyToResponse (ic, destination, ref src, ref dest); + } + + if (dest == null) + return UniqueIdMap.Empty; + + return new UniqueIdMap (src, dest); } /// @@ -5423,64 +5509,55 @@ public override UniqueIdMap CopyTo (IList uids, IMailFolder destinatio /// /// The server replied with a NO or BAD response. /// - public override Task CopyToAsync (IList uids, IMailFolder destination, CancellationToken cancellationToken = default) + public override async Task CopyToAsync (IList uids, IMailFolder destination, CancellationToken cancellationToken = default) { - return CopyToAsync (uids, destination, true, cancellationToken); - } + ValidateArguments (uids, destination); - async Task MoveToAsync (IList uids, IMailFolder destination, bool doAsync, CancellationToken cancellationToken) - { - if ((Engine.Capabilities & ImapCapabilities.Move) == 0) { - var copied = await CopyToAsync (uids, destination, doAsync, cancellationToken).ConfigureAwait (false); - await StoreAsync (uids, AddDeletedFlag, doAsync, cancellationToken).ConfigureAwait (false); - await ExpungeAsync (uids, doAsync, cancellationToken).ConfigureAwait (false); - return copied; - } + CheckState (true, false); + + if (uids.Count == 0) + return UniqueIdMap.Empty; if ((Engine.Capabilities & ImapCapabilities.UidPlus) == 0) { - var indexes = await GetIndexesAsync (uids, doAsync, cancellationToken).ConfigureAwait (false); - await MoveToAsync (indexes, destination, doAsync, cancellationToken).ConfigureAwait (false); + var indexes = await GetIndexesAsync (uids, cancellationToken).ConfigureAwait (false); + await CopyToAsync (indexes, destination, cancellationToken).ConfigureAwait (false); return UniqueIdMap.Empty; } - if (uids == null) - throw new ArgumentNullException (nameof (uids)); + UniqueIdSet dest = null; + UniqueIdSet src = null; - CheckValidDestination (destination); + foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID COPY %s %F\r\n", uids, destination)) { + await Engine.RunAsync (ic).ConfigureAwait (false); - CheckState (true, true); + ProcessCopyToResponse (ic, destination, ref src, ref dest); + } - if (uids.Count == 0) + if (dest == null) return UniqueIdMap.Empty; - UniqueIdSet dest = null; - UniqueIdSet src = null; - - foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID MOVE %s %F\r\n", uids, destination)) { - await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); + return new UniqueIdMap (src, dest); + } - ProcessResponseCodes (ic, destination); + // FIXME: This is identical to the ProcessCopyToResponse() implementation *except* for the ImapCommandException.Create() call... + void ProcessMoveToResponse (ImapCommand ic, IMailFolder destination, ref UniqueIdSet src, ref UniqueIdSet dest) + { + ProcessResponseCodes (ic, destination); - if (ic.Response != ImapCommandResponse.Ok) - throw ImapCommandException.Create ("MOVE", ic); + if (ic.Response != ImapCommandResponse.Ok) + throw ImapCommandException.Create ("MOVE", ic); - var copy = (CopyUidResponseCode) ic.GetResponseCode (ImapResponseCodeType.CopyUid); + var copy = (CopyUidResponseCode) ic.GetResponseCode (ImapResponseCodeType.CopyUid); - if (copy != null) { - if (dest == null) { - dest = copy.DestUidSet; - src = copy.SrcUidSet; - } else { - dest.AddRange (copy.DestUidSet); - src.AddRange (copy.SrcUidSet); - } + if (copy != null) { + if (dest == null) { + dest = copy.DestUidSet; + src = copy.SrcUidSet; + } else { + dest.AddRange (copy.DestUidSet); + src.AddRange (copy.SrcUidSet); } } - - if (dest == null) - return UniqueIdMap.Empty; - - return new UniqueIdMap (src, dest); } /// @@ -5543,7 +5620,39 @@ async Task MoveToAsync (IList uids, IMailFolder destinati /// public override UniqueIdMap MoveTo (IList uids, IMailFolder destination, CancellationToken cancellationToken = default) { - return MoveToAsync (uids, destination, false, cancellationToken).GetAwaiter ().GetResult (); + if ((Engine.Capabilities & ImapCapabilities.Move) == 0) { + var copied = CopyTo (uids, destination, cancellationToken); + Store (uids, AddDeletedFlag, cancellationToken); + Expunge (uids, cancellationToken); + return copied; + } + + if ((Engine.Capabilities & ImapCapabilities.UidPlus) == 0) { + var indexes = GetIndexes (uids, cancellationToken); + MoveTo (indexes, destination, cancellationToken); + return UniqueIdMap.Empty; + } + + ValidateArguments (uids, destination); + + CheckState (true, true); + + if (uids.Count == 0) + return UniqueIdMap.Empty; + + UniqueIdSet dest = null; + UniqueIdSet src = null; + + foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID MOVE %s %F\r\n", uids, destination)) { + Engine.Run (ic); + + ProcessMoveToResponse (ic, destination, ref src, ref dest); + } + + if (dest == null) + return UniqueIdMap.Empty; + + return new UniqueIdMap (src, dest); } /// @@ -5604,32 +5713,70 @@ public override UniqueIdMap MoveTo (IList uids, IMailFolder destinatio /// /// The server replied with a NO or BAD response. /// - public override Task MoveToAsync (IList uids, IMailFolder destination, CancellationToken cancellationToken = default) + public override async Task MoveToAsync (IList uids, IMailFolder destination, CancellationToken cancellationToken = default) { - return MoveToAsync (uids, destination, true, cancellationToken); + if ((Engine.Capabilities & ImapCapabilities.Move) == 0) { + var copied = await CopyToAsync (uids, destination, cancellationToken).ConfigureAwait (false); + await StoreAsync (uids, AddDeletedFlag, cancellationToken).ConfigureAwait (false); + await ExpungeAsync (uids, cancellationToken).ConfigureAwait (false); + return copied; + } + + if ((Engine.Capabilities & ImapCapabilities.UidPlus) == 0) { + var indexes = await GetIndexesAsync (uids, cancellationToken).ConfigureAwait (false); + await MoveToAsync (indexes, destination, cancellationToken).ConfigureAwait (false); + return UniqueIdMap.Empty; + } + + ValidateArguments (uids, destination); + + CheckState (true, true); + + if (uids.Count == 0) + return UniqueIdMap.Empty; + + UniqueIdSet dest = null; + UniqueIdSet src = null; + + foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID MOVE %s %F\r\n", uids, destination)) { + await Engine.RunAsync (ic).ConfigureAwait (false); + + ProcessMoveToResponse (ic, destination, ref src, ref dest); + } + + if (dest == null) + return UniqueIdMap.Empty; + + return new UniqueIdMap (src, dest); } - async Task CopyToAsync (IList indexes, IMailFolder destination, bool doAsync, CancellationToken cancellationToken) + void ValidateArguments (IList indexes, IMailFolder destination) { if (indexes == null) throw new ArgumentNullException (nameof (indexes)); CheckValidDestination (destination); + } + + ImapCommand QueueCopyTo (IList indexes, IMailFolder destination, CancellationToken cancellationToken) + { + ValidateArguments (indexes, destination); CheckState (true, false); CheckAllowIndexes (); if (indexes.Count == 0) - return; + return null; var command = new StringBuilder ("COPY "); ImapUtils.FormatIndexSet (Engine, command, indexes); command.Append (" %F\r\n"); - var ic = Engine.QueueCommand (cancellationToken, this, command.ToString (), destination); - - await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); + return Engine.QueueCommand (cancellationToken, this, command.ToString (), destination); + } + void ProcessCopyToResponse (ImapCommand ic, IMailFolder destination) + { ProcessResponseCodes (ic, destination); if (ic.Response != ImapCommandResponse.Ok) @@ -5687,7 +5834,14 @@ async Task CopyToAsync (IList indexes, IMailFolder destination, bool doAsyn /// public override void CopyTo (IList indexes, IMailFolder destination, CancellationToken cancellationToken = default) { - CopyToAsync (indexes, destination, false, cancellationToken).GetAwaiter ().GetResult (); + var ic = QueueCopyTo (indexes, destination, cancellationToken); + + if (ic == null) + return; + + Engine.Run (ic); + + ProcessCopyToResponse (ic, destination); } /// @@ -5740,38 +5894,37 @@ public override void CopyTo (IList indexes, IMailFolder destination, Cancel /// /// The server replied with a NO or BAD response. /// - public override Task CopyToAsync (IList indexes, IMailFolder destination, CancellationToken cancellationToken = default) + public override async Task CopyToAsync (IList indexes, IMailFolder destination, CancellationToken cancellationToken = default) { - return CopyToAsync (indexes, destination, true, cancellationToken); - } + var ic = QueueCopyTo (indexes, destination, cancellationToken); - async Task MoveToAsync (IList indexes, IMailFolder destination, bool doAsync, CancellationToken cancellationToken) - { - if ((Engine.Capabilities & ImapCapabilities.Move) == 0) { - await CopyToAsync (indexes, destination, doAsync, cancellationToken).ConfigureAwait (false); - await StoreAsync (indexes, AddDeletedFlag, doAsync, cancellationToken).ConfigureAwait (false); + if (ic == null) return; - } - if (indexes == null) - throw new ArgumentNullException (nameof (indexes)); + await Engine.RunAsync (ic).ConfigureAwait (false); - CheckValidDestination (destination); + ProcessCopyToResponse (ic, destination); + } + + ImapCommand QueueMoveTo (IList indexes, IMailFolder destination, CancellationToken cancellationToken) + { + ValidateArguments (indexes, destination); CheckState (true, true); CheckAllowIndexes (); if (indexes.Count == 0) - return; + return null; var command = new StringBuilder ("MOVE "); ImapUtils.FormatIndexSet (Engine, command, indexes); command.Append (" %F\r\n"); - var ic = Engine.QueueCommand (cancellationToken, this, command.ToString (), destination); - - await Engine.RunAsync (ic, doAsync).ConfigureAwait (false); + return Engine.QueueCommand (cancellationToken, this, command.ToString (), destination); + } + void ProcessMoveToResponse (ImapCommand ic, IMailFolder destination) + { ProcessResponseCodes (ic, destination); if (ic.Response != ImapCommandResponse.Ok) @@ -5830,7 +5983,20 @@ async Task MoveToAsync (IList indexes, IMailFolder destination, bool doAsyn /// public override void MoveTo (IList indexes, IMailFolder destination, CancellationToken cancellationToken = default) { - MoveToAsync (indexes, destination, false, cancellationToken).GetAwaiter ().GetResult (); + if ((Engine.Capabilities & ImapCapabilities.Move) == 0) { + CopyTo (indexes, destination, cancellationToken); + Store (indexes, AddDeletedFlag, cancellationToken); + return; + } + + var ic = QueueMoveTo (indexes, destination, cancellationToken); + + if (ic == null) + return; + + Engine.Run (ic); + + ProcessMoveToResponse (ic, destination); } /// @@ -5884,9 +6050,22 @@ public override void MoveTo (IList indexes, IMailFolder destination, Cancel /// /// The server replied with a NO or BAD response. /// - public override Task MoveToAsync (IList indexes, IMailFolder destination, CancellationToken cancellationToken = default) + public override async Task MoveToAsync (IList indexes, IMailFolder destination, CancellationToken cancellationToken = default) { - return MoveToAsync (indexes, destination, true, cancellationToken); + if ((Engine.Capabilities & ImapCapabilities.Move) == 0) { + await CopyToAsync (indexes, destination, cancellationToken).ConfigureAwait (false); + await StoreAsync (indexes, AddDeletedFlag, cancellationToken).ConfigureAwait (false); + return; + } + + var ic = QueueMoveTo (indexes, destination, cancellationToken); + + if (ic == null) + return; + + await Engine.RunAsync (ic).ConfigureAwait (false); + + ProcessMoveToResponse (ic, destination); } #region IEnumerable implementation