diff options
author | Alex Auvolat <alex@adnab.me> | 2022-06-29 15:39:54 +0200 |
---|---|---|
committer | Alex Auvolat <alex@adnab.me> | 2022-06-29 15:39:54 +0200 |
commit | b95028f89e4db7c3158fab3b71ea56a742daba21 (patch) | |
tree | 262a212c7eec8cf26ec70cbb7d12c2264a1e3da2 /src/mail | |
parent | 8b7eb1ca918d26901b0739526341128067ca1cbc (diff) | |
download | aerogramme-b95028f89e4db7c3158fab3b71ea56a742daba21.tar.gz aerogramme-b95028f89e4db7c3158fab3b71ea56a742daba21.zip |
Some refactoring on mailbox structures and views
Diffstat (limited to 'src/mail')
-rw-r--r-- | src/mail/mailbox.rs | 88 | ||||
-rw-r--r-- | src/mail/mod.rs | 7 | ||||
-rw-r--r-- | src/mail/user.rs | 44 |
3 files changed, 83 insertions, 56 deletions
diff --git a/src/mail/mailbox.rs b/src/mail/mailbox.rs index a2d28fb..9e8f0db 100644 --- a/src/mail/mailbox.rs +++ b/src/mail/mailbox.rs @@ -3,6 +3,7 @@ use std::convert::TryFrom; use anyhow::Result; use k2v_client::K2vClient; use rusoto_s3::S3Client; +use tokio::sync::RwLock; use crate::bayou::Bayou; use crate::cryptoblob::Key; @@ -11,16 +12,16 @@ use crate::mail::mail_ident::*; use crate::mail::uidindex::*; use crate::mail::IMF; -pub struct Summary<'a> { +pub struct Summary { pub validity: ImapUidvalidity, pub next: ImapUid, pub exists: u32, pub recent: u32, - pub flags: FlagIter<'a>, - pub unseen: Option<&'a ImapUid>, + pub flags: Vec<String>, + pub unseen: Option<ImapUid>, } -impl std::fmt::Display for Summary<'_> { +impl std::fmt::Display for Summary { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, @@ -30,70 +31,40 @@ impl std::fmt::Display for Summary<'_> { } } -// Non standard but common flags: -// https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml -pub struct Mailbox { - bucket: String, - pub name: String, - key: Key, - - k2v: K2vClient, - s3: S3Client, - - uid_index: Bayou<UidIndex>, - mail_path: String, -} +pub struct Mailbox(RwLock<MailboxInternal>); impl Mailbox { - pub(super) fn new(creds: &Credentials, name: &str) -> Result<Self> { + pub(super) async fn open(creds: &Credentials, name: &str) -> Result<Self> { let index_path = format!("index/{}", name); let mail_path = format!("mail/{}", name); - let uid_index = Bayou::<UidIndex>::new(creds, index_path)?; - Ok(Self { + let mut uid_index = Bayou::<UidIndex>::new(creds, index_path)?; + uid_index.sync().await?; + + Ok(Self(RwLock::new(MailboxInternal { bucket: creds.bucket().to_string(), - name: name.to_string(), // TODO: don't use name field if possible, use mail_path instead key: creds.keys.master.clone(), k2v: creds.k2v_client()?, s3: creds.s3_client()?, uid_index, mail_path, - }) + }))) } - /// Get a summary of the mailbox, useful for the SELECT command for example - pub async fn summary(&mut self) -> Result<Summary> { - self.uid_index.sync().await?; - let state = self.uid_index.state(); - - let unseen = state - .idx_by_flag - .get(&"$unseen".to_string()) - .and_then(|os| os.get_min()); - let recent = state - .idx_by_flag - .get(&"\\Recent".to_string()) - .map(|os| os.len()) - .unwrap_or(0); - - return Ok(Summary { - validity: state.uidvalidity, - next: state.uidnext, - exists: u32::try_from(state.idx_by_uid.len())?, - recent: u32::try_from(recent)?, - flags: state.idx_by_flag.flags(), - unseen, - }); + /// Get a clone of the current UID Index of this mailbox + /// (cloning is cheap so don't hesitate to use this) + pub async fn current_uid_index(&self) -> UidIndex { + self.0.read().await.uid_index.state().clone() } /// Insert an email in the mailbox - pub async fn append(&mut self, _msg: IMF) -> Result<()> { + pub async fn append<'a>(&self, _msg: IMF<'a>) -> Result<()> { unimplemented!() } /// Copy an email from an other Mailbox to this mailbox /// (use this when possible, as it allows for a certain number of storage optimizations) - pub async fn copy(&mut self, _from: &Mailbox, _uid: ImapUid) -> Result<()> { + pub async fn copy(&self, _from: &Mailbox, _uid: ImapUid) -> Result<()> { unimplemented!() } @@ -101,21 +72,36 @@ impl Mailbox { /// Can be called by CLOSE and EXPUNGE /// @FIXME do we want to implement this feature or a simpler "delete" command /// The controller could then "fetch \Delete" and call delete on each email? - pub async fn expunge(&mut self) -> Result<()> { + pub async fn expunge(&self) -> Result<()> { unimplemented!() } /// Update flags of a range of emails - pub async fn store(&mut self) -> Result<()> { + pub async fn store(&self) -> Result<()> { unimplemented!() } - pub async fn fetch(&mut self) -> Result<()> { + pub async fn fetch(&self) -> Result<()> { unimplemented!() } +} + +// ---- + +// Non standard but common flags: +// https://www.iana.org/assignments/imap-jmap-keywords/imap-jmap-keywords.xhtml +struct MailboxInternal { + bucket: String, + key: Key, - // ---- + k2v: K2vClient, + s3: S3Client, + + uid_index: Bayou<UidIndex>, + mail_path: String, +} +impl MailboxInternal { pub async fn test(&mut self) -> Result<()> { self.uid_index.sync().await?; diff --git a/src/mail/mod.rs b/src/mail/mod.rs index 4339038..70182a9 100644 --- a/src/mail/mod.rs +++ b/src/mail/mod.rs @@ -1,6 +1,6 @@ pub mod mail_ident; pub mod mailbox; -mod uidindex; +pub mod uidindex; pub mod user; use std::convert::TryFrom; @@ -17,4 +17,7 @@ use crate::mail::uidindex::*; // Internet Message Format // aka RFC 822 - RFC 2822 - RFC 5322 -pub struct IMF(Vec<u8>); +pub struct IMF<'a> { + raw: &'a [u8], + parsed: mail_parser::Message<'a>, +} diff --git a/src/mail/user.rs b/src/mail/user.rs index 4864509..e2b33e2 100644 --- a/src/mail/user.rs +++ b/src/mail/user.rs @@ -1,9 +1,13 @@ +use std::collections::HashMap; +use std::sync::{Arc, Weak}; + use anyhow::Result; +use lazy_static::lazy_static; use k2v_client::K2vClient; use rusoto_s3::S3Client; -use crate::login::Credentials; +use crate::login::{Credentials, StorageCredentials}; use crate::mail::mailbox::Mailbox; pub struct User { @@ -31,8 +35,24 @@ impl User { } /// Opens an existing mailbox given its IMAP name. - pub fn open_mailbox(&self, name: &str) -> Result<Option<Mailbox>> { - Mailbox::new(&self.creds, name).map(Some) + pub async fn open_mailbox(&self, name: &str) -> Result<Option<Arc<Mailbox>>> { + { + let cache = MAILBOX_CACHE.cache.lock().unwrap(); + if let Some(mb) = cache.get(&self.creds.storage).and_then(Weak::upgrade) { + return Ok(Some(mb)); + } + } + + let mb = Arc::new(Mailbox::open(&self.creds, name).await?); + + let mut cache = MAILBOX_CACHE.cache.lock().unwrap(); + if let Some(concurrent_mb) = cache.get(&self.creds.storage).and_then(Weak::upgrade) { + drop(mb); // we worked for nothing but at least we didn't starve someone else + Ok(Some(concurrent_mb)) + } else { + cache.insert(self.creds.storage.clone(), Arc::downgrade(&mb)); + Ok(Some(mb)) + } } /// Creates a new mailbox in the user's IMAP namespace. @@ -50,3 +70,21 @@ impl User { unimplemented!() } } + +// ---- Mailbox cache ---- + +struct MailboxCache { + cache: std::sync::Mutex<HashMap<StorageCredentials, Weak<Mailbox>>>, +} + +impl MailboxCache { + fn new() -> Self { + Self { + cache: std::sync::Mutex::new(HashMap::new()), + } + } +} + +lazy_static! { + static ref MAILBOX_CACHE: MailboxCache = MailboxCache::new(); +} |