aboutsummaryrefslogtreecommitdiff
path: root/src/uidindex.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/uidindex.rs')
-rw-r--r--src/uidindex.rs168
1 files changed, 168 insertions, 0 deletions
diff --git a/src/uidindex.rs b/src/uidindex.rs
new file mode 100644
index 0000000..7c5500f
--- /dev/null
+++ b/src/uidindex.rs
@@ -0,0 +1,168 @@
+use im::OrdMap;
+use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
+
+use crate::bayou::*;
+
+type ImapUid = u32;
+type ImapUidvalidity = u32;
+
+/// A Mail UUID is composed of two components:
+/// - a process identifier, 128 bits
+/// - a sequence number, 64 bits
+#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
+pub struct MailUuid(pub [u8; 24]);
+
+#[derive(Clone)]
+pub struct UidIndex {
+ mail_uid: OrdMap<MailUuid, ImapUid>,
+ mail_flags: OrdMap<MailUuid, Vec<String>>,
+
+ mails_by_uid: OrdMap<ImapUid, MailUuid>,
+
+ uidvalidity: ImapUidvalidity,
+ uidnext: ImapUid,
+ internalseq: ImapUid,
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+pub enum UidIndexOp {
+ MailAdd(MailUuid, ImapUid, Vec<String>),
+ MailDel(MailUuid),
+}
+
+impl Default for UidIndex {
+ fn default() -> Self {
+ Self {
+ mail_flags: OrdMap::new(),
+ mail_uid: OrdMap::new(),
+ mails_by_uid: OrdMap::new(),
+ uidvalidity: 1,
+ uidnext: 1,
+ internalseq: 1,
+ }
+ }
+}
+
+impl BayouState for UidIndex {
+ type Op = UidIndexOp;
+
+ fn apply(&self, op: &UidIndexOp) -> Self {
+ let mut new = self.clone();
+ match op {
+ UidIndexOp::MailAdd(uuid, uid, flags) => {
+ if *uid < new.internalseq {
+ new.uidvalidity += new.internalseq - *uid;
+ }
+ let new_uid = new.internalseq;
+
+ if let Some(prev_uid) = new.mail_uid.get(uuid) {
+ new.mails_by_uid.remove(prev_uid);
+ } else {
+ new.mail_flags.insert(*uuid, flags.clone());
+ }
+ new.mails_by_uid.insert(new_uid, *uuid);
+ new.mail_uid.insert(*uuid, new_uid);
+
+ new.internalseq += 1;
+ new.uidnext = new.internalseq;
+ }
+ UidIndexOp::MailDel(uuid) => {
+ if let Some(uid) = new.mail_uid.get(uuid) {
+ new.mails_by_uid.remove(uid);
+ new.mail_uid.remove(uuid);
+ new.mail_flags.remove(uuid);
+ }
+ new.internalseq += 1;
+ }
+ }
+ new
+ }
+}
+
+// ---- CUSTOM SERIALIZATION AND DESERIALIZATION ----
+
+#[derive(Serialize, Deserialize)]
+struct UidIndexSerializedRepr {
+ mails: Vec<(ImapUid, MailUuid, Vec<String>)>,
+ uidvalidity: ImapUidvalidity,
+ uidnext: ImapUid,
+ internalseq: ImapUid,
+}
+
+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 {
+ mail_flags: OrdMap::new(),
+ mail_uid: OrdMap::new(),
+ mails_by_uid: OrdMap::new(),
+ uidvalidity: val.uidvalidity,
+ uidnext: val.uidnext,
+ internalseq: val.internalseq,
+ };
+
+ for (uid, uuid, flags) in val.mails {
+ uidindex.mail_flags.insert(uuid, flags);
+ uidindex.mail_uid.insert(uuid, uid);
+ uidindex.mails_by_uid.insert(uid, uuid);
+ }
+
+ 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 (uid, uuid) in self.mails_by_uid.iter() {
+ mails.push((
+ *uid,
+ *uuid,
+ self.mail_flags.get(uuid).cloned().unwrap_or_default(),
+ ));
+ }
+
+ let val = UidIndexSerializedRepr {
+ mails,
+ uidvalidity: self.uidvalidity,
+ uidnext: self.uidnext,
+ internalseq: self.internalseq,
+ };
+
+ val.serialize(serializer)
+ }
+}
+
+impl<'de> Deserialize<'de> for MailUuid {
+ fn deserialize<D>(d: D) -> Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ let v = String::deserialize(d)?;
+ let bytes = hex::decode(v).map_err(|_| D::Error::custom("invalid hex"))?;
+
+ if bytes.len() != 24 {
+ return Err(D::Error::custom("bad length"));
+ }
+
+ let mut tmp = [0u8; 24];
+ tmp[..].copy_from_slice(&bytes);
+ Ok(Self(tmp))
+ }
+}
+
+impl Serialize for MailUuid {
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ serializer.serialize_str(&hex::encode(self.0))
+ }
+}