diff --git a/CHANGELOG.md b/CHANGELOG.md index 00417300b..4b747891d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - call slow `delete_expired_imap_messages()` less often #3037 - make synchronization of Seen status more robust in case unsolicited FETCH result without UID is returned #3022 +- fetch Inbox before scanning folders to ensure iOS does + not kill the app before it gets to fetch the Inbox in background #3040 ## 1.72.0 diff --git a/src/imap/scan_folders.rs b/src/imap/scan_folders.rs index 2fcef3505..bf0e38480 100644 --- a/src/imap/scan_folders.rs +++ b/src/imap/scan_folders.rs @@ -10,7 +10,8 @@ use async_std::prelude::*; use super::{get_folder_meaning, get_folder_meaning_by_name}; impl Imap { - pub(crate) async fn scan_folders(&mut self, context: &Context) -> Result<()> { + /// Returns true if folders were scanned, false if scanning was postponed. + pub(crate) async fn scan_folders(&mut self, context: &Context) -> Result { // First of all, debounce to once per minute: let mut last_scan = context.last_full_folder_scan.lock().await; if let Some(last_scan) = *last_scan { @@ -20,7 +21,7 @@ impl Imap { .await?; if elapsed_secs < debounce_secs { - return Ok(()); + return Ok(false); } } info!(context, "Starting full folder scan"); @@ -98,7 +99,7 @@ impl Imap { } last_scan.replace(Instant::now()); - Ok(()) + Ok(true) } } diff --git a/src/scheduler.rs b/src/scheduler.rs index fe1637a10..0cbfd5e92 100644 --- a/src/scheduler.rs +++ b/src/scheduler.rs @@ -169,25 +169,42 @@ async fn fetch_idle(ctx: &Context, connection: &mut Imap, folder: Config) -> Int warn!(ctx, "{:#}", err); } - // Scan other folders before fetching from watched folder. This may result in the - // messages being moved into the watched folder, for example from the Spam folder to - // the Inbox folder. - if folder == Config::ConfiguredInboxFolder { - // Only scan on the Inbox thread in order to prevent parallel scans, which might lead to duplicate messages - if let Err(err) = connection.scan_folders(ctx).await { - // Don't reconnect, if there is a problem with the connection we will realize this when IDLEing - // but maybe just one folder can't be selected or something - warn!(ctx, "{}", err); - } - } - - // fetch + // Fetch the watched folder. if let Err(err) = connection.fetch_move_delete(ctx, &watch_folder).await { connection.trigger_reconnect(ctx).await; warn!(ctx, "{:#}", err); return InterruptInfo::new(false); } + // Scan additional folders only after finishing fetching the watched folder. + // + // On iOS the application has strictly limited time to work in background, so we may not + // be able to scan all folders before time is up if there are many of them. + if folder == Config::ConfiguredInboxFolder { + // Only scan on the Inbox thread in order to prevent parallel scans, which might lead to duplicate messages + match connection.scan_folders(ctx).await { + Err(err) => { + // Don't reconnect, if there is a problem with the connection we will realize this when IDLEing + // but maybe just one folder can't be selected or something + warn!(ctx, "{}", err); + } + Ok(true) => { + // Fetch the watched folder again in case scanning other folder moved messages + // there. + // + // In most cases this will select the watched folder and return because there are + // no new messages. We want to select the watched folder anyway before going IDLE + // there, so this does not take additional protocol round-trip. + if let Err(err) = connection.fetch_move_delete(ctx, &watch_folder).await { + connection.trigger_reconnect(ctx).await; + warn!(ctx, "{:#}", err); + return InterruptInfo::new(false); + } + } + Ok(false) => {} + } + } + // Synchronize Seen flags. if let Err(err) = connection .sync_seen_flags(ctx, &watch_folder)