From 4a291f2bc29c735e8f73aa2b476fe932517b83b5 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Sat, 5 Sep 2015 16:44:51 +0200 Subject: Sample UIDs in SELECT $mailbox (QRESYNC ...) commands. This should avoids most false-positive among messages reported as VANISHED by the server but unknown from the database. The reason for this server behavior is that QRESYNC [RFC7162] doesn't force the server to remember the MODSEQs of EXPUNGEd messages. By passing a sample of known UIDs/sequence numbers we let the server know that the messages have been EXPUNGEd [RFC7162, section 3.2.5.2]. --- imapsync | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 68 insertions(+), 5 deletions(-) (limited to 'imapsync') diff --git a/imapsync b/imapsync index f7c5234..ac63577 100755 --- a/imapsync +++ b/imapsync @@ -559,6 +559,13 @@ my $STH_GET_INTERRUPTED_BY_IDX = $DBH->prepare(q{ WHERE m.idx = ? AND (lUID >= l.UIDNEXT OR rUID >= r.UIDNEXT) }); +# Count messages +my $STH_COUNT_MESSAGES = $DBH->prepare(q{SELECT COUNT(*) FROM mapping WHERE idx = ?}); + +# List last 1024 messages UIDs +my $STH_LASTUIDs_LOCAL = $DBH->prepare(q{SELECT rUID FROM mapping WHERE idx = ? ORDER BY rUID DESC LIMIT 1024}); +my $STH_LASTUIDs_REMOTE = $DBH->prepare(q{SELECT lUID FROM mapping WHERE idx = ? ORDER BY lUID DESC LIMIT 1024}); + # Download some missing UIDs from $source; returns the thew allocated UIDs sub download_missing($$$@) { @@ -612,6 +619,64 @@ sub delete_mapping($$) { } +# Create a sample (UIDs, sequence numbers) to use as 3rd and 4th +# argument of the QRESYNC parameters to the SELECT command. +# QRESYNC [RFC7162] doesn't force the server to remember the MODSEQs of +# EXPUNGEd messages. By passing a sample of known UIDs/sequence numbers +# we let the server know that the messages have been EXPUNGEd [RFC7162, +# section 3.2.5.2]. +# The UID set is the largest set of higest UIDs with at most 1024 UIDs, +# of length (after compacting) at most 64. +# The reason why we sample with the highest UIDs is that lowest UIDs are +# less likely to be deleted. +sub sample($$$) { + my ($idx, $count, $sth) = @_; + return unless $count > 0; + + my ($n, $uids, $min, $max); + $sth->execute($idx); + while (defined (my $row = $sth->fetchrow_arrayref())) { + my $k = $row->[0]; + if (!defined $min and !defined $max) { + $n = 0; + $min = $max = $k; + } + elsif ($k == $min - 1) { + $min--; + } + else { + $n += $max - $min + 1; + $uids = ($min == $max ? $min : "$min:$max") + .(defined $uids ? ','.$uids : ''); + $min = $max = $k; + if (length($uids) > 64) { + $sth->finish(); # done with the statement + last; + } + } + } + if (!defined $uids or length($uids) <= 64) { + $n += $max - $min + 1; + $uids = ($min == $max ? $min : "$min:$max") + .(defined $uids ? ','.$uids : ''); + } + return ( $uids, ($count - $n + 1).':'.$count ); +} + + +# Issue a SELECT command with the given $mailbox. +sub select_mbx($$) { + my ($idx, $mailbox) = @_; + + $STH_COUNT_MESSAGES->execute($idx); + my ($count) = $STH_COUNT_MESSAGES->fetchrow_array(); + die if defined $STH_COUNT_MESSAGES->fetch(); # sanity check + + $lIMAP->select($mailbox, sample($idx, $count, $STH_LASTUIDs_LOCAL)); + $rIMAP->select($mailbox, sample($idx, $count, $STH_LASTUIDs_REMOTE)); +} + + # Check and repair synchronization of a mailbox between the two servers # (in a very crude way, by downloading all existing UID with their flags) sub repair($) { @@ -622,8 +687,7 @@ sub repair($) { die if defined $STH_GET_INDEX->fetch(); # sanity check return unless defined $idx; # not in the database - $lIMAP->select($mailbox); - $rIMAP->select($mailbox); + select_mbx($idx, $mailbox); $STH_GET_CACHE_BY_IDX->execute($idx); my $cache = $STH_GET_CACHE_BY_IDX->fetchrow_hashref() // return; # no cache @@ -1005,7 +1069,7 @@ sub wait_notifications(;$) { ############################################################################# -# Resume interrupted mailbox syncs. +# Resume interrupted mailbox syncs (before initializing the cache). # my ($MAILBOX, $IDX); $STH_LIST_INTERRUPTED->execute(); @@ -1101,8 +1165,7 @@ while(1) { die if defined $STH_GET_INDEX->fetch(); # sanity check die unless defined $IDX; # sanity check; - $lIMAP->select($MAILBOX); - $rIMAP->select($MAILBOX); + select_mbx($IDX, $MAILBOX); if (!$KNOWN_INDEXES{$IDX}) { $STH_INSERT_LOCAL->execute( $IDX, $lIMAP->uidvalidity($MAILBOX)); -- cgit v1.2.3