mirror of
https://github.com/soywod/himalaya.git
synced 2024-07-08 18:45:13 +00:00
extract flags from msg model
This commit is contained in:
parent
563f9a6538
commit
e108729491
|
@ -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();
|
||||||
|
|
|
@ -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
86
src/flag/model.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
52
src/imap.rs
52
src/imap.rs
|
@ -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>> {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
101
src/msg.rs
101
src/msg.rs
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue