aboutsummaryrefslogtreecommitdiff
path: root/src/mail/uidindex.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/mail/uidindex.rs')
-rw-r--r--src/mail/uidindex.rs132
1 files changed, 102 insertions, 30 deletions
diff --git a/src/mail/uidindex.rs b/src/mail/uidindex.rs
index 01f8c9c..f1c522c 100644
--- a/src/mail/uidindex.rs
+++ b/src/mail/uidindex.rs
@@ -6,10 +6,11 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::bayou::*;
use crate::mail::unique_ident::UniqueIdent;
+pub type ModSeq = NonZeroU32;
pub type ImapUid = NonZeroU32;
pub type ImapUidvalidity = NonZeroU32;
pub type Flag = String;
-pub type IndexEntry = (ImapUid, Vec<Flag>);
+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
@@ -23,28 +24,33 @@ pub struct UidIndex {
// Indexes optimized for queries
pub idx_by_uid: OrdMap<ImapUid, UniqueIdent>,
+ pub idx_by_modseq: OrdMap<ModSeq, UniqueIdent>,
pub idx_by_flag: FlagIndex,
- // Counters
+ // "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, Vec<Flag>),
+ MailAdd(UniqueIdent, ImapUid, ModSeq, Vec<Flag>),
MailDel(UniqueIdent),
- FlagAdd(UniqueIdent, Vec<Flag>),
- FlagDel(UniqueIdent, Vec<Flag>),
- FlagSet(UniqueIdent, Vec<Flag>),
+ 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, flags)
+ UidIndexOp::MailAdd(ident, self.internalseq, self.internalmodseq, flags)
}
#[must_use]
@@ -54,17 +60,17 @@ impl UidIndex {
#[must_use]
pub fn op_flag_add(&self, ident: UniqueIdent, flags: Vec<Flag>) -> UidIndexOp {
- UidIndexOp::FlagAdd(ident, flags)
+ UidIndexOp::FlagAdd(ident, self.internalmodseq, flags)
}
#[must_use]
pub fn op_flag_del(&self, ident: UniqueIdent, flags: Vec<Flag>) -> UidIndexOp {
- UidIndexOp::FlagDel(ident, flags)
+ UidIndexOp::FlagDel(ident, self.internalmodseq, flags)
}
#[must_use]
pub fn op_flag_set(&self, ident: UniqueIdent, flags: Vec<Flag>) -> UidIndexOp {
- UidIndexOp::FlagSet(ident, flags)
+ UidIndexOp::FlagSet(ident, self.internalmodseq, flags)
}
#[must_use]
@@ -74,18 +80,19 @@ impl UidIndex {
// INTERNAL functions to keep state consistent
- fn reg_email(&mut self, ident: UniqueIdent, uid: ImapUid, flags: &[Flag]) {
+ fn reg_email(&mut self, ident: UniqueIdent, uid: ImapUid, modseq: ModSeq, flags: &[Flag]) {
// Insert the email in our table
- self.table.insert(ident, (uid, flags.to_owned()));
+ 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, flags) = match self.table.get(ident) {
+ let (uid, modseq, flags) = match self.table.get(ident) {
Some(v) => v,
None => return,
};
@@ -93,6 +100,7 @@ impl UidIndex {
// 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);
@@ -103,11 +111,17 @@ 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: NonZeroU32::new(1).unwrap(),
+
internalseq: NonZeroU32::new(1).unwrap(),
+ internalmodseq: NonZeroU32::new(1).unwrap(),
}
}
}
@@ -118,17 +132,24 @@ impl BayouState for UidIndex {
fn apply(&self, op: &UidIndexOp) -> Self {
let mut new = self.clone();
match op {
- UidIndexOp::MailAdd(ident, uid, flags) => {
- // Change UIDValidity if there is a conflict
- if *uid < new.internalseq {
+ 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.highestmodseq {
+ let bump_uid = new.internalseq.get() - uid.get();
+ let bump_modseq = new.internalmodseq.get() - modseq.get();
new.uidvalidity =
- NonZeroU32::new(new.uidvalidity.get() + new.internalseq.get() - uid.get())
+ 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.highestmodseq;
+
// 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
@@ -137,10 +158,14 @@ impl BayouState for UidIndex {
new.unreg_email(ident);
// We record our email and update ou caches
- new.reg_email(*ident, new_uid, flags);
+ 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 = NonZeroU32::new(new.internalmodseq.get() + 1).unwrap();
+
new.uidnext = new.internalseq;
}
UidIndexOp::MailDel(ident) => {
@@ -150,8 +175,16 @@ impl BayouState for UidIndex {
// We update the counter
new.internalseq = NonZeroU32::new(new.internalseq.get() + 1).unwrap();
}
- UidIndexOp::FlagAdd(ident, new_flags) => {
- if let Some((uid, existing_flags)) = new.table.get_mut(ident) {
+ UidIndexOp::FlagAdd(ident, modseq, new_flags) => {
+ if let Some((uid, modseq, existing_flags)) = new.table.get_mut(ident) {
+ // Bump UIDValidity if required
+ if *modseq < new.highestmodseq {
+ let bump_modseq = new.internalmodseq.get() - modseq.get();
+ 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()
@@ -159,18 +192,38 @@ impl BayouState for UidIndex {
.cloned()
.collect();
new.idx_by_flag.insert(*uid, &to_add);
+ new.idx_by_modseq.insert(*modseq, *ident);
existing_flags.append(&mut to_add);
+
+ // Update counters
+ new.highestmodseq = new.internalmodseq;
+ new.internalmodseq = NonZeroU32::new(new.internalmodseq.get() + 1).unwrap();
}
}
- UidIndexOp::FlagDel(ident, rm_flags) => {
- if let Some((uid, existing_flags)) = new.table.get_mut(ident) {
+ UidIndexOp::FlagDel(ident, modseq, rm_flags) => {
+ if let Some((uid, modseq, existing_flags)) = new.table.get_mut(ident) {
+ // Bump UIDValidity if required
+ if *modseq < new.highestmodseq {
+ let bump_modseq = new.internalmodseq.get() - modseq.get();
+ 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(*modseq, *ident);
+
+ // Update counters
+ new.highestmodseq = new.internalmodseq;
+ new.internalmodseq = NonZeroU32::new(new.internalmodseq.get() + 1).unwrap();
}
}
- UidIndexOp::FlagSet(ident, new_flags) => {
- if let Some((uid, existing_flags)) = new.table.get_mut(ident) {
+ UidIndexOp::FlagSet(ident, modseq, new_flags) => {
+ if let Some((uid, modseq, existing_flags)) = new.table.get_mut(ident) {
// Remove flags from the source of trust and the cache
let (keep_flags, rm_flags): (Vec<String>, Vec<String>) = existing_flags
.iter()
@@ -185,6 +238,13 @@ impl BayouState for UidIndex {
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(*modseq, *ident);
+
+ // Update counters
+ new.highestmodseq = new.internalmodseq;
+ new.internalmodseq = NonZeroU32::new(new.internalmodseq.get() + 1).unwrap();
}
}
UidIndexOp::BumpUidvalidity(count) => {
@@ -238,10 +298,14 @@ impl FlagIndex {
#[derive(Serialize, Deserialize)]
struct UidIndexSerializedRepr {
- mails: Vec<(ImapUid, UniqueIdent, Vec<Flag>)>,
+ mails: Vec<(ImapUid, ModSeq, UniqueIdent, Vec<Flag>)>,
+
uidvalidity: ImapUidvalidity,
uidnext: ImapUid,
+ highestmodseq: ModSeq,
+
internalseq: ImapUid,
+ internalmodseq: ModSeq,
}
impl<'de> Deserialize<'de> for UidIndex {
@@ -253,16 +317,22 @@ impl<'de> Deserialize<'de> for UidIndex {
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(|(u, i, f)| uidindex.reg_email(*i, *u, f));
+ .for_each(|(uid, modseq, uuid, flags)| uidindex.reg_email(*uuid, *uid, *modseq, flags));
Ok(uidindex)
}
@@ -274,15 +344,17 @@ impl Serialize for UidIndex {
S: Serializer,
{
let mut mails = vec![];
- for (ident, (uid, flags)) in self.table.iter() {
- mails.push((*uid, *ident, flags.clone()));
+ 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)
@@ -308,7 +380,7 @@ mod tests {
// Early checks
assert_eq!(state.table.len(), 1);
- let (uid, flags) = state.table.get(&m).unwrap();
+ let (uid, modseq, flags) = state.table.get(&m).unwrap();
assert_eq!(*uid, NonZeroU32::new(1).unwrap());
assert_eq!(flags.len(), 2);
let ident = state.idx_by_uid.get(&NonZeroU32::new(1).unwrap()).unwrap();
@@ -364,7 +436,7 @@ mod tests {
{
let m = UniqueIdent([0x03; 24]);
let f = vec!["\\Archive".to_string(), "\\Recent".to_string()];
- let ev = UidIndexOp::MailAdd(m, NonZeroU32::new(1).unwrap(), f);
+ let ev = UidIndexOp::MailAdd(m, NonZeroU32::new(1).unwrap(), NonZeroU32::new(1).unwrap(), f);
state = state.apply(&ev);
}