diff options
author | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2020-08-28 00:24:43 +0300 |
---|---|---|
committer | Manos Pitsidianakis <el13635@mail.ntua.gr> | 2020-08-28 00:31:35 +0300 |
commit | b4fe34eacf7ac41ad0983d2aaaa39d18e930f3a0 (patch) | |
tree | f00a8db294e846db3d3a5560837e93731bc3a324 /melib/src/backends | |
parent | e878c50af5f208557eae7099ea408aaa6fb4a5ae (diff) |
melib/imap: add ImapCache trait
Diffstat (limited to 'melib/src/backends')
-rw-r--r-- | melib/src/backends/imap.rs | 57 | ||||
-rw-r--r-- | melib/src/backends/imap/cache.rs | 469 | ||||
-rw-r--r-- | melib/src/backends/imap/cache/sync.rs | 81 | ||||
-rw-r--r-- | melib/src/backends/imap/connection.rs | 28 | ||||
-rw-r--r-- | melib/src/backends/imap/mailbox.rs | 15 | ||||
-rw-r--r-- | melib/src/backends/imap/operations.rs | 14 | ||||
-rw-r--r-- | melib/src/backends/imap/protocol_parser.rs | 87 | ||||
-rw-r--r-- | melib/src/backends/imap/untagged.rs | 26 | ||||
-rw-r--r-- | melib/src/backends/imap/watch.rs | 28 |
9 files changed, 533 insertions, 272 deletions
diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index 2f7ef811..648ee2b6 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -51,12 +51,17 @@ use futures::stream::Stream; use std::collections::{hash_map::DefaultHasher, BTreeMap}; use std::collections::{BTreeSet, HashMap, HashSet}; use std::convert::TryFrom; +use std::convert::TryInto; use std::hash::Hasher; use std::pin::Pin; use std::str::FromStr; use std::sync::{Arc, Mutex, RwLock}; use std::time::{Duration, Instant}; -pub type UID = usize; + +pub type ImapNum = usize; +pub type UID = ImapNum; +pub type UIDVALIDITY = UID; +pub type MessageSequenceNumber = ImapNum; pub static SUPPORTED_CAPABILITIES: &[&str] = &[ #[cfg(feature = "deflate_compression")] @@ -202,7 +207,6 @@ pub struct ImapType { connection: Arc<FutureMutex<ImapConnection>>, server_conf: ImapServerConf, uid_store: Arc<UIDStore>, - can_create_flags: Arc<Mutex<bool>>, } impl MailBackend for ImapType { @@ -299,7 +303,6 @@ impl MailBackend for ImapType { }, connection: self.connection.clone(), mailbox_hash, - can_create_flags: self.can_create_flags.clone(), uid_store: self.uid_store.clone(), }; @@ -742,11 +745,7 @@ impl MailBackend for ImapType { } fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> { - if *self.can_create_flags.lock().unwrap() { - Some(self.uid_store.tag_index.clone()) - } else { - None - } + Some(self.uid_store.tag_index.clone()) } fn as_any(&self) -> &dyn Any { @@ -1111,7 +1110,7 @@ impl MailBackend for ImapType { l["* SEARCH".len()..] .trim() .split_whitespace() - .map(usize::from_str) + .map(UID::from_str) .filter_map(std::result::Result::ok) .filter_map(|uid| uid_index.get(&(mailbox_hash, uid))) .copied(), @@ -1157,7 +1156,17 @@ impl ImapType { let use_starttls = use_tls && get_conf_val!(s["use_starttls"], !(server_port == 993))?; let danger_accept_invalid_certs: bool = get_conf_val!(s["danger_accept_invalid_certs"], false)?; + #[cfg(feature = "sqlite3")] let keep_offline_cache = get_conf_val!(s["offline_cache"], true)?; + #[cfg(not(feature = "sqlite3"))] + let keep_offline_cache = get_conf_val!(s["offline_cache"], false)?; + #[cfg(not(feature = "sqlite3"))] + if keep_offline_cache { + return Err(MeliError::new(format!( + "({}) keep_offline_cache is true but melib is not compiled with sqlite3", + s.name, + ))); + } let server_conf = ImapServerConf { server_hostname: server_hostname.to_string(), server_username: server_username.to_string(), @@ -1190,7 +1199,6 @@ impl ImapType { Ok(Box::new(ImapType { server_conf, is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)), - can_create_flags: Arc::new(Mutex::new(false)), connection: Arc::new(FutureMutex::new(connection)), uid_store, })) @@ -1199,14 +1207,18 @@ impl ImapType { pub fn shell(&mut self) { let mut conn = ImapConnection::new_connection(&self.server_conf, self.uid_store.clone()); - futures::executor::block_on(timeout(Duration::from_secs(3), conn.connect())).unwrap(); + futures::executor::block_on(timeout(Duration::from_secs(3), conn.connect())) + .unwrap() + .unwrap(); let mut res = String::with_capacity(8 * 1024); futures::executor::block_on(timeout(Duration::from_secs(3), conn.send_command(b"NOOP"))) + .unwrap() .unwrap(); futures::executor::block_on(timeout( Duration::from_secs(3), conn.read_response(&mut res, RequiredResponses::empty()), )) + .unwrap() .unwrap(); let mut input = String::new(); @@ -1220,11 +1232,13 @@ impl ImapType { Duration::from_secs(3), conn.send_command(input.as_bytes()), )) + .unwrap() .unwrap(); futures::executor::block_on(timeout( Duration::from_secs(3), conn.read_lines(&mut res, String::new()), )) + .unwrap() .unwrap(); if input.trim().eq_ignore_ascii_case("logout") { break; @@ -1368,7 +1382,18 @@ impl ImapType { ))); } get_conf_val!(s["danger_accept_invalid_certs"], false)?; + #[cfg(feature = "sqlite3")] get_conf_val!(s["offline_cache"], true)?; + #[cfg(not(feature = "sqlite3"))] + { + let keep_offline_cache = get_conf_val!(s["offline_cache"], false)?; + if keep_offline_cache { + return Err(MeliError::new(format!( + "({}) keep_offline_cache is true but melib is not compiled with sqlite3", + s.name, + ))); + } + } get_conf_val!(s["use_idle"], true)?; get_conf_val!(s["use_condstore"], true)?; #[cfg(feature = "deflate_compression")] @@ -1399,7 +1424,7 @@ enum FetchStage { InitialFresh, InitialCache, ResyncCache, - FreshFetch { max_uid: usize }, + FreshFetch { max_uid: UID }, Finished, } @@ -1408,7 +1433,6 @@ struct FetchState { stage: FetchStage, connection: Arc<FutureMutex<ImapConnection>>, mailbox_hash: MailboxHash, - can_create_flags: Arc<Mutex<bool>>, uid_store: Arc<UIDStore>, } @@ -1423,7 +1447,6 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> { .await .init_mailbox(state.mailbox_hash) .await?; - *state.can_create_flags.lock().unwrap() = select_response.can_create_flags; if select_response.exists == 0 { state.stage = FetchStage::Finished; return Ok(Vec::new()); @@ -1481,7 +1504,6 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> { ref mut stage, ref connection, mailbox_hash, - can_create_flags: _, ref uid_store, } = state; let mailbox_hash = *mailbox_hash; @@ -1565,8 +1587,9 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> { } } } + #[cfg(feature = "sqlite3")] if uid_store.keep_offline_cache { - let mut cache_handle = cache::CacheHandle::get(uid_store.clone())?; + let mut cache_handle = cache::Sqlite3Cache::get(uid_store.clone())?; debug!(cache_handle .insert_envelopes(mailbox_hash, &v) .chain_err_summary(|| { @@ -1601,7 +1624,7 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> { .unwrap() .entry(mailbox_hash) .or_default() - .insert(message_sequence_number - 1, uid); + .insert((message_sequence_number - 1).try_into().unwrap(), uid); uid_store .hash_index .lock() diff --git a/melib/src/backends/imap/cache.rs b/melib/src/backends/imap/cache.rs index b72515a8..53ec5d9b 100644 --- a/melib/src/backends/imap/cache.rs +++ b/melib/src/backends/imap/cache.rs @@ -26,7 +26,6 @@ use crate::{ email::{Envelope, EnvelopeHash}, error::*, }; - use std::convert::TryFrom; #[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Copy, Clone)] @@ -55,10 +54,36 @@ pub struct CachedEnvelope { pub modsequence: Option<ModSequence>, } -pub struct CacheHandle { - #[cfg(feature = "sqlite3")] - connection: crate::sqlite3::Connection, - uid_store: Arc<UIDStore>, +pub trait ImapCache: Send { + fn mailbox_state(&mut self, mailbox_hash: MailboxHash) -> Result<Option<()>>; + + fn find_envelope( + &mut self, + identifier: std::result::Result<UID, EnvelopeHash>, + mailbox_hash: MailboxHash, + ) -> Result<Option<CachedEnvelope>>; + + fn update( + &mut self, + mailbox_hash: MailboxHash, + refresh_events: &[(UID, RefreshEvent)], + ) -> Result<()>; + + fn insert_envelopes( + &mut self, + mailbox_hash: MailboxHash, + fetches: &[FetchResponse<'_>], + ) -> Result<()>; + + fn envelopes(&mut self, mailbox_hash: MailboxHash) -> Result<Option<Vec<EnvelopeHash>>>; + + fn clear(&mut self, mailbox_hash: MailboxHash, select_response: &SelectResponse) -> Result<()>; + + fn rfc822( + &mut self, + identifier: std::result::Result<UID, EnvelopeHash>, + mailbox_hash: MailboxHash, + ) -> Result<Option<Vec<u8>>>; } #[cfg(feature = "sqlite3")] @@ -71,28 +96,43 @@ mod sqlite3_m { FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, }; use crate::sqlite3::{self, DatabaseDescription}; + + type Sqlite3UID = i32; + + pub struct Sqlite3Cache { + connection: crate::sqlite3::Connection, + loaded_mailboxes: BTreeSet<MailboxHash>, + uid_store: Arc<UIDStore>, + } + const DB_DESCRIPTION: DatabaseDescription = DatabaseDescription { name: "header_cache.db", - init_script: Some("PRAGMA foreign_keys = true; + init_script: Some( + "PRAGMA foreign_keys = true; PRAGMA encoding = 'UTF-8'; CREATE TABLE IF NOT EXISTS envelopes ( - mailbox_hash INTEGER, - uid INTEGER, + hash INTEGER NOT NULL, + mailbox_hash INTEGER NOT NULL, + uid INTEGER NOT NULL, modsequence INTEGER, + rfc822 BLOB, envelope BLOB NOT NULL, PRIMARY KEY (mailbox_hash, uid), - FOREIGN KEY (mailbox_hash) REFERENCES uidvalidity(mailbox_hash) ON DELETE CASCADE + FOREIGN KEY (mailbox_hash) REFERENCES mailbox(mailbox_hash) ON DELETE CASCADE ); - CREATE TABLE IF NOT EXISTS uidvalidity ( - uid INTEGER UNIQUE, + CREATE TABLE IF NOT EXISTS mailbox ( mailbox_hash INTEGER UNIQUE, + uidvalidity INTEGER, + flags BLOB NOT NULL, highestmodseq INTEGER, - PRIMARY KEY (mailbox_hash, uid) + PRIMARY KEY (mailbox_hash) ); - CREATE INDEX IF NOT EXISTS envelope_idx ON envelopes(mailbox_hash); - CREATE INDEX IF NOT EXISTS uidvalidity_idx ON uidvalidity(mailbox_hash);"), - version: 1, + CREATE INDEX IF NOT EXISTS envelope_uid_idx ON envelopes(mailbox_hash, uid); + CREATE INDEX IF NOT EXISTS envelope_idx ON envelopes(hash); + CREATE INDEX IF NOT EXISTS mailbox_idx ON mailbox(mailbox_hash);", + ), + version: 1, }; impl ToSql for ModSequence { @@ -111,47 +151,108 @@ mod sqlite3_m { } } - impl CacheHandle { - pub fn get(uid_store: Arc<UIDStore>) -> Result<Self> { - Ok(Self { + impl Sqlite3Cache { + pub fn get(uid_store: Arc<UIDStore>) -> Result<Box<dyn ImapCache>> { + Ok(Box::new(Self { connection: sqlite3::open_or_create_db( &DB_DESCRIPTION, Some(uid_store.account_name.as_str()), )?, + loaded_mailboxes: BTreeSet::default(), uid_store, - }) + })) } - pub fn mailbox_state( - &self, - mailbox_hash: MailboxHash, - ) -> Result<Option<(UID, Option<ModSequence>)>> { + fn max_uid(&self, mailbox_hash: MailboxHash) -> Result<UID> { let mut stmt = self .connection - .prepare("SELECT uid, highestmodseq FROM uidvalidity WHERE mailbox_hash = ?1;")?; + .prepare("SELECT MAX(uid) FROM envelopes WHERE mailbox_hash = ?1;")?; + + let mut ret: Vec<UID> = stmt + .query_map(sqlite3::params![mailbox_hash as i64], |row| { + Ok(row.get(0).map(|i: Sqlite3UID| i as UID)?) + })? + .collect::<std::result::Result<_, _>>()?; + Ok(ret.pop().unwrap_or(0)) + } + } + + impl ImapCache for Sqlite3Cache { + fn mailbox_state(&mut self, mailbox_hash: MailboxHash) -> Result<Option<()>> { + if self.loaded_mailboxes.contains(&mailbox_hash) { + return Ok(Some(())); + } + debug!("loading mailbox state {} from cache", mailbox_hash); + let mut stmt = self.connection.prepare( + "SELECT uidvalidity, flags, highestmodseq FROM mailbox WHERE mailbox_hash = ?1;", + )?; let mut ret = stmt.query_map(sqlite3::params![mailbox_hash as i64], |row| { - Ok((row.get(0).map(|u: i64| u as usize)?, row.get(1)?)) + Ok(( + row.get(0).map(|u: Sqlite3UID| u as UID)?, + row.get(1)?, + row.get(2)?, + )) })?; - if let Some(row_res) = ret.next() { - Ok(Some(row_res?)) + if let Some(v) = ret.next() { + let (uidvalidity, flags, highestmodseq): ( + UIDVALIDITY, + Vec<u8>, + Option<ModSequence>, + ) = v?; + debug!( + "mailbox state {} in cache uidvalidity {}", + mailbox_hash, uidvalidity + ); + debug!( + "mailbox state {} in cache highestmodseq {:?}", + mailbox_hash, &highestmodseq + ); + debug!( + "mailbox state {} inserting flags: {:?}", + mailbox_hash, + to_str!(&flags) + ); + self.uid_store + .highestmodseqs + .lock() + .unwrap() + .entry(mailbox_hash) + .and_modify(|entry| *entry = highestmodseq.ok_or(())) + .or_insert(highestmodseq.ok_or(())); + self.uid_store + .uidvalidity + .lock() + .unwrap() + .entry(mailbox_hash) + .and_modify(|entry| *entry = uidvalidity) + .or_insert(uidvalidity); + let mut tag_lck = self.uid_store.tag_index.write().unwrap(); + for f in to_str!(&flags).split('\0') { + let hash = tag_hash!(f); + //debug!("hash {} flag {}", hash, &f); + if !tag_lck.contains_key(&hash) { + tag_lck.insert(hash, f.to_string()); + } + } + self.loaded_mailboxes.insert(mailbox_hash); + Ok(Some(())) } else { + debug!("mailbox state {} not in cache", mailbox_hash); Ok(None) } } - pub fn clear( - &self, + fn clear( + &mut self, mailbox_hash: MailboxHash, - new_uidvalidity: UID, - highestmodseq: Option<ModSequence>, + select_response: &SelectResponse, ) -> Result<()> { - debug!("clear mailbox_hash {}", mailbox_hash); - debug!(new_uidvalidity); - debug!(&highestmodseq); + debug!("clear mailbox_hash {} {:?}", mailbox_hash, select_response); + self.loaded_mailboxes.remove(&mailbox_hash); self.connection .execute( - "DELETE FROM uidvalidity WHERE mailbox_hash = ?1", + "DELETE FROM mailbox WHERE mailbox_hash = ?1", sqlite3::params![mailbox_hash as i64], ) .chain_err_summary(|| { @@ -161,20 +262,38 @@ mod sqlite3_m { ) })?; - self.connection.execute( - "INSERT OR IGNORE INTO uidvalidity (uid, highestmodseq, mailbox_hash) VALUES (?1, ?2, ?3)", - sqlite3::params![new_uidvalidity as i64, highestmodseq, mailbox_hash as i64], + if let Some(Ok(highestmodseq)) = select_response.highestmodseq { + self.connection.execute( + "INSERT OR IGNORE INTO mailbox (uidvalidity, flags, highestmodseq, mailbox_hash) VALUES (?1, ?2, ?3, ?4)", + sqlite3::params![select_response.uidvalidity as Sqlite3UID, select_response.flags.1.iter().map(|s| s.as_str()).collect::<Vec<&str>>().join("\0").as_bytes(), highestmodseq, mailbox_hash as i64], ) .chain_err_summary(|| { format!( "Could not insert uidvalidity {} in header_cache of account {}", - new_uidvalidity, self.uid_store.account_name + select_response.uidvalidity, self.uid_store.account_name ) })?; + } else { + self.connection + .execute( + "INSERT OR IGNORE INTO mailbox (uidvalidity, flags, mailbox_hash) VALUES (?1, ?2, ?3)", + sqlite3::params![ + select_response.uidvalidity as Sqlite3UID, + select_response.flags.1.iter().map(|s| s.as_str()).collect::<Vec<&str>>().join("\0").as_bytes(), + mailbox_hash as i64 + ], + ) + .chain_err_summary(|| { + format!( + "Could not insert mailbox {} in header_cache of account {}", + select_response.uidvalidity, self.uid_store.account_name + ) + })?; + } Ok(()) } - pub fn envelopes(&self, mailbox_hash: MailboxHash) -> Result<Option<Vec<EnvelopeHash>>> { + fn envelopes(&mut self, mailbox_hash: MailboxHash) -> Result<Option<Vec<EnvelopeHash>>> { debug!("envelopes mailbox_hash {}", mailbox_hash); if debug!(self.mailbox_state(mailbox_hash)?.is_none()) { return Ok(None); @@ -187,7 +306,7 @@ mod sqlite3_m { let ret: Vec<(UID, Envelope, Option<ModSequence>)> = stmt .query_map(sqlite3::params![mailbox_hash as i64], |row| { Ok(( - row.get(0).map(|i: i64| i as usize)?, + row.get(0).map(|i: Sqlite3UID| i as UID)?, row.get(1)?, row.get(2)?, )) @@ -221,7 +340,7 @@ mod sqlite3_m { Ok(Some(env_hashes)) } - pub fn insert_envelopes( + fn insert_envelopes( &mut self, mailbox_hash: MailboxHash, fetches: &[FetchResponse<'_>], @@ -231,35 +350,22 @@ mod sqlite3_m { mailbox_hash, fetches.len() ); + let mut max_uid = self + .uid_store + .max_uids + .lock() + .unwrap() + .get(&mailbox_hash) + .cloned() + .unwrap_or_default(); if self.mailbox_state(mailbox_hash)?.is_none() { debug!(self.mailbox_state(mailbox_hash)?.is_none()); - let uidvalidity = self - .uid_store - .uidvalidity - .lock() - .unwrap() - .get(&mailbox_hash) - .cloned(); - let highestmodseq = self - .uid_store - .highestmodseqs - .lock() - .unwrap() - .get(&mailbox_hash) - .cloned(); - debug!(&uidvalidity); - debug!(&highestmodseq); - if let Some(uidvalidity) = uidvalidity { - debug!(self.clear( - mailbox_hash, - uidvalidity, - highestmodseq.and_then(|v| v.ok()), - ))?; - } + return Err(MeliError::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()?; for item in fetches { @@ -272,17 +378,23 @@ mod sqlite3_m { envelope: Some(envelope), } = item { + max_uid = std::cmp::max(max_uid, *uid); tx.execute( - "INSERT OR REPLACE INTO envelopes (uid, mailbox_hash, modsequence, envelope) VALUES (?1, ?2, ?3, ?4)", - sqlite3::params![*uid as i64, mailbox_hash as i64, modseq, &envelope], + "INSERT OR REPLACE INTO envelopes (hash, uid, mailbox_hash, modsequence, envelope) VALUES (?1, ?2, ?3, ?4, ?5)", + sqlite3::params![envelope.hash() as i64, *uid as Sqlite3UID, mailbox_hash as i64, modseq, &envelope], ).chain_err_summary(|| format!("Could not insert envelope {} {} in header_cache of account {}", envelope.message_id(), envelope.hash(), uid_store.account_name))?; } } tx.commit()?; + self.uid_store + .max_uids + .lock() + .unwrap() + .insert(mailbox_hash, max_uid); Ok(()) } - pub fn update( + fn update( &mut self, mailbox_hash: MailboxHash, refresh_events: &[(UID, RefreshEvent)], @@ -294,33 +406,12 @@ mod sqlite3_m { ); if self.mailbox_state(mailbox_hash)?.is_none() { debug!(self.mailbox_state(mailbox_hash)?.is_none()); - let uidvalidity = self - .uid_store - .uidvalidity - .lock() - .unwrap() - .get(&mailbox_hash) - .cloned(); - let highestmodseq = self - .uid_store - .highestmodseqs - .lock() - .unwrap() - .get(&mailbox_hash) - .cloned(); - debug!(&uidvalidity); - debug!(&highestmodseq); - if let Some(uidvalidity) = uidvalidity { - debug!(self.clear( - mailbox_hash, - uidvalidity, - highestmodseq.and_then(|v| v.ok()), - ))?; - } + return Err(MeliError::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 mut hash_index_lck = uid_store.hash_index.lock().unwrap(); @@ -330,7 +421,7 @@ mod sqlite3_m { hash_index_lck.remove(&env_hash); tx.execute( "DELETE FROM envelopes WHERE mailbox_hash = ?1 AND uid = ?2;", - sqlite3::params![mailbox_hash as i64, *uid as i64], + sqlite3::params![mailbox_hash as i64, *uid as Sqlite3UID], ) .chain_err_summary(|| { format!( @@ -345,9 +436,10 @@ mod sqlite3_m { )?; let mut ret: Vec<Envelope> = stmt - .query_map(sqlite3::params![mailbox_hash as i64, *uid as i64], |row| { - Ok(row.get(0)?) - })? + .query_map( + sqlite3::params![mailbox_hash as i64, *uid as Sqlite3UID], + |row| Ok(row.get(0)?), + )? .collect::<std::result::Result<_, _>>()?; if let Some(mut env) = ret.pop() { env.set_flags(*flags); @@ -355,7 +447,7 @@ mod sqlite3_m { env.labels_mut().extend(tags.iter().map(|t| tag_hash!(t))); tx.execute( "UPDATE envelopes SET envelope = ?1 WHERE mailbox_hash = ?2 AND uid = ?3;", - sqlite3::params![&env, mailbox_hash as i64, *uid as i64], + sqlite3::params![&env, mailbox_hash as i64, *uid as Sqlite3UID], ) .chain_err_summary(|| { format!( @@ -377,48 +469,108 @@ mod sqlite3_m { } } tx.commit()?; + let new_max_uid = self.max_uid(mailbox_hash)?; + self.uid_store + .max_uids + .lock() + .unwrap() + .insert(mailbox_hash, new_max_uid); Ok(()) } - } -} - -#[cfg(not(feature = "sqlite3"))] -pub use filesystem_m::*; - -#[cfg(not(feature = "sqlite3"))] -mod filesystem_m { - use super::*; - impl CacheHandle { - pub fn get(uid_store: Arc<UIDStore>) -> Result<Self> { - Ok(Self { uid_store }) - } - pub fn mailbox_state( - &self, - _mailbox_hash: MailboxHash, - ) -> Result<Option<(UID, Option<ModSequence>)>> { - Ok(None) - } + fn find_envelope( + &mut self, + identifier: std::result::Result<UID, EnvelopeHash>, + mailbox_hash: MailboxHash, + ) -> Result<Option<CachedEnvelope>> { + let mut ret: Vec<(UID, Envelope, Option<ModSequence>)> = match identifier { + Ok(uid) => { + let mut stmt = self.connection.prepare( + "SELECT uid, envelope, modsequence FROM envelopes WHERE mailbox_hash = ?1 AND uid = ?2;", + )?; - pub fn clear( - &self, - _mailbox_hash: MailboxHash, - _new_uidvalidity: UID, - _highestmodseq: Option<ModSequence>, - ) -> Result<()> { - Ok(()) - } + let x = stmt + .query_map( + sqlite3::params![mailbox_hash as i64, uid as Sqlite3UID], + |row| { + Ok(( + row.get(0).map(|u: Sqlite3UID| u as UID)?, + row.get(1)?, + row.get(2)?, + )) + }, + )? + .collect::<std::result::Result<_, _>>()?; + x + } + Err(env_hash) => { + let mut stmt = self.connection.prepare( + "SELECT uid, envelope, modsequence FROM envelopes WHERE mailbox_hash = ?1 AND hash = ?2;", + )?; - pub fn envelopes(&self, _mailbox_hash: MailboxHash) -> Result<Option<Vec<EnvelopeHash>>> { - Ok(None) + let x = stmt + .query_map( + sqlite3::params![mailbox_hash as i64, env_hash as i64], + |row| { + Ok(( + row.get(0).map(|u: Sqlite3UID| u as UID)?, + row.get(1)?, + row.get(2)?, + )) + }, + )? + .collect::<std::result::Result<_, _>>()?; + x + } + }; + if ret.len() != 1 { + return Ok(None); + } + let (uid, inner, modsequence) = ret.pop().unwrap(); + return Ok(Some(CachedEnvelope { + inner, + uid, + mailbox_hash, + modsequence, + })); } - pub fn insert_envelopes( + fn rfc822( &mut self, - _mailbox_hash: MailboxHash, - _fetches: &[FetchResponse<'_>], - ) -> Result<()> { - Ok(()) + identifier: std::result::Result<UID, EnvelopeHash>, + mailbox_hash: MailboxHash, + ) -> Result<Option<Vec<u8>>> { + let mut ret: Vec<Option<Vec<u8>>> = match identifier { + Ok(uid) => { + let mut stmt = self.connection.prepare( + "SELECT rfc822 FROM envelopes WHERE mailbox_hash = ?1 AND uid = ?2;", + )?; + let x = stmt + .query_map( + sqlite3::params![mailbox_hash as i64, uid as Sqlite3UID], + |row| Ok(row.get(0)?), + )? + .collect::<std::result::Result<_, _>>()?; + x + } + Err(env_hash) => { + let mut stmt = self.connection.prepare( + "SELECT rfc822 FROM envelopes WHERE mailbox_hash = ?1 AND hash = ?2;", + )?; + let x = stmt + .query_map( + sqlite3::params![mailbox_hash as i64, env_hash as i64], + |row| Ok(row.get(0)?), + )? + .collect::<std::result::Result<_, _>>()?; + x + } + }; + + if ret.len() != 1 { + return Ok(None); + } + Ok(ret.pop().unwrap()) } } } @@ -428,7 +580,6 @@ pub(super) async fn fetch_cached_envs(state: &mut FetchState) -> Result<Option<V stage: _, ref mut connection, mailbox_hash, - can_create_flags: _, ref uid_store, } = state; debug!(uid_store.keep_offline_cache); @@ -481,3 +632,59 @@ pub(super) async fn fetch_cached_envs(state: &mut FetchState) -> Result<Option<V } } } + +#[cfg(not(feature = "sqlite3"))] +pub use default_m::*; + +#[cfg(not(feature = "sqlite3"))] +mod default_m { + pub struct DefaultCache; + + impl DefaultCache { + pub fn get(_uid_store: Arc<UIDStore>) -> Result<Box<dyn ImapCache>> { + Ok(Box::new(Self)) + } + } + + impl ImapCache for DefaultCache { + fn mailbox_state(&mut self, _mailbox_hash: MailboxHash) -> Result<Option<()>> { + Err(MeliError::new("melib is not built with any imap cache").set_kind(ErrorKind::Bug)) + } + + fn clear( + &mut self, + _mailbox_hash: MailboxHash, + _select_response: &SelectResponse, + ) -> Result<()> { + Err(MeliError::new("melib is not built with any imap cache").set_kind(ErrorKind::Bug)) + } + + fn envelopes(&mut self, _mailbox_hash: MailboxHash) -> Result<Option<Vec<EnvelopeHash>>> { + Err(MeliError::new("melib is not built with any imap cache").set_kind(ErrorKind::Bug)) + } + + fn insert_envelopes( + &mut self, + _mailbox_hash: MailboxHash, + _fetches: &[FetchResponse<'_>], + ) -> Result<()> { + Err(MeliError::new("melib is not built with any imap cache").set_kind(ErrorKind::Bug)) + } + + fn update( + &mut self, + _mailbox_hash: MailboxHash, + _refresh_events: &[(UID, RefreshEvent)], + ) -> Result<()> { + Err(MeliError::new("melib is not built with any imap cache").set_kind(ErrorKind::Bug)) + } + + fn find_envelope( + &mut self, + _identifier: std::result::Result<UID, EnvelopeHash>, + _mailbox_hash: MailboxHash, + ) -> Result<Option<CachedEnvelope>> { + Err(MeliError::new("melib is not built with any imap cache").set_kind(ErrorKind::Bug)) + } + } +} diff --git a/melib/src/backends/imap/cache/sync.rs b/melib/src/backends/imap/cache/sync.rs index f390a9d4..7569fc82 100644 --- a/melib/src/backends/imap/cache/sync.rs +++ b/melib/src/backends/imap/cache/sync.rs @@ -29,7 +29,10 @@ impl ImapConnection { return Ok(None); } - let cache_handle = CacheHandle::get(self.uid_store.clone())?; + #[cfg(not(feature = "sqlite3"))] + let mut cache_handle = DefaultCache::get(self.uid_store.clone())?; + #[cfg(feature = "sqlite3")] + let mut cache_handle = Sqlite3Cache::get(self.uid_store.clone())?; if cache_handle.mailbox_state(mailbox_hash)?.is_none() { return Ok(None); } @@ -52,29 +55,23 @@ impl ImapConnection { mailbox_hash: MailboxHash, ) -> Option<Result<Vec<EnvelopeHash>>> { debug!("load_cache {}", mailbox_hash); - let cache_handle = match CacheHandle::get(self.uid_store.clone()) { + #[cfg(not(feature = "sqlite3"))] + let mut cache_handle = match DefaultCache::get(self.ui |