list: mail viewer

This commit is contained in:
FliegendeWurst 2021-03-29 16:35:15 +02:00 committed by Arne Keller
parent 9658862e25
commit 19347d3249
5 changed files with 303 additions and 8 deletions

174
Cargo.lock generated
View File

@ -134,6 +134,27 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.6.1" version = "1.6.1"
@ -149,6 +170,12 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]] [[package]]
name = "fallible-iterator" name = "fallible-iterator"
version = "0.2.0" version = "0.2.0"
@ -161,6 +188,16 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "gethostname" name = "gethostname"
version = "0.2.1" version = "0.2.1"
@ -171,6 +208,17 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "getrandom"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.9.1" version = "0.9.1"
@ -223,8 +271,10 @@ dependencies = [
"itertools", "itertools",
"maildir", "maildir",
"mailparse", "mailparse",
"moins",
"rusqlite", "rusqlite",
"rustls-connector", "rustls-connector",
"rustyline",
] ]
[[package]] [[package]]
@ -314,6 +364,35 @@ version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "moins"
version = "0.5.0"
source = "git+https://github.com/FliegendeWurst/moins?branch=master#fd6d6ccebbe33effe49d13fd26ff2214f1655d41"
dependencies = [
"termion",
]
[[package]]
name = "nibble_vec"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [
"smallvec",
]
[[package]]
name = "nix"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa9b4819da1bc61c0ea48b63b7bc8604064dd43013e7cc325df098d49cd7c18a"
dependencies = [
"bitflags",
"cc",
"cfg-if",
"libc",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "5.1.2" version = "5.1.2"
@ -344,6 +423,12 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.7.2" version = "1.7.2"
@ -386,6 +471,44 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2" checksum = "47b080c5db639b292ac79cbd34be0cfc5d36694768d8341109634d90b86930e2"
[[package]]
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]]
name = "redox_syscall"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_termios"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
dependencies = [
"redox_syscall",
]
[[package]]
name = "redox_users"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom",
"redox_syscall",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.4.5" version = "1.4.5"
@ -469,6 +592,29 @@ dependencies = [
"security-framework", "security-framework",
] ]
[[package]]
name = "rustyline"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e1b597fcd1eeb1d6b25b493538e5aa19629eb08932184b85fef931ba87e893"
dependencies = [
"bitflags",
"cfg-if",
"dirs-next",
"fs2",
"libc",
"log",
"memchr",
"nix",
"radix_trie",
"scopeguard",
"smallvec",
"unicode-segmentation",
"unicode-width",
"utf8parse",
"winapi",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.5" version = "1.0.5"
@ -485,6 +631,12 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "sct" name = "sct"
version = "0.6.0" version = "0.6.0"
@ -538,15 +690,27 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.65" version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663" checksum = "6498a9efc342871f91cc2d0d694c674368b4ceb40f62b65a7a08c3792935e702"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "termion"
version = "1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
dependencies = [
"libc",
"numtoa",
"redox_syscall",
"redox_termios",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.44" version = "0.1.44"
@ -582,6 +746,12 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "utf8parse"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.11" version = "0.2.11"

View File

@ -17,3 +17,5 @@ ascii_table = { git = "https://gitlab.com/arnekeller/ascii-table.git", branch =
chrono = "0.4.19" chrono = "0.4.19"
# remove when 0.24.3 is released # remove when 0.24.3 is released
rusqlite = { git = "https://github.com/rusqlite/rusqlite", branch = "master", features = ["bundled"] } rusqlite = { git = "https://github.com/rusqlite/rusqlite", branch = "master", features = ["bundled"] }
rustyline = "8.0.0"
moins = { git = "https://github.com/FliegendeWurst/moins", branch = "master" }

125
src/bin/list.rs Normal file
View File

@ -0,0 +1,125 @@
use std::{collections::HashMap, env};
use ascii_table::{Align, AsciiTable, Column};
use chrono::{Local, NaiveDateTime, TimeZone};
use inboxid::*;
use itertools::Itertools;
use mailparse::{MailHeaderMap, dateparse};
use rustyline::{Editor, error::ReadlineError};
fn main() -> Result<()> {
let args = env::args().collect_vec();
if args.len() > 1 {
show_listing(&args[1])
} else {
show_listing("INBOX")
}
}
fn show_listing(mailbox: &str) -> Result<()> {
let maildir = get_maildir(mailbox)?;
let mut rows = Vec::new();
let mut mail_list = Vec::new();
let mut i = 0;
// TODO(refactor) merge with new
let mut list = maildir.list_cur_sorted(Box::new(|name| {
// sort by UID
name.splitn(2, '_').nth(1).map(|x| x.parse().unwrap_or(0)).unwrap_or(0)
})).collect_vec();
let list = list.iter_mut().map(
|x| x.as_mut().map(|x| (x.flags().to_owned(), x.id().to_owned(), x.parsed()))).collect_vec();
for maile in &list {
match maile {
Ok((flags, id, Ok(mail))) => {
let headers = mail.get_headers();
let from = headers.get_all_values("From").join(" ");
let subj = headers.get_all_values("Subject").join(" ");
let date = headers.get_all_values("Date").join(" ");
let date = dateparse(&date).map(|x| {
let dt = Local.from_utc_datetime(&NaiveDateTime::from_timestamp(x, 0));
dt.format("%Y-%m-%d %H:%M").to_string()
}).unwrap_or(date);
let mut flags_display = String::new();
if flags.contains('F') {
flags_display.push('+');
}
if flags.contains('R') {
flags_display.push('R');
}
if flags.contains('S') {
flags_display.push(' ');
} else {
flags_display.push('*');
}
rows.push(vec![i.to_string(), flags_display, from, subj, date]);
i += 1;
mail_list.push((flags, id, mail));
}
Ok((_, _, Err(e))) => {
println!("error parsing mail: {:?}", e);
}
Err(e) => {
println!("error: {:?}", e);
}
}
}
rows.sort_unstable_by(|x, y| x[4].cmp(&y[4]));
let count = rows.len();
let mut mails = HashMap::new();
for (i, row) in rows.iter_mut().enumerate() {
mails.insert(count - i, &mail_list[row[0].parse::<usize>().unwrap()]);
row[0] = (count - i).to_string();
}
let mut ascii_table = AsciiTable::default();
ascii_table.draw_lines = false;
ascii_table.max_width = usize::MAX;
for (i, &(header, align)) in [
("i", Align::Right),
("---", Align::Right),
("From", Align::Left),
("Subject", Align::Left),
("Date", Align::Left),
].iter().enumerate() {
let mut column = Column::default();
column.header = header.to_owned();
column.align = align;
column.max_width = usize::MAX;
ascii_table.columns.insert(i, column);
}
ascii_table.print(rows); // prints a 0 if empty :)
if mails.is_empty() {
return Ok(());
}
let mut rl = Editor::<()>::new();
loop {
let readline = rl.readline(">> ");
match readline {
Ok(line) => {
let mail = &mails[&line.trim().parse::<usize>().unwrap()];
println!("{}", std::str::from_utf8(&mail.2.get_body_raw().unwrap()).unwrap());
for x in &mail.2.subparts {
if x.ctype.mimetype == "text/html" {
continue; // TODO
}
let mut content = x.get_body().unwrap();
moins::Moins::run(&mut content, None);
}
},
Err(ReadlineError::Interrupted) => {
break
},
Err(ReadlineError::Eof) => {
break
},
Err(err) => {
println!("Error: {:?}", err);
break
}
}
}
Ok(())
}

View File

@ -23,7 +23,7 @@ fn show_listing(mailbox: &str) -> Result<()> {
let mut seen = Vec::new(); let mut seen = Vec::new();
for mut maile in maildir.list_new_sorted(Box::new(|name| { for mut maile in maildir.list_new_sorted(Box::new(|name| {
// sort by UID // sort by UID
name.splitn(2, '_').nth(1).map(|x| x.parse().unwrap_or(0)).unwrap_or(0) u32::MAX - name.splitn(2, '_').nth(1).map(|x| x.parse().unwrap_or(0)).unwrap_or(0)
})) { })) {
match maile.as_mut().map(|x| x.parsed()) { match maile.as_mut().map(|x| x.parsed()) {
Ok(Ok(mail)) => { Ok(Ok(mail)) => {

View File

@ -1,5 +1,3 @@
#![feature(string_remove_matches)]
use std::{collections::HashMap, env}; use std::{collections::HashMap, env};
use imap::types::Flag; use imap::types::Flag;
@ -130,17 +128,17 @@ fn sync(
if flags.contains(&Flag::Seen) { if flags.contains(&Flag::Seen) {
f.push('S'); f.push('S');
} else { } else {
f.remove_matches('S'); f = f.replace('S', "");
} }
if flags.contains(&Flag::Answered) { if flags.contains(&Flag::Answered) {
f.push('R'); f.push('R');
} else { } else {
f.remove_matches('R'); f = f.replace('R', "");
} }
if flags.contains(&Flag::Flagged) { if flags.contains(&Flag::Flagged) {
f.push('F'); f.push('F');
} else { } else {
f.remove_matches('F'); f = f.replace('F', "");
} }
Maildir::normalize_flags(&f) Maildir::normalize_flags(&f)
}); });