browse: show mail author and timestamp

This commit is contained in:
FliegendeWurst 2021-04-05 20:48:37 +02:00 committed by Arne Keller
parent a171abfdae
commit 7d2e6de827
6 changed files with 125 additions and 40 deletions

40
Cargo.lock generated
View File

@ -213,7 +213,7 @@ dependencies = [
"log", "log",
"num", "num",
"owning_ref", "owning_ref",
"syn 1.0.67", "syn 1.0.68",
"unicode-segmentation", "unicode-segmentation",
"unicode-width", "unicode-width",
"wasmer_enumset", "wasmer_enumset",
@ -223,11 +223,11 @@ dependencies = [
[[package]] [[package]]
name = "cursive_tree_view" name = "cursive_tree_view"
version = "0.7.0" version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://github.com/FliegendeWurst/cursive_tree_view.git?branch=master#f3a0470f229a5ab57da600cf552b916ad33c6390"
checksum = "cd59affc6d600a69df27972fb5fe7f38a743aeb0710299e3324022c7d80717a0"
dependencies = [ dependencies = [
"cursive_core", "cursive_core",
"debug_stub_derive", "debug_stub_derive",
"unicode-segmentation",
] ]
[[package]] [[package]]
@ -251,7 +251,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.9", "quote 1.0.9",
"strsim", "strsim",
"syn 1.0.67", "syn 1.0.68",
] ]
[[package]] [[package]]
@ -262,7 +262,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [ dependencies = [
"darling_core", "darling_core",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.67", "syn 1.0.68",
] ]
[[package]] [[package]]
@ -335,7 +335,7 @@ checksum = "e5c450cf304c9e18d45db562025a14fb1ca0f5c769b6f609309f81d4c31de455"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.67", "syn 1.0.68",
] ]
[[package]] [[package]]
@ -525,8 +525,8 @@ checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
[[package]] [[package]]
name = "libsqlite3-sys" name = "libsqlite3-sys"
version = "0.21.0" version = "0.22.0"
source = "git+https://github.com/rusqlite/rusqlite?branch=master#ed3bfbdf9d9e577e8d4cff937b053a0e429a4cd7" source = "git+https://github.com/rusqlite/rusqlite?branch=master#ddf69f749a67fdb673837ebb3c881a522fd7f638"
dependencies = [ dependencies = [
"cc", "cc",
"pkg-config", "pkg-config",
@ -595,9 +595,9 @@ dependencies = [
[[package]] [[package]]
name = "mime2ext" name = "mime2ext"
version = "0.1.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b88947611258697e12f8602a44003b0885ca5fe30f27132d63c8f47fe98f2f2e" checksum = "d8b337a0b7c1d5d8d3c08096823831a5d7a3f6aadea1150b7fe622c38c14e283"
dependencies = [ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
@ -751,9 +751,9 @@ checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.24" version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec"
dependencies = [ dependencies = [
"unicode-xid 0.2.1", "unicode-xid 0.2.1",
] ]
@ -851,8 +851,8 @@ dependencies = [
[[package]] [[package]]
name = "rusqlite" name = "rusqlite"
version = "0.24.2" version = "0.25.0"
source = "git+https://github.com/rusqlite/rusqlite?branch=master#ed3bfbdf9d9e577e8d4cff937b053a0e429a4cd7" source = "git+https://github.com/rusqlite/rusqlite?branch=master#ddf69f749a67fdb673837ebb3c881a522fd7f638"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"fallible-iterator", "fallible-iterator",
@ -995,7 +995,7 @@ checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.67", "syn 1.0.68",
] ]
[[package]] [[package]]
@ -1081,9 +1081,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.67" version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6498a9efc342871f91cc2d0d694c674368b4ceb40f62b65a7a08c3792935e702" checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.9", "quote 1.0.9",
@ -1206,7 +1206,7 @@ dependencies = [
"log", "log",
"proc-macro2", "proc-macro2",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.67", "syn 1.0.68",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -1228,7 +1228,7 @@ checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.67", "syn 1.0.68",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -1258,7 +1258,7 @@ dependencies = [
"darling", "darling",
"proc-macro2", "proc-macro2",
"quote 1.0.9", "quote 1.0.9",
"syn 1.0.67", "syn 1.0.68",
] ]
[[package]] [[package]]

View File

@ -25,4 +25,7 @@ subprocess = "0.2.6"
mime2ext = "0.1.2" mime2ext = "0.1.2"
petgraph = "0.5.1" petgraph = "0.5.1"
cursive = { version = "0.16.3", default-features = false, features = ["termion-backend"] } cursive = { version = "0.16.3", default-features = false, features = ["termion-backend"] }
cursive_tree_view = "0.7.0" cursive_tree_view = { git = "https://github.com/FliegendeWurst/cursive_tree_view.git", branch = "master" }
[profile.release]
overflow-checks = true

View File

@ -6,7 +6,7 @@ use cursive::{Cursive, CursiveExt};
use cursive::traits::Identifiable; use cursive::traits::Identifiable;
use cursive::view::{Scrollable, SizeConstraint}; use cursive::view::{Scrollable, SizeConstraint};
use cursive::views::{LinearLayout, ResizedView, TextView}; use cursive::views::{LinearLayout, ResizedView, TextView};
use cursive_tree_view::{Placement, TreeView}; use cursive_tree_view::{Placement, TreeEntry, TreeView};
use inboxid::*; use inboxid::*;
use io::Write; use io::Write;
use itertools::Itertools; use itertools::Itertools;
@ -25,8 +25,8 @@ fn main() -> Result<()> {
} }
}); });
match result { match result {
Ok(res) => res, Ok(res) => res,
Err(_) => { Err(_) => {
if let Err(e) = io::stderr().lock().write_all(&sink.lock().unwrap()) { if let Err(e) = io::stderr().lock().write_all(&sink.lock().unwrap()) {
println!("{:?}", e); println!("{:?}", e);
} }
@ -62,7 +62,7 @@ fn show_listing(mailbox: &str) -> Result<()> {
} else { } else {
flags_display.push('*'); flags_display.push('*');
} }
rows.push(IntoIter::new([(mails.len() - i).to_string(), flags_display, mail.from.clone(), mail.subject.clone(), mail.date_iso.clone()])); rows.push(IntoIter::new([(mails.len() - i).to_string(), flags_display, mail.from(), mail.subject.clone(), mail.date_iso.clone()]));
} }
let mut mails_by_id = HashMap::new(); let mut mails_by_id = HashMap::new();
@ -251,15 +251,17 @@ struct MailPart {
} }
impl Display for MailPart { impl Display for MailPart {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.part.ctype.mimetype) write!(f, "{}", self.part.ctype.mimetype)
} }
} }
impl From<&'static ParsedMail<'static>> for MailPart { impl From<&'static ParsedMail<'static>> for MailPart {
fn from(part: &'static ParsedMail<'static>) -> Self { fn from(part: &'static ParsedMail<'static>) -> Self {
Self { Self {
part part
} }
} }
} }
impl TreeEntry for MailPart {}

