From adf4d33f226a745330a3bb802fe9b96f263a0895 Mon Sep 17 00:00:00 2001 From: Quentin Dufour Date: Fri, 5 Jan 2024 17:46:16 +0100 Subject: [PATCH] added some utility structures --- src/mail/mailbox.rs | 4 +++ src/mail/query.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++ src/mail/snapshot.rs | 45 ++++++++++++++++++++++-- src/mail/uidindex.rs | 3 +- 4 files changed, 130 insertions(+), 3 deletions(-) diff --git a/src/mail/mailbox.rs b/src/mail/mailbox.rs index b011110..306fd7d 100644 --- a/src/mail/mailbox.rs +++ b/src/mail/mailbox.rs @@ -82,6 +82,10 @@ impl Mailbox { self.mbox.read().await.fetch_full(id, message_key).await } + async fn frozen(self: &std::sync::Arc) -> super::snapshot::FrozenMailbox { + super::snapshot::FrozenMailbox::new(self.clone()).await + } + // ---- Functions for changing the mailbox ---- /// Add flags to message diff --git a/src/mail/query.rs b/src/mail/query.rs index e69de29..631ad56 100644 --- a/src/mail/query.rs +++ b/src/mail/query.rs @@ -0,0 +1,81 @@ +use anyhow::{Result, anyhow}; +use super::mailbox::MailMeta; +use super::snapshot::FrozenMailbox; +use super::unique_ident::UniqueIdent; +use super::uidindex::IndexEntry; +use futures::stream::{FuturesUnordered, StreamExt}; + +/// Query is in charge of fetching efficiently +/// requested data for a list of emails +pub struct Query<'a,'b> { + pub frozen: &'a FrozenMailbox, + pub emails: &'b [UniqueIdent], +} + +impl<'a,'b> Query<'a,'b> { + pub fn index(&self) -> Result> { + self + .emails + .iter() + .map(|uuid| { + self + .frozen + .snapshot + .table + .get(uuid) + .map(|index| IndexResult { uuid: *uuid, index }) + .ok_or(anyhow!("missing email in index")) + }) + .collect::, _>>() + } + + pub async fn partial(&self) -> Result> { + let meta = self.frozen.mailbox.fetch_meta(self.emails).await?; + let result = meta + .into_iter() + .zip(self.index()?) + .map(|(metadata, index)| PartialResult { uuid: index.uuid, index: index.index, metadata }) + .collect::>(); + Ok(result) + } + + /// @FIXME WARNING: THIS CAN ALLOCATE A LOT OF MEMORY + /// AND GENERATE SO MUCH NETWORK TRAFFIC. + /// THIS FUNCTION SHOULD BE REWRITTEN, FOR EXAMPLE WITH + /// SOMETHING LIKE AN ITERATOR + pub async fn full(&self) -> Result> { + let meta_list = self.partial().await?; + meta_list + .into_iter() + .map(|meta| async move { + let content = self.frozen.mailbox.fetch_full(meta.uuid, &meta.metadata.message_key).await?; + Ok(FullResult { + uuid: meta.uuid, + index: meta.index, + metadata: meta.metadata, + content, + }) + }) + .collect::>() + .collect::>() + .await + .into_iter() + .collect::, _>>() + } +} + +pub struct IndexResult<'a> { + pub uuid: UniqueIdent, + pub index: &'a IndexEntry, +} +pub struct PartialResult<'a> { + pub uuid: UniqueIdent, + pub index: &'a IndexEntry, + pub metadata: MailMeta, +} +pub struct FullResult<'a> { + pub uuid: UniqueIdent, + pub index: &'a IndexEntry, + pub metadata: MailMeta, + pub content: Vec, +} diff --git a/src/mail/snapshot.rs b/src/mail/snapshot.rs index 7256d50..54bec64 100644 --- a/src/mail/snapshot.rs +++ b/src/mail/snapshot.rs @@ -1,11 +1,52 @@ use std::sync::Arc; + +use anyhow::Result; + use super::mailbox::Mailbox; use super::uidindex::UidIndex; -pub struct Snapshot { +/// A Frozen Mailbox has a snapshot of the current mailbox +/// state that is desynchronized with the real mailbox state. +/// It's up to the user to choose when their snapshot must be updated +/// to give useful information to their clients +/// +/// +pub struct FrozenMailbox { pub mailbox: Arc, pub snapshot: UidIndex, } -impl Snapshot { +impl FrozenMailbox { + /// Create a snapshot from a mailbox, the mailbox + the snapshot + /// becomes the "Frozen Mailbox". + pub async fn new(mailbox: Arc) -> Self { + let state = mailbox.current_uid_index().await; + + Self { + mailbox, + snapshot: state, + } + } + + /// Force the synchronization of the inner mailbox + /// but do not update the local snapshot + pub async fn sync(&self) -> Result<()> { + self.mailbox.opportunistic_sync().await + } + + /// Peek snapshot without updating the frozen mailbox + /// Can be useful if you want to plan some writes + /// while sending a diff to the client later + pub async fn peek(&self) -> UidIndex { + self.mailbox.current_uid_index().await + } + + /// Update the FrozenMailbox local snapshot. + /// Returns the old snapshot, so you can build a diff + pub async fn update(&mut self) -> UidIndex { + let old_snapshot = self.snapshot.clone(); + self.snapshot = self.mailbox.current_uid_index().await; + + old_snapshot + } } diff --git a/src/mail/uidindex.rs b/src/mail/uidindex.rs index 956b194..01f8c9c 100644 --- a/src/mail/uidindex.rs +++ b/src/mail/uidindex.rs @@ -9,6 +9,7 @@ use crate::mail::unique_ident::UniqueIdent; pub type ImapUid = NonZeroU32; pub type ImapUidvalidity = NonZeroU32; pub type Flag = String; +pub type IndexEntry = (ImapUid, Vec); /// A UidIndex handles the mutable part of a mailbox /// It is built by running the event log on it @@ -18,7 +19,7 @@ pub type Flag = String; #[derive(Clone)] pub struct UidIndex { // Source of trust - pub table: OrdMap)>, + pub table: OrdMap, // Indexes optimized for queries pub idx_by_uid: OrdMap,