diff --git a/deltachat-rpc-client/tests/test_something.py b/deltachat-rpc-client/tests/test_something.py index 5f178ec6ca..460726e13f 100644 --- a/deltachat-rpc-client/tests/test_something.py +++ b/deltachat-rpc-client/tests/test_something.py @@ -473,7 +473,7 @@ def track(e): def test_wait_next_messages(acfactory) -> None: - alice = acfactory.new_configured_account() + alice = acfactory.get_online_account() # Create a bot account so it does not receive device messages in the beginning. addr, password = acfactory.get_credentials() @@ -481,6 +481,7 @@ def test_wait_next_messages(acfactory) -> None: bot.set_config("bot", "1") bot.add_or_update_transport({"addr": addr, "password": password}) assert bot.is_configured() + bot.bring_online() # There are no old messages and the call returns immediately. assert not bot.wait_next_messages() @@ -902,7 +903,10 @@ def test_delete_deltachat_folder(acfactory, direct_imap): # Wait until new folder is created and UIDVALIDITY is updated. while True: event = ac1.wait_for_event() - if event.kind == EventType.INFO and "uid/validity change folder DeltaChat" in event.msg: + if ( + event.kind == EventType.INFO + and "UID validity for folder DeltaChat and transport 1 changed from " in event.msg + ): break ac2 = acfactory.get_online_account() diff --git a/src/configure.rs b/src/configure.rs index dc5bafb0ed..0636b0329c 100644 --- a/src/configure.rs +++ b/src/configure.rs @@ -566,12 +566,6 @@ async fn configure(ctx: &Context, param: &EnteredLoginParam) -> Result Result<(usize, bool)> { - let uid_validity = get_uidvalidity(context, folder).await?; - let old_uid_next = get_uid_next(context, folder).await?; + let transport_id = self.transport_id; + let uid_validity = get_uidvalidity(context, transport_id, folder).await?; + let old_uid_next = get_uid_next(context, transport_id, folder).await?; info!( context, "fetch_new_msg_batch({folder}): UIDVALIDITY={uid_validity}, UIDNEXT={old_uid_next}." @@ -782,7 +793,7 @@ impl Imap { prefetch_uid_next < mailbox_uid_next }; if new_uid_next > old_uid_next { - set_uid_next(context, folder, new_uid_next).await?; + set_uid_next(context, self.transport_id, folder, new_uid_next).await?; } info!(context, "{} mails read from \"{}\".", read_cnt, folder); @@ -862,6 +873,7 @@ impl Session { let folder_exists = self .select_with_uidvalidity(context, folder, create) .await?; + let transport_id = self.transport_id(); if folder_exists { let mut list = self .uid_fetch("1:*", RFC724MID_UID) @@ -894,13 +906,14 @@ impl Session { msgs.len(), ); - uid_validity = get_uidvalidity(context, folder).await?; + uid_validity = get_uidvalidity(context, transport_id, folder).await?; } else { warn!(context, "resync_folder_uids: No folder {folder}."); uid_validity = 0; } - let transport_id = 1; // FIXME + let transport_id = self.transport_id(); + // Write collected UIDs to SQLite database. context .sql @@ -1237,11 +1250,12 @@ impl Session { return Ok(()); } + let transport_id = self.transport_id(); let mut updated_chat_ids = BTreeSet::new(); - let uid_validity = get_uidvalidity(context, folder) + let uid_validity = get_uidvalidity(context, transport_id, folder) .await .with_context(|| format!("failed to get UID validity for folder {folder}"))?; - let mut highest_modseq = get_modseq(context, folder) + let mut highest_modseq = get_modseq(context, transport_id, folder) .await .with_context(|| format!("failed to get MODSEQ for folder {folder}"))?; let mut list = self @@ -1293,7 +1307,7 @@ impl Session { self.new_mail = true; } - set_modseq(context, folder, highest_modseq) + set_modseq(context, transport_id, folder, highest_modseq) .await .with_context(|| format!("failed to set MODSEQ for folder {folder}"))?; if !updated_chat_ids.is_empty() { @@ -2422,8 +2436,12 @@ pub(crate) async fn markseen_on_imap_table(context: &Context, message_id: &str) /// uid_next is the next unique identifier value from the last time we fetched a folder /// See /// This function is used to update our uid_next after fetching messages. -pub(crate) async fn set_uid_next(context: &Context, folder: &str, uid_next: u32) -> Result<()> { - let transport_id = 1; // FIXME +pub(crate) async fn set_uid_next( + context: &Context, + transport_id: u32, + folder: &str, + uid_next: u32, +) -> Result<()> { context .sql .execute( @@ -2440,20 +2458,23 @@ pub(crate) async fn set_uid_next(context: &Context, folder: &str, uid_next: u32) /// This method returns the uid_next from the last time we fetched messages. /// We can compare this to the current uid_next to find out whether there are new messages /// and fetch from this value on to get all new messages. -async fn get_uid_next(context: &Context, folder: &str) -> Result { +async fn get_uid_next(context: &Context, transport_id: u32, folder: &str) -> Result { Ok(context .sql - .query_get_value("SELECT uid_next FROM imap_sync WHERE folder=?;", (folder,)) + .query_get_value( + "SELECT uid_next FROM imap_sync WHERE transport_id=? AND folder=?", + (transport_id, folder), + ) .await? .unwrap_or(0)) } pub(crate) async fn set_uidvalidity( context: &Context, + transport_id: u32, folder: &str, uidvalidity: u32, ) -> Result<()> { - let transport_id = 1; context .sql .execute( @@ -2465,19 +2486,23 @@ pub(crate) async fn set_uidvalidity( Ok(()) } -async fn get_uidvalidity(context: &Context, folder: &str) -> Result { +async fn get_uidvalidity(context: &Context, transport_id: u32, folder: &str) -> Result { Ok(context .sql .query_get_value( - "SELECT uidvalidity FROM imap_sync WHERE folder=?;", - (folder,), + "SELECT uidvalidity FROM imap_sync WHERE transport_id=? AND folder=?", + (transport_id, folder), ) .await? .unwrap_or(0)) } -pub(crate) async fn set_modseq(context: &Context, folder: &str, modseq: u64) -> Result<()> { - let transport_id = 1; // FIXME +pub(crate) async fn set_modseq( + context: &Context, + transport_id: u32, + folder: &str, + modseq: u64, +) -> Result<()> { context .sql .execute( @@ -2489,10 +2514,13 @@ pub(crate) async fn set_modseq(context: &Context, folder: &str, modseq: u64) -> Ok(()) } -async fn get_modseq(context: &Context, folder: &str) -> Result { +async fn get_modseq(context: &Context, transport_id: u32, folder: &str) -> Result { Ok(context .sql - .query_get_value("SELECT modseq FROM imap_sync WHERE folder=?;", (folder,)) + .query_get_value( + "SELECT modseq FROM imap_sync WHERE transport_id=? AND folder=?", + (transport_id, folder), + ) .await? .unwrap_or(0)) } diff --git a/src/imap/imap_tests.rs b/src/imap/imap_tests.rs index 304b9b5e20..88d0ac0570 100644 --- a/src/imap/imap_tests.rs +++ b/src/imap/imap_tests.rs @@ -11,17 +11,23 @@ fn test_get_folder_meaning_by_name() { #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn test_set_uid_next_validity() { let t = TestContext::new_alice().await; - assert_eq!(get_uid_next(&t.ctx, "Inbox").await.unwrap(), 0); - assert_eq!(get_uidvalidity(&t.ctx, "Inbox").await.unwrap(), 0); + assert_eq!(get_uid_next(&t.ctx, 1, "Inbox").await.unwrap(), 0); + assert_eq!(get_uidvalidity(&t.ctx, 1, "Inbox").await.unwrap(), 0); - set_uidvalidity(&t.ctx, "Inbox", 7).await.unwrap(); - assert_eq!(get_uidvalidity(&t.ctx, "Inbox").await.unwrap(), 7); - assert_eq!(get_uid_next(&t.ctx, "Inbox").await.unwrap(), 0); + set_uidvalidity(&t.ctx, 1, "Inbox", 7).await.unwrap(); + assert_eq!(get_uidvalidity(&t.ctx, 1, "Inbox").await.unwrap(), 7); + assert_eq!(get_uid_next(&t.ctx, 1, "Inbox").await.unwrap(), 0); - set_uid_next(&t.ctx, "Inbox", 5).await.unwrap(); - set_uidvalidity(&t.ctx, "Inbox", 6).await.unwrap(); - assert_eq!(get_uid_next(&t.ctx, "Inbox").await.unwrap(), 5); - assert_eq!(get_uidvalidity(&t.ctx, "Inbox").await.unwrap(), 6); + // For another transport there is still no UIDVALIDITY set. + assert_eq!(get_uidvalidity(&t.ctx, 2, "Inbox").await.unwrap(), 0); + + set_uid_next(&t.ctx, 1, "Inbox", 5).await.unwrap(); + set_uidvalidity(&t.ctx, 1, "Inbox", 6).await.unwrap(); + assert_eq!(get_uid_next(&t.ctx, 1, "Inbox").await.unwrap(), 5); + assert_eq!(get_uidvalidity(&t.ctx, 1, "Inbox").await.unwrap(), 6); + + assert_eq!(get_uid_next(&t.ctx, 2, "Inbox").await.unwrap(), 0); + assert_eq!(get_uidvalidity(&t.ctx, 2, "Inbox").await.unwrap(), 0); } #[test] diff --git a/src/imap/select_folder.rs b/src/imap/select_folder.rs index 6927bd736c..e8164c229d 100644 --- a/src/imap/select_folder.rs +++ b/src/imap/select_folder.rs @@ -146,15 +146,21 @@ impl ImapSession { }, } }; + let transport_id = self.transport_id(); + + // Folders should not be selected when transport_id is not assigned yet + // because we cannot save UID validity then. + debug_assert!(transport_id > 0); + let mailbox = self .selected_mailbox .as_mut() .with_context(|| format!("No mailbox selected, folder: {folder:?}"))?; - let old_uid_validity = get_uidvalidity(context, folder) + let old_uid_validity = get_uidvalidity(context, transport_id, folder) .await .with_context(|| format!("Failed to get old UID validity for folder {folder:?}"))?; - let old_uid_next = get_uid_next(context, folder) + let old_uid_next = get_uid_next(context, transport_id, folder) .await .with_context(|| format!("Failed to get old UID NEXT for folder {folder:?}"))?; @@ -205,8 +211,8 @@ impl ImapSession { 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?; self.resync_request_sender.try_send(()).ok(); + set_uid_next(context, transport_id, folder, new_uid_next).await?; } // If UIDNEXT changed, there are new emails. @@ -223,22 +229,23 @@ impl ImapSession { return Ok(true); } + let transport_id = self.transport_id(); // UIDVALIDITY is modified, reset highest seen MODSEQ. - set_modseq(context, folder, 0).await?; + set_modseq(context, transport_id, folder, 0).await?; // ============== uid_validity has changed or is being set the first time. ============== let new_uid_next = new_uid_next.unwrap_or_default(); - set_uid_next(context, folder, new_uid_next).await?; - set_uidvalidity(context, folder, new_uid_validity).await?; + set_uid_next(context, transport_id, folder, new_uid_next).await?; + set_uidvalidity(context, transport_id, folder, new_uid_validity).await?; self.new_mail = true; // Collect garbage entries in `imap` table. context .sql .execute( - "DELETE FROM imap WHERE folder=? AND uidvalidity!=?", - (&folder, new_uid_validity), + "DELETE FROM imap WHERE transport_id=? AND folder=? AND uidvalidity!=?", + (transport_id, &folder, new_uid_validity), ) .await?; @@ -247,12 +254,7 @@ impl ImapSession { } info!( context, - "uid/validity change folder {}: new {}/{} previous {}/{}.", - folder, - new_uid_next, - new_uid_validity, - old_uid_next, - old_uid_validity, + "UID validity for folder {folder} and transport {transport_id} changed from {old_uid_validity}/{old_uid_next} to {new_uid_validity}/{new_uid_next}.", ); Ok(true) } diff --git a/src/imap/session.rs b/src/imap/session.rs index 8cf0a17de0..0da1d7936f 100644 --- a/src/imap/session.rs +++ b/src/imap/session.rs @@ -30,6 +30,8 @@ const PREFETCH_FLAGS: &str = "(UID INTERNALDATE RFC822.SIZE BODY.PEEK[HEADER.FIE #[derive(Debug)] pub(crate) struct Session { + transport_id: u32, + pub(super) inner: ImapSession>, pub capabilities: Capabilities, @@ -71,8 +73,10 @@ impl Session { inner: ImapSession>, capabilities: Capabilities, resync_request_sender: async_channel::Sender<()>, + transport_id: u32, ) -> Self { Self { + transport_id, inner, capabilities, selected_folder: None, @@ -84,6 +88,11 @@ impl Session { } } + /// Returns ID of the transport for which this session was created. + pub(crate) fn transport_id(&self) -> u32 { + self.transport_id + } + pub fn can_idle(&self) -> bool { self.capabilities.can_idle }