melib/imap: add ImapCache trait

master
Manos Pitsidianakis 2020-08-28 00:24:43 +03:00
parent e878c50af5
commit b4fe34eacf
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
13 changed files with 574 additions and 289 deletions

24
Cargo.lock generated
View File

@ -814,9 +814,9 @@ dependencies = [
[[package]] [[package]]
name = "libsqlite3-sys" name = "libsqlite3-sys"
version = "0.16.0" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5b95e89c330291768dc840238db7f9e204fd208511ab6319b56193a7f2ae25" checksum = "e3a245984b1b06c291f46e27ebda9f369a94a1ab8461d0e845e23f9ced01f5db"
dependencies = [ dependencies = [
"pkg-config", "pkg-config",
"vcpkg", "vcpkg",
@ -1476,9 +1476,9 @@ dependencies = [
[[package]] [[package]]
name = "rusqlite" name = "rusqlite"
version = "0.20.0" version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a194373ef527035645a1bc21b10dc2125f73497e6e155771233eb187aedd051" checksum = "4c78c3275d9d6eb684d2db4b2388546b32fdae0586c20a82f3905d21ea78b9ef"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"fallible-iterator", "fallible-iterator",
@ -1486,7 +1486,7 @@ dependencies = [
"libsqlite3-sys", "libsqlite3-sys",
"lru-cache", "lru-cache",
"memchr", "memchr",
"time", "smallvec",
] ]
[[package]] [[package]]
@ -1795,13 +1795,6 @@ dependencies = [
"redox_termios", "redox_termios",
] ]
[[package]]
name = "testing"
version = "0.4.1"
dependencies = [
"melib",
]
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.11.0" version = "0.11.0"
@ -1840,6 +1833,13 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "tools"
version = "0.4.1"
dependencies = [
"melib",
]
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.18" version = "0.1.18"

View File

@ -40,7 +40,7 @@ isahc = { version = "0.9.7", optional = true, default-features = false, features
serde_json = { version = "1.0", optional = true, features = ["raw_value",] } serde_json = { version = "1.0", optional = true, features = ["raw_value",] }
smallvec = { version = "^1.4.0", features = ["serde", ] } smallvec = { version = "^1.4.0", features = ["serde", ] }
nix = "0.17.0" nix = "0.17.0"
rusqlite = {version = "0.20.0", optional = true } rusqlite = {version = "0.24.0", optional = true }
libloading = "0.6.2" libloading = "0.6.2"
futures = "0.3.5" futures = "0.3.5"

View File

@ -226,6 +226,16 @@ pub enum BackendEvent {
//Job(Box<Future<Output = Result<()>> + Send + 'static>) //Job(Box<Future<Output = Result<()>> + Send + 'static>)
} }
impl From<MeliError> for BackendEvent {
fn from(val: MeliError) -> BackendEvent {
BackendEvent::Notice {
description: val.summary.as_ref().map(|s| s.to_string()),
content: val.to_string(),
level: crate::LoggingLevel::ERROR,
}
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum RefreshEventKind { pub enum RefreshEventKind {
Update(EnvelopeHash, Box<Envelope>), Update(EnvelopeHash, Box<Envelope>),

View File

@ -51,12 +51,17 @@ use futures::stream::Stream;
use std::collections::{hash_map::DefaultHasher, BTreeMap}; use std::collections::{hash_map::DefaultHasher, BTreeMap};
use std::collections::{BTreeSet, HashMap, HashSet}; use std::collections::{BTreeSet, HashMap, HashSet};
use std::convert::TryFrom; use std::convert::TryFrom;
use std::convert::TryInto;
use std::hash::Hasher; use std::hash::Hasher;
use std::pin::Pin; use std::pin::Pin;
use std::str::FromStr; use std::str::FromStr;
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
use std::time::{Duration, Instant}; 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] = &[ pub static SUPPORTED_CAPABILITIES: &[&str] = &[
#[cfg(feature = "deflate_compression")] #[cfg(feature = "deflate_compression")]
@ -202,7 +207,6 @@ pub struct ImapType {
connection: Arc<FutureMutex<ImapConnection>>, connection: Arc<FutureMutex<ImapConnection>>,
server_conf: ImapServerConf, server_conf: ImapServerConf,
uid_store: Arc<UIDStore>, uid_store: Arc<UIDStore>,
can_create_flags: Arc<Mutex<bool>>,
} }
impl MailBackend for ImapType { impl MailBackend for ImapType {
@ -299,7 +303,6 @@ impl MailBackend for ImapType {
}, },
connection: self.connection.clone(), connection: self.connection.clone(),
mailbox_hash, mailbox_hash,
can_create_flags: self.can_create_flags.clone(),
uid_store: self.uid_store.clone(), uid_store: self.uid_store.clone(),
}; };
@ -742,11 +745,7 @@ impl MailBackend for ImapType {
} }
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> { fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {
if *self.can_create_flags.lock().unwrap() { Some(self.uid_store.tag_index.clone())
Some(self.uid_store.tag_index.clone())
} else {
None
}
} }
fn as_any(&self) -> &dyn Any { fn as_any(&self) -> &dyn Any {
@ -1111,7 +1110,7 @@ impl MailBackend for ImapType {
l["* SEARCH".len()..] l["* SEARCH".len()..]
.trim() .trim()
.split_whitespace() .split_whitespace()
.map(usize::from_str) .map(UID::from_str)
.filter_map(std::result::Result::ok) .filter_map(std::result::Result::ok)
.filter_map(|uid| uid_index.get(&(mailbox_hash, uid))) .filter_map(|uid| uid_index.get(&(mailbox_hash, uid)))
.copied(), .copied(),
@ -1157,7 +1156,17 @@ impl ImapType {
let use_starttls = use_tls && get_conf_val!(s["use_starttls"], !(server_port == 993))?; let use_starttls = use_tls && get_conf_val!(s["use_starttls"], !(server_port == 993))?;
let danger_accept_invalid_certs: bool = let danger_accept_invalid_certs: bool =
get_conf_val!(s["danger_accept_invalid_certs"], false)?; get_conf_val!(s["danger_accept_invalid_certs"], false)?;
#[cfg(feature = "sqlite3")]
let keep_offline_cache = get_conf_val!(s["offline_cache"], true)?; 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 { let server_conf = ImapServerConf {
server_hostname: server_hostname.to_string(), server_hostname: server_hostname.to_string(),
server_username: server_username.to_string(), server_username: server_username.to_string(),
@ -1190,7 +1199,6 @@ impl ImapType {
Ok(Box::new(ImapType { Ok(Box::new(ImapType {
server_conf, server_conf,
is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)), is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)),
can_create_flags: Arc::new(Mutex::new(false)),
connection: Arc::new(FutureMutex::new(connection)), connection: Arc::new(FutureMutex::new(connection)),
uid_store, uid_store,
})) }))
@ -1199,14 +1207,18 @@ impl ImapType {
pub fn shell(&mut self) { pub fn shell(&mut self) {
let mut conn = ImapConnection::new_connection(&self.server_conf, self.uid_store.clone()); 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); let mut res = String::with_capacity(8 * 1024);
futures::executor::block_on(timeout(Duration::from_secs(3), conn.send_command(b"NOOP"))) futures::executor::block_on(timeout(Duration::from_secs(3), conn.send_command(b"NOOP")))
.unwrap()
.unwrap(); .unwrap();
futures::executor::block_on(timeout( futures::executor::block_on(timeout(
Duration::from_secs(3), Duration::from_secs(3),
conn.read_response(&mut res, RequiredResponses::empty()), conn.read_response(&mut res, RequiredResponses::empty()),
)) ))
.unwrap()
.unwrap(); .unwrap();
let mut input = String::new(); let mut input = String::new();
@ -1220,11 +1232,13 @@ impl ImapType {
Duration::from_secs(3), Duration::from_secs(3),
conn.send_command(input.as_bytes()), conn.send_command(input.as_bytes()),
)) ))
.unwrap()
.unwrap(); .unwrap();
futures::executor::block_on(timeout( futures::executor::block_on(timeout(
Duration::from_secs(3), Duration::from_secs(3),
conn.read_lines(&mut res, String::new()), conn.read_lines(&mut res, String::new()),
)) ))
.unwrap()
.unwrap(); .unwrap();
if input.trim().eq_ignore_ascii_case("logout") { if input.trim().eq_ignore_ascii_case("logout") {
break; break;
@ -1368,7 +1382,18 @@ impl ImapType {
))); )));
} }
get_conf_val!(s["danger_accept_invalid_certs"], false)?; get_conf_val!(s["danger_accept_invalid_certs"], false)?;
#[cfg(feature = "sqlite3")]
get_conf_val!(s["offline_cache"], true)?; 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_idle"], true)?;
get_conf_val!(s["use_condstore"], true)?; get_conf_val!(s["use_condstore"], true)?;
#[cfg(feature = "deflate_compression")] #[cfg(feature = "deflate_compression")]
@ -1399,7 +1424,7 @@ enum FetchStage {
InitialFresh, InitialFresh,
InitialCache, InitialCache,
ResyncCache, ResyncCache,
FreshFetch { max_uid: usize }, FreshFetch { max_uid: UID },
Finished, Finished,
} }
@ -1408,7 +1433,6 @@ struct FetchState {
stage: FetchStage, stage: FetchStage,
connection: Arc<FutureMutex<ImapConnection>>, connection: Arc<FutureMutex<ImapConnection>>,
mailbox_hash: MailboxHash, mailbox_hash: MailboxHash,
can_create_flags: Arc<Mutex<bool>>,
uid_store: Arc<UIDStore>, uid_store: Arc<UIDStore>,
} }
@ -1423,7 +1447,6 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> {
.await .await
.init_mailbox(state.mailbox_hash) .init_mailbox(state.mailbox_hash)
.await?; .await?;
*state.can_create_flags.lock().unwrap() = select_response.can_create_flags;
if select_response.exists == 0 { if select_response.exists == 0 {
state.stage = FetchStage::Finished; state.stage = FetchStage::Finished;
return Ok(Vec::new()); return Ok(Vec::new());
@ -1481,7 +1504,6 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> {
ref mut stage, ref mut stage,
ref connection, ref connection,
mailbox_hash, mailbox_hash,
can_create_flags: _,
ref uid_store, ref uid_store,
} = state; } = state;
let mailbox_hash = *mailbox_hash; 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 { 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 debug!(cache_handle
.insert_envelopes(mailbox_hash, &v) .insert_envelopes(mailbox_hash, &v)
.chain_err_summary(|| { .chain_err_summary(|| {
@ -1601,7 +1624,7 @@ async fn fetch_hlpr(state: &mut FetchState) -> Result<Vec<Envelope>> {
.unwrap() .unwrap()
.entry(mailbox_hash) .entry(mailbox_hash)
.or_default() .or_default()
.insert(message_sequence_number - 1, uid); .insert((message_sequence_number - 1).try_into().unwrap(), uid);
uid_store uid_store
.hash_index .hash_index
.lock() .lock()

View File

@ -26,7 +26,6 @@ use crate::{
email::{Envelope, EnvelopeHash}, email::{Envelope, EnvelopeHash},
error::*, error::*,
}; };
use std::convert::TryFrom; use std::convert::TryFrom;
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Copy, Clone)] #[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Copy, Clone)]
@ -55,10 +54,36 @@ pub struct CachedEnvelope {
pub modsequence: Option<ModSequence>, pub modsequence: Option<ModSequence>,
} }
pub struct CacheHandle { pub trait ImapCache: Send {
#[cfg(feature = "sqlite3")] fn mailbox_state(&mut self, mailbox_hash: MailboxHash) -> Result<Option<()>>;
connection: crate::sqlite3::Connection,
uid_store: Arc<UIDStore>, 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")] #[cfg(feature = "sqlite3")]
@ -71,28 +96,43 @@ mod sqlite3_m {
FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput,
}; };
use crate::sqlite3::{self, DatabaseDescription}; 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 { const DB_DESCRIPTION: DatabaseDescription = DatabaseDescription {
name: "header_cache.db", name: "header_cache.db",
init_script: Some("PRAGMA foreign_keys = true; init_script: Some(
"PRAGMA foreign_keys = true;
PRAGMA encoding = 'UTF-8'; PRAGMA encoding = 'UTF-8';
CREATE TABLE IF NOT EXISTS envelopes ( CREATE TABLE IF NOT EXISTS envelopes (
mailbox_hash INTEGER, hash INTEGER NOT NULL,
uid INTEGER, mailbox_hash INTEGER NOT NULL,
uid INTEGER NOT NULL,
modsequence INTEGER, modsequence INTEGER,
rfc822 BLOB,
envelope BLOB NOT NULL, envelope BLOB NOT NULL,
PRIMARY KEY (mailbox_hash, uid), 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 ( CREATE TABLE IF NOT EXISTS mailbox (
uid INTEGER UNIQUE,
mailbox_hash INTEGER UNIQUE, mailbox_hash INTEGER UNIQUE,
uidvalidity INTEGER,
flags BLOB NOT NULL,
highestmodseq INTEGER, 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 envelope_uid_idx ON envelopes(mailbox_hash, uid);
CREATE INDEX IF NOT EXISTS uidvalidity_idx ON uidvalidity(mailbox_hash);"), CREATE INDEX IF NOT EXISTS envelope_idx ON envelopes(hash);
version: 1, CREATE INDEX IF NOT EXISTS mailbox_idx ON mailbox(mailbox_hash);",
),
version: 1,
}; };
impl ToSql for ModSequence { impl ToSql for ModSequence {
@ -111,47 +151,108 @@ mod sqlite3_m {
} }
} }
impl CacheHandle { impl Sqlite3Cache {
pub fn get(uid_store: Arc<UIDStore>) -> Result<Self> { pub fn get(uid_store: Arc<UIDStore>) -> Result<Box<dyn ImapCache>> {
Ok(Self { Ok(Box::new(Self {
connection: sqlite3::open_or_create_db( connection: sqlite3::open_or_create_db(
&DB_DESCRIPTION, &DB_DESCRIPTION,
Some(uid_store.account_name.as_str()), Some(uid_store.account_name.as_str()),
)?, )?,
loaded_mailboxes: BTreeSet::default(),
uid_store, uid_store,
}) }))
} }
pub fn mailbox_state( fn max_uid(&self, mailbox_hash: MailboxHash) -> Result<UID> {
&self,
mailbox_hash: MailboxHash,
) -> Result<Option<(UID, Option<ModSequence>)>> {
let mut stmt = self let mut stmt = self
.connection .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| { 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() { if let Some(v) = ret.next() {
Ok(Some(row_res?)) 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 { } else {
debug!("mailbox state {} not in cache", mailbox_hash);
Ok(None) Ok(None)
} }
} }
pub fn clear( fn clear(
&self, &mut self,
mailbox_hash: MailboxHash, mailbox_hash: MailboxHash,
new_uidvalidity: UID, select_response: &SelectResponse,
highestmodseq: Option<ModSequence>,
) -> Result<()> { ) -> Result<()> {
debug!("clear mailbox_hash {}", mailbox_hash); debug!("clear mailbox_hash {} {:?}", mailbox_hash, select_response);
debug!(new_uidvalidity); self.loaded_mailboxes.remove(&mailbox_hash);
debug!(&highestmodseq);
self.connection self.connection
.execute( .execute(
"DELETE FROM uidvalidity WHERE mailbox_hash = ?1", "DELETE FROM mailbox WHERE mailbox_hash = ?1",
sqlite3::params![mailbox_hash as i64], sqlite3::params![mailbox_hash as i64],
) )
.chain_err_summary(|| { .chain_err_summary(|| {
@ -161,20 +262,38 @@ mod sqlite3_m {
) )
})?; })?;
self.connection.execute( if let Some(Ok(highestmodseq)) = select_response.highestmodseq {
"INSERT OR IGNORE INTO uidvalidity (uid, highestmodseq, mailbox_hash) VALUES (?1, ?2, ?3)", self.connection.execute(
sqlite3::params![new_uidvalidity as i64, highestmodseq, mailbox_hash as i64], "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(|| { .chain_err_summary(|| {
format!( format!(
"Could not insert uidvalidity {} in header_cache of account {}", "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(()) 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); debug!("envelopes mailbox_hash {}", mailbox_hash);
if debug!(self.mailbox_state(mailbox_hash)?.is_none()) { if debug!(self.mailbox_state(mailbox_hash)?.is_none()) {
return Ok(None); return Ok(None);
@ -187,7 +306,7 @@ mod sqlite3_m {
let ret: Vec<(UID, Envelope, Option<ModSequence>)> = stmt let ret: Vec<(UID, Envelope, Option<ModSequence>)> = stmt
.query_map(sqlite3::params![mailbox_hash as i64], |row| { .query_map(sqlite3::params![mailbox_hash as i64], |row| {
Ok(( Ok((
row.get(0).map(|i: i64| i as usize)?, row.get(0).map(|i: Sqlite3UID| i as UID)?,
row.get(1)?, row.get(1)?,
row.get(2)?, row.get(2)?,
)) ))
@ -221,7 +340,7 @@ mod sqlite3_m {
Ok(Some(env_hashes)) Ok(Some(env_hashes))
} }
pub fn insert_envelopes( fn insert_envelopes(
&mut self, &mut self,
mailbox_hash: MailboxHash, mailbox_hash: MailboxHash,
fetches: &[FetchResponse<'_>], fetches: &[FetchResponse<'_>],
@ -231,35 +350,22 @@ mod sqlite3_m {
mailbox_hash, mailbox_hash,
fetches.len() 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() { if self.mailbox_state(mailbox_hash)?.is_none() {
debug!(self.mailbox_state(mailbox_hash)?.is_none()); debug!(self.mailbox_state(mailbox_hash)?.is_none());
let uidvalidity = self return Err(MeliError::new("Mailbox is not in cache").set_kind(ErrorKind::Bug));
.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()),
))?;
}
} }
let Self { let Self {
ref mut connection, ref mut connection,
ref uid_store, ref uid_store,
loaded_mailboxes: _,
} = self; } = self;
let tx = connection.transaction()?; let tx = connection.transaction()?;
for item in fetches { for item in fetches {
@ -272,17 +378,23 @@ mod sqlite3_m {
envelope: Some(envelope), envelope: Some(envelope),
} = item } = item
{ {
max_uid = std::cmp::max(max_uid, *uid);
tx.execute( tx.execute(
"INSERT OR REPLACE INTO envelopes (uid, mailbox_hash, modsequence, envelope) VALUES (?1, ?2, ?3, ?4)", "INSERT OR REPLACE INTO envelopes (hash, uid, mailbox_hash, modsequence, envelope) VALUES (?1, ?2, ?3, ?4, ?5)",
sqlite3::params![*uid as i64, mailbox_hash as i64, modseq, &envelope], 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))?; ).chain_err_summary(|| format!("Could not insert envelope {} {} in header_cache of account {}", envelope.message_id(), envelope.hash(), uid_store.account_name))?;
} }
} }
tx.commit()?; tx.commit()?;
self.uid_store
.max_uids
.lock()
.unwrap()
.insert(mailbox_hash, max_uid);
Ok(()) Ok(())
} }
pub fn update( fn update(
&mut self, &mut self,
mailbox_hash: MailboxHash, mailbox_hash: MailboxHash,
refresh_events: &[(UID, RefreshEvent)], refresh_events: &[(UID, RefreshEvent)],
@ -294,33 +406,12 @@ mod sqlite3_m {
); );
if self.mailbox_state(mailbox_hash)?.is_none() { if self.mailbox_state(mailbox_hash)?.is_none() {
debug!(self.mailbox_state(mailbox_hash)?.is_none()); debug!(self.mailbox_state(mailbox_hash)?.is_none());
let uidvalidity = self return Err(MeliError::new("Mailbox is not in cache").set_kind(ErrorKind::Bug));
.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()),
))?;
}
} }
let Self { let Self {
ref mut connection, ref mut connection,
ref uid_store, ref uid_store,
loaded_mailboxes: _,
} = self; } = self;
let tx = connection.transaction()?; let tx = connection.transaction()?;
let mut hash_index_lck = uid_store.hash_index.lock().unwrap(); let mut hash_index_lck = uid_store.hash_index.lock().unwrap();
@ -330,7 +421,7 @@ mod sqlite3_m {
hash_index_lck.remove(&env_hash); hash_index_lck.remove(&env_hash);
tx.execute( tx.execute(
"DELETE FROM envelopes WHERE mailbox_hash = ?1 AND uid = ?2;", "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(|| { .chain_err_summary(|| {
format!( format!(
@ -345,9 +436,10 @@ mod sqlite3_m {
)?; )?;
let mut ret: Vec<Envelope> = stmt let mut ret: Vec<Envelope> = stmt
.query_map(sqlite3::params![mailbox_hash as i64, *uid as i64], |row| { .query_map(
Ok(row.get(0)?) sqlite3::params![mailbox_hash as i64, *uid as Sqlite3UID],
})? |row| Ok(row.get(0)?),
)?
.collect::<std::result::Result<_, _>>()?; .collect::<std::result::Result<_, _>>()?;
if let Some(mut env) = ret.pop() { if let Some(mut env) = ret.pop() {
env.set_flags(*flags); env.set_flags(*flags);
@ -355,7 +447,7 @@ mod sqlite3_m {
env.labels_mut().extend(tags.iter().map(|t| tag_hash!(t))); env.labels_mut().extend(tags.iter().map(|t| tag_hash!(t)));
tx.execute( tx.execute(
"UPDATE envelopes SET envelope = ?1 WHERE mailbox_hash = ?2 AND uid = ?3;", "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(|| { .chain_err_summary(|| {
format!( format!(
@ -377,48 +469,108 @@ mod sqlite3_m {
} }
} }
tx.commit()?; tx.commit()?;
Ok(()) let new_max_uid = self.max_uid(mailbox_hash)?;
} self.uid_store
} .max_uids
} .lock()
.unwrap()
#[cfg(not(feature = "sqlite3"))] .insert(mailbox_hash, new_max_uid);
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)
}
pub fn clear(
&self,
_mailbox_hash: MailboxHash,
_new_uidvalidity: UID,
_highestmodseq: Option<ModSequence>,
) -> Result<()> {
Ok(()) Ok(())
} }
pub fn envelopes(&self, _mailbox_hash: MailboxHash) -> Result<Option<Vec<EnvelopeHash>>> { fn find_envelope(
Ok(None)
}
pub fn insert_envelopes(
&mut self, &mut self,
_mailbox_hash: MailboxHash, identifier: std::result::Result<UID, EnvelopeHash>,
_fetches: &[FetchResponse<'_>], mailbox_hash: MailboxHash,
) -> Result<()> { ) -> Result<Option<CachedEnvelope>> {
Ok(()) 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;",
)?;
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;",
)?;
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,
}));
}
fn rfc822(
&mut self,
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: _, stage: _,
ref mut connection, ref mut connection,
mailbox_hash, mailbox_hash,
can_create_flags: _,
ref uid_store, ref uid_store,
} = state; } = state;
debug!(uid_store.keep_offline_cache); 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))
}
}
}

View File

@ -29,7 +29,10 @@ impl ImapConnection {
return Ok(None); 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() { if cache_handle.mailbox_state(mailbox_hash)?.is_none() {
return Ok(None); return Ok(None);
} }
@ -52,29 +55,23 @@ impl ImapConnection {
mailbox_hash: MailboxHash, mailbox_hash: MailboxHash,
) -> Option<Result<Vec<EnvelopeHash>>> { ) -> Option<Result<Vec<EnvelopeHash>>> {
debug!("load_cache {}", mailbox_hash); 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.uid_store.clone()) {
Ok(v) => v, Ok(v) => v,
Err(err) => return Some(Err(err)), Err(err) => return Some(Err(err)),
}; };
let (uidvalidity, highestmodseq) = match debug!(cache_handle.mailbox_state(mailbox_hash)) { #[cfg(feature = "sqlite3")]
let mut cache_handle = match Sqlite3Cache::get(self.uid_store.clone()) {
Ok(v) => v,
Err(err) => return Some(Err(err)), Err(err) => return Some(Err(err)),
Ok(Some(v)) => v, };
match debug!(cache_handle.mailbox_state(mailbox_hash)) {
Err(err) => return Some(Err(err)),
Ok(Some(())) => {}
Ok(None) => { Ok(None) => {
return None; return None;
} }
}; };
self.uid_store
.uidvalidity
.lock()
.unwrap()
.entry(mailbox_hash)
.or_insert(uidvalidity);
self.uid_store
.highestmodseqs
.lock()
.unwrap()
.entry(mailbox_hash)
.or_insert(highestmodseq.ok_or(()));
match debug!(cache_handle.envelopes(mailbox_hash)) { match debug!(cache_handle.envelopes(mailbox_hash)) {
Ok(Some(envs)) => Some(Ok(envs)), Ok(Some(envs)) => Some(Ok(envs)),
Ok(None) => None, Ok(None) => None,
@ -84,7 +81,7 @@ impl ImapConnection {
pub async fn build_cache( pub async fn build_cache(
&mut self, &mut self,
cache_handle: &mut CacheHandle, cache_handle: &mut Box<dyn ImapCache>,
mailbox_hash: MailboxHash, mailbox_hash: MailboxHash,
) -> Result<()> { ) -> Result<()> {
debug!("build_cache {}", mailbox_hash); debug!("build_cache {}", mailbox_hash);
@ -111,11 +108,7 @@ impl ImapConnection {
.unwrap() .unwrap()
.insert(mailbox_hash, v); .insert(mailbox_hash, v);
} }
cache_handle.clear( cache_handle.clear(mailbox_hash, &select_response)?;
mailbox_hash,
select_response.uidvalidity,
select_response.highestmodseq.and_then(|i| i.ok()),
)?;
self.send_command(b"UID FETCH 1:* (UID FLAGS ENVELOPE BODYSTRUCTURE)") self.send_command(b"UID FETCH 1:* (UID FLAGS ENVELOPE BODYSTRUCTURE)")
.await?; .await?;
self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED) self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED)
@ -128,18 +121,11 @@ impl ImapConnection {
//rfc4549_Synchronization_Operations_for_Disconnected_IMAP4_Clients //rfc4549_Synchronization_Operations_for_Disconnected_IMAP4_Clients
pub async fn resync_basic( pub async fn resync_basic(
&mut self, &mut self,
mut cache_handle: CacheHandle, mut cache_handle: Box<dyn ImapCache>,
mailbox_hash: MailboxHash, mailbox_hash: MailboxHash,
) -> Result<Option<Vec<Envelope>>> { ) -> Result<Option<Vec<Envelope>>> {
let mut payload = vec![]; let mut payload = vec![];
debug!("resync_basic"); debug!("resync_basic");
debug!(self
.uid_store
.uidvalidity
.lock()
.unwrap()
.get(&mailbox_hash));
debug!(self.uid_store.max_uids.lock().unwrap().get(&mailbox_hash));
let mut response = String::with_capacity(8 * 1024); let mut response = String::with_capacity(8 * 1024);
let cached_uidvalidity = self let cached_uidvalidity = self
.uid_store .uid_store
@ -182,11 +168,7 @@ impl ImapConnection {
); );
// 1. check UIDVALIDITY. If fail, discard cache and rebuild // 1. check UIDVALIDITY. If fail, discard cache and rebuild
if select_response.uidvalidity != current_uidvalidity { if select_response.uidvalidity != current_uidvalidity {
cache_handle.clear( cache_handle.clear(mailbox_hash, &select_response)?;
mailbox_hash,
select_response.uidvalidity,
select_response.highestmodseq.and_then(|i| i.ok()),
)?;
return Ok(None); return Ok(None);
} }
@ -234,7 +216,6 @@ impl ImapConnection {
} }
} }
{ {
let mut cache_handle = cache::CacheHandle::get(self.uid_store.clone())?;
debug!(cache_handle debug!(cache_handle
.insert_envelopes(mailbox_hash, &v) .insert_envelopes(mailbox_hash, &v)
.chain_err_summary(|| { .chain_err_summary(|| {
@ -363,18 +344,11 @@ impl ImapConnection {
//Section 6.1 //Section 6.1
pub async fn resync_condstore( pub async fn resync_condstore(
&mut self, &mut self,
mut cache_handle: CacheHandle, mut cache_handle: Box<dyn ImapCache>,
mailbox_hash: MailboxHash, mailbox_hash: MailboxHash,
) -> Result<Option<Vec<Envelope>>> { ) -> Result<Option<Vec<Envelope>>> {
let mut payload = vec![]; let mut payload = vec![];
debug!("resync_condstore"); debug!("resync_condstore");
debug!(self
.uid_store
.uidvalidity
.lock()
.unwrap()
.get(&mailbox_hash));
debug!(self.uid_store.max_uids.lock().unwrap().get(&mailbox_hash));
let mut response = String::with_capacity(8 * 1024); let mut response = String::with_capacity(8 * 1024);
let cached_uidvalidity = self let cached_uidvalidity = self
.uid_store .uid_store
@ -397,6 +371,9 @@ impl ImapConnection {
.unwrap() .unwrap()
.get(&mailbox_hash) .get(&mailbox_hash)
.cloned(); .cloned();
debug!(&cached_uidvalidity);
debug!(&cached_max_uid);
debug!(&cached_highestmodseq);
if cached_uidvalidity.is_none() if cached_uidvalidity.is_none()
|| cached_max_uid.is_none() || cached_max_uid.is_none()
|| cached_highestmodseq.is_none() || cached_highestmodseq.is_none()
@ -444,11 +421,7 @@ impl ImapConnection {
// mailbox (note that this doesn't affect actions performed on // mailbox (note that this doesn't affect actions performed on
// client-generated fake UIDs; see Section 5); and // client-generated fake UIDs; see Section 5); and
// * skip steps 1b and 2-II; // * skip steps 1b and 2-II;
cache_handle.clear( cache_handle.clear(mailbox_hash, &select_response)?;
mailbox_hash,
select_response.uidvalidity,
select_response.highestmodseq.and_then(|i| i.ok()),
)?;
return Ok(None); return Ok(None);
} }
if select_response.highestmodseq.is_none() if select_response.highestmodseq.is_none()
@ -525,7 +498,6 @@ impl ImapConnection {
} }
} }
{ {
let mut cache_handle = cache::CacheHandle::get(self.uid_store.clone())?;
debug!(cache_handle debug!(cache_handle
.insert_envelopes(mailbox_hash, &v) .insert_envelopes(mailbox_hash, &v)
.chain_err_summary(|| { .chain_err_summary(|| {
@ -668,7 +640,7 @@ impl ImapConnection {
//rfc7162_Quick Flag Changes Resynchronization (CONDSTORE)_and Quick Mailbox Resynchronization (QRESYNC) //rfc7162_Quick Flag Changes Resynchronization (CONDSTORE)_and Quick Mailbox Resynchronization (QRESYNC)
pub async fn resync_condstoreqresync( pub async fn resync_condstoreqresync(
&mut self, &mut self,
_cache_handle: CacheHandle, _cache_handle: Box<dyn ImapCache>,
_mailbox_hash: MailboxHash, _mailbox_hash: MailboxHash,
) -> Result<Option<Vec<Envelope>>> { ) -> Result<Option<Vec<Envelope>>> {
Ok(None) Ok(None)
@ -706,6 +678,13 @@ impl ImapConnection {
.or_insert(select_response.uidvalidity); .or_insert(select_response.uidvalidity);
*v = select_response.uidvalidity; *v = select_response.uidvalidity;
} }
{
if let Some(highestmodseq) = select_response.highestmodseq {
let mut highestmodseqs = self.uid_store.highestmodseqs.lock().unwrap();
let v = highestmodseqs.entry(mailbox_hash).or_insert(highestmodseq);
*v = highestmodseq;
}
}
let mut permissions = permissions.lock().unwrap(); let mut permissions = permissions.lock().unwrap();
permissions.create_messages = !select_response.read_only; permissions.create_messages = !select_response.read_only;
permissions.remove_messages = !select_response.read_only; permissions.remove_messages = !select_response.read_only;

View File

@ -762,6 +762,34 @@ impl ImapConnection {
.await?; .await?;
debug!("select response {}", ret); debug!("select response {}", ret);
let select_response = protocol_parser::select_response(&ret)?; let select_response = protocol_parser::select_response(&ret)?;
{
if self.uid_store.keep_offline_cache {
#[cfg(not(feature = "sqlite3"))]
let mut cache_handle = super::cache::DefaultCache::get(self.uid_store.clone())?;
#[cfg(feature = "sqlite3")]
let mut cache_handle = super::cache::Sqlite3Cache::get(self.uid_store.clone())?;
if let Err(err) = cache_handle.mailbox_state(mailbox_hash).and_then(|r| {
if r.is_none() {
cache_handle.clear(mailbox_hash, &select_response)
} else {
Ok(())
}
}) {
(self.uid_store.event_consumer)(
self.uid_store.account_hash,
crate::backends::BackendEvent::from(err),
);
}
}
self.uid_store
.mailboxes
.lock()
.await
.entry(mailbox_hash)
.and_modify(|entry| {
*entry.select.write().unwrap() = Some(select_response.clone());
});
}
{ {
let mut permissions = permissions.lock().unwrap(); let mut permissions = permissions.lock().unwrap();
permissions.create_messages = !select_response.read_only; permissions.create_messages = !select_response.read_only;

View File

@ -18,6 +18,8 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use super::protocol_parser::SelectResponse;
use crate::backends::{ use crate::backends::{
BackendMailbox, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox, BackendMailbox, Mailbox, MailboxHash, MailboxPermissions, SpecialUsageMailbox,
}; };
@ -95,14 +97,15 @@ fn test_lazy_count_set() {
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct ImapMailbox { pub struct ImapMailbox {
pub(super) hash: MailboxHash, pub hash: MailboxHash,
pub(super) imap_path: String, pub imap_path: String,
pub(super) path: String, pub path: String,
pub(super) name: String, pub name: String,
pub(super) parent: Option<MailboxHash>, pub parent: Option<MailboxHash>,
pub(super) children: Vec<MailboxHash>, pub children: Vec<MailboxHash>,
pub separator: u8, pub separator: u8,
pub usage: Arc<RwLock<SpecialUsageMailbox>>, pub usage: Arc<RwLock<SpecialUsageMailbox>>,
pub select: Arc<RwLock<Option<SelectResponse>>>,
pub no_select: bool, pub no_select: bool,
pub is_subscribed: bool, pub is_subscribed: bool,

View File

@ -29,7 +29,7 @@ use std::sync::Arc;
/// `BackendOp` implementor for Imap /// `BackendOp` implementor for Imap
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct ImapOp { pub struct ImapOp {
uid: usize, uid: UID,
mailbox_hash: MailboxHash, mailbox_hash: MailboxHash,
connection: Arc<FutureMutex<ImapConnection>>, connection: Arc<FutureMutex<ImapConnection>>,
uid_store: Arc<UIDStore>, uid_store: Arc<UIDStore>,
@ -37,7 +37,7 @@ pub struct ImapOp {
impl ImapOp { impl ImapOp {
pub fn new( pub fn new(
uid: usize, uid: UID,
mailbox_hash: MailboxHash, mailbox_hash: MailboxHash,
connection: Arc<FutureMutex<ImapConnection>>, connection: Arc<FutureMutex<ImapConnection>>,
uid_store: Arc<UIDStore>, uid_store: Arc<UIDStore>,
@ -80,12 +80,20 @@ impl BackendOp for ImapOp {
response.len(), response.len(),
response.lines().count() response.lines().count()
); );
let mut results = protocol_parser::fetch_responses(&response)?.1;
if results.len() != 1 {
return Err(MeliError::new(format!(
"Invalid/unexpected response: {:?}",
response
))
.set_summary(format!("message with UID {} was not found?", uid)));
}
let FetchResponse { let FetchResponse {
uid: _uid, uid: _uid,
flags: _flags, flags: _flags,
body, body,
.. ..
} = protocol_parser::fetch_response(&response)?.1; } = results.pop().unwrap();
let _uid = _uid.unwrap(); let _uid = _uid.unwrap();
assert_eq!(_uid, uid); assert_eq!(_uid, uid);
assert!(body.is_some()); assert!(body.is_some());

View File

@ -185,7 +185,7 @@ pub enum ResponseCode {
/// Followed by a decimal number, indicates the unique identifier validity value. Refer to section 2.3.1.1 for more information. /// Followed by a decimal number, indicates the unique identifier validity value. Refer to section 2.3.1.1 for more information.
Uidvalidity(UID), Uidvalidity(UID),
/// Followed by a decimal number, indicates the number of the first message without the \Seen flag set. /// Followed by a decimal number, indicates the number of the first message without the \Seen flag set.
Unseen(usize), Unseen(ImapNum),
} }
impl std::fmt::Display for ResponseCode { impl std::fmt::Display for ResponseCode {
@ -452,7 +452,7 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct FetchResponse<'a> { pub struct FetchResponse<'a> {
pub uid: Option<UID>, pub uid: Option<UID>,
pub message_sequence_number: usize, pub message_sequence_number: MessageSequenceNumber,
pub modseq: Option<ModSequence>, pub modseq: Option<ModSequence>,
pub flags: Option<(Flag, Vec<String>)>, pub flags: Option<(Flag, Vec<String>)>,
pub body: Option<&'a [u8]>, pub body: Option<&'a [u8]>,
@ -516,7 +516,7 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
while input.as_bytes()[i].is_ascii_digit() { while input.as_bytes()[i].is_ascii_digit() {
let b: u8 = input.as_bytes()[i] - 0x30; let b: u8 = input.as_bytes()[i] - 0x30;
ret.message_sequence_number *= 10; ret.message_sequence_number *= 10;
ret.message_sequence_number += b as usize; ret.message_sequence_number += b as MessageSequenceNumber;
i += 1; i += 1;
bounds!(); bounds!();
} }
@ -537,7 +537,7 @@ pub fn fetch_response(input: &str) -> ImapParseResult<FetchResponse<'_>> {
{ {
i += input.len() - i - rest.len(); i += input.len() - i - rest.len();
ret.uid = ret.uid =
Some(usize::from_str(unsafe { std::str::from_utf8_unchecked(uid) }).unwrap()); Some(UID::from_str(unsafe { std::str::from_utf8_unchecked(uid) }).unwrap());
} else { } else {
return debug!(Err(MeliError::new(format!( return debug!(Err(MeliError::new(format!(
"Unexpected input while parsing UID FETCH response. Got: `{:.40}`", "Unexpected input while parsing UID FETCH response. Got: `{:.40}`",
@ -695,23 +695,21 @@ pub fn fetch_responses(mut input: &str) -> ImapParseResult<Vec<FetchResponse<'_>
))); )));
} }
} }
Ok((input, ret, None)) Ok((input, ret, alert))
} }
pub fn uid_fetch_flags_responses( pub fn uid_fetch_flags_responses(input: &[u8]) -> IResult<&[u8], Vec<(UID, (Flag, Vec<String>))>> {
input: &[u8],
) -> IResult<&[u8], Vec<(usize, (Flag, Vec<String>))>> {
many0(uid_fetch_flags_response)(input) many0(uid_fetch_flags_response)(input)
} }
pub fn uid_fetch_flags_response(input: &[u8]) -> IResult<&[u8], (usize, (Flag, Vec<String>))> { pub fn uid_fetch_flags_response(input: &[u8]) -> IResult<&[u8], (UID, (Flag, Vec<String>))> {
let (input, _) = tag("* ")(input)?; let (input, _) = tag("* ")(input)?;
let (input, _msn) = take_while(is_digit)(input)?; let (input, _msn) = take_while(is_digit)(input)?;
let (input, _) = tag(" FETCH (")(input)?; let (input, _) = tag(" FETCH (")(input)?;
let (input, uid_flags) = permutation(( let (input, uid_flags) = permutation((
preceded( preceded(
alt((tag("UID "), tag(" UID "))), alt((tag("UID "), tag(" UID "))),
map_res(digit1, |s| usize::from_str(to_str!(s))), map_res(digit1, |s| UID::from_str(to_str!(s))),
), ),
preceded( preceded(
alt((tag("FLAGS "), tag(" FLAGS "))), alt((tag("FLAGS "), tag(" FLAGS "))),
@ -815,7 +813,7 @@ pub enum UntaggedResponse<'s> {
/// The update from the EXPUNGE response MUST be recorded by the /// The update from the EXPUNGE response MUST be recorded by the
/// client. /// client.
/// ``` /// ```
Expunge(usize), Expunge(MessageSequenceNumber),
/// ```text /// ```text
/// 7.3.1. EXISTS Response /// 7.3.1. EXISTS Response
/// ///
@ -826,7 +824,7 @@ pub enum UntaggedResponse<'s> {
/// The update from the EXISTS response MUST be recorded by the /// The update from the EXISTS response MUST be recorded by the
/// client. /// client.
/// ``` /// ```
Exists(usize), Exists(ImapNum),
/// ```text /// ```text
/// 7.3.2. RECENT Response /// 7.3.2. RECENT Response
/// The RECENT response reports the number of messages with the /// The RECENT response reports the number of messages with the
@ -834,7 +832,7 @@ pub enum UntaggedResponse<'s> {
/// EXAMINE command, and if the size of the mailbox changes (e.g., new /// EXAMINE command, and if the size of the mailbox changes (e.g., new
/// messages). /// messages).
/// ``` /// ```
Recent(usize), Recent(ImapNum),
Fetch(FetchResponse<'s>), Fetch(FetchResponse<'s>),
Bye { Bye {
reason: &'s str, reason: &'s str,
@ -844,10 +842,9 @@ pub enum UntaggedResponse<'s> {
pub fn untagged_responses(input: &str) -> ImapParseResult<Option<UntaggedResponse<'_>>> { pub fn untagged_responses(input: &str) -> ImapParseResult<Option<UntaggedResponse<'_>>> {
let orig_input = input; let orig_input = input;
let (input, _) = tag::<_, &str, (&str, nom::error::ErrorKind)>("* ")(input)?; let (input, _) = tag::<_, &str, (&str, nom::error::ErrorKind)>("* ")(input)?;
let (input, num) = let (input, num) = map_res::<_, _, _, (&str, nom::error::ErrorKind), _, _, _>(digit1, |s| {
map_res::<_, _, _, (&str, nom::error::ErrorKind), _, _, _>(digit1, |s| usize::from_str(s))( ImapNum::from_str(s)
input, })(input)?;
)?;
let (input, _) = tag::<_, &str, (&str, nom::error::ErrorKind)>(" ")(input)?; let (input, _) = tag::<_, &str, (&str, nom::error::ErrorKind)>(" ")(input)?;
let (input, _tag) = take_until::<_, &str, (&str, nom::error::ErrorKind)>("\r\n")(input)?; let (input, _tag) = take_until::<_, &str, (&str, nom::error::ErrorKind)>("\r\n")(input)?;
let (input, _) = tag::<_, &str, (&str, nom::error::ErrorKind)>("\r\n")(input)?; let (input, _) = tag::<_, &str, (&str, nom::error::ErrorKind)>("\r\n")(input)?;
@ -912,20 +909,20 @@ fn test_untagged_responses() {
); );
} }
pub fn search_results<'a>(input: &'a [u8]) -> IResult<&'a [u8], Vec<usize>> { pub fn search_results<'a>(input: &'a [u8]) -> IResult<&'a [u8], Vec<ImapNum>> {
alt(( alt((
|input: &'a [u8]| -> IResult<&'a [u8], Vec<usize>> { |input: &'a [u8]| -> IResult<&'a [u8], Vec<ImapNum>> {
let (input, _) = tag("* SEARCH ")(input)?; let (input, _) = tag("* SEARCH ")(input)?;
let (input, list) = separated_nonempty_list( let (input, list) = separated_nonempty_list(
tag(b" "), tag(b" "),
map_res(is_not(" \r\n"), |s: &[u8]| { map_res(is_not(" \r\n"), |s: &[u8]| {
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) ImapNum::from_str(unsafe { std::str::from_utf8_unchecked(s) })
}), }),
)(input)?; )(input)?;
let (input, _) = tag("\r\n")(input)?; let (input, _) = tag("\r\n")(input)?;
Ok((input, list)) Ok((input, list))
}, },
|input: &'a [u8]| -> IResult<&'a [u8], Vec<usize>> { |input: &'a [u8]| -> IResult<&'a [u8], Vec<ImapNum>> {
let (input, _) = tag("* SEARCH\r\n")(input)?; let (input, _) = tag("* SEARCH\r\n")(input)?;
Ok((input, vec![])) Ok((input, vec![]))
}, },
@ -966,12 +963,12 @@ fn test_imap_search() {
#[derive(Debug, Default, PartialEq, Clone)] #[derive(Debug, Default, PartialEq, Clone)]
pub struct SelectResponse { pub struct SelectResponse {
pub exists: usize, pub exists: ImapNum,
pub recent: usize, pub recent: ImapNum,
pub flags: (Flag, Vec<String>), pub flags: (Flag, Vec<String>),
pub unseen: usize, pub unseen: MessageSequenceNumber,
pub uidvalidity: usize, pub uidvalidity: UIDVALIDITY,
pub uidnext: usize, pub uidnext: UID,
pub permanentflags: (Flag, Vec<String>), pub permanentflags: (Flag, Vec<String>),
/// if SELECT returns \* we can set arbritary flags permanently. /// if SELECT returns \* we can set arbritary flags permanently.
pub can_create_flags: bool, pub can_create_flags: bool,
@ -1006,18 +1003,20 @@ pub fn select_response(input: &str) -> Result<SelectResponse> {
let mut ret = SelectResponse::default(); let mut ret = SelectResponse::default();
for l in input.split_rn() { for l in input.split_rn() {
if l.starts_with("* ") && l.ends_with(" EXISTS\r\n") { if l.starts_with("* ") && l.ends_with(" EXISTS\r\n") {
ret.exists = usize::from_str(&l["* ".len()..l.len() - " EXISTS\r\n".len()])?; ret.exists = ImapNum::from_str(&l["* ".len()..l.len() - " EXISTS\r\n".len()])?;
} else if l.starts_with("* ") && l.ends_with(" RECENT\r\n") { } else if l.starts_with("* ") && l.ends_with(" RECENT\r\n") {
ret.recent = usize::from_str(&l["* ".len()..l.len() - " RECENT\r\n".len()])?; ret.recent = ImapNum::from_str(&l["* ".len()..l.len() - " RECENT\r\n".len()])?;
} else if l.starts_with("* FLAGS (") { } else if l.starts_with("* FLAGS (") {
ret.flags = flags(&l["* FLAGS (".len()..l.len() - ")".len()]).map(|(_, v)| v)?; ret.flags = flags(&l["* FLAGS (".len()..l.len() - ")".len()]).map(|(_, v)| v)?;
} else if l.starts_with("* OK [UNSEEN ") { } else if l.starts_with("* OK [UNSEEN ") {
ret.unseen = usize::from_str(&l["* OK [UNSEEN ".len()..l.find(']').unwrap()])?; ret.unseen = MessageSequenceNumber::from_str(
&l["* OK [UNSEEN ".len()..l.find(']').unwrap()],
)?;
} else if l.starts_with("* OK [UIDVALIDITY ") { } else if l.starts_with("* OK [UIDVALIDITY ") {
ret.uidvalidity = ret.uidvalidity =
usize::from_str(&l["* OK [UIDVALIDITY ".len()..l.find(']').unwrap()])?; UIDVALIDITY::from_str(&l["* OK [UIDVALIDITY ".len()..l.find(']').unwrap()])?;
} else if l.starts_with("* OK [UIDNEXT ") { } else if l.starts_with("* OK [UIDNEXT ") {
ret.uidnext = usize::from_str(&l["* OK [UIDNEXT ".len()..l.find(']').unwrap()])?; ret.uidnext = UID::from_str(&l["* OK [UIDNEXT ".len()..l.find(']').unwrap()])?;
} else if l.starts_with("* OK [PERMANENTFLAGS (") { } else if l.starts_with("* OK [PERMANENTFLAGS (") {
ret.permanentflags = ret.permanentflags =
flags(&l["* OK [PERMANENTFLAGS (".len()..l.find(')').unwrap()]) flags(&l["* OK [PERMANENTFLAGS (".len()..l.find(')').unwrap()])
@ -1392,9 +1391,9 @@ pub fn quoted_or_nil(input: &[u8]) -> IResult<&[u8], Option<Vec<u8>>> {
pub fn uid_fetch_envelopes_response( pub fn uid_fetch_envelopes_response(
input: &[u8], input: &[u8],
) -> IResult<&[u8], Vec<(usize, Option<(Flag, Vec<String>)>, Envelope)>> { ) -> IResult<&[u8], Vec<(UID, Option<(Flag, Vec<String>)>, Envelope)>> {
many0( many0(
|input: &[u8]| -> IResult<&[u8], (usize, Option<(Flag, Vec<String>)>, Envelope)> { |input: &[u8]| -> IResult<&[u8], (UID, Option<(Flag, Vec<String>)>, Envelope)> {
let (input, _) = tag("* ")(input)?; let (input, _) = tag("* ")(input)?;
let (input, _) = take_while(is_digit)(input)?; let (input, _) = take_while(is_digit)(input)?;
let (input, _) = tag(" FETCH (")(input)?; let (input, _) = tag(" FETCH (")(input)?;
@ -1402,7 +1401,7 @@ pub fn uid_fetch_envelopes_response(
preceded( preceded(
alt((tag("UID "), tag(" UID "))), alt((tag("UID "), tag(" UID "))),
map_res(digit1, |s| { map_res(digit1, |s| {
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) UID::from_str(unsafe { std::str::from_utf8_unchecked(s) })
}), }),
), ),
opt(preceded( opt(preceded(
@ -1432,11 +1431,11 @@ pub fn bodystructure_has_attachments(input: &[u8]) -> bool {
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct StatusResponse { pub struct StatusResponse {
pub mailbox: Option<MailboxHash>, pub mailbox: Option<MailboxHash>,
pub messages: Option<usize>, pub messages: Option<ImapNum>,
pub recent: Option<usize>, pub recent: Option<ImapNum>,
pub uidnext: Option<usize>, pub uidnext: Option<UID>,
pub uidvalidity: Option<usize>, pub uidvalidity: Option<UID>,
pub unseen: Option<usize>, pub unseen: Option<ImapNum>,
} }
// status = "STATUS" SP mailbox SP "(" status-att *(SP status-att) ")" // status = "STATUS" SP mailbox SP "(" status-att *(SP status-att) ")"
@ -1451,31 +1450,31 @@ pub fn status_response(input: &[u8]) -> IResult<&[u8], StatusResponse> {
opt(preceded( opt(preceded(
alt((tag("MESSAGES "), tag(" MESSAGES "))), alt((tag("MESSAGES "), tag(" MESSAGES "))),
map_res(digit1, |s| { map_res(digit1, |s| {
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) ImapNum::from_str(unsafe { std::str::from_utf8_unchecked(s) })
}), }),
)), )),
opt(preceded( opt(preceded(
alt((tag("RECENT "), tag(" RECENT "))), alt((tag("RECENT "), tag(" RECENT "))),
map_res(digit1, |s| { map_res(digit1, |s| {
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) ImapNum::from_str(unsafe { std::str::from_utf8_unchecked(s) })
}), }),
)), )),
opt(preceded( opt(preceded(
alt((tag("UIDNEXT "), tag(" UIDNEXT "))), alt((tag("UIDNEXT "), tag(" UIDNEXT "))),
map_res(digit1, |s| { map_res(digit1, |s| {
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) UID::from_str(unsafe { std::str::from_utf8_unchecked(s) })
}), }),
)), )),
opt(preceded( opt(preceded(
alt((tag("UIDVALIDITY "), tag(" UIDVALIDITY "))), alt((tag("UIDVALIDITY "), tag(" UIDVALIDITY "))),
map_res(digit1, |s| { map_res(digit1, |s| {
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) UIDVALIDITY::from_str(unsafe { std::str::from_utf8_unchecked(s) })
}), }),
)), )),
opt(preceded( opt(preceded(
alt((tag("UNSEEN "), tag(" UNSEEN "))), alt((tag("UNSEEN "), tag(" UNSEEN "))),
map_res(digit1, |s| { map_res(digit1, |s| {
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) ImapNum::from_str(unsafe { std::str::from_utf8_unchecked(s) })
}), }),
)), )),
))(input)?; ))(input)?;

View File

@ -30,6 +30,7 @@ use crate::backends::{
}; };
use crate::email::Envelope; use crate::email::Envelope;
use crate::error::*; use crate::error::*;
use std::convert::TryInto;
use std::time::Instant; use std::time::Instant;
impl ImapConnection { impl ImapConnection {
@ -58,7 +59,10 @@ impl ImapConnection {
let mailbox = let mailbox =
std::clone::Clone::clone(&self.uid_store.mailboxes.lock().await[&mailbox_hash]); std::clone::Clone::clone(&self.uid_store.mailboxes.lock().await[&mailbox_hash]);
let mut cache_handle = super::cache::CacheHandle::get(self.uid_store.clone())?; #[cfg(not(feature = "sqlite3"))]
let mut cache_handle = super::cache::DefaultCache::get(self.uid_store.clone())?;
#[cfg(feature = "sqlite3")]
let mut cache_handle = super::cache::Sqlite3Cache::get(self.uid_store.clone())?;
let mut response = String::with_capacity(8 * 1024); let mut response = String::with_capacity(8 * 1024);
let untagged_response = let untagged_response =
match super::protocol_parser::untagged_responses(line).map(|(_, v, _)| v) { match super::protocol_parser::untagged_responses(line).map(|(_, v, _)| v) {
@ -79,7 +83,7 @@ impl ImapConnection {
.lock() .lock()
.unwrap() .unwrap()
.get(&mailbox_hash) .get(&mailbox_hash)
.map(|i| i.len() < n) .map(|i| i.len() < n.try_into().unwrap())
.unwrap_or(true) .unwrap_or(true)
{ {
debug!( debug!(
@ -96,7 +100,7 @@ impl ImapConnection {
.unwrap() .unwrap()
.entry(mailbox_hash) .entry(mailbox_hash)
.or_default() .or_default()
.remove(n); .remove(n.try_into().unwrap());
debug!("expunge {}, UID = {}", n, deleted_uid); debug!("expunge {}, UID = {}", n, deleted_uid);
let deleted_hash: crate::email::EnvelopeHash = match self let deleted_hash: crate::email::EnvelopeHash = match self
.uid_store .uid_store
@ -121,7 +125,9 @@ impl ImapConnection {
kind: Remove(deleted_hash), kind: Remove(deleted_hash),
}, },
)]; )];
cache_handle.update(mailbox_hash, &event)?; if self.uid_store.keep_offline_cache {
cache_handle.update(mailbox_hash, &event)?;
}
self.add_refresh_event(std::mem::replace( self.add_refresh_event(std::mem::replace(
&mut event[0].1, &mut event[0].1,
RefreshEvent { RefreshEvent {
@ -206,7 +212,9 @@ impl ImapConnection {
kind: Create(Box::new(env)), kind: Create(Box::new(env)),
}, },
)]; )];
cache_handle.update(mailbox_hash, &event)?; if self.uid_store.keep_offline_cache {
cache_handle.update(mailbox_hash, &event)?;
}
self.add_refresh_event(std::mem::replace( self.add_refresh_event(std::mem::replace(
&mut event[0].1, &mut event[0].1,
RefreshEvent { RefreshEvent {
@ -308,7 +316,9 @@ impl ImapConnection {
kind: Create(Box::new(env)), kind: Create(Box::new(env)),
}, },
)]; )];
cache_handle.update(mailbox_hash, &event)?; if self.uid_store.keep_offline_cache {
cache_handle.update(mailbox_hash, &event)?;
}
self.add_refresh_event(std::mem::replace( self.add_refresh_event(std::mem::replace(
&mut event[0].1, &mut event[0].1,
RefreshEvent { RefreshEvent {
@ -425,7 +435,9 @@ impl ImapConnection {
kind: NewFlags(env_hash, flags), kind: NewFlags(env_hash, flags),
}, },
)]; )];
cache_handle.update(mailbox_hash, &event)?; if self.uid_store.keep_offline_cache {
cache_handle.update(mailbox_hash, &event)?;
}
self.add_refresh_event(std::mem::replace( self.add_refresh_event(std::mem::replace(
&mut event[0].1, &mut event[0].1,
RefreshEvent { RefreshEvent {

View File

@ -83,12 +83,13 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
if let Some(v) = uidvalidities.get(&mailbox_hash) { if let Some(v) = uidvalidities.get(&mailbox_hash) {
if *v != select_response.uidvalidity { if *v != select_response.uidvalidity {
let cache_handle = cache::CacheHandle::get(uid_store.clone())?; if uid_store.keep_offline_cache {
cache_handle.clear( #[cfg(not(feature = "sqlite3"))]
mailbox_hash, let mut cache_handle = super::cache::DefaultCache::get(uid_store.clone())?;
select_response.uidvalidity, #[cfg(feature = "sqlite3")]
select_response.highestmodseq.and_then(|i| i.ok()), let mut cache_handle = super::cache::Sqlite3Cache::get(uid_store.clone())?;
)?; cache_handle.clear(mailbox_hash, &select_response)?;
}
conn.add_refresh_event(RefreshEvent { conn.add_refresh_event(RefreshEvent {
account_hash: uid_store.account_hash, account_hash: uid_store.account_hash,
mailbox_hash, mailbox_hash,
@ -149,6 +150,7 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
conn.examine_mailbox(mailbox_hash, &mut response, false) conn.examine_mailbox(mailbox_hash, &mut response, false)
.await?; .await?;
for l in to_str!(&line).split_rn() { for l in to_str!(&line).split_rn() {
debug!("process_untagged {:?}", &l);
conn.process_untagged(l).await?; conn.process_untagged(l).await?;
} }
} }
@ -185,6 +187,10 @@ pub async fn examine_updates(
}); });
} }
} else { } else {
#[cfg(not(feature = "sqlite3"))]
let mut cache_handle = super::cache::DefaultCache::get(uid_store.clone())?;
#[cfg(feature = "sqlite3")]
let mut cache_handle = super::cache::Sqlite3Cache::get(uid_store.clone())?;
let mut response = String::with_capacity(8 * 1024); let mut response = String::with_capacity(8 * 1024);
let select_response = conn let select_response = conn
.examine_mailbox(mailbox_hash, &mut response, true) .examine_mailbox(mailbox_hash, &mut response, true)
@ -197,12 +203,9 @@ pub async fn examine_updates(
if let Some(v) = uidvalidities.get(&mailbox_hash) { if let Some(v) = uidvalidities.get(&mailbox_hash) {
if *v != select_response.uidvalidity { if *v != select_response.uidvalidity {
let cache_handle = cache::CacheHandle::get(uid_store.clone())?; if uid_store.keep_offline_cache {
cache_handle.clear( cache_handle.clear(mailbox_hash, &select_response)?;
mailbox_hash, }
select_response.uidvalidity,
select_response.highestmodseq.and_then(|i| i.ok()),
)?;
conn.add_refresh_event(RefreshEvent { conn.add_refresh_event(RefreshEvent {
account_hash: uid_store.account_hash, account_hash: uid_store.account_hash,
mailbox_hash, mailbox_hash,
@ -219,7 +222,6 @@ pub async fn examine_updates(
uidvalidities.insert(mailbox_hash, select_response.uidvalidity); uidvalidities.insert(mailbox_hash, select_response.uidvalidity);
} }
} }
let mut cache_handle = cache::CacheHandle::get(uid_store.clone())?;
if debug!(select_response.recent > 0) { if debug!(select_response.recent > 0) {
/* UID SEARCH RECENT */ /* UID SEARCH RECENT */
conn.send_command(b"UID SEARCH RECENT").await?; conn.send_command(b"UID SEARCH RECENT").await?;

View File

@ -38,6 +38,7 @@ pub type Result<T> = result::Result<T, MeliError>;
pub enum ErrorKind { pub enum ErrorKind {
None, None,
Authentication, Authentication,
Bug,
Network, Network,
Timeout, Timeout,
} }
@ -170,6 +171,19 @@ impl fmt::Display for MeliError {
if let Some(source) = self.source.as_ref() { if let Some(source) = self.source.as_ref() {
write!(f, "\nCaused by: {}", source)?; write!(f, "\nCaused by: {}", source)?;
} }
if self.kind != ErrorKind::None {
write!(
f,
"\nKind: {}",
match self.kind {
ErrorKind::None => "None",
ErrorKind::Authentication => "Authentication",
ErrorKind::Bug => "Bug, please report this!",
ErrorKind::Network => "Network",
ErrorKind::Timeout => "Timeout",
}
)?;
}
Ok(()) Ok(())
} }
} }