From 23507932f94257a71f2ca8db23840ee0716072b6 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Mon, 27 Nov 2023 09:23:50 +0200 Subject: [PATCH] imap: update cache on set_flags Signed-off-by: Manos Pitsidianakis --- meli/src/accounts/backend_ops.rs | 2 +- melib/Cargo.toml | 2 +- melib/src/imap/cache.rs | 85 +++++++++++++++++++++++++++++++- melib/src/imap/mod.rs | 6 +++ melib/src/imap/watch.rs | 5 -- melib/src/utils/sqlite3.rs | 8 ++- 6 files changed, 98 insertions(+), 10 deletions(-) diff --git a/meli/src/accounts/backend_ops.rs b/meli/src/accounts/backend_ops.rs index 3876a592..139caf1c 100644 --- a/meli/src/accounts/backend_ops.rs +++ b/meli/src/accounts/backend_ops.rs @@ -38,7 +38,7 @@ impl Account { let handle = self .main_loop_handler .job_executor - .spawn_specialized("set_unseen".into(), fut); + .spawn_specialized("set_flags".into(), fut); let job_id = handle.job_id; self.insert_job( job_id, diff --git a/melib/Cargo.toml b/melib/Cargo.toml index 185e1700..74411b2f 100644 --- a/melib/Cargo.toml +++ b/melib/Cargo.toml @@ -42,7 +42,7 @@ nom = { version = "7" } notify = { version = "4.0.15", optional = true } polling = "2.8" regex = { version = "1" } -rusqlite = { version = "^0.29", default-features = false, optional = true } +rusqlite = { version = "^0.29", default-features = false, features = ["array"], optional = true } serde = { version = "1.0", features = ["rc"] } serde_derive = "1.0" serde_json = { version = "1.0", features = ["raw_value"] } diff --git a/melib/src/imap/cache.rs b/melib/src/imap/cache.rs index 7ae942da..8487ae47 100644 --- a/melib/src/imap/cache.rs +++ b/melib/src/imap/cache.rs @@ -92,6 +92,13 @@ pub trait ImapCache: Send + std::fmt::Debug { identifier: std::result::Result, mailbox_hash: MailboxHash, ) -> Result>>; + + fn update_flags( + &mut self, + env_hashes: EnvelopeHashBatch, + mailbox_hash: MailboxHash, + flags: SmallVec<[FlagOp; 8]>, + ) -> Result<()>; } pub trait ImapCacheReset: Send + std::fmt::Debug { @@ -108,7 +115,7 @@ pub mod sqlite3_m { use super::*; use crate::utils::sqlite3::{ self, - rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput}, + rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, Value}, Connection, DatabaseDescription, }; @@ -151,6 +158,12 @@ pub mod sqlite3_m { version: 3, }; + impl From for Value { + fn from(env_hash: EnvelopeHash) -> Self { + (env_hash.0 as i64).into() + } + } + impl ToSql for ModSequence { fn to_sql(&self) -> rusqlite::Result { Ok(ToSqlOutput::from(self.0.get() as i64)) @@ -539,6 +552,67 @@ pub mod sqlite3_m { Ok(()) } + fn update_flags( + &mut self, + env_hashes: EnvelopeHashBatch, + mailbox_hash: MailboxHash, + flags: SmallVec<[FlagOp; 8]>, + ) -> Result<()> { + if self.mailbox_state(mailbox_hash)?.is_none() { + return Err(Error::new("Mailbox is not in cache").set_kind(ErrorKind::Bug)); + } + let Self { + ref mut connection, + ref uid_store, + loaded_mailboxes: _, + } = self; + let tx = connection.transaction()?; + let values = + std::rc::Rc::new(env_hashes.iter().map(Value::from).collect::>()); + + let mut stmt = + tx.prepare("SELECT uid, envelope FROM envelopes WHERE hash IN rarray(?1);")?; + let rows = stmt + .query_map([values], |row| Ok((row.get(0)?, row.get(1)?)))? + .filter_map(|r| r.ok()) + .collect::>(); + drop(stmt); + let mut stmt = tx.prepare( + "UPDATE envelopes SET envelope = ?1 WHERE mailbox_hash = ?2 AND uid = ?3;", + )?; + for (uid, mut env) in rows { + for op in flags.iter() { + match op { + FlagOp::UnSet(flag) | FlagOp::Set(flag) => { + let mut f = env.flags(); + f.set(*flag, op.as_bool()); + env.set_flags(f); + } + FlagOp::UnSetTag(tag) | FlagOp::SetTag(tag) => { + let hash = TagHash::from_bytes(tag.as_bytes()); + if op.as_bool() { + env.tags_mut().insert(hash); + } else { + env.tags_mut().remove(&hash); + } + } + } + } + stmt.execute(sqlite3::params![&env, mailbox_hash, uid as Sqlite3UID])?; + uid_store + .envelopes + .lock() + .unwrap() + .entry(env.hash()) + .and_modify(|entry| { + entry.inner = env; + }); + } + drop(stmt); + tx.commit()?; + Ok(()) + } + fn update( &mut self, mailbox_hash: MailboxHash, @@ -829,5 +903,14 @@ pub mod default_m { ) -> Result>> { Err(Error::new("melib is not built with any imap cache").set_kind(ErrorKind::Bug)) } + + fn update_flags( + &mut self, + _env_hashes: EnvelopeHashBatch, + _mailbox_hash: MailboxHash, + _flags: SmallVec<[FlagOp; 8]>, + ) -> Result<()> { + Err(Error::new("melib is not built with any imap cache").set_kind(ErrorKind::Bug)) + } } } diff --git a/melib/src/imap/mod.rs b/melib/src/imap/mod.rs index a0b0d66a..df31b29f 100644 --- a/melib/src/imap/mod.rs +++ b/melib/src/imap/mod.rs @@ -857,6 +857,12 @@ impl MailBackend for ImapType { } } } + #[cfg(feature = "sqlite3")] + if *uid_store.keep_offline_cache.lock().unwrap() { + let mut cache_handle = cache::Sqlite3Cache::get(uid_store.clone())?; + let res = cache_handle.update_flags(env_hashes, mailbox_hash, flags); + log::trace!("update_flags in cache: {:?}", res); + } Ok(()) })) } diff --git a/melib/src/imap/watch.rs b/melib/src/imap/watch.rs index 99fc2ab9..ca1b0478 100644 --- a/melib/src/imap/watch.rs +++ b/melib/src/imap/watch.rs @@ -102,11 +102,6 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> { mailbox_hash, kind: RefreshEventKind::Rescan, }); - /* - uid_store.uid_index.lock().unwrap().clear(); - uid_store.hash_index.lock().unwrap().clear(); - uid_store.byte_cache.lock().unwrap().clear(); - */ } } else { uidvalidities.insert(mailbox_hash, select_response.uidvalidity); diff --git a/melib/src/utils/sqlite3.rs b/melib/src/utils/sqlite3.rs index a50762bf..c84e95da 100644 --- a/melib/src/utils/sqlite3.rs +++ b/melib/src/utils/sqlite3.rs @@ -45,7 +45,10 @@ pub fn open_db(db_path: PathBuf) -> Result { if !db_path.exists() { return Err(Error::new("Database doesn't exist")); } - Connection::open(&db_path).map_err(|e| Error::new(e.to_string())) + Ok(Connection::open(&db_path).and_then(|db| { + rusqlite::vtab::array::load_module(&db)?; + Ok(db) + })?) } pub fn open_or_create_db( @@ -66,7 +69,8 @@ pub fn open_or_create_db( db_path.display() ); } - let conn = Connection::open(&db_path).map_err(|e| Error::new(e.to_string()))?; + let conn = Connection::open(&db_path)?; + rusqlite::vtab::array::load_module(&conn)?; if set_mode { use std::os::unix::fs::PermissionsExt; let file = std::fs::File::open(&db_path)?;