From e1087294917c928894c3681b54f413c69625766e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20DOUIN?= Date: Mon, 22 Mar 2021 12:45:47 +0100 Subject: [PATCH] extract flags from msg model --- src/app.rs | 6 ++- src/flag/cli.rs | 3 +- src/flag/model.rs | 86 +++++++++++++++++++++++++++++++++++++++ src/imap.rs | 52 ++++++++++++------------ src/main.rs | 1 + src/msg.rs | 101 ++++++++++++++++++---------------------------- 6 files changed, 161 insertions(+), 88 deletions(-) create mode 100644 src/flag/model.rs diff --git a/src/app.rs b/src/app.rs index 5628f0e..c840b55 100644 --- a/src/app.rs +++ b/src/app.rs @@ -7,7 +7,7 @@ use crate::{ flag::cli::{flags_matches, flags_subcommand}, imap::{self, ImapConnector}, input, - msg::{self, Attachments, Msg, ReadableMsg}, + msg::{self, Attachments, Msg, Msgs, ReadableMsg}, output::{self, print}, smtp, }; @@ -241,6 +241,8 @@ impl<'a> App<'a> { let page: u32 = matches.value_of("page").unwrap().parse().unwrap(); let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?; + let msgs = Msgs::from(&msgs); + print(&output_type, msgs)?; imap_conn.logout(); @@ -279,6 +281,8 @@ impl<'a> App<'a> { .join(" "); let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?; + let msgs = Msgs::from(&msgs); + print(&output_type, msgs)?; imap_conn.logout(); diff --git a/src/flag/cli.rs b/src/flag/cli.rs index bc02cee..57553c5 100644 --- a/src/flag/cli.rs +++ b/src/flag/cli.rs @@ -30,7 +30,8 @@ fn flags_arg<'a, 'b>() -> Arg<'a, 'b> { pub fn flags_subcommand<'a, 'b>() -> App<'a, 'b> { SubCommand::with_name("flags") - .aliases(&["flag", "f"]) + .aliases(&["flag", "fg"]) + .about("Manages flags") .subcommand( SubCommand::with_name("set") .aliases(&["s"]) diff --git a/src/flag/model.rs b/src/flag/model.rs new file mode 100644 index 0000000..5980859 --- /dev/null +++ b/src/flag/model.rs @@ -0,0 +1,86 @@ +pub(crate) use imap::types::Flag; +use serde::ser::{Serialize, SerializeSeq, Serializer}; +use std::ops::Deref; + +// Serializable wrapper for `imap::types::Flag` + +#[derive(Debug, PartialEq)] +struct SerializableFlag<'f>(&'f imap::types::Flag<'f>); + +impl<'f> Serialize for SerializableFlag<'f> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(match &self.0 { + Flag::Seen => "Seen", + Flag::Answered => "Answered", + Flag::Flagged => "Flagged", + Flag::Deleted => "Deleted", + Flag::Draft => "Draft", + Flag::Recent => "Recent", + Flag::MayCreate => "MayCreate", + Flag::Custom(cow) => cow, + }) + } +} + +// Flags + +#[derive(Debug, PartialEq)] +pub struct Flags<'f>(&'f [Flag<'f>]); + +impl<'f> Flags<'f> { + pub fn new(flags: &'f [imap::types::Flag<'f>]) -> Self { + Self(flags) + } +} + +impl<'f> ToString for Flags<'f> { + fn to_string(&self) -> String { + let mut flags = String::new(); + + flags.push_str(if self.0.contains(&Flag::Seen) { + " " + } else { + "🟓" + }); + + flags.push_str(if self.0.contains(&Flag::Answered) { + "↩" + } else { + " " + }); + + flags.push_str(if self.0.contains(&Flag::Flagged) { + "!" + } else { + " " + }); + + flags + } +} + +impl<'f> Deref for Flags<'f> { + type Target = &'f [Flag<'f>]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'f> Serialize for Flags<'f> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut seq = serializer.serialize_seq(Some(self.0.len()))?; + + for flag in self.0 { + seq.serialize_element(&SerializableFlag(flag))?; + } + + seq.end() + } +} diff --git a/src/imap.rs b/src/imap.rs index ccc9fc3..d8a6686 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -6,7 +6,7 @@ use std::net::TcpStream; use crate::{ config::{self, Account, Config}, mbox::{Mbox, Mboxes}, - msg::{Msg, Msgs}, + msg::Msg, }; error_chain! { @@ -21,8 +21,8 @@ pub struct ImapConnector<'a> { pub sess: imap::Session>, } -impl<'a> ImapConnector<'a> { - pub fn new(account: &'a Account) -> Result { +impl<'ic> ImapConnector<'ic> { + pub fn new(account: &'ic Account) -> Result { let tls = TlsConnector::new().chain_err(|| "Cannot create TLS connector")?; let client = if account.imap_starttls() { imap::connect_starttls(account.imap_addr(), &account.imap_host, &tls) @@ -104,17 +104,18 @@ impl<'a> ImapConnector<'a> { if let Some(seq) = self.last_new_seq()? { if prev_seq != seq { - if let Some(msg) = self + let msgs = self .sess .uid_fetch(seq.to_string(), "(ENVELOPE)") - .chain_err(|| "Cannot fetch enveloppe")? + .chain_err(|| "Cannot fetch enveloppe")?; + let msg = msgs .iter() .next() - .map(Msg::from) - { - config.run_notify_cmd(&msg.subject, &msg.sender)?; - prev_seq = seq; - } + .ok_or_else(|| "Cannot fetch first message") + .map(Msg::from)?; + + config.run_notify_cmd(&msg.subject, &msg.sender)?; + prev_seq = seq; } } } @@ -132,7 +133,12 @@ impl<'a> ImapConnector<'a> { Ok(Mboxes(mboxes)) } - pub fn list_msgs(&mut self, mbox: &str, page_size: &u32, page: &u32) -> Result { + pub fn list_msgs( + &mut self, + mbox: &str, + page_size: &u32, + page: &u32, + ) -> Result>> { let last_seq = self .sess .select(mbox) @@ -149,16 +155,12 @@ impl<'a> ImapConnector<'a> { let end = begin - begin.min(*page_size as i64) + 1; let range = format!("{}:{}", begin, end); - let msgs = self + let fetches = self .sess .fetch(range, "(UID FLAGS ENVELOPE INTERNALDATE)") - .chain_err(|| "Cannot fetch messages")? - .iter() - .rev() - .map(Msg::from) - .collect::>(); + .chain_err(|| "Cannot fetch messages")?; - Ok(Msgs(msgs)) + Ok(fetches) } pub fn search_msgs( @@ -167,7 +169,7 @@ impl<'a> ImapConnector<'a> { query: &str, page_size: &usize, page: &usize, - ) -> Result { + ) -> Result>> { self.sess .select(mbox) .chain_err(|| format!("Cannot select mailbox `{}`", mbox))?; @@ -183,15 +185,15 @@ impl<'a> ImapConnector<'a> { .collect::>(); let range = uids[begin..end.min(uids.len())].join(","); - let msgs = self + let fetches = self .sess .fetch(&range, "(UID FLAGS ENVELOPE INTERNALDATE)") - .chain_err(|| format!("Cannot fetch range `{}`", &range))? - .iter() - .map(Msg::from) - .collect::>(); + .chain_err(|| format!("Cannot fetch range `{}`", &range))?; + // .iter() + // .map(|fetch| Msg::from(fetch)) + // .collect::>(); - Ok(Msgs(msgs)) + Ok(fetches) } pub fn read_msg(&mut self, mbox: &str, uid: &str) -> Result> { diff --git a/src/main.rs b/src/main.rs index df7ea94..2db212c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ mod smtp; mod table; mod flag { pub(crate) mod cli; + pub(crate) mod model; } use crate::app::App; diff --git a/src/msg.rs b/src/msg.rs index 195575e..28e76fa 100644 --- a/src/msg.rs +++ b/src/msg.rs @@ -10,6 +10,7 @@ use std::{fmt, result}; use uuid::Uuid; use crate::config::{Account, Config}; +use crate::flag::model::{Flag, Flags}; use crate::table::{self, DisplayRow, DisplayTable}; error_chain! { @@ -170,29 +171,29 @@ impl<'a> ReadableMsg { // Message -#[derive(Debug, Serialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum Flag { - Seen, - Answered, - Flagged, -} +// #[derive(Debug, Serialize, PartialEq)] +// #[serde(rename_all = "lowercase")] +// pub enum Flag { +// Seen, +// Answered, +// Flagged, +// } -impl Flag { - fn from_imap_flag(flag: &imap::types::Flag<'_>) -> Option { - match flag { - imap::types::Flag::Seen => Some(Self::Seen), - imap::types::Flag::Answered => Some(Self::Answered), - imap::types::Flag::Flagged => Some(Self::Flagged), - _ => None, - } - } -} +// impl Flag { +// fn from_imap_flag(flag: &imap::types::Flag<'_>) -> Option { +// match flag { +// imap::types::Flag::Seen => Some(Self::Seen), +// imap::types::Flag::Answered => Some(Self::Answered), +// imap::types::Flag::Flagged => Some(Self::Flagged), +// _ => None, +// } +// } +// } #[derive(Debug, Serialize)] -pub struct Msg { +pub struct Msg<'m> { pub uid: u32, - pub flags: Vec, + pub flags: Flags<'m>, pub subject: String, pub sender: String, pub date: String, @@ -201,11 +202,11 @@ pub struct Msg { raw: Vec, } -impl From> for Msg { +impl<'m> From> for Msg<'m> { fn from(raw: Vec) -> Self { Self { uid: 0, - flags: vec![], + flags: Flags::new(&[]), subject: String::from(""), sender: String::from(""), date: String::from(""), @@ -214,23 +215,19 @@ impl From> for Msg { } } -impl From for Msg { +impl<'m> From for Msg<'m> { fn from(raw: String) -> Self { Self::from(raw.as_bytes().to_vec()) } } -impl From<&imap::types::Fetch> for Msg { - fn from(fetch: &imap::types::Fetch) -> Self { +impl<'m> From<&'m imap::types::Fetch> for Msg<'m> { + fn from(fetch: &'m imap::types::Fetch) -> Self { match fetch.envelope() { None => Self::from(fetch.body().unwrap_or_default().to_vec()), Some(envelope) => Self { uid: fetch.uid.unwrap_or_default(), - flags: fetch - .flags() - .into_iter() - .filter_map(Flag::from_imap_flag) - .collect::>(), + flags: Flags::new(fetch.flags()), subject: envelope .subject .and_then(|subj| rfc2047_decoder::decode(subj).ok()) @@ -251,32 +248,8 @@ impl From<&imap::types::Fetch> for Msg { } } -impl<'a> Msg { - pub fn display_flags(&self) -> String { - let mut flags = String::new(); - - flags.push_str(if self.flags.contains(&Flag::Seen) { - " " - } else { - "✶" - }); - - flags.push_str(if self.flags.contains(&Flag::Answered) { - "↩" - } else { - " " - }); - - flags.push_str(if self.flags.contains(&Flag::Flagged) { - "!" - } else { - " " - }); - - flags - } - - pub fn parse(&'a self) -> Result> { +impl<'m> Msg<'m> { + pub fn parse(&'m self) -> Result> { Ok(mailparse::parse_mail(&self.raw)?) } @@ -549,7 +522,7 @@ impl<'a> Msg { } } -impl DisplayRow for Msg { +impl<'m> DisplayRow for Msg<'m> { fn to_row(&self) -> Vec { use crate::table::*; @@ -561,7 +534,7 @@ impl DisplayRow for Msg { vec![ Cell::new(&[unseen.to_owned(), RED], &self.uid.to_string()), - Cell::new(&[unseen.to_owned(), WHITE], &self.display_flags()), + Cell::new(&[unseen.to_owned(), WHITE], &self.flags.to_string()), FlexCell::new(&[unseen.to_owned(), GREEN], &self.subject), Cell::new(&[unseen.to_owned(), BLUE], &self.sender), Cell::new(&[unseen.to_owned(), YELLOW], &self.date), @@ -572,9 +545,9 @@ impl DisplayRow for Msg { // Msgs #[derive(Debug, Serialize)] -pub struct Msgs(pub Vec); +pub struct Msgs<'m>(pub Vec>); -impl<'a> DisplayTable<'a, Msg> for Msgs { +impl<'m> DisplayTable<'m, Msg<'m>> for Msgs<'m> { fn header_row() -> Vec { use crate::table::*; @@ -587,12 +560,18 @@ impl<'a> DisplayTable<'a, Msg> for Msgs { ] } - fn rows(&self) -> &Vec { + fn rows(&self) -> &Vec> { &self.0 } } -impl fmt::Display for Msgs { +impl<'m> From<&'m imap::types::ZeroCopy>> for Msgs<'m> { + fn from(fetches: &'m imap::types::ZeroCopy>) -> Self { + Self(fetches.iter().map(Msg::from).collect::>()) + } +} + +impl<'m> fmt::Display for Msgs<'m> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "\n{}", self.to_table()) }