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},
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();

View file

@ -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"])

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::{
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<TlsStream<TcpStream>>,
}
impl<'a> ImapConnector<'a> {
pub fn new(account: &'a Account) -> Result<Self> {
impl<'ic> ImapConnector<'ic> {
pub fn new(account: &'ic Account) -> Result<Self> {
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<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
.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::<Vec<_>>();
.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<Msgs> {
) -> Result<imap::types::ZeroCopy<Vec<imap::types::Fetch>>> {
self.sess
.select(mbox)
.chain_err(|| format!("Cannot select mailbox `{}`", mbox))?;
@ -183,15 +185,15 @@ impl<'a> ImapConnector<'a> {
.collect::<Vec<_>>();
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::<Vec<_>>();
.chain_err(|| format!("Cannot fetch range `{}`", &range))?;
// .iter()
// .map(|fetch| Msg::from(fetch))
// .collect::<Vec<_>>();
Ok(Msgs(msgs))
Ok(fetches)
}
pub fn read_msg(&mut self, mbox: &str, uid: &str) -> Result<Vec<u8>> {

View file

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

View file

@ -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<Self> {
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<Self> {
// 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<Flag>,
pub flags: Flags<'m>,
pub subject: String,
pub sender: String,
pub date: String,
@ -201,11 +202,11 @@ pub struct Msg {
raw: Vec<u8>,
}
impl From<Vec<u8>> for Msg {
impl<'m> From<Vec<u8>> for Msg<'m> {
fn from(raw: Vec<u8>) -> Self {
Self {
uid: 0,
flags: vec![],
flags: Flags::new(&[]),
subject: String::from(""),
sender: 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 {
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::<Vec<_>>(),
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<mailparse::ParsedMail<'a>> {
impl<'m> Msg<'m> {
pub fn parse(&'m self) -> Result<mailparse::ParsedMail<'m>> {
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> {
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<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> {
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
}
}
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 {
write!(f, "\n{}", self.to_table())
}