aboutsummaryrefslogtreecommitdiff
path: root/src/mail
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2022-06-29 15:39:54 +0200
committerAlex Auvolat <alex@adnab.me>2022-06-29 15:39:54 +0200
commitb95028f89e4db7c3158fab3b71ea56a742daba21 (patch)
tree262a212c7eec8cf26ec70cbb7d12c2264a1e3da2 /src/mail
parent8b7eb1ca918d26901b0739526341128067ca1cbc (diff)
downloadaerogramme-b95028f89e4db7c3158fab3b71ea56a742daba21.tar.gz
aerogramme-b95028f89e4db7c3158fab3b71ea56a742daba21.zip
Some refactoring on mailbox structures and views
Diffstat (limited to 'src/mail')
-rw-r--r--src/mail/mailbox.rs88
-rw-r--r--src/mail/mod.rs7
-rw-r--r--src/mail/user.rs44
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();
+}