mirror of https://git.code.sf.net/p/isync/isync
Oswald Buddenhagen
3 years ago
7 changed files with 427 additions and 78 deletions
@ -0,0 +1,153 @@ |
|||||||
|
// SPDX-FileCopyrightText: 2022 Oswald Buddenhagen <ossi@users.sf.net>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
|
||||||
|
//
|
||||||
|
// mbsync - mailbox synchronizer
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "imap_p.h" |
||||||
|
|
||||||
|
#ifdef DEBUG_IMAP_MSGS |
||||||
|
# define dbg(...) print(__VA_ARGS__) |
||||||
|
#else |
||||||
|
# define dbg(...) do { } while (0) |
||||||
|
#endif |
||||||
|
|
||||||
|
imap_message_t * |
||||||
|
imap_new_msg( imap_messages_t *msgs ) |
||||||
|
{ |
||||||
|
imap_message_t *msg = nfzalloc( sizeof(*msg) ); |
||||||
|
*msgs->tail = msg; |
||||||
|
msgs->tail = &msg->next; |
||||||
|
msgs->count++; |
||||||
|
return msg; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
reset_imap_messages( imap_messages_t *msgs ) |
||||||
|
{ |
||||||
|
free_generic_messages( &msgs->head->gen ); |
||||||
|
msgs->head = NULL; |
||||||
|
msgs->tail = &msgs->head; |
||||||
|
msgs->count = 0; |
||||||
|
msgs->cursor_ptr = NULL; |
||||||
|
msgs->cursor_seq = 0; |
||||||
|
} |
||||||
|
|
||||||
|
static int |
||||||
|
imap_compare_msgs( const void *a_, const void *b_ ) |
||||||
|
{ |
||||||
|
const imap_message_t *a = *(const imap_message_t * const *)a_; |
||||||
|
const imap_message_t *b = *(const imap_message_t * const *)b_; |
||||||
|
|
||||||
|
if (a->uid < b->uid) |
||||||
|
return -1; |
||||||
|
if (a->uid > b->uid) |
||||||
|
return 1; |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
imap_ensure_relative( imap_messages_t *msgs ) |
||||||
|
{ |
||||||
|
if (msgs->cursor_ptr) |
||||||
|
return; |
||||||
|
uint count = msgs->count; |
||||||
|
if (!count) |
||||||
|
return; |
||||||
|
if (count > 1) { |
||||||
|
imap_message_t **t = nfmalloc( sizeof(*t) * count ); |
||||||
|
|
||||||
|
imap_message_t *m = msgs->head; |
||||||
|
for (uint i = 0; i < count; i++) { |
||||||
|
t[i] = m; |
||||||
|
m = m->next; |
||||||
|
} |
||||||
|
|
||||||
|
qsort( t, count, sizeof(*t), imap_compare_msgs ); |
||||||
|
|
||||||
|
imap_message_t *nm = t[0]; |
||||||
|
msgs->head = nm; |
||||||
|
nm->prev = NULL; |
||||||
|
uint seq, nseq = nm->seq; |
||||||
|
for (uint j = 0; m = nm, seq = nseq, j < count - 1; j++) { |
||||||
|
nm = t[j + 1]; |
||||||
|
m->next = nm; |
||||||
|
m->next->prev = m; |
||||||
|
nseq = nm->seq; |
||||||
|
nm->seq = nseq - seq; |
||||||
|
} |
||||||
|
msgs->tail = &m->next; |
||||||
|
*msgs->tail = NULL; |
||||||
|
|
||||||
|
free( t ); |
||||||
|
} |
||||||
|
msgs->cursor_ptr = msgs->head; |
||||||
|
msgs->cursor_seq = msgs->head->seq; |
||||||
|
} |
||||||
|
|
||||||
|
void |
||||||
|
imap_ensure_absolute( imap_messages_t *msgs ) |
||||||
|
{ |
||||||
|
if (!msgs->cursor_ptr) |
||||||
|
return; |
||||||
|
uint seq = 0; |
||||||
|
for (imap_message_t *msg = msgs->head; msg; msg = msg->next) { |
||||||
|
seq += msg->seq; |
||||||
|
msg->seq = seq; |
||||||
|
} |
||||||
|
msgs->cursor_ptr = NULL; |
||||||
|
msgs->cursor_seq = 0; |
||||||
|
} |
||||||
|
|
||||||
|
imap_message_t * |
||||||
|
imap_expunge_msg( imap_messages_t *msgs, uint fseq ) |
||||||
|
{ |
||||||
|
dbg( "expunge %u\n", fseq ); |
||||||
|
imap_ensure_relative( msgs ); |
||||||
|
imap_message_t *ret = NULL, *msg = msgs->cursor_ptr; |
||||||
|
if (msg) { |
||||||
|
uint seq = msgs->cursor_seq; |
||||||
|
for (;;) { |
||||||
|
dbg( " now on message %u (uid %u), %sdead\n", seq, msg->uid, (msg->status & M_DEAD) ? "" : "not " ); |
||||||
|
if (seq == fseq && !(msg->status & M_DEAD)) { |
||||||
|
dbg( " => expunging\n" ); |
||||||
|
msg->status = M_DEAD; |
||||||
|
ret = msg; |
||||||
|
break; |
||||||
|
} |
||||||
|
if (seq < fseq) { |
||||||
|
dbg( " is below\n" ); |
||||||
|
if (!msg->next) { |
||||||
|
dbg( " no next\n" ); |
||||||
|
goto done; |
||||||
|
} |
||||||
|
msg = msg->next; |
||||||
|
seq += msg->seq; |
||||||
|
} else { |
||||||
|
dbg( " is not below\n" ); |
||||||
|
if (!msg->prev) { |
||||||
|
dbg( " no prev\n" ); |
||||||
|
break; |
||||||
|
} |
||||||
|
uint pseq = seq - msg->seq; |
||||||
|
if (pseq < fseq) { |
||||||
|
dbg( " prev too low\n" ); |
||||||
|
break; |
||||||
|
} |
||||||
|
seq = pseq; |
||||||
|
msg = msg->prev; |
||||||
|
} |
||||||
|
} |
||||||
|
dbg( " => lowering\n" ); |
||||||
|
assert( msg->seq ); |
||||||
|
msg->seq--; |
||||||
|
seq--; |
||||||
|
done: |
||||||
|
dbg( " saving cursor on %u (uid %u)\n", seq, msg->uid ); |
||||||
|
msgs->cursor_ptr = msg; |
||||||
|
msgs->cursor_seq = seq; |
||||||
|
} else { |
||||||
|
dbg( " => no messages\n" ); |
||||||
|
} |
||||||
|
return ret; |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
// SPDX-FileCopyrightText: 2022 Oswald Buddenhagen <ossi@users.sf.net>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later WITH LicenseRef-isync-GPL-exception
|
||||||
|
//
|
||||||
|
// mbsync - mailbox synchronizer
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef IMAP_P_H |
||||||
|
#define IMAP_P_H |
||||||
|
|
||||||
|
#include "driver.h" |
||||||
|
|
||||||
|
//#define DEBUG_IMAP_MSGS
|
||||||
|
|
||||||
|
typedef union imap_message { |
||||||
|
message_t gen; |
||||||
|
struct { |
||||||
|
MESSAGE(union imap_message) |
||||||
|
|
||||||
|
union imap_message *prev; // Used to optimize lookup by seq.
|
||||||
|
// This is made relative once the fetches complete - to avoid that
|
||||||
|
// each expunge re-enumerates all subsequent messages. Dead messages
|
||||||
|
// "occupy" no sequence number themselves, but may still jump a gap.
|
||||||
|
// Note that use of sequence numbers to address messages in commands
|
||||||
|
// imposes limitations on permissible pipelining. We don't do that,
|
||||||
|
// so this is of no concern; however, we might miss the closing of
|
||||||
|
// a gap, which would result in a tiny performance hit.
|
||||||
|
uint seq; |
||||||
|
}; |
||||||
|
} imap_message_t; |
||||||
|
|
||||||
|
typedef struct { |
||||||
|
imap_message_t *head; |
||||||
|
imap_message_t **tail; |
||||||
|
// Bulk changes (which is where performance matters) are assumed to be
|
||||||
|
// reported sequentially (be it forward or reverse), so walking the
|
||||||
|
// sorted linked list from the previously used message is efficient.
|
||||||
|
imap_message_t *cursor_ptr; |
||||||
|
uint cursor_seq; |
||||||
|
uint count; |
||||||
|
} imap_messages_t; |
||||||
|
|
||||||
|
imap_message_t *imap_new_msg( imap_messages_t *msgs ); |
||||||
|
imap_message_t *imap_expunge_msg( imap_messages_t *msgs, uint fseq ); |
||||||
|
void reset_imap_messages( imap_messages_t *msgs ); |
||||||
|
void imap_ensure_relative( imap_messages_t *msgs ); |
||||||
|
void imap_ensure_absolute( imap_messages_t *msgs ); |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,166 @@ |
|||||||
|
// SPDX-FileCopyrightText: 2022 Oswald Buddenhagen <ossi@users.sf.net>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
//
|
||||||
|
// isync test suite
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "imap_p.h" |
||||||
|
|
||||||
|
static imap_messages_t smsgs; |
||||||
|
|
||||||
|
// from driver.c
|
||||||
|
void |
||||||
|
free_generic_messages( message_t *msgs ) |
||||||
|
{ |
||||||
|
message_t *tmsg; |
||||||
|
|
||||||
|
for (; msgs; msgs = tmsg) { |
||||||
|
tmsg = msgs->next; |
||||||
|
// free( msgs->msgid );
|
||||||
|
free( msgs ); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
dump_messages( void ) |
||||||
|
{ |
||||||
|
print( "=>" ); |
||||||
|
uint seq = 0; |
||||||
|
for (imap_message_t *msg = smsgs.head; msg; msg = msg->next) { |
||||||
|
seq += msg->seq; |
||||||
|
if (msg->status & M_DEAD) |
||||||
|
print( " (%u:%u)", seq, msg->uid ); |
||||||
|
else |
||||||
|
print( " %u:%u", seq, msg->uid ); |
||||||
|
} |
||||||
|
print( "\n" ); |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
init( uint *in ) |
||||||
|
{ |
||||||
|
reset_imap_messages( &smsgs ); |
||||||
|
for (; *in; in++) { |
||||||
|
imap_message_t *msg = imap_new_msg( &smsgs ); |
||||||
|
msg->seq = *in; |
||||||
|
// We (ab)use the initial sequence number as the UID. That's not
|
||||||
|
// exactly realistic, but it's valid, and saves us redundant data.
|
||||||
|
msg->uid = *in; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
modify( uint *in ) |
||||||
|
{ |
||||||
|
for (; *in; in++) { |
||||||
|
imap_expunge_msg( &smsgs, *in ); |
||||||
|
#ifdef DEBUG_IMAP_MSGS |
||||||
|
dump_messages(); |
||||||
|
#endif |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
verify( uint *in, const char *name ) |
||||||
|
{ |
||||||
|
int fails = 0; |
||||||
|
imap_message_t *msg = smsgs.head; |
||||||
|
for (;;) { |
||||||
|
if (msg && *in && msg->uid == *in) { |
||||||
|
if (msg->status & M_DEAD) { |
||||||
|
printf( "*** %s: message %u is dead\n", name, msg->uid ); |
||||||
|
fails++; |
||||||
|
} else { |
||||||
|
assert( msg->seq ); |
||||||
|
} |
||||||
|
msg = msg->next; |
||||||
|
in++; |
||||||
|
} else if (*in && (!msg || msg->uid > *in)) { |
||||||
|
printf( "*** %s: message %u is missing\n", name, *in ); |
||||||
|
fails++; |
||||||
|
in++; |
||||||
|
} else if (msg) { |
||||||
|
if (!(msg->status & M_DEAD)) { |
||||||
|
printf( "*** %s: excess message %u\n", name, msg->uid ); |
||||||
|
fails++; |
||||||
|
} |
||||||
|
msg = msg->next; |
||||||
|
} else { |
||||||
|
assert( !*in ); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if (fails) |
||||||
|
dump_messages(); |
||||||
|
} |
||||||
|
|
||||||
|
static void |
||||||
|
test( uint *ex, uint *out, const char *name ) |
||||||
|
{ |
||||||
|
printf( "test %s ...\n", name ); |
||||||
|
modify( ex ); |
||||||
|
verify( out, name ); |
||||||
|
} |
||||||
|
|
||||||
|
int |
||||||
|
main( void ) |
||||||
|
{ |
||||||
|
static uint arr_0[] = { 0 }; |
||||||
|
static uint arr_1[] = { 1, 0 }; |
||||||
|
|
||||||
|
static uint full_in[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 0 }; |
||||||
|
init( full_in ); |
||||||
|
#if 0 |
||||||
|
static uint nop[] = { 0 }; |
||||||
|
static uint nop_out[] = { /* 1, */ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, /* 17, */ 18 /*!*/, 0 }; |
||||||
|
test( nop, nop_out, "self-test" ); |
||||||
|
#endif |
||||||
|
static uint full_ex_fw1[] = { 18, 13, 13, 13, 1, 1, 1, 0 }; |
||||||
|
static uint full_out_fw1[] = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 16, 17, 0 }; |
||||||
|
test( full_ex_fw1, full_out_fw1, "full, forward 1" ); |
||||||
|
static uint full_ex_fw2[] = { 10, 10, 0 }; |
||||||
|
static uint full_out_fw2[] = { 4, 5, 6, 7, 8, 9, 10, 11, 12, 0 }; |
||||||
|
test( full_ex_fw2, full_out_fw2, "full, forward 2" ); |
||||||
|
|
||||||
|
init( full_in ); |
||||||
|
static uint full_ex_bw1[] = { 18, 17, 16, 15, 14, 13, 5, 4, 3, 0 }; |
||||||
|
static uint full_out_bw1[] = { 1, 2, 6, 7, 8, 9, 10, 11, 12, 0 }; |
||||||
|
test( full_ex_bw1, full_out_bw1, "full, backward 1" ); |
||||||
|
static uint full_ex_bw2[] = { 2, 1, 0 }; |
||||||
|
static uint full_out_bw2[] = { 6, 7, 8, 9, 10, 11, 12, 0 }; |
||||||
|
test( full_ex_bw2, full_out_bw2, "full, backward 2" ); |
||||||
|
|
||||||
|
static uint hole_wo1_in[] = { 10, 11, 12, 20, 21, 31, 32, 33, 34, 35, 36, 37, 0 }; |
||||||
|
init( hole_wo1_in ); |
||||||
|
static uint hole_wo1_ex_1[] = { 31, 30, 29, 28, 22, 21, 11, 2, 1, 0 }; |
||||||
|
static uint hole_wo1_out_1[] = { 10, 12, 20, 32, 33, 34, 35, 36, 37, 0 }; |
||||||
|
test( hole_wo1_ex_1, hole_wo1_out_1, "hole w/o 1, backward" ); |
||||||
|
|
||||||
|
init( hole_wo1_in ); |
||||||
|
static uint hole_wo1_ex_2[] = { 1, 1, 9, 18, 18, 23, 23, 23, 23, 0 }; |
||||||
|
test( hole_wo1_ex_2, hole_wo1_out_1, "hole w/o 1, forward" ); |
||||||
|
test( arr_1, hole_wo1_out_1, "hole w/o 1, forward 2" ); |
||||||
|
static uint hole_wo1_ex_4[] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 }; |
||||||
|
static uint hole_wo1_out_4[] = { 37, 0 }; |
||||||
|
test( hole_wo1_ex_4, hole_wo1_out_4, "hole w/o 1, forward 3" ); |
||||||
|
test( arr_1, arr_0, "hole w/o 1, forward 4" ); |
||||||
|
test( arr_1, arr_0, "hole w/o 1, forward 5" ); |
||||||
|
|
||||||
|
static uint hole_w1_in[] = { 1, 10, 11, 12, 0 }; |
||||||
|
init( hole_w1_in ); |
||||||
|
static uint hole_w1_ex_1[] = { 11, 10, 2, 1, 0 }; |
||||||
|
static uint hole_w1_out_1[] = { 12, 0 }; |
||||||
|
test( hole_w1_ex_1, hole_w1_out_1, "hole w/ 1, backward" ); |
||||||
|
test( arr_1, hole_w1_out_1, "hole w/ 1, backward 2" ); |
||||||
|
|
||||||
|
init( hole_w1_in ); |
||||||
|
static uint hole_w1_ex_2[] = { 1, 1, 8, 8, 0 }; |
||||||
|
test( hole_w1_ex_2, hole_w1_out_1, "hole w/ 1, forward" ); |
||||||
|
static uint hole_w1_ex_4[] = { 1, 1, 1, 1, 1, 1, 1, 0 }; |
||||||
|
static uint hole_w1_out_4[] = { 12, 0 }; |
||||||
|
test( hole_w1_ex_4, hole_w1_out_4, "hole w/ 1, forward 2" ); |
||||||
|
test( arr_1, arr_0, "hole w/ 1, forward 3" ); |
||||||
|
test( arr_1, arr_0, "hole w/ 1, forward 4" ); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
Loading…
Reference in new issue