Skip to content

Commit

Permalink
Split ImapEngine's UpdateStatus/UpdateStatusAsync
Browse files Browse the repository at this point in the history
Part of an ongoing effort to fix issue #1335
  • Loading branch information
jstedfast committed Dec 28, 2023
1 parent de29451 commit d232175
Showing 1 changed file with 126 additions and 68 deletions.
194 changes: 126 additions & 68 deletions MailKit/Net/Imap/ImapEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -899,16 +899,6 @@ internal Task<string> ReadLineAsync (bool doAsync, CancellationToken cancellatio
return Task.FromResult (ReadLine (cancellationToken));
}

internal ValueTask<ImapToken> ReadTokenAsync (string specials, bool doAsync, CancellationToken cancellationToken)
{
if (doAsync)
return Stream.ReadTokenAsync (specials, cancellationToken);

var token = Stream.ReadToken (specials, cancellationToken);

return new ValueTask<ImapToken> (token);
}

internal ValueTask<ImapToken> ReadTokenAsync (bool doAsync, CancellationToken cancellationToken)
{
if (doAsync)
Expand Down Expand Up @@ -1195,16 +1185,6 @@ public async Task<string> ReadLiteralAsync (CancellationToken cancellationToken)
}
}

Task<string> ReadLiteralAsync (bool doAsync, CancellationToken cancellationToken)
{
if (doAsync)
return ReadLiteralAsync (cancellationToken);

var value = ReadLiteral (cancellationToken);

return Task.FromResult (value);
}

void SkipLine (CancellationToken cancellationToken)
{
ImapToken token;
Expand Down Expand Up @@ -2111,16 +2091,79 @@ public async ValueTask<ImapResponseCode> ParseResponseCodeAsync (bool isTagged,
return code;
}

async ValueTask UpdateStatusAsync (bool doAsync, CancellationToken cancellationToken)
bool UpdateSimpleStatusValue (ImapFolder folder, string atom, ImapToken token)
{
var token = await ReadTokenAsync (ImapStream.AtomSpecials, doAsync, cancellationToken).ConfigureAwait (false);
uint count, uid;
ulong modseq;

if (atom.Equals ("HIGHESTMODSEQ", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);

modseq = ParseNumber64 (token, false, GenericItemSyntaxErrorFormat, atom, token);

folder?.UpdateHighestModSeq (modseq);
} else if (atom.Equals ("MESSAGES", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);

count = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token);

folder?.OnExists ((int) count);
} else if (atom.Equals ("RECENT", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);

count = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token);

folder?.OnRecent ((int) count);
} else if (atom.Equals ("UIDNEXT", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);

uid = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token);

folder?.UpdateUidNext (uid > 0 ? new UniqueId (uid) : UniqueId.Invalid);
} else if (atom.Equals ("UIDVALIDITY", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);

uid = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token);

folder?.UpdateUidValidity (uid);
} else if (atom.Equals ("UNSEEN", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);

count = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token);

folder?.UpdateUnread ((int) count);
} else if (atom.Equals ("APPENDLIMIT", StringComparison.OrdinalIgnoreCase)) {
if (token.Type == ImapTokenType.Atom) {
var limit = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token);

folder?.UpdateAppendLimit (limit);
} else {
AssertToken (token, ImapTokenType.Nil, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);

folder?.UpdateAppendLimit (null);
}
} else if (atom.Equals ("SIZE", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);

var size = ParseNumber64 (token, false, GenericItemSyntaxErrorFormat, atom, token);

folder?.UpdateSize (size);
} else {
// This is probably the MAILBOXID value which is multiple tokens and can't be handled here.
return false;
}

return true;
}

void UpdateStatus (CancellationToken cancellationToken)
{
var token = ReadToken (ImapStream.AtomSpecials, cancellationToken);
string name;

switch (token.Type) {
case ImapTokenType.Literal:
name = await ReadLiteralAsync (doAsync, cancellationToken).ConfigureAwait (false);
name = ReadLiteral (cancellationToken);
break;
case ImapTokenType.QString:
case ImapTokenType.Atom:
Expand All @@ -2138,12 +2181,12 @@ async ValueTask UpdateStatusAsync (bool doAsync, CancellationToken cancellationT
// and hasn't yet requested the folder. That's ok.
TryGetCachedFolder (name, out var folder);

token = await ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false);
token = ReadToken (cancellationToken);

AssertToken (token, ImapTokenType.OpenParen, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);

do {
token = await ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false);
token = ReadToken (cancellationToken);

if (token.Type == ImapTokenType.CloseParen)
break;
Expand All @@ -2152,76 +2195,91 @@ async ValueTask UpdateStatusAsync (bool doAsync, CancellationToken cancellationT

var atom = (string) token.Value;

token = await ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false);
token = ReadToken (cancellationToken);

if (atom.Equals ("HIGHESTMODSEQ", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);
if (UpdateSimpleStatusValue (folder, atom, token))
continue;

modseq = ParseNumber64 (token, false, GenericItemSyntaxErrorFormat, atom, token);
if (atom.Equals ("MAILBOXID", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.OpenParen, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);

folder?.UpdateHighestModSeq (modseq);
} else if (atom.Equals ("MESSAGES", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);
token = ReadToken (cancellationToken);

count = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token);
AssertToken (token, ImapTokenType.Atom, GenericItemSyntaxErrorFormat, atom, token);

folder?.OnExists ((int) count);
} else if (atom.Equals ("RECENT", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);
folder?.UpdateId ((string) token.Value);

count = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token);
token = ReadToken (cancellationToken);

folder?.OnRecent ((int) count);
} else if (atom.Equals ("UIDNEXT", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);
AssertToken (token, ImapTokenType.CloseParen, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);
}
} while (true);