View File

@ -40,7 +40,7 @@ fn show_listing(mailbox: &str) -> Result<()> {
} else { } else {
flags_display.push('*'); flags_display.push('*');
} }
rows.push(IntoIter::new([(mails.len() - i).to_string(), flags_display, mail.from.clone(), mail.subject.clone(), mail.date_iso.clone()])); rows.push(IntoIter::new([(mails.len() - i).to_string(), flags_display, mail.from(), mail.subject.clone(), mail.date_iso.clone()]));
} }
let mut ascii_table = AsciiTable::default(); let mut ascii_table = AsciiTable::default();

View File

@ -26,7 +26,7 @@ fn show_listing(mailbox: &str) -> Result<()> {
let mut rows = Vec::new(); let mut rows = Vec::new();
for mail in &mails { for mail in &mails {
rows.push(IntoIter::new([mail.from.clone(), mail.subject.clone(), mail.date_iso.clone()])); rows.push(IntoIter::new([mail.from(), mail.subject.clone(), mail.date_iso.clone()]));
} }
let mut ascii_table = AsciiTable::default(); let mut ascii_table = AsciiTable::default();

View File

@ -1,10 +1,12 @@
use std::{borrow::Cow, convert::{TryFrom, TryInto}, env, fmt::{Debug, Display}, fs, hash::Hash, io, net::TcpStream, ops::Deref}; use std::{borrow::Cow, cmp, convert::{TryFrom, TryInto}, env, fmt::{Debug, Display}, fs, hash::Hash, io, net::TcpStream, ops::Deref};
use anyhow::Context; use anyhow::Context;
use chrono::{DateTime, Local, NaiveDateTime, TimeZone}; use chrono::{DateTime, Local, NaiveDateTime, TimeZone};
use cursive::{theme::Style, utils::span::{IndexedCow, IndexedSpan, SpannedString}};
use cursive_tree_view::TreeEntry;
use imap::{Session, types::Flag}; use imap::{Session, types::Flag};
use maildir::{MailEntry, Maildir}; use maildir::{MailEntry, Maildir};
use mailparse::{MailHeaderMap, ParsedMail, dateparse}; use mailparse::{MailHeaderMap, ParsedMail, SingleInfo, addrparse, dateparse};
use petgraph::{Graph, graph::NodeIndex}; use petgraph::{Graph, graph::NodeIndex};
use rusqlite::{Connection, params}; use rusqlite::{Connection, params};
use rustls_connector::{RustlsConnector, rustls::{ClientSession, StreamOwned}}; use rustls_connector::{RustlsConnector, rustls::{ClientSession, StreamOwned}};
@ -96,7 +98,7 @@ pub struct EasyMail<'a> {
mail: Option<ParsedMail<'a>>, mail: Option<ParsedMail<'a>>,
pub id: MaildirID, pub id: MaildirID,
pub flags: String, pub flags: String,
pub from: String, from: SingleInfo,
pub subject: String, pub subject: String,
pub date: DateTime<Local>, pub date: DateTime<Local>,
pub date_iso: String, pub date_iso: String,
@ -108,7 +110,10 @@ impl EasyMail<'_> {
mail: None, mail: None,
id: MaildirID::new(0, 0), id: MaildirID::new(0, 0),
flags: "S".to_owned(), flags: "S".to_owned(),
from: String::new(), from: SingleInfo {
display_name: None,
addr: String::new()
},
subject, subject,
date: Local.from_utc_datetime(&NaiveDateTime::from_timestamp(0, 0)), date: Local.from_utc_datetime(&NaiveDateTime::from_timestamp(0, 0)),
date_iso: "????-??-??".to_owned() date_iso: "????-??-??".to_owned()
@ -119,6 +124,10 @@ impl EasyMail<'_> {
self.mail.is_none() self.mail.is_none()
} }
pub fn from(&self) -> String {
self.from.display_name.as_deref().unwrap_or_default().to_owned()
}
pub fn get_header(&self, header: &str) -> String { pub fn get_header(&self, header: &str) -> String {
self.get_headers().get_all_values(header).join(" ") self.get_headers().get_all_values(header).join(" ")
} }
@ -151,7 +160,8 @@ impl Eq for EasyMail<'_> {}
impl Hash for EasyMail<'_> { impl Hash for EasyMail<'_> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state); self.id.hash(state);
self.from.hash(state); self.from.display_name.hash(state);
self.from.addr.hash(state);
self.subject.hash(state); self.subject.hash(state);
} }
} }
@ -164,6 +174,76 @@ impl<'a> Deref for EasyMail<'a> {
} }
} }
impl TreeEntry for &EasyMail<'_> {
fn display(&self, width: usize) -> SpannedString<Style> {
if self.is_pseudo() {
return self.subject.clone().into();
}
let from = self.from();
let mut line = self.subject.clone();
let mut i = width.saturating_sub(1 + from.len() + 1 + self.date_iso.len());
while i <= line.len() && !line.is_char_boundary(i) {
if i == 0 {
break;
}
i -= 1;
}
line.truncate(i);
let subj_len = line.len();
while line.len() < i {
line.push(' ');
}
line.push(' ');
line += &from;
line.push(' ');
line += &self.date_iso;
let spans = vec![
IndexedSpan {
content: IndexedCow::Borrowed {
start: 0,
end: subj_len
},
attr: Style::default(),
width: subj_len
},
IndexedSpan {
content: IndexedCow::Borrowed {
start: 0,
end: 0
},
attr: Style::default(),
width: line.len() - subj_len - from.len() - self.date_iso.len() - 1
},
IndexedSpan {
content: IndexedCow::Borrowed {
start: line.len() - self.date_iso.len() - 1 - from.len(),
end: line.len() - self.date_iso.len() - 1
},
attr: Style::default(),
width: from.len()
},
IndexedSpan {
content: IndexedCow::Borrowed {
start: 0,
end: 0
},
attr: Style::default(),
width: 1
},
IndexedSpan {
content: IndexedCow::Borrowed {
start: line.len() - self.date_iso.len(),
end: line.len()
},
attr: Style::default(),
width: self.date_iso.len()
},
];
SpannedString::with_spans(&line, spans)
}
}
pub trait MailExtension { pub trait MailExtension {
fn get_tree_structure<'a>(&'a self, graph: &mut Graph<&'a ParsedMail<'a>, ()>, parent: Option<NodeIndex>); fn get_tree_structure<'a>(&'a self, graph: &mut Graph<&'a ParsedMail<'a>, ()>, parent: Option<NodeIndex>);
fn print_tree_structure(&self, depth: usize, counter: &mut usize); fn print_tree_structure(&self, depth: usize, counter: &mut usize);
@ -232,7 +312,7 @@ impl MaildirExtension for Maildir {
let flags = maile.flags().to_owned(); let flags = maile.flags().to_owned();
let mail = maile.parsed()?; let mail = maile.parsed()?;
let headers = mail.get_headers(); let headers = mail.get_headers();
let from = headers.get_all_values("From").join(" "); let from = addrparse(&headers.get_all_values("From").join(" "))?.extract_single_info().context("failed to extract from")?;
let subject = headers.get_all_values("Subject").join(" "); let subject = headers.get_all_values("Subject").join(" ");
let date = headers.get_all_values("Date").join(" "); let date = headers.get_all_values("Date").join(" ");
let date = dateparse(&date).map(|x| let date = dateparse(&date).map(|x|
@ -259,7 +339,7 @@ impl MaildirExtension for Maildir {
let flags = maile.flags().to_owned(); let flags = maile.flags().to_owned();
let mail = maile.parsed()?; let mail = maile.parsed()?;
let headers = mail.get_headers(); let headers = mail.get_headers();
let from = headers.get_all_values("From").join(" "); let from = addrparse(&headers.get_all_values("From").join(" "))?.extract_single_info().context("failed to extract from")?;
let subject = headers.get_all_values("Subject").join(" "); let subject = headers.get_all_values("Subject").join(" ");
let date = headers.get_all_values("Date").join(" "); let date = headers.get_all_values("Date").join(" ");
let date = dateparse(&date).map(|x| let date = dateparse(&date).map(|x|