784 lines
29 KiB
Rust
784 lines
29 KiB
Rust
/*
|
|
* meli - imap melib
|
|
*
|
|
* Copyright 2020 Manos Pitsidianakis
|
|
*
|
|
* This file is part of meli.
|
|
*
|
|
* meli is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* meli is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
use super::*;
|
|
mod sync;
|
|
use crate::{
|
|
backends::MailboxHash,
|
|
email::{Envelope, EnvelopeHash},
|
|
error::*,
|
|
};
|
|
use std::convert::TryFrom;
|
|
|
|
#[derive(Debug, PartialEq, Hash, Eq, Ord, PartialOrd, Copy, Clone)]
|
|
pub struct ModSequence(pub std::num::NonZeroU64);
|
|
|
|
impl TryFrom<i64> for ModSequence {
|
|
type Error = ();
|
|
fn try_from(val: i64) -> std::result::Result<ModSequence, ()> {
|
|
std::num::NonZeroU64::new(val as u64)
|
|
.map(|u| Ok(ModSequence(u)))
|
|
.unwrap_or(Err(()))
|
|
}
|
|
}
|
|
|
|
impl core::fmt::Display for ModSequence {
|
|
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
|
write!(fmt, "{}", &self.0)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct CachedEnvelope {
|
|
pub inner: Envelope,
|
|
pub uid: UID,
|
|
pub mailbox_hash: MailboxHash,
|
|
pub modsequence: Option<ModSequence>,
|
|
}
|
|
|
|
pub trait ImapCache: Send + core::fmt::Debug {
|
|
fn reset(&mut self) -> Result<()>;
|
|
fn mailbox_state(&mut self, mailbox_hash: MailboxHash) -> Result<Option<()>>;
|
|
|
|
fn find_envelope(
|
|
&mut self,
|
|
identifier: std::result::Result<UID, EnvelopeHash>,
|
|
mailbox_hash: MailboxHash,
|
|
) -> Result<Option<CachedEnvelope>>;
|
|
|
|
fn update(
|
|
&mut self,
|
|
mailbox_hash: MailboxHash,
|
|
refresh_events: &[(UID, RefreshEvent)],
|
|
) -> Result<()>;
|
|
|
|
fn update_mailbox(
|
|
&mut self,
|
|
mailbox_hash: MailboxHash,
|
|
select_response: &SelectResponse,
|
|
) -> 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")]
|
|
pub use sqlite3_m::*;
|
|
|
|
#[cfg(feature = "sqlite3")]
|
|
mod sqlite3_m {
|
|
use super::*;
|
|
use crate::sqlite3::rusqlite::types::{
|
|
FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput,
|
|
};
|
|
use crate::sqlite3::{self, DatabaseDescription};
|
|
|
|
type Sqlite3UID = i32;
|
|
|
|
#[derive(Debug)]
|
|
pub struct Sqlite3Cache {
|
|
connection: crate::sqlite3::Connection,
|
|
loaded_mailboxes: BTreeSet<MailboxHash>,
|
|
uid_store: Arc<UIDStore>,
|
|
}
|
|
|
|
const DB_DESCRIPTION: DatabaseDescription = DatabaseDescription {
|
|
name: "header_cache.db",
|
|
init_script: Some(
|
|
"PRAGMA foreign_keys = true;
|
|
PRAGMA encoding = 'UTF-8';
|
|
|
|
CREATE TABLE IF NOT EXISTS envelopes (
|
|
hash INTEGER NOT NULL,
|
|
mailbox_hash INTEGER NOT NULL,
|
|
uid INTEGER NOT NULL,
|
|
modsequence INTEGER,
|
|
rfc822 BLOB,
|
|
envelope BLOB NOT NULL,
|
|
PRIMARY KEY (mailbox_hash, uid),
|
|
FOREIGN KEY (mailbox_hash) REFERENCES mailbox(mailbox_hash) ON DELETE CASCADE
|
|
);
|
|
CREATE TABLE IF NOT EXISTS mailbox (
|
|
mailbox_hash INTEGER UNIQUE,
|
|
uidvalidity INTEGER,
|
|
flags BLOB NOT NULL,
|
|
highestmodseq INTEGER,
|
|
PRIMARY KEY (mailbox_hash)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS envelope_uid_idx ON envelopes(mailbox_hash, uid);
|
|
CREATE INDEX IF NOT EXISTS envelope_idx ON envelopes(hash);
|
|
CREATE INDEX IF NOT EXISTS mailbox_idx ON mailbox(mailbox_hash);",
|
|
),
|
|
version: 1,
|
|
};
|
|
|
|
impl ToSql for ModSequence {
|
|
fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
|
|
Ok(ToSqlOutput::from(self.0.get() as i64))
|
|
}
|
|
}
|
|
|
|
impl FromSql for ModSequence {
|
|
fn column_result(value: rusqlite::types::ValueRef) -> FromSqlResult<Self> {
|
|
let i: i64 = FromSql::column_result(value)?;
|
|
if i == 0 {
|
|
return Err(FromSqlError::OutOfRange(0));
|
|
}
|
|
Ok(ModSequence::try_from(i).unwrap())
|
|
}
|
|
}
|
|
|
|
impl Sqlite3Cache {
|
|
pub fn get(uid_store: Arc<UIDStore>) -> Result<Box<dyn ImapCache>> {
|
|
Ok(Box::new(Self {
|
|
connection: sqlite3::open_or_create_db(
|
|
&DB_DESCRIPTION,
|
|
Some(uid_store.account_name.as_str()),
|
|
)?,
|
|
loaded_mailboxes: BTreeSet::default(),
|
|
uid_store,
|
|
}))
|
|
}
|
|
|
|
fn max_uid(&self, mailbox_hash: MailboxHash) -> Result<UID> {
|
|
let mut stmt = self
|
|
.connection
|
|
.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 reset(&mut self) -> Result<()> {
|
|
sqlite3::reset_db(&DB_DESCRIPTION, Some(self.uid_store.account_name.as_str()))
|
|
}
|
|
|
|
fn mailbox_state(&mut self, mailbox_hash: MailboxHash) -> Result<Option<()>> {
|
|
if self.loaded_mailboxes.contains(&mailbox_hash) {
|
|
return Ok(Some(()));
|
|
}
|
|
debug!("loading mailbox state {} from cache", mailbox_hash);
|
|
let mut stmt = self.connection.prepare(
|
|
"SELECT uidvalidity, flags, highestmodseq FROM mailbox WHERE mailbox_hash = ?1;",
|
|
)?;
|
|
|
|
let mut ret = stmt.query_map(sqlite3::params![mailbox_hash as i64], |row| {
|
|
Ok((
|
|
row.get(0).map(|u: Sqlite3UID| u as UID)?,
|
|
row.get(1)?,
|
|
row.get(2)?,
|
|
))
|
|
})?;
|
|
if let Some(v) = ret.next() {
|
|
let (uidvalidity, flags, highestmodseq): (
|
|
UIDVALIDITY,
|
|
Vec<u8>,
|
|
Option<ModSequence>,
|
|
) = v?;
|
|
debug!(
|
|
"mailbox state {} in cache uidvalidity {}",
|
|
mailbox_hash, uidvalidity
|
|
);
|
|
debug!(
|
|
"mailbox state {} in cache highestmodseq {:?}",
|
|
mailbox_hash, &highestmodseq
|
|
);
|
|
debug!(
|
|
"mailbox state {} inserting flags: {:?}",
|
|
mailbox_hash,
|
|
to_str!(&flags)
|
|
);
|
|
self.uid_store
|
|
.highestmodseqs
|
|
.lock()
|
|
.unwrap()
|
|
.entry(mailbox_hash)
|
|
.and_modify(|entry| *entry = highestmodseq.ok_or(()))
|
|
.or_insert(highestmodseq.ok_or(()));
|
|
self.uid_store
|
|
.uidvalidity
|
|
.lock()
|
|
.unwrap()
|
|
.entry(mailbox_hash)
|
|
.and_modify(|entry| *entry = uidvalidity)
|
|
.or_insert(uidvalidity);
|
|
let mut tag_lck = self.uid_store.tag_index.write().unwrap();
|
|
for f in to_str!(&flags).split('\0') {
|
|
let hash = tag_hash!(f);
|
|
//debug!("hash {} flag {}", hash, &f);
|
|
if !tag_lck.contains_key(&hash) {
|
|
tag_lck.insert(hash, f.to_string());
|
|
}
|
|
}
|
|
self.loaded_mailboxes.insert(mailbox_hash);
|
|
Ok(Some(()))
|
|
} else {
|
|
debug!("mailbox state {} not in cache", mailbox_hash);
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
fn clear(
|
|
&mut self,
|
|
mailbox_hash: MailboxHash,
|
|
select_response: &SelectResponse,
|
|
) -> Result<()> {
|
|
debug!("clear mailbox_hash {} {:?}", mailbox_hash, select_response);
|
|
self.loaded_mailboxes.remove(&mailbox_hash);
|
|
self.connection
|
|
.execute(
|
|
"DELETE FROM mailbox WHERE mailbox_hash = ?1",
|
|
sqlite3::params![mailbox_hash as i64],
|
|
)
|
|
.chain_err_summary(|| {
|
|
format!(
|
|
"Could not clear cache of mailbox {} account {}",
|
|
mailbox_hash, self.uid_store.account_name
|
|
)
|
|
})?;
|
|
|
|
if let Some(Ok(highestmodseq)) = select_response.highestmodseq {
|
|
self.connection.execute(
|
|
"INSERT OR IGNORE INTO mailbox (uidvalidity, flags, highestmodseq, mailbox_hash) VALUES (?1, ?2, ?3, ?4)",
|
|
sqlite3::params![select_response.uidvalidity as Sqlite3UID, select_response.flags.1.iter().map(|s| s.as_str()).collect::<Vec<&str>>().join("\0").as_bytes(), highestmodseq, mailbox_hash as i64],
|
|
)
|
|
.chain_err_summary(|| {
|
|
format!(
|
|
"Could not insert uidvalidity {} in header_cache of account {}",
|
|
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(())
|
|
}
|
|
|
|
fn update_mailbox(
|
|
&mut self,
|
|
mailbox_hash: MailboxHash,
|
|
select_response: &SelectResponse,
|
|
) -> Result<()> {
|
|
if self.mailbox_state(mailbox_hash)?.is_none() {
|
|
return self.clear(mailbox_hash, select_response);
|
|
}
|
|
|
|
if let Some(Ok(highestmodseq)) = select_response.highestmodseq {
|
|
self.connection
|
|
.execute(
|
|
"UPDATE mailbox SET flags=?1, highestmodseq =?2 where mailbox_hash = ?3;",
|
|
sqlite3::params![
|
|
select_response
|
|
.flags
|
|
.1
|
|
.iter()
|
|
.map(|s| s.as_str())
|
|
.collect::<Vec<&str>>()
|
|
.join("\0")
|
|
.as_bytes(),
|
|
highestmodseq,
|
|
mailbox_hash as i64
|
|
],
|
|
)
|
|
.chain_err_summary(|| {
|
|
format!(
|
|
"Could not update mailbox {} in header_cache of account {}",
|
|
mailbox_hash, self.uid_store.account_name
|
|
)
|
|
})?;
|
|
} else {
|
|
self.connection
|
|
.execute(
|
|
"UPDATE mailbox SET flags=?1 where mailbox_hash = ?2;",
|
|
sqlite3::params![
|
|
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 update mailbox {} in header_cache of account {}",
|
|
mailbox_hash, self.uid_store.account_name
|
|
)
|
|
})?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn envelopes(&mut self, mailbox_hash: MailboxHash) -> Result<Option<Vec<EnvelopeHash>>> {
|
|
debug!("envelopes mailbox_hash {}", mailbox_hash);
|
|
if debug!(self.mailbox_state(mailbox_hash)?.is_none()) {
|
|
return Ok(None);
|
|
}
|
|
|
|
let mut stmt = self.connection.prepare(
|
|
"SELECT uid, envelope, modsequence FROM envelopes WHERE mailbox_hash = ?1;",
|
|
)?;
|
|
|
|
let ret: Vec<(UID, Envelope, Option<ModSequence>)> = stmt
|
|
.query_map(sqlite3::params![mailbox_hash as i64], |row| {
|
|
Ok((
|
|
row.get(0).map(|i: Sqlite3UID| i as UID)?,
|
|
row.get(1)?,
|
|
row.get(2)?,
|
|
))
|
|
})?
|
|
.collect::<std::result::Result<_, _>>()?;
|
|
let mut max_uid = 0;
|
|
let mut env_lck = self.uid_store.envelopes.lock().unwrap();
|
|
let mut hash_index_lck = self.uid_store.hash_index.lock().unwrap();
|
|
let mut uid_index_lck = self.uid_store.uid_index.lock().unwrap();
|
|
let mut env_hashes = Vec::with_capacity(ret.len());
|
|
for (uid, env, modseq) in ret {
|
|
env_hashes.push(env.hash());
|
|
max_uid = std::cmp::max(max_uid, uid);
|
|
hash_index_lck.insert(env.hash(), (uid, mailbox_hash));
|
|
uid_index_lck.insert((mailbox_hash, uid), env.hash());
|
|
env_lck.insert(
|
|
env.hash(),
|
|
CachedEnvelope {
|
|
inner: env,
|
|
uid,
|
|
mailbox_hash,
|
|
modsequence: modseq,
|
|
},
|
|
);
|
|
}
|
|
self.uid_store
|
|
.max_uids
|
|
.lock()
|
|
.unwrap()
|
|
.insert(mailbox_hash, max_uid);
|
|
Ok(Some(env_hashes))
|
|
}
|
|
|
|
fn insert_envelopes(
|
|
&mut self,
|
|
mailbox_hash: MailboxHash,
|
|
fetches: &[FetchResponse<'_>],
|
|
) -> Result<()> {
|
|
debug!(
|
|
"insert_envelopes mailbox_hash {} len {}",
|
|
mailbox_hash,
|
|
fetches.len()
|
|
);
|
|
let mut max_uid = self
|
|
.uid_store
|
|
.max_uids
|
|
.lock()
|
|
.unwrap()
|
|
.get(&mailbox_hash)
|
|
.cloned()
|
|
.unwrap_or_default();
|
|
if self.mailbox_state(mailbox_hash)?.is_none() {
|
|
debug!(self.mailbox_state(mailbox_hash)?.is_none());
|
|
return Err(MeliError::new("Mailbox is not in cache").set_kind(ErrorKind::Bug));
|
|
}
|
|
let Self {
|
|
ref mut connection,
|
|
ref uid_store,
|
|
loaded_mailboxes: _,
|
|
} = self;
|
|
let tx = connection.transaction()?;
|
|
for item in fetches {
|
|
if let FetchResponse {
|
|
uid: Some(uid),
|
|
message_sequence_number: _,
|
|
modseq,
|
|
flags: _,
|
|
body: _,
|
|
envelope: Some(envelope),
|
|
} = item
|
|
{
|
|
max_uid = std::cmp::max(max_uid, *uid);
|
|
tx.execute(
|
|
"INSERT OR REPLACE INTO envelopes (hash, uid, mailbox_hash, modsequence, envelope) VALUES (?1, ?2, ?3, ?4, ?5)",
|
|
sqlite3::params![envelope.hash() as i64, *uid as Sqlite3UID, mailbox_hash as i64, modseq, &envelope],
|
|
).chain_err_summary(|| format!("Could not insert envelope {} {} in header_cache of account {}", envelope.message_id(), envelope.hash(), uid_store.account_name))?;
|
|
}
|
|
}
|
|
tx.commit()?;
|
|
self.uid_store
|
|
.max_uids
|
|
.lock()
|
|
.unwrap()
|
|
.insert(mailbox_hash, max_uid);
|
|
Ok(())
|
|
}
|
|
|
|
fn update(
|
|
&mut self,
|
|
mailbox_hash: MailboxHash,
|
|
refresh_events: &[(UID, RefreshEvent)],
|
|
) -> Result<()> {
|
|
debug!(
|
|
"update with refresh_events mailbox_hash {} len {}",
|
|
mailbox_hash,
|
|
refresh_events.len()
|
|
);
|
|
if self.mailbox_state(mailbox_hash)?.is_none() {
|
|
debug!(self.mailbox_state(mailbox_hash)?.is_none());
|
|
return Err(MeliError::new("Mailbox is not in cache").set_kind(ErrorKind::Bug));
|
|
}
|
|
let Self {
|
|
ref mut connection,
|
|
ref uid_store,
|
|
loaded_mailboxes: _,
|
|
} = self;
|
|
let tx = connection.transaction()?;
|
|
let mut hash_index_lck = uid_store.hash_index.lock().unwrap();
|
|
for (uid, event) in refresh_events {
|
|
match debug!(&event.kind) {
|
|
RefreshEventKind::Remove(env_hash) => {
|
|
hash_index_lck.remove(&env_hash);
|
|
tx.execute(
|
|
"DELETE FROM envelopes WHERE mailbox_hash = ?1 AND uid = ?2;",
|
|
sqlite3::params![mailbox_hash as i64, *uid as Sqlite3UID],
|
|
)
|
|
.chain_err_summary(|| {
|
|
format!(
|
|
"Could not remove envelope {} uid {} from mailbox {} account {}",
|
|
env_hash, *uid, mailbox_hash, uid_store.account_name
|
|
)
|
|
})?;
|
|
}
|
|
RefreshEventKind::NewFlags(env_hash, (flags, tags)) => {
|
|
let mut stmt = tx.prepare(
|
|
"SELECT envelope FROM envelopes WHERE mailbox_hash = ?1 AND uid = ?2;",
|
|
)?;
|
|
|
|
let mut ret: Vec<Envelope> = stmt
|
|
.query_map(
|
|
sqlite3::params![mailbox_hash as i64, *uid as Sqlite3UID],
|
|
|row| Ok(row.get(0)?),
|
|
)?
|
|
.collect::<std::result::Result<_, _>>()?;
|
|
if let Some(mut env) = ret.pop() {
|
|
env.set_flags(*flags);
|
|
env.labels_mut().clear();
|
|
env.labels_mut().extend(tags.iter().map(|t| tag_hash!(t)));
|
|
tx.execute(
|
|
"UPDATE envelopes SET envelope = ?1 WHERE mailbox_hash = ?2 AND uid = ?3;",
|
|
sqlite3::params![&env, mailbox_hash as i64, *uid as Sqlite3UID],
|
|
)
|
|
.chain_err_summary(|| {
|
|
format!(
|
|
"Could not update envelope {} uid {} from mailbox {} account {}",
|
|
env_hash, *uid, mailbox_hash, uid_store.account_name
|
|
)
|
|
})?;
|
|
uid_store
|
|
.envelopes
|
|
.lock()
|
|
.unwrap()
|
|
.entry(*env_hash)
|
|
.and_modify(|entry| {
|
|
entry.inner = env;
|
|
});
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
tx.commit()?;
|
|
let new_max_uid = self.max_uid(mailbox_hash).unwrap_or(0);
|
|
self.uid_store
|
|
.max_uids
|
|
.lock()
|
|
.unwrap()
|
|
.insert(mailbox_hash, new_max_uid);
|
|
Ok(())
|
|
}
|
|
|
|
fn find_envelope(
|
|
&mut self,
|
|
identifier: std::result::Result<UID, EnvelopeHash>,
|
|
mailbox_hash: MailboxHash,
|
|
) -> Result<Option<CachedEnvelope>> {
|
|
let mut ret: Vec<(UID, Envelope, Option<ModSequence>)> = match identifier {
|
|
Ok(uid) => {
|
|
let mut stmt = self.connection.prepare(
|
|
"SELECT uid, envelope, modsequence FROM envelopes WHERE mailbox_hash = ?1 AND uid = ?2;",
|
|
)?;
|
|
|
|
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())
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) async fn fetch_cached_envs(state: &mut FetchState) -> Result<Option<Vec<Envelope>>> {
|
|
let FetchState {
|
|
stage: _,
|
|
ref mut connection,
|
|
mailbox_hash,
|
|
ref uid_store,
|
|
cache_handle: _,
|
|
} = state;
|
|
debug!(uid_store.keep_offline_cache);
|
|
let mailbox_hash = *mailbox_hash;
|
|
if !uid_store.keep_offline_cache {
|
|
return Ok(None);
|
|
}
|
|
{
|
|
let mut conn = connection.lock().await;
|
|
match debug!(conn.load_cache(mailbox_hash).await) {
|
|
None => return Ok(None),
|
|
Some(Ok(env_hashes)) => {
|
|
uid_store
|
|
.mailboxes
|
|
.lock()
|
|
.await
|
|
.entry(mailbox_hash)
|
|
.and_modify(|entry| {
|
|
entry
|
|
.exists
|
|
.lock()
|
|
.unwrap()
|
|
.insert_set(env_hashes.iter().cloned().collect());
|
|
let env_lck = uid_store.envelopes.lock().unwrap();
|
|
entry.unseen.lock().unwrap().insert_set(
|
|
env_hashes
|
|
.iter()
|
|
.filter_map(|h| {
|
|
if !env_lck[h].inner.is_seen() {
|
|
Some(*h)
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect(),
|
|
);
|
|
});
|
|
let env_lck = uid_store.envelopes.lock().unwrap();
|
|
|
|
return Ok(Some(
|
|
env_hashes
|
|
.into_iter()
|
|
.filter_map(|env_hash| {
|
|
env_lck.get(&env_hash).map(|c_env| c_env.inner.clone())
|
|
})
|
|
.collect::<Vec<Envelope>>(),
|
|
));
|
|
}
|
|
Some(Err(err)) => return debug!(Err(err)),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(not(feature = "sqlite3"))]
|
|
pub use default_m::*;
|
|
|
|
#[cfg(not(feature = "sqlite3"))]
|
|
mod default_m {
|
|
use super::*;
|
|
#[derive(Debug)]
|
|
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 reset(&mut self) -> Result<()> {
|
|
Err(MeliError::new("melib is not built with any imap cache").set_kind(ErrorKind::Bug))
|
|
}
|
|
|
|
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_mailbox(
|
|
&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 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))
|
|
}
|
|
|
|
fn rfc822(
|
|
&mut self,
|
|
_identifier: std::result::Result<UID, EnvelopeHash>,
|
|
_mailbox_hash: MailboxHash,
|
|
) -> Result<Option<Vec<u8>>> {
|
|
Err(MeliError::new("melib is not built with any imap cache").set_kind(ErrorKind::Bug))
|
|
}
|
|
}
|
|
}
|