mirror of
https://github.com/chatmail/core.git
synced 2026-05-08 09:26:29 +03:00
fix: do not miss new messages while expunging the folder
This should fix flaky `test_verified_group_vs_delete_server_after`.
This commit is contained in:
@@ -184,7 +184,7 @@ impl Session {
|
|||||||
bail!("Attempt to fetch UID 0");
|
bail!("Attempt to fetch UID 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
self.select_folder(context, folder).await?;
|
self.select_with_uidvalidity(context, folder).await?;
|
||||||
|
|
||||||
// we are connected, and the folder is selected
|
// we are connected, and the folder is selected
|
||||||
info!(context, "Downloading message {}/{} fully...", folder, uid);
|
info!(context, "Downloading message {}/{} fully...", folder, uid);
|
||||||
|
|||||||
13
src/imap.rs
13
src/imap.rs
@@ -543,15 +543,16 @@ impl Imap {
|
|||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_emails = session
|
session
|
||||||
.select_with_uidvalidity(context, folder)
|
.select_with_uidvalidity(context, folder)
|
||||||
.await
|
.await
|
||||||
.with_context(|| format!("Failed to select folder {folder:?}"))?;
|
.with_context(|| format!("Failed to select folder {folder:?}"))?;
|
||||||
|
|
||||||
if !new_emails && !fetch_existing_msgs {
|
if !session.new_mail && !fetch_existing_msgs {
|
||||||
info!(context, "No new emails in folder {folder:?}.");
|
info!(context, "No new emails in folder {folder:?}.");
|
||||||
return Ok(false);
|
return Ok(false);
|
||||||
}
|
}
|
||||||
|
session.new_mail = false;
|
||||||
|
|
||||||
let uid_validity = get_uidvalidity(context, folder).await?;
|
let uid_validity = get_uidvalidity(context, folder).await?;
|
||||||
let old_uid_next = get_uid_next(context, folder).await?;
|
let old_uid_next = get_uid_next(context, folder).await?;
|
||||||
@@ -838,7 +839,7 @@ impl Session {
|
|||||||
// Collect pairs of UID and Message-ID.
|
// Collect pairs of UID and Message-ID.
|
||||||
let mut msgs = BTreeMap::new();
|
let mut msgs = BTreeMap::new();
|
||||||
|
|
||||||
self.select_folder(context, folder).await?;
|
self.select_with_uidvalidity(context, folder).await?;
|
||||||
|
|
||||||
let mut list = self
|
let mut list = self
|
||||||
.uid_fetch("1:*", RFC724MID_UID)
|
.uid_fetch("1:*", RFC724MID_UID)
|
||||||
@@ -1039,7 +1040,7 @@ impl Session {
|
|||||||
// MOVE/DELETE operations. This does not result in multiple SELECT commands
|
// MOVE/DELETE operations. This does not result in multiple SELECT commands
|
||||||
// being sent because `select_folder()` does nothing if the folder is already
|
// being sent because `select_folder()` does nothing if the folder is already
|
||||||
// selected.
|
// selected.
|
||||||
self.select_folder(context, folder).await?;
|
self.select_with_uidvalidity(context, folder).await?;
|
||||||
|
|
||||||
// Empty target folder name means messages should be deleted.
|
// Empty target folder name means messages should be deleted.
|
||||||
if target.is_empty() {
|
if target.is_empty() {
|
||||||
@@ -1087,7 +1088,7 @@ impl Session {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for (folder, rowid_set, uid_set) in UidGrouper::from(rows) {
|
for (folder, rowid_set, uid_set) in UidGrouper::from(rows) {
|
||||||
self.select_folder(context, &folder)
|
self.select_with_uidvalidity(context, &folder)
|
||||||
.await
|
.await
|
||||||
.context("failed to select folder")?;
|
.context("failed to select folder")?;
|
||||||
|
|
||||||
@@ -1131,7 +1132,7 @@ impl Session {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
self.select_folder(context, folder)
|
self.select_with_uidvalidity(context, folder)
|
||||||
.await
|
.await
|
||||||
.context("failed to select folder")?;
|
.context("failed to select folder")?;
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,13 @@ impl Session {
|
|||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
use futures::future::FutureExt;
|
use futures::future::FutureExt;
|
||||||
|
|
||||||
self.select_folder(context, folder).await?;
|
self.select_with_uidvalidity(context, folder).await?;
|
||||||
|
|
||||||
if self.server_sent_unsolicited_exists(context)? {
|
if self.server_sent_unsolicited_exists(context)? {
|
||||||
|
self.new_mail = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.new_mail {
|
||||||
return Ok(self);
|
return Ok(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +96,9 @@ impl Session {
|
|||||||
session.as_mut().set_read_timeout(Some(IMAP_TIMEOUT));
|
session.as_mut().set_read_timeout(Some(IMAP_TIMEOUT));
|
||||||
self.inner = session;
|
self.inner = session;
|
||||||
|
|
||||||
|
// Fetch mail once we exit IDLE.
|
||||||
|
self.new_mail = true;
|
||||||
|
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ impl ImapSession {
|
|||||||
info!(context, "close/expunge succeeded");
|
info!(context, "close/expunge succeeded");
|
||||||
self.selected_folder = None;
|
self.selected_folder = None;
|
||||||
self.selected_folder_needs_expunge = false;
|
self.selected_folder_needs_expunge = false;
|
||||||
|
self.new_mail = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -47,11 +48,7 @@ impl ImapSession {
|
|||||||
/// Selects a folder, possibly updating uid_validity and, if needed,
|
/// Selects a folder, possibly updating uid_validity and, if needed,
|
||||||
/// expunging the folder to remove delete-marked messages.
|
/// expunging the folder to remove delete-marked messages.
|
||||||
/// Returns whether a new folder was selected.
|
/// Returns whether a new folder was selected.
|
||||||
pub(crate) async fn select_folder(
|
async fn select_folder(&mut self, context: &Context, folder: &str) -> Result<NewlySelected> {
|
||||||
&mut self,
|
|
||||||
context: &Context,
|
|
||||||
folder: &str,
|
|
||||||
) -> Result<NewlySelected> {
|
|
||||||
// if there is a new folder and the new folder is equal to the selected one, there's nothing to do.
|
// if there is a new folder and the new folder is equal to the selected one, there's nothing to do.
|
||||||
// if there is _no_ new folder, we continue as we might want to expunge below.
|
// if there is _no_ new folder, we continue as we might want to expunge below.
|
||||||
if let Some(selected_folder) = &self.selected_folder {
|
if let Some(selected_folder) = &self.selected_folder {
|
||||||
@@ -119,13 +116,14 @@ impl ImapSession {
|
|||||||
/// When selecting a folder for the first time, sets the uid_next to the current
|
/// When selecting a folder for the first time, sets the uid_next to the current
|
||||||
/// mailbox.uid_next so that no old emails are fetched.
|
/// mailbox.uid_next so that no old emails are fetched.
|
||||||
///
|
///
|
||||||
/// Returns Result<new_emails> (i.e. whether new emails arrived),
|
/// Updates `self.new_mail` if folder was previously unselected
|
||||||
/// if in doubt, returns new_emails=true so emails are fetched.
|
/// and new mails are detected after selecting,
|
||||||
|
/// i.e. UIDNEXT advanced while the folder was closed.
|
||||||
pub(crate) async fn select_with_uidvalidity(
|
pub(crate) async fn select_with_uidvalidity(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
folder: &str,
|
folder: &str,
|
||||||
) -> Result<bool> {
|
) -> Result<()> {
|
||||||
let newly_selected = self
|
let newly_selected = self
|
||||||
.select_or_create_folder(context, folder)
|
.select_or_create_folder(context, folder)
|
||||||
.await
|
.await
|
||||||
@@ -182,28 +180,26 @@ impl ImapSession {
|
|||||||
mailbox.uid_next = new_uid_next;
|
mailbox.uid_next = new_uid_next;
|
||||||
|
|
||||||
if new_uid_validity == old_uid_validity {
|
if new_uid_validity == old_uid_validity {
|
||||||
let new_emails = if newly_selected == NewlySelected::No {
|
if newly_selected == NewlySelected::Yes {
|
||||||
// The folder was not newly selected i.e. no SELECT command was run. This means that mailbox.uid_next
|
if let Some(new_uid_next) = new_uid_next {
|
||||||
// was not updated and may contain an incorrect value. So, just return true so that
|
if new_uid_next < old_uid_next {
|
||||||
// the caller tries to fetch new messages (we could of course run a SELECT command now, but trying to fetch
|
warn!(
|
||||||
// new messages is only one command, just as a SELECT command)
|
context,
|
||||||
true
|
"The server illegally decreased the uid_next of folder {folder:?} from {old_uid_next} to {new_uid_next} without changing validity ({new_uid_validity}), resyncing UIDs...",
|
||||||
} else if let Some(new_uid_next) = new_uid_next {
|
);
|
||||||
if new_uid_next < old_uid_next {
|
set_uid_next(context, folder, new_uid_next).await?;
|
||||||
warn!(
|
context.schedule_resync().await?;
|
||||||
context,
|
}
|
||||||
"The server illegally decreased the uid_next of folder {folder:?} from {old_uid_next} to {new_uid_next} without changing validity ({new_uid_validity}), resyncing UIDs...",
|
|
||||||
);
|
|
||||||
set_uid_next(context, folder, new_uid_next).await?;
|
|
||||||
context.schedule_resync().await?;
|
|
||||||
}
|
|
||||||
new_uid_next != old_uid_next // If UIDNEXT changed, there are new emails
|
|
||||||
} else {
|
|
||||||
// We have no UIDNEXT and if in doubt, return true.
|
|
||||||
true
|
|
||||||
};
|
|
||||||
|
|
||||||
return Ok(new_emails);
|
// If UIDNEXT changed, there are new emails.
|
||||||
|
self.new_mail |= new_uid_next != old_uid_next;
|
||||||
|
} else {
|
||||||
|
warn!(context, "Folder {folder} was just selected but we failed to determine UIDNEXT, assume that it has new mail.");
|
||||||
|
self.new_mail = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// UIDVALIDITY is modified, reset highest seen MODSEQ.
|
// UIDVALIDITY is modified, reset highest seen MODSEQ.
|
||||||
@@ -214,6 +210,7 @@ impl ImapSession {
|
|||||||
let new_uid_next = new_uid_next.unwrap_or_default();
|
let new_uid_next = new_uid_next.unwrap_or_default();
|
||||||
set_uid_next(context, folder, new_uid_next).await?;
|
set_uid_next(context, folder, new_uid_next).await?;
|
||||||
set_uidvalidity(context, folder, new_uid_validity).await?;
|
set_uidvalidity(context, folder, new_uid_validity).await?;
|
||||||
|
self.new_mail = true;
|
||||||
|
|
||||||
// Collect garbage entries in `imap` table.
|
// Collect garbage entries in `imap` table.
|
||||||
context
|
context
|
||||||
@@ -236,7 +233,7 @@ impl ImapSession {
|
|||||||
old_uid_next,
|
old_uid_next,
|
||||||
old_uid_validity,
|
old_uid_validity,
|
||||||
);
|
);
|
||||||
Ok(false)
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ pub(crate) struct Session {
|
|||||||
pub selected_mailbox: Option<Mailbox>,
|
pub selected_mailbox: Option<Mailbox>,
|
||||||
|
|
||||||
pub selected_folder_needs_expunge: bool,
|
pub selected_folder_needs_expunge: bool,
|
||||||
|
|
||||||
|
/// True if currently selected folder has new messages.
|
||||||
|
///
|
||||||
|
/// Should be false if no folder is currently selected.
|
||||||
|
pub new_mail: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Session {
|
impl Deref for Session {
|
||||||
@@ -67,6 +72,7 @@ impl Session {
|
|||||||
selected_folder: None,
|
selected_folder: None,
|
||||||
selected_mailbox: None,
|
selected_mailbox: None,
|
||||||
selected_folder_needs_expunge: false,
|
selected_folder_needs_expunge: false,
|
||||||
|
new_mail: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user