uid = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token);
token = ReadToken (cancellationToken);

folder?.UpdateUidNext (uid > 0 ? new UniqueId (uid) : UniqueId.Invalid);
} else if (atom.Equals ("UIDVALIDITY", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);
AssertToken (token, ImapTokenType.Eoln, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);
}

uid = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token);
async ValueTask UpdateStatusAsync (CancellationToken cancellationToken)
{
var token = await ReadTokenAsync (ImapStream.AtomSpecials, cancellationToken).ConfigureAwait (false);
string name;

folder?.UpdateUidValidity (uid);
} else if (atom.Equals ("UNSEEN", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);
switch (token.Type) {
case ImapTokenType.Literal:
name = await ReadLiteralAsync (cancellationToken).ConfigureAwait (false);
break;
case ImapTokenType.QString:
case ImapTokenType.Atom:
name = (string) token.Value;
break;
case ImapTokenType.Nil:
// Note: according to rfc3501, section 4.5, NIL is acceptable as a mailbox name.
name = (string) token.Value;
break;
default:
throw UnexpectedToken (GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);
}

count = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token);
// Note: if the folder is null, then it probably means the user is using NOTIFY
// and hasn't yet requested the folder. That's ok.
TryGetCachedFolder (name, out var folder);

folder?.UpdateUnread ((int) count);
} else if (atom.Equals ("APPENDLIMIT", StringComparison.OrdinalIgnoreCase)) {
if (token.Type == ImapTokenType.Atom) {
var limit = ParseNumber (token, false, GenericItemSyntaxErrorFormat, atom, token);
token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false);

folder?.UpdateAppendLimit (limit);
} else {
AssertToken (token, ImapTokenType.Nil, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);
AssertToken (token, ImapTokenType.OpenParen, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);

folder?.UpdateAppendLimit (null);
}
} else if (atom.Equals ("SIZE", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);
do {
token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false);

var size = ParseNumber64 (token, false, GenericItemSyntaxErrorFormat, atom, token);
if (token.Type == ImapTokenType.CloseParen)
break;

folder?.UpdateSize (size);
} else if (atom.Equals ("MAILBOXID", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.Atom, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);

var atom = (string) token.Value;

token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false);

if (UpdateSimpleStatusValue (folder, atom, token))
continue;

if (atom.Equals ("MAILBOXID", StringComparison.OrdinalIgnoreCase)) {
AssertToken (token, ImapTokenType.OpenParen, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);

token = await ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false);
token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false);

AssertToken (token, ImapTokenType.Atom, GenericItemSyntaxErrorFormat, atom, token);

folder?.UpdateId ((string) token.Value);

token = await ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false);
token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false);

AssertToken (token, ImapTokenType.CloseParen, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);
}
} while (true);

token = await ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false);
token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false);

AssertToken (token, ImapTokenType.Eoln, GenericUntaggedResponseSyntaxErrorFormat, "STATUS", token);
}
Expand Down Expand Up @@ -2326,7 +2384,7 @@ internal void ProcessUntaggedResponse (CancellationToken cancellationToken)
} else if (atom.Equals ("NAMESPACE", StringComparison.OrdinalIgnoreCase)) {
UpdateNamespacesAsync (false, cancellationToken).GetAwaiter ().GetResult ();
} else if (atom.Equals ("STATUS", StringComparison.OrdinalIgnoreCase)) {
UpdateStatusAsync (false, cancellationToken).GetAwaiter ().GetResult ();
UpdateStatus (cancellationToken);
} else if (IsOkNoOrBad (atom, out var result)) {
token = ReadToken (cancellationToken);

Expand Down Expand Up @@ -2479,7 +2537,7 @@ internal async Task ProcessUntaggedResponseAsync (CancellationToken cancellation
} else if (atom.Equals ("NAMESPACE", StringComparison.OrdinalIgnoreCase)) {
await UpdateNamespacesAsync (doAsync: true, cancellationToken).ConfigureAwait (false);
} else if (atom.Equals ("STATUS", StringComparison.OrdinalIgnoreCase)) {
await UpdateStatusAsync (doAsync: true, cancellationToken).ConfigureAwait (false);
await UpdateStatusAsync (cancellationToken).ConfigureAwait (false);
} else if (IsOkNoOrBad (atom, out var result)) {
token = await ReadTokenAsync (cancellationToken).ConfigureAwait (false);

Expand Down

0 comments on commit d232175

Please sign in to comment.