diff --git a/daemon/migrations/20211013053733_initial.up.sql b/daemon/migrations/20211013053733_initial.up.sql index 1a8af5d..5777524 100644 --- a/daemon/migrations/20211013053733_initial.up.sql +++ b/daemon/migrations/20211013053733_initial.up.sql @@ -3,12 +3,23 @@ CREATE TABLE "accounts" ( ); CREATE TABLE "mailboxes" ( - "account" INTEGER, - "name" TEXT, + "account" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "uidvalidity" INTEGER NOT NULL, PRIMARY KEY ("account", "name") ); CREATE TABLE "messages" ( - "id" TEXT PRIMARY KEY + "id" TEXT PRIMARY KEY, + "date" DATETIME, + "subject" TEXT, + "from" JSON, + "sender" JSON, + "reply_to" JSON, + "to" JSON, + "cc" JSON, + "bcc" JSON, + "in_reply_to" TEXT, + "message_id" TEXT, ); diff --git a/imap/src/client/client.rs b/imap/src/client/client.rs index b259a99..d68feb6 100644 --- a/imap/src/client/client.rs +++ b/imap/src/client/client.rs @@ -219,7 +219,7 @@ impl ClientAuthenticated { /// Runs the SEARCH command pub async fn uid_search(&mut self) -> Result> { let cmd = Command::UidSearch(CommandSearch { - criteria: SearchCriteria::All, + criteria: SearchCriteria::all(), }); let stream = self.execute(cmd).await?; let (_, data) = stream.wait().await?; diff --git a/imap/src/proto/command.rs b/imap/src/proto/command.rs index 83ac12f..882df30 100644 --- a/imap/src/proto/command.rs +++ b/imap/src/proto/command.rs @@ -1,4 +1,4 @@ -use std::io::{self, Write}; +use std::{collections::HashSet, io::{self, Write}, ops::{Bound, RangeBounds}}; use format_bytes::DisplayBytes; use panorama_proto_common::{quote_string, Bytes}; @@ -175,7 +175,63 @@ impl DisplayBytes for FetchItems { #[derive(Clone, Debug)] pub enum FetchAttr {} +// TODO: not the most efficient representation but I'll optimize if this ever actually becomes a +// problem #[derive(Clone, Debug)] -pub enum SearchCriteria { - All, +pub struct SearchCriteria(HashSet<(Bound, Bound)>); + +impl SearchCriteria { + pub fn all() -> Self { + let mut set = HashSet::new(); + set.insert((Bound::Unbounded, Bound::Unbounded)); + SearchCriteria(set) + } + + pub fn contains(&self, n: u32) -> bool { + for range in self.0.iter() { + if range.contains(&n) { + return true; + } + } + + false + } +} + +impl DisplayBytes for SearchCriteria { + fn display_bytes(&self, w: &mut dyn Write) -> io::Result<()> { + // TODO: is it faster to batch these up or not? + for (i, range) in self.0.iter().enumerate() { + if i != 0 { + write_bytes!(w, b",")?; + } + + match range.0 { + Bound::Excluded(n) => write_bytes!(w, b"{}", &(n + 1))?, + Bound::Included(n) => write_bytes!(w, b"{}", &n)?, + Bound::Unbounded => write_bytes!(w, b"*")?, + } + + write_bytes!(w, b":")?; + + match range.1 { + Bound::Excluded(n) => write_bytes!(w, b"{}", &(n - 1))?, + Bound::Included(n) => write_bytes!(w, b"{}", &n)?, + Bound::Unbounded => write_bytes!(w, b"*")?, + } + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn display_search_criteria() { + // TODO: is there a trivial case? + assert_eq!(format_bytes!(b"{}", SearchCriteria::all()), b"*:*"); + } }