2020-06-07 14:00:13 +03:00
/*
* 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/>.
* /
2020-08-25 12:49:31 +03:00
use super ::* ;
mod sync ;
2020-06-07 14:00:13 +03:00
use crate ::{
2020-08-25 12:49:31 +03:00
backends ::MailboxHash ,
email ::{ Envelope , EnvelopeHash } ,
2020-06-07 14:00:13 +03:00
error ::* ,
} ;
2020-08-25 12:49:31 +03:00
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 mailbox_hash : MailboxHash ,
pub modsequence : Option < ModSequence > ,
}
pub struct CacheHandle {
#[ cfg(feature = " sqlite3 " ) ]
connection : crate ::sqlite3 ::Connection ,
uid_store : Arc < UIDStore > ,
}
2020-06-07 14:00:13 +03:00
#[ cfg(feature = " sqlite3 " ) ]
pub use sqlite3_m ::* ;
#[ cfg(feature = " sqlite3 " ) ]
mod sqlite3_m {
use super ::* ;
2020-08-25 12:49:31 +03:00
use crate ::sqlite3 ::rusqlite ::types ::{
FromSql , FromSqlError , FromSqlResult , ToSql , ToSqlOutput ,
} ;
use crate ::sqlite3 ::{ self , DatabaseDescription } ;
const DB_DESCRIPTION : DatabaseDescription = DatabaseDescription {
name : " header_cache.db " ,
init_script : Some ( " PRAGMA foreign_keys = true;
2020-06-07 14:00:13 +03:00
PRAGMA encoding = ' UTF - 8 ' ;
CREATE TABLE IF NOT EXISTS envelopes (
mailbox_hash INTEGER ,
uid INTEGER ,
2020-08-25 12:49:31 +03:00
modsequence INTEGER ,
envelope BLOB NOT NULL ,
PRIMARY KEY ( mailbox_hash , uid ) ,
FOREIGN KEY ( mailbox_hash ) REFERENCES uidvalidity ( mailbox_hash ) ON DELETE CASCADE
2020-06-07 14:00:13 +03:00
) ;
CREATE TABLE IF NOT EXISTS uidvalidity (
uid INTEGER UNIQUE ,
mailbox_hash INTEGER UNIQUE ,
2020-08-25 12:49:31 +03:00
highestmodseq INTEGER ,
2020-06-07 14:00:13 +03:00
PRIMARY KEY ( mailbox_hash , uid )
) ;
2020-08-25 12:49:31 +03:00
CREATE INDEX IF NOT EXISTS envelope_idx ON envelopes ( mailbox_hash ) ;
CREATE INDEX IF NOT EXISTS uidvalidity_idx ON uidvalidity ( 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 CacheHandle {
pub fn get ( uid_store : Arc < UIDStore > ) -> Result < Self > {
Ok ( Self {
connection : sqlite3 ::open_or_create_db (
& DB_DESCRIPTION ,
Some ( uid_store . account_name . as_str ( ) ) ,
) ? ,
uid_store ,
} )
}
pub fn mailbox_state (
& self ,
mailbox_hash : MailboxHash ,
) -> Result < Option < ( UID , Option < ModSequence > ) > > {
let mut stmt = self
. connection
. prepare ( " SELECT uid, highestmodseq FROM uidvalidity WHERE mailbox_hash = ?1; " ) ? ;
let mut ret = stmt . query_map ( sqlite3 ::params! [ mailbox_hash as i64 ] , | row | {
Ok ( ( row . get ( 0 ) . map ( | u : i64 | u as usize ) ? , row . get ( 1 ) ? ) )
} ) ? ;
if let Some ( row_res ) = ret . next ( ) {
Ok ( Some ( row_res ? ) )
} else {
Ok ( None )
}
}
pub fn clear (
& self ,
mailbox_hash : MailboxHash ,
new_uidvalidity : UID ,
highestmodseq : Option < ModSequence > ,
) -> Result < ( ) > {
debug! ( " clear mailbox_hash {} " , mailbox_hash ) ;
debug! ( new_uidvalidity ) ;
debug! ( & highestmodseq ) ;
self . connection
. execute (
" DELETE FROM uidvalidity WHERE mailbox_hash = ?1 " ,
sqlite3 ::params! [ mailbox_hash as i64 ] ,
2020-06-07 14:00:13 +03:00
)
2020-08-25 12:49:31 +03:00
. chain_err_summary ( | | {
format! (
" Could not clear cache of mailbox {} account {} " ,
mailbox_hash , self . uid_store . account_name
)
} ) ? ;
self . connection . execute (
" INSERT OR IGNORE INTO uidvalidity (uid, highestmodseq, mailbox_hash) VALUES (?1, ?2, ?3) " ,
sqlite3 ::params! [ new_uidvalidity as i64 , highestmodseq , mailbox_hash as i64 ] ,
2020-06-07 14:00:13 +03:00
)
. chain_err_summary ( | | {
format! (
2020-08-25 12:49:31 +03:00
" Could not insert uidvalidity {} in header_cache of account {} " ,
new_uidvalidity , self . uid_store . account_name
2020-06-07 14:00:13 +03:00
)
2020-08-25 12:49:31 +03:00
} ) ? ;
Ok ( ( ) )
}
pub fn envelopes ( & 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 | {
2020-06-07 14:00:13 +03:00
Ok ( (
2020-08-25 12:49:31 +03:00
row . get ( 0 ) . map ( | i : i64 | i as usize ) ? ,
row . get ( 1 ) ? ,
row . get ( 2 ) ? ,
2020-06-07 14:00:13 +03:00
) )
2020-08-25 12:49:31 +03:00
} ) ?
. 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 ,
mailbox_hash ,
modsequence : modseq ,
} ,
) ;
}
self . uid_store
. max_uids
. lock ( )
. unwrap ( )
. insert ( mailbox_hash , max_uid ) ;
Ok ( Some ( env_hashes ) )
}
2020-06-07 14:00:13 +03:00
2020-08-25 12:49:31 +03:00
pub fn insert_envelopes (
& mut self ,
mailbox_hash : MailboxHash ,
fetches : & [ FetchResponse < '_ > ] ,
) -> Result < ( ) > {
debug! (
" insert_envelopes mailbox_hash {} len {} " ,
mailbox_hash ,
fetches . len ( )
) ;
if self . mailbox_state ( mailbox_hash ) ? . is_none ( ) {
debug! ( self . mailbox_state ( mailbox_hash ) ? . is_none ( ) ) ;
let uidvalidity = self
. uid_store
. uidvalidity
. lock ( )
. unwrap ( )
. get ( & mailbox_hash )
. cloned ( ) ;
let highestmodseq = self
. uid_store
. highestmodseqs
. lock ( )
. unwrap ( )
. get ( & mailbox_hash )
. cloned ( ) ;
debug! ( & uidvalidity ) ;
debug! ( & highestmodseq ) ;
if let Some ( uidvalidity ) = uidvalidity {
debug! ( self . clear (
mailbox_hash ,
uidvalidity ,
highestmodseq . and_then ( | v | v . ok ( ) ) ,
) ) ? ;
}
}
let Self {
ref mut connection ,
ref uid_store ,
} = 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
{
tx . execute (
" INSERT OR REPLACE INTO envelopes (uid, mailbox_hash, modsequence, envelope) VALUES (?1, ?2, ?3, ?4) " ,
sqlite3 ::params! [ * uid as i64 , mailbox_hash as i64 , modseq , & envelope ] ,
) . chain_err_summary ( | | format! ( " Could not insert envelope {} {} in header_cache of account {} " , envelope . message_id ( ) , envelope . hash ( ) , uid_store . account_name ) ) ? ;
}
}
tx . commit ( ) ? ;
Ok ( ( ) )
2020-06-07 14:00:13 +03:00
}
}
}
#[ cfg(not(feature = " sqlite3 " )) ]
pub use filesystem_m ::* ;
#[ cfg(not(feature = " sqlite3 " )) ]
mod filesystem_m {
use super ::* ;
2020-08-25 12:49:31 +03:00
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 ( ( ) )
}
pub fn envelopes ( & self , _mailbox_hash : MailboxHash ) -> Result < Option < Vec < EnvelopeHash > > > {
Ok ( None )
}
pub fn insert_envelopes (
& mut self ,
_mailbox_hash : MailboxHash ,
_fetches : & [ FetchResponse < '_ > ] ,
) -> Result < ( ) > {
Ok ( ( ) )
}
2020-06-07 14:00:13 +03:00
}
2020-08-25 12:49:31 +03:00
}
2020-06-07 14:00:13 +03:00
2020-08-25 12:49:31 +03:00
pub ( super ) async fn fetch_cached_envs ( state : & mut FetchState ) -> Result < Option < Vec < Envelope > > > {
let FetchState {
stage : _ ,
ref mut connection ,
mailbox_hash ,
can_create_flags : _ ,
ref uid_store ,
} = 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 ) ) ,
}
2020-06-07 14:00:13 +03:00
}
}