melib/imap: add ImapCache trait
parent
e878c50af5
commit
b4fe34eacf
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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>),
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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)?;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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?;
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue