Skip to content

Commit

Permalink
conversations: sort thread by createdmodseq if same internaldate
Browse files Browse the repository at this point in the history
This gives us a stable sort when multiple external messages are appended
very quickly, giving them the same internaldate.

This is being added without a new version number, as it extends a data
structure in a backwards and forwards compatible way.  If not set, the
CREATEDMODSEQ field will be zero, which is fine.  This will still lead
to correct sorting for newly added emails on any thread.  The only
slight weirdness might be if an existing message in a thread gets moved
or copied into a new folder, which will lead to it getting a
createdmodseq value, and hence sorting to the end for that internaldate.
This will be very rare, and a convdb rebuild will fix it.
  • Loading branch information
brong committed Aug 16, 2023
1 parent c53219d commit a70ef12
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 1 deletion.
23 changes: 22 additions & 1 deletion imap/conversations.c
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,7 @@ static void conv_to_buf(conversation_t *conv, struct buf *buf, int flagcount)
dlist_setguid(nn, "GUID", &thread->guid);
dlist_setnum32(nn, "EXISTS", thread->exists);
dlist_setnum32(nn, "INTERNALDATE", thread->internaldate);
dlist_setnum32(nn, "CREATEDMODSEQ", thread->createdmodseq);
}

dlist_setnum64(dl, "CREATEDMODSEQ", conv->createdmodseq);
Expand Down Expand Up @@ -1489,6 +1490,11 @@ int _saxconvparse(int type, struct dlistsax_data *d)
rock->thread->internaldate = atol(d->data);
rock->substate = 3;
return 0;

case 3:
rock->thread->createdmodseq = atoll(d->data);
rock->substate = 4;
return 0;
}
return 0; // there might be following fields that we ignore here

Expand Down Expand Up @@ -1771,6 +1777,11 @@ static int _thread_datesort(const void **a, const void **b)
if (r < 0) return -1;
if (r > 0) return 1;

// if same internaldate, use createdmodseq for a stable sort
int64_t r2 = (ta->createdmodseq - tb->createdmodseq);
if (r2 < 0) return -1;
if (r2 > 0) return 1;

return message_guid_cmp(&ta->guid, &tb->guid);
}

Expand Down Expand Up @@ -1804,6 +1815,7 @@ static void conversations_thread_sort(conversation_t *conv)
static void conversation_update_thread(conversation_t *conv,
const struct message_guid *guid,
time_t internaldate,
modseq_t createdmodseq,
int delta_exists)
{
conv_thread_t *thread, **nextp = &conv->thread;
Expand All @@ -1829,7 +1841,15 @@ static void conversation_update_thread(conversation_t *conv,
}

message_guid_copy(&thread->guid, guid);
thread->internaldate = internaldate;
// these should always be the same for all copies of an email!
// but if not (e.g. IMAP append) we want the earliest non-zero value
if (!thread->internaldate || thread->internaldate > internaldate)
thread->internaldate = internaldate;
// the same email may exist multiple times in a folder or in multiple
// folders with different createdmodseq. We want to track the earliest
// one
if (!thread->createdmodseq || thread->createdmodseq > createdmodseq)
thread->createdmodseq = createdmodseq;
_apply_delta(&thread->exists, delta_exists);

conversations_thread_sort(conv);
Expand Down Expand Up @@ -2585,6 +2605,7 @@ EXPORTED int conversations_update_record(struct conversations_state *cstate,
conversation_update_thread(conv,
&record->guid,
record->internaldate,
record->createdmodseq,
delta_exists);

r = conversation_update(cstate, conv, &ecounts,
Expand Down
1 change: 1 addition & 0 deletions imap/conversations.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ struct conv_thread {
struct message_guid guid;
uint32_t exists;
time_t internaldate;
modseq_t createdmodseq;
};

struct conv_folder {
Expand Down

0 comments on commit a70ef12

Please sign in to comment.