extract flags from msg model

This commit is contained in:
Clément DOUIN 2021-03-22 12:45:47 +01:00
parent 563f9a6538
commit e108729491
No known key found for this signature in database
GPG key ID: 69C9B9CFFDEE2DEF
6 changed files with 161 additions and 88 deletions

View file

@ -7,7 +7,7 @@ use crate::{
flag::cli::{flags_matches, flags_subcommand}, flag::cli::{flags_matches, flags_subcommand},
imap::{self, ImapConnector}, imap::{self, ImapConnector},
input, input,
msg::{self, Attachments, Msg, ReadableMsg}, msg::{self, Attachments, Msg, Msgs, ReadableMsg},
output::{self, print}, output::{self, print},
smtp, smtp,
}; };
@ -241,6 +241,8 @@ impl<'a> App<'a> {
let page: u32 = matches.value_of("page").unwrap().parse().unwrap(); let page: u32 = matches.value_of("page").unwrap().parse().unwrap();
let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?; let msgs = imap_conn.list_msgs(&mbox, &page_size, &page)?;
let msgs = Msgs::from(&msgs);
print(&output_type, msgs)?; print(&output_type, msgs)?;
imap_conn.logout(); imap_conn.logout();
@ -279,6 +281,8 @@ impl<'a> App<'a> {
.join(" "); .join(" ");
let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?; let msgs = imap_conn.search_msgs(&mbox, &query, &page_size, &page)?;
let msgs = Msgs::from(&msgs);
print(&output_type, msgs)?; print(&output_type, msgs)?;
imap_conn.logout(); imap_conn.logout();

View file

@ -30,7 +30,8 @@ fn flags_arg<'a, 'b>() -> Arg<'a, 'b> {
pub fn flags_subcommand<'a, 'b>() -> App<'a, 'b> { pub fn flags_subcommand<'a, 'b>() -> App<'a, 'b> {
SubCommand::with_name("flags") SubCommand::with_name("flags")
.aliases(&["flag", "f"]) .aliases(&["flag", "fg"])
.about("Manages flags")
.subcommand( .subcommand(
SubCommand::with_name("set") SubCommand::with_name("set")
.aliases(&["s"]) .aliases(&["s"])

86
src/flag/model.rs Normal file
View file

@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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()
}
}

View file

@ -6,7 +6,7 @@ use std::net::TcpStream;
use crate::{ use crate::{
config::{self, Account, Config}, config::{self, Account, Config},
mbox::{Mbox, Mboxes}, mbox::{Mbox, Mboxes},
msg::{Msg, Msgs}, msg::Msg,
}; };
error_chain! { error_chain! {
@ -21,8 +21,8 @@ pub struct ImapConnector<'a> {
pub sess: imap::Session<TlsStream<TcpStream>>, pub sess: imap::Session<TlsStream<TcpStream>>,
} }
impl<'a> ImapConnector<'a> { impl<'ic> ImapConnector<'ic> {
pub fn new(account: &'a Account) -> Result<Self> { pub fn new(account: &'ic Account) -> Result<Self> {
let tls = TlsConnector::new().chain_err(|| "Cannot create TLS connector")?; let tls = TlsConnector::new().chain_err(|| "Cannot create TLS connector")?;
let client = if account.imap_starttls() { let client = if account.imap_starttls() {
imap::connect_starttls(account.imap_addr(), &account.imap_host, &tls) 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 let Some(seq) = self.last_new_seq()? {
if prev_seq != seq { if prev_seq != seq {
if let Some(msg) = self let msgs = self
.sess .sess
.uid_fetch(seq.to_string(), "(ENVELOPE)") .uid_fetch(seq.to_string(), "(ENVELOPE)")
.chain_err(|| "Cannot fetch enveloppe")? .chain_err(|| "Cannot fetch enveloppe")?;
let msg = msgs
.iter() .iter()
.next() .next()
.map(Msg::from) .ok_or_else(|| "Cannot fetch first message")
{ .map(Msg::from)?;
config.run_notify_cmd(&msg.subject, &msg.sender)?;
prev_seq = seq; config.run_notify_cmd(&msg.subject, &msg.sender)?;
} prev_seq = seq;
} }
} }
} }
@ -132,7 +133,12 @@ impl<'a> ImapConnector<'a> {
Ok(Mboxes(mboxes)) Ok(Mboxes(mboxes))
} }
pub fn list_msgs(&mut self, mbox: &str, page_size: &u32, page: &u32) -> Result<Msgs> { pub fn list_msgs(
&mut self,
mbox: &str,
page_size: &u32,
page: &u32,
) -> Result<imap::types::ZeroCopy<Vec<imap::types::Fetch>>> {
let last_seq = self let last_seq = self
.sess .sess
.select(mbox) .select(mbox)
@ -149,16 +155,12 @@ impl<'a> ImapConnector<'a> {
let end = begin - begin.min(*page_size as i64) + 1; let end = begin - begin.min(*page_size as i64) + 1;
let range = format!("{}:{}", begin, end); let range = format!("{}:{}", begin, end);
let msgs = self let fetches = self
.sess .sess
.fetch(range, "(UID FLAGS ENVELOPE INTERNALDATE)") .fetch(range, "(UID FLAGS ENVELOPE INTERNALDATE)")
.chain_err(|| "Cannot fetch messages")? .chain_err(|| "Cannot fetch messages")?;
.iter()
.rev()
.map(Msg::from)
.collect::<Vec<_>>();
Ok(Msgs(msgs)) Ok(fetches)
} }
pub fn search_msgs( pub fn search_msgs(
@ -167,7 +169,7 @@ impl<'a> ImapConnector<'a> {
query: &str, query: &str,
page_size: &usize, page_size: &usize,
page: &usize, page: &usize,
) -> Result<Msgs> { ) -> Result<imap::types::ZeroCopy<Vec<imap::types::Fetch>>> {
self.sess self.sess
.select(mbox) .select(mbox)
.chain_err(|| format!("Cannot select mailbox `{}`", mbox))?; .chain_err(|| format!("Cannot select mailbox `{}`", mbox))?;
@ -183,15 +185,15 @@ impl<'a> ImapConnector<'a> {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let range = uids[begin..end.min(uids.len())].join(","); let range = uids[begin..end.min(uids.len())].join(",");
let msgs = self let fetches = self
.sess .sess
.fetch(&range, "(UID FLAGS ENVELOPE INTERNALDATE)") .fetch(&range, "(UID FLAGS ENVELOPE INTERNALDATE)")
.chain_err(|| format!("Cannot fetch range `{}`", &range))? .chain_err(|| format!("Cannot fetch range `{}`", &range))?;
.iter() // .iter()
.map(Msg::from) // .map(|fetch| Msg::from(fetch))
.collect::<Vec<_>>(); // .collect::<Vec<_>>();
Ok(Msgs(msgs)) Ok(fetches)
} }
pub fn read_msg(&mut self, mbox: &str, uid: &str) -> Result<Vec<u8>> { pub fn read_msg(&mut self, mbox: &str, uid: &str) -> Result<Vec<u8>> {

View file

@ -9,6 +9,7 @@ mod smtp;
mod table; mod table;
mod flag { mod flag {
pub(crate) mod cli; pub(crate) mod cli;
pub(crate) mod model;
} }
use crate::app::App; use crate::app::App;

View file

@ -10,6 +10,7 @@ use std::{fmt, result};
use uuid::Uuid; use uuid::Uuid;
use crate::config::{Account, Config}; use crate::config::{Account, Config};
use crate::flag::model::{Flag, Flags};
use crate::table::{self, DisplayRow, DisplayTable}; use crate::table::{self, DisplayRow, DisplayTable};
error_chain! { error_chain! {
@ -170,29 +171,29 @@ impl<'a> ReadableMsg {
// Message // Message
#[derive(Debug, Serialize, PartialEq)] // #[derive(Debug, Serialize, PartialEq)]
#[serde(rename_all = "lowercase")] // #[serde(rename_all = "lowercase")]
pub enum Flag { // pub enum Flag {
Seen, // Seen,
Answered, // Answered,
Flagged, // Flagged,
} // }
impl Flag { // impl Flag {
fn from_imap_flag(flag: &imap::types::Flag<'_>) -> Option<Self> { // fn from_imap_flag(flag: &imap::types::Flag<'_>) -> Option<Self> {
match flag { // match flag {
imap::types::Flag::Seen => Some(Self::Seen), // imap::types::Flag::Seen => Some(Self::Seen),
imap::types::Flag::Answered => Some(Self::Answered), // imap::types::Flag::Answered => Some(Self::Answered),
imap::types::Flag::Flagged => Some(Self::Flagged), // imap::types::Flag::Flagged => Some(Self::Flagged),
_ => None, // _ => None,
} // }
} // }
} // }
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct Msg { pub struct Msg<'m> {
pub uid: u32, pub uid: u32,
pub flags: Vec<Flag>, pub flags: Flags<'m>,
pub subject: String, pub subject: String,
pub sender: String, pub sender: String,
pub date: String, pub date: String,
@ -201,11 +202,11 @@ pub struct Msg {
raw: Vec<u8>, raw: Vec<u8>,
} }
impl From<Vec<u8>> for Msg { impl<'m> From<Vec<u8>> for Msg<'m> {
fn from(raw: Vec<u8>) -> Self { fn from(raw: Vec<u8>) -> Self {
Self { Self {
uid: 0, uid: 0,
flags: vec![], flags: Flags::new(&[]),
subject: String::from(""), subject: String::from(""),
sender: String::from(""), sender: String::from(""),
date: String::from(""), date: String::from(""),
@ -214,23 +215,19 @@ impl From<Vec<u8>> for Msg {
} }
} }
impl From<String> for Msg { impl<'m> From<String> for Msg<'m> {
fn from(raw: String) -> Self { fn from(raw: String) -> Self {
Self::from(raw.as_bytes().to_vec()) Self::from(raw.as_bytes().to_vec())
} }
} }
impl From<&imap::types::Fetch> for Msg { impl<'m> From<&'m imap::types::Fetch> for Msg<'m> {
fn from(fetch: &imap::types::Fetch) -> Self { fn from(fetch: &'m imap::types::Fetch) -> Self {
match fetch.envelope() { match fetch.envelope() {
None => Self::from(fetch.body().unwrap_or_default().to_vec()), None => Self::from(fetch.body().unwrap_or_default().to_vec()),
Some(envelope) => Self { Some(envelope) => Self {
uid: fetch.uid.unwrap_or_default(), uid: fetch.uid.unwrap_or_default(),
flags: fetch flags: Flags::new(fetch.flags()),
.flags()
.into_iter()
.filter_map(Flag::from_imap_flag)
.collect::<Vec<_>>(),
subject: envelope subject: envelope
.subject .subject
.and_then(|subj| rfc2047_decoder::decode(subj).ok()) .and_then(|subj| rfc2047_decoder::decode(subj).ok())
@ -251,32 +248,8 @@ impl From<&imap::types::Fetch> for Msg {
} }
} }
impl<'a> Msg { impl<'m> Msg<'m> {
pub fn display_flags(&self) -> String { pub fn parse(&'m self) -> Result<mailparse::ParsedMail<'m>> {
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<mailparse::ParsedMail<'a>> {
Ok(mailparse::parse_mail(&self.raw)?) 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<table::Cell> { fn to_row(&self) -> Vec<table::Cell> {
use crate::table::*; use crate::table::*;
@ -561,7 +534,7 @@ impl DisplayRow for Msg {
vec![ vec![
Cell::new(&[unseen.to_owned(), RED], &self.uid.to_string()), 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), FlexCell::new(&[unseen.to_owned(), GREEN], &self.subject),
Cell::new(&[unseen.to_owned(), BLUE], &self.sender), Cell::new(&[unseen.to_owned(), BLUE], &self.sender),
Cell::new(&[unseen.to_owned(), YELLOW], &self.date), Cell::new(&[unseen.to_owned(), YELLOW], &self.date),
@ -572,9 +545,9 @@ impl DisplayRow for Msg {
// Msgs // Msgs
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct Msgs(pub Vec<Msg>); pub struct Msgs<'m>(pub Vec<Msg<'m>>);
impl<'a> DisplayTable<'a, Msg> for Msgs { impl<'m> DisplayTable<'m, Msg<'m>> for Msgs<'m> {
fn header_row() -> Vec<table::Cell> { fn header_row() -> Vec<table::Cell> {
use crate::table::*; use crate::table::*;
@ -587,12 +560,18 @@ impl<'a> DisplayTable<'a, Msg> for Msgs {
] ]
} }
fn rows(&self) -> &Vec<Msg> { fn rows(&self) -> &Vec<Msg<'m>> {
&self.0 &self.0
} }
} }
impl fmt::Display for Msgs { impl<'m> From<&'m imap::types::ZeroCopy<Vec<imap::types::Fetch>>> for Msgs<'m> {
fn from(fetches: &'m imap::types::ZeroCopy<Vec<imap::types::Fetch>>) -> Self {
Self(fetches.iter().map(Msg::from).collect::<Vec<_>>())
}
}
impl<'m> fmt::Display for Msgs<'m> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "\n{}", self.to_table()) write!(f, "\n{}", self.to_table())
} }