aboutsummaryrefslogtreecommitdiff
path: root/src/mail/uidindex.rs
diff options
context:
space:
mode:
authorQuentin Dufour <quentin@deuxfleurs.fr>2024-03-08 08:17:03 +0100
committerQuentin Dufour <quentin@deuxfleurs.fr>2024-03-08 08:17:03 +0100
commit1a43ce5ac7033c148f64a033f2b1d335e95e11d5 (patch)
tree60b234604170fe207248458a9c4cdd3f4b7c36f2 /src/mail/uidindex.rs
parentbb9cb386b65834c44cae86bd100f800883022062 (diff)
downloadaerogramme-1a43ce5ac7033c148f64a033f2b1d335e95e11d5.tar.gz
aerogramme-1a43ce5ac7033c148f64a033f2b1d335e95e11d5.zip
WIP refactor
Diffstat (limited to 'src/mail/uidindex.rs')
-rw-r--r--src/mail/uidindex.rs474
1 files changed, 0 insertions, 474 deletions
diff --git a/src/mail/uidindex.rs b/src/mail/uidindex.rs
deleted file mode 100644
index 5a06670..0000000
--- a/src/mail/uidindex.rs
+++ /dev/null
@@ -1,474 +0,0 @@
-use std::num::{NonZeroU32, NonZeroU64};
-
-use im::{HashMap, OrdMap, OrdSet};
-use serde::{Deserialize, Deserializer, Serialize, Serializer};
-
-use crate::bayou::*;
-use crate::mail::unique_ident::UniqueIdent;
-
-pub type ModSeq = NonZeroU64;
-pub type ImapUid = NonZeroU32;
-pub type ImapUidvalidity = NonZeroU32;
-pub type Flag = String;
-pub type IndexEntry = (ImapUid, ModSeq, Vec<Flag>);
-
-/// A UidIndex handles the mutable part of a mailbox
-/// It is built by running the event log on it
-/// Each applied log generates a new UidIndex by cloning the previous one
-/// and applying the event. This is why we use immutable datastructures:
-/// they are cheap to clone.
-#[derive(Clone)]
-pub struct UidIndex {
- // Source of trust
- pub table: OrdMap<UniqueIdent, IndexEntry>,
-
- // Indexes optimized for queries
- pub idx_by_uid: OrdMap<ImapUid, UniqueIdent>,
- pub idx_by_modseq: OrdMap<ModSeq, UniqueIdent>,
- pub idx_by_flag: FlagIndex,
-
- // "Public" Counters
- pub uidvalidity: ImapUidvalidity,
- pub uidnext: ImapUid,
- pub highestmodseq: ModSeq,
-
- // "Internal" Counters
- pub internalseq: ImapUid,
- pub internalmodseq: ModSeq,
-}
-
-#[derive(Clone, Serialize, Deserialize, Debug)]
-pub enum UidIndexOp {
- MailAdd(UniqueIdent, ImapUid, ModSeq, Vec<Flag>),
- MailDel(UniqueIdent),
- FlagAdd(UniqueIdent, ModSeq, Vec<Flag>),
- FlagDel(UniqueIdent, ModSeq, Vec<Flag>),
- FlagSet(UniqueIdent, ModSeq, Vec<Flag>),
- BumpUidvalidity(u32),
-}
-
-impl UidIndex {
- #[must_use]
- pub fn op_mail_add(&self, ident: UniqueIdent, flags: Vec<Flag>) -> UidIndexOp {
- UidIndexOp::MailAdd(ident, self.internalseq, self.internalmodseq, flags)
- }
-
- #[must_use]
- pub fn op_mail_del(&self, ident: UniqueIdent) -> UidIndexOp {
- UidIndexOp::MailDel(ident)
- }
-
- #[must_use]
- pub fn op_flag_add(&self, ident: UniqueIdent, flags: Vec<Flag>) -> UidIndexOp {
- UidIndexOp::FlagAdd(ident, self.internalmodseq, flags)
- }
-
- #[must_use]
- pub fn op_flag_del(&self, ident: UniqueIdent, flags: Vec<Flag>) -> UidIndexOp {
- UidIndexOp::FlagDel(ident, self.internalmodseq, flags)
- }
-
- #[must_use]
- pub fn op_flag_set(&self, ident: UniqueIdent, flags: Vec<Flag>) -> UidIndexOp {
- UidIndexOp::FlagSet(ident, self.internalmodseq, flags)
- }
-
- #[must_use]
- pub fn op_bump_uidvalidity(&self, count: u32) -> UidIndexOp {
- UidIndexOp::BumpUidvalidity(count)
- }
-
- // INTERNAL functions to keep state consistent
-
- fn reg_email(&mut self, ident: UniqueIdent, uid: ImapUid, modseq: ModSeq, flags: &[Flag]) {
- // Insert the email in our table
- self.table.insert(ident, (uid, modseq, flags.to_owned()));
-
- // Update the indexes/caches
- self.idx_by_uid.insert(uid, ident);
- self.idx_by_flag.insert(uid, flags);
- self.idx_by_modseq.insert(modseq, ident);
- }
-
- fn unreg_email(&mut self, ident: &UniqueIdent) {
- // We do nothing if the mail does not exist
- let (uid, modseq, flags) = match self.table.get(ident) {
- Some(v) => v,
- None => return,
- };
-
- // Delete all cache entries
- self.idx_by_uid.remove(uid);
- self.idx_by_flag.remove(*uid, flags);
- self.idx_by_modseq.remove(modseq);
-
- // Remove from source of trust
- self.table.remove(ident);
- }
-}
-
-impl Default for UidIndex {
- fn default() -> Self {
- Self {
- table: OrdMap::new(),
-
- idx_by_uid: OrdMap::new(),
- idx_by_modseq: OrdMap::new(),
- idx_by_flag: FlagIndex::new(),
-
- uidvalidity: NonZeroU32::new(1).unwrap(),
- uidnext: NonZeroU32::new(1).unwrap(),
- highestmodseq: NonZeroU64::new(1).unwrap(),
-
- internalseq: NonZeroU32::new(1).unwrap(),
- internalmodseq: NonZeroU64::new(1).unwrap(),
- }
- }
-}
-
-impl BayouState for UidIndex {
- type Op = UidIndexOp;
-
- fn apply(&self, op: &UidIndexOp) -> Self {
- let mut new = self.clone();
- match op {
- UidIndexOp::MailAdd(ident, uid, modseq, flags) => {
- // Change UIDValidity if there is a UID conflict or a MODSEQ conflict
- // @FIXME Need to prove that summing work
- // The intuition: we increase the UIDValidity by the number of possible conflicts
- if *uid < new.internalseq || *modseq < new.internalmodseq {
- let bump_uid = new.internalseq.get() - uid.get();
- let bump_modseq = (new.internalmodseq.get() - modseq.get()) as u32;
- new.uidvalidity =
- NonZeroU32::new(new.uidvalidity.get() + bump_uid + bump_modseq).unwrap();
- }
-
- // Assign the real uid of the email
- let new_uid = new.internalseq;
-
- // Assign the real modseq of the email and its new flags
- let new_modseq = new.internalmodseq;
-
- // Delete the previous entry if any.
- // Our proof has no assumption on `ident` uniqueness,
- // so we must handle this case even it is very unlikely
- // In this case, we overwrite the email.
- // Note: assigning a new UID is mandatory.
- new.unreg_email(ident);
-
- // We record our email and update ou caches
- new.reg_email(*ident, new_uid, new_modseq, flags);
-
- // Update counters
- new.highestmodseq = new.internalmodseq;
-
- new.internalseq = NonZeroU32::new(new.internalseq.get() + 1).unwrap();
- new.internalmodseq = NonZeroU64::new(new.internalmodseq.get() + 1).unwrap();
-
- new.uidnext = new.internalseq;
- }
- UidIndexOp::MailDel(ident) => {
- // If the email is known locally, we remove its references in all our indexes
- new.unreg_email(ident);
-
- // We update the counter
- new.internalseq = NonZeroU32::new(new.internalseq.get() + 1).unwrap();
- }
- UidIndexOp::FlagAdd(ident, candidate_modseq, new_flags) => {
- if let Some((uid, email_modseq, existing_flags)) = new.table.get_mut(ident) {
- // Bump UIDValidity if required
- if *candidate_modseq < new.internalmodseq {
- let bump_modseq =
- (new.internalmodseq.get() - candidate_modseq.get()) as u32;
- new.uidvalidity =
- NonZeroU32::new(new.uidvalidity.get() + bump_modseq).unwrap();
- }
-
- // Add flags to the source of trust and the cache
- let mut to_add: Vec<Flag> = new_flags
- .iter()
- .filter(|f| !existing_flags.contains(f))
- .cloned()
- .collect();
- new.idx_by_flag.insert(*uid, &to_add);
- *email_modseq = new.internalmodseq;
- new.idx_by_modseq.insert(new.internalmodseq, *ident);
- existing_flags.append(&mut to_add);
-
- // Update counters
- new.highestmodseq = new.internalmodseq;
- new.internalmodseq = NonZeroU64::new(new.internalmodseq.get() + 1).unwrap();
- }
- }
- UidIndexOp::FlagDel(ident, candidate_modseq, rm_flags) => {
- if let Some((uid, email_modseq, existing_flags)) = new.table.get_mut(ident) {
- // Bump UIDValidity if required
- if *candidate_modseq < new.internalmodseq {
- let bump_modseq =
- (new.internalmodseq.get() - candidate_modseq.get()) as u32;
- new.uidvalidity =
- NonZeroU32::new(new.uidvalidity.get() + bump_modseq).unwrap();
- }
-
- // Remove flags from the source of trust and the cache
- existing_flags.retain(|x| !rm_flags.contains(x));
- new.idx_by_flag.remove(*uid, rm_flags);
-
- // Register that email has been modified
- new.idx_by_modseq.insert(new.internalmodseq, *ident);
- *email_modseq = new.internalmodseq;
-
- // Update counters
- new.highestmodseq = new.internalmodseq;
- new.internalmodseq = NonZeroU64::new(new.internalmodseq.get() + 1).unwrap();
- }
- }
- UidIndexOp::FlagSet(ident, candidate_modseq, new_flags) => {
- if let Some((uid, email_modseq, existing_flags)) = new.table.get_mut(ident) {
- // Bump UIDValidity if required
- if *candidate_modseq < new.internalmodseq {
- let bump_modseq =
- (new.internalmodseq.get() - candidate_modseq.get()) as u32;
- new.uidvalidity =
- NonZeroU32::new(new.uidvalidity.get() + bump_modseq).unwrap();
- }
-
- // Remove flags from the source of trust and the cache
- let (keep_flags, rm_flags): (Vec<String>, Vec<String>) = existing_flags
- .iter()
- .cloned()
- .partition(|x| new_flags.contains(x));
- *existing_flags = keep_flags;
- let mut to_add: Vec<Flag> = new_flags
- .iter()
- .filter(|f| !existing_flags.contains(f))
- .cloned()
- .collect();
- existing_flags.append(&mut to_add);
- new.idx_by_flag.remove(*uid, &rm_flags);
- new.idx_by_flag.insert(*uid, &to_add);
-
- // Register that email has been modified
- new.idx_by_modseq.insert(new.internalmodseq, *ident);
- *email_modseq = new.internalmodseq;
-
- // Update counters
- new.highestmodseq = new.internalmodseq;
- new.internalmodseq = NonZeroU64::new(new.internalmodseq.get() + 1).unwrap();
- }
- }
- UidIndexOp::BumpUidvalidity(count) => {
- new.uidvalidity = ImapUidvalidity::new(new.uidvalidity.get() + *count)
- .unwrap_or(ImapUidvalidity::new(u32::MAX).unwrap());
- }
- }
- new
- }
-}
-
-// ---- FlagIndex implementation ----
-
-#[derive(Clone)]
-pub struct FlagIndex(HashMap<Flag, OrdSet<ImapUid>>);
-pub type FlagIter<'a> = im::hashmap::Keys<'a, Flag, OrdSet<ImapUid>>;
-
-impl FlagIndex {
- fn new() -> Self {
- Self(HashMap::new())
- }
- fn insert(&mut self, uid: ImapUid, flags: &[Flag]) {
- flags.iter().for_each(|flag| {
- self.0
- .entry(flag.clone())
- .or_insert(OrdSet::new())
- .insert(uid);
- });
- }
- fn remove(&mut self, uid: ImapUid, flags: &[Flag]) {
- for flag in flags.iter() {
- if let Some(set) = self.0.get_mut(flag) {
- set.remove(&uid);
- if set.is_empty() {
- self.0.remove(flag);
- }
- }
- }
- }
-
- pub fn get(&self, f: &Flag) -> Option<&OrdSet<ImapUid>> {
- self.0.get(f)
- }
-
- pub fn flags(&self) -> FlagIter {
- self.0.keys()
- }
-}
-
-// ---- CUSTOM SERIALIZATION AND DESERIALIZATION ----
-
-#[derive(Serialize, Deserialize)]
-struct UidIndexSerializedRepr {
- mails: Vec<(ImapUid, ModSeq, UniqueIdent, Vec<Flag>)>,
-
- uidvalidity: ImapUidvalidity,
- uidnext: ImapUid,
- highestmodseq: ModSeq,
-
- internalseq: ImapUid,
- internalmodseq: ModSeq,
-}
-
-impl<'de> Deserialize<'de> for UidIndex {
- fn deserialize<D>(d: D) -> Result<Self, D::Error>
- where
- D: Deserializer<'de>,
- {
- let val: UidIndexSerializedRepr = UidIndexSerializedRepr::deserialize(d)?;
-
- let mut uidindex = UidIndex {
- table: OrdMap::new(),
-
- idx_by_uid: OrdMap::new(),
- idx_by_modseq: OrdMap::new(),
- idx_by_flag: FlagIndex::new(),
-
- uidvalidity: val.uidvalidity,
- uidnext: val.uidnext,
- highestmodseq: val.highestmodseq,
-
- internalseq: val.internalseq,
- internalmodseq: val.internalmodseq,
- };
-
- val.mails
- .iter()
- .for_each(|(uid, modseq, uuid, flags)| uidindex.reg_email(*uuid, *uid, *modseq, flags));
-
- Ok(uidindex)
- }
-}
-
-impl Serialize for UidIndex {
- fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
- where
- S: Serializer,
- {
- let mut mails = vec![];
- for (ident, (uid, modseq, flags)) in self.table.iter() {
- mails.push((*uid, *modseq, *ident, flags.clone()));
- }
-
- let val = UidIndexSerializedRepr {
- mails,
- uidvalidity: self.uidvalidity,
- uidnext: self.uidnext,
- highestmodseq: self.highestmodseq,
- internalseq: self.internalseq,
- internalmodseq: self.internalmodseq,
- };
-
- val.serialize(serializer)
- }
-}
-
-// ---- TESTS ----
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_uidindex() {
- let mut state = UidIndex::default();
-
- // Add message 1
- {
- let m = UniqueIdent([0x01; 24]);
- let f = vec!["\\Recent".to_string(), "\\Archive".to_string()];
- let ev = state.op_mail_add(m, f);
- state = state.apply(&ev);
-
- // Early checks
- assert_eq!(state.table.len(), 1);
- let (uid, modseq, flags) = state.table.get(&m).unwrap();
- assert_eq!(*uid, NonZeroU32::new(1).unwrap());
- assert_eq!(*modseq, NonZeroU64::new(1).unwrap());
- assert_eq!(flags.len(), 2);
- let ident = state.idx_by_uid.get(&NonZeroU32::new(1).unwrap()).unwrap();
- assert_eq!(&m, ident);
- let recent = state.idx_by_flag.0.get("\\Recent").unwrap();
- assert_eq!(recent.len(), 1);
- assert_eq!(recent.iter().next().unwrap(), &NonZeroU32::new(1).unwrap());
- assert_eq!(state.uidnext, NonZeroU32::new(2).unwrap());
- assert_eq!(state.uidvalidity, NonZeroU32::new(1).unwrap());
- }
-
- // Add message 2
- {
- let m = UniqueIdent([0x02; 24]);
- let f = vec!["\\Seen".to_string(), "\\Archive".to_string()];
- let ev = state.op_mail_add(m, f);
- state = state.apply(&ev);
-
- let archive = state.idx_by_flag.0.get("\\Archive").unwrap();
- assert_eq!(archive.len(), 2);
- }
-
- // Add flags to message 1
- {
- let m = UniqueIdent([0x01; 24]);
- let f = vec!["Important".to_string(), "$cl_1".to_string()];
- let ev = state.op_flag_add(m, f);
- state = state.apply(&ev);
- }
-
- // Delete flags from message 1
- {
- let m = UniqueIdent([0x01; 24]);
- let f = vec!["\\Recent".to_string()];
- let ev = state.op_flag_del(m, f);
- state = state.apply(&ev);
-
- let archive = state.idx_by_flag.0.get("\\Archive").unwrap();
- assert_eq!(archive.len(), 2);
- }
-
- // Delete message 2
- {
- let m = UniqueIdent([0x02; 24]);
- let ev = state.op_mail_del(m);
- state = state.apply(&ev);
-
- let archive = state.idx_by_flag.0.get("\\Archive").unwrap();
- assert_eq!(archive.len(), 1);
- }
-
- // Add a message 3 concurrent to message 1 (trigger a uid validity change)
- {
- let m = UniqueIdent([0x03; 24]);
- let f = vec!["\\Archive".to_string(), "\\Recent".to_string()];
- let ev = UidIndexOp::MailAdd(
- m,
- NonZeroU32::new(1).unwrap(),
- NonZeroU64::new(1).unwrap(),
- f,
- );
- state = state.apply(&ev);
- }
-
- // Checks
- {
- assert_eq!(state.table.len(), 2);
- assert!(state.uidvalidity > NonZeroU32::new(1).unwrap());
-
- let (last_uid, ident) = state.idx_by_uid.get_max().unwrap();
- assert_eq!(ident, &UniqueIdent([0x03; 24]));
-
- let archive = state.idx_by_flag.0.get("\\Archive").unwrap();
- assert_eq!(archive.len(), 2);
- let mut iter = archive.iter();
- assert_eq!(iter.next().unwrap(), &NonZeroU32::new(1).unwrap());
- assert_eq!(iter.next().unwrap(), last_uid);
- }
- }
-}