fix(imap): downgrade empty tail-fetch from error to info #342

This commit is contained in:
rustmailer
2026-08-19 17:31:47 +08:00
parent d9097f85cf
commit 02b8741725

View File

@@ -151,13 +151,7 @@ impl ImapExecutor {
match before {
Some(date) => {
Self::fetch_new_mail_with_before(
session,
account,
mailbox,
start_uid,
date,
examined,
token,
session, account, mailbox, start_uid, date, examined, token,
)
.await
}
@@ -226,7 +220,7 @@ impl ImapExecutor {
start_uid,
examined.uid_next,
) {
tracing::warn!(
tracing::info!(
account_id = account.id,
mailbox = %mailbox.name,
start_uid,
@@ -234,14 +228,14 @@ impl ImapExecutor {
"{}",
msg
);
DownloadState::append_session_error(account.id, msg)?;
//DownloadState::append_session_error(account.id, msg)?;
DownloadState::update_folder_progress(
account.id,
mailbox.name.clone(),
0,
0,
FolderStatus::Failed,
Some("UID enumeration came back empty despite new mail on server. Retrying on next sync.".into()),
FolderStatus::Success,
Some("UID FETCH range returned empty; tail UIDs appear to have been purged. highest_uid unchanged, will retry on next sync.".into()),
)?;
return Ok(None);
}
@@ -373,15 +367,11 @@ impl ImapExecutor {
})?;
let mut entries: Vec<(u32, u64, i64)> = Vec::new();
let mut skipped_oversized = 0u64;
while let Some(fetch) = uid_stream
.try_next()
.await
.map_err(|e| {
while let Some(fetch) = uid_stream.try_next().await.map_err(|e| {
let err_msg = format!("UID FETCH stream failed in [{}]: {:#?}", mailbox.name, e);
let _ = DownloadState::append_session_error(account_id, err_msg);
raise_error!(format!("{:#?}", e), classify_imap_error(&e))
})?
{
})? {
if token.is_cancelled() {
DownloadState::update_session_status(
account_id,
@@ -499,7 +489,7 @@ impl ImapExecutor {
start_uid,
examined.uid_next,
) {
tracing::warn!(
tracing::info!(
account_id = account.id,
mailbox = %mailbox.name,
start_uid,
@@ -507,14 +497,17 @@ impl ImapExecutor {
"{}",
msg
);
DownloadState::append_session_error(account.id, msg)?;
//The gap between highest_uid and uid_next may be caused by messages being deleted on the server side.
//Reporting this as a warning/error makes users think that something is wrong with the sync.
//DownloadState::append_session_error(account.id, msg)?;
DownloadState::update_folder_progress(
account.id,
mailbox.name.clone(),
0,
0,
FolderStatus::Failed,
Some("UID enumeration came back empty despite new mail on server. Retrying on next sync.".into()),
FolderStatus::Success,
Some("UID FETCH range returned empty; tail UIDs appear to have been purged. highest_uid unchanged, will retry on next sync.".into()),
)?;
return Ok(None);
}
@@ -760,8 +753,8 @@ impl ImapExecutor {
// On error, report the number of emails already stored through the
// progress callback so a partial batch is not counted as fully
// failed by callers (gap_fill uses the last reported count).
let item = match tokio::time::timeout(STALL_REPORT_INTERVAL, body_stream.try_next()).await
{
let item =
match tokio::time::timeout(STALL_REPORT_INTERVAL, body_stream.try_next()).await {
Ok(Ok(item)) => Ok(item),
Ok(Err(e)) => Err((count, e)),
Err(_) => {
@@ -789,10 +782,7 @@ impl ImapExecutor {
if let Some(progress) = progress {
let _ = progress(processed, None, None);
}
return Err(raise_error!(
format!("{:#?}", e),
classify_imap_error(&e)
));
return Err(raise_error!(format!("{:#?}", e), classify_imap_error(&e)));
}
};
let Some(fetch) = item else { break };
@@ -944,12 +934,13 @@ impl ImapExecutor {
session: &mut Session<Box<dyn SessionStream>>,
uid_set: &str,
token: CancellationToken,
progress: Option<
&(dyn Fn(u64, Option<f64>) -> BichonResult<()> + Send + Sync),
>,
progress: Option<&(dyn Fn(u64, Option<f64>) -> BichonResult<()> + Send + Sync)>,
) -> BichonResult<Vec<crate::archive::imap::download::gap_fill::RemoteHeader>> {
let mut stream = session
.uid_fetch(uid_set, "(UID RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (MESSAGE-ID)])")
.uid_fetch(
uid_set,
"(UID RFC822.SIZE INTERNALDATE BODY.PEEK[HEADER.FIELDS (MESSAGE-ID)])",
)
.await
.map_err(|e| raise_error!(format!("{:#?}", e), classify_imap_error(&e)))?;
@@ -959,13 +950,9 @@ impl ImapExecutor {
// seconds so the UI shows the wait climbing instead of a stale state.
const STALL_REPORT_INTERVAL: std::time::Duration = std::time::Duration::from_secs(5);
loop {
let item =
match tokio::time::timeout(STALL_REPORT_INTERVAL, stream.try_next()).await {
let item = match tokio::time::timeout(STALL_REPORT_INTERVAL, stream.try_next()).await {
Ok(Ok(item)) => Ok(item),
Ok(Err(e)) => Err(raise_error!(
format!("{:#?}", e),
classify_imap_error(&e)
)),
Ok(Err(e)) => Err(raise_error!(format!("{:#?}", e), classify_imap_error(&e))),
Err(_) => {
if token.is_cancelled() {
return Err(raise_error!(
@@ -1126,10 +1113,7 @@ pub fn generate_uid_sequence_hashset(
/// Returns the matching UIDs sorted ascending. Errors if the date cannot be
/// parsed — a silently-broken date filter would download mail the user asked
/// to exclude.
fn filter_before_date(
entries: &[(u32, u64, i64)],
date: &str,
) -> BichonResult<Vec<u32>> {
fn filter_before_date(entries: &[(u32, u64, i64)], date: &str) -> BichonResult<Vec<u32>> {
let cutoff = chrono::NaiveDate::parse_from_str(date, "%d-%b-%Y").map_err(|e| {
raise_error!(
format!("Invalid BEFORE date '{date}': {e}"),
@@ -1277,10 +1261,7 @@ mod test {
fn filter_before_date_timezone_ignored() {
// Internal date late in the day in +1400 still counts as that day:
// 2025-05-26 23:59:00 +1400 == 2025-05-26 09:59 UTC.
let entries = vec![
(1, 100, ms(2025, 5, 25)),
(2, 200, ms(2025, 5, 26)),
];
let entries = vec![(1, 100, ms(2025, 5, 25)), (2, 200, ms(2025, 5, 26))];
let uids = filter_before_date(&entries, "26-May-2025").unwrap();
assert_eq!(uids, vec![1]);
}