mirror of
https://github.com/FliegendeWurst/inboxid.git
synced 2024-11-08 10:20:39 +00:00
browse: take the thread view from list
This commit is contained in:
parent
f264b7230d
commit
6d59a24cd4
128
src/bin/browse.rs
Normal file
128
src/bin/browse.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use std::{array::IntoIter, cell::RefCell, cmp, collections::{HashMap, HashSet}, env};
|
||||
|
||||
use inboxid::*;
|
||||
use itertools::Itertools;
|
||||
use mailparse::ParsedMail;
|
||||
use petgraph::{EdgeDirection, graph::{DiGraph, NodeIndex}, visit::{Dfs, IntoNodeReferences}};
|
||||
|
||||
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 mails = Vec::new();
|
||||
for x in maildir.list_cur() {
|
||||
mails.push(x?);
|
||||
}
|
||||
let mut mails = maildir.get_mails(&mut mails)?;
|
||||
mails.sort_by_key(|x| x.date);
|
||||
|
||||
let mut rows = Vec::new();
|
||||
for (i, mail) in mails.iter().enumerate() {
|
||||
let flags = &mail.flags;
|
||||
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(IntoIter::new([(mails.len() - i).to_string(), flags_display, mail.from.clone(), mail.subject.clone(), mail.date_iso.clone()]));
|
||||
}
|
||||
|
||||
let mut mails_by_id = HashMap::new();
|
||||
let mut threads: HashMap<_, Vec<_>> = HashMap::new();
|
||||
for mail in &mails {
|
||||
let mid = mail.get_header("Message-ID");
|
||||
threads.entry(mid.clone()).or_default().push(mail);
|
||||
if mails_by_id.insert(mid, mail).is_some() {
|
||||
println!("error: missing/duplicate Message-ID");
|
||||
return Ok(());
|
||||
}
|
||||
for value in mail.get_header_values("References") {
|
||||
for mid in value.split(' ').map(ToOwned::to_owned) {
|
||||
threads.entry(mid).or_default().push(mail);
|
||||
}
|
||||
}
|
||||
for value in mail.get_header_values("In-Reply-To") {
|
||||
for mid in value.split(' ').map(ToOwned::to_owned) {
|
||||
threads.entry(mid).or_default().push(mail);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut threads = threads.into_iter().collect_vec();
|
||||
threads.sort_unstable_by_key(|(_, mails)| mails.len());
|
||||
threads.reverse();
|
||||
let mut graph = DiGraph::new();
|
||||
let mut nodes = HashMap::new();
|
||||
let mut nodes_inv = HashMap::new();
|
||||
for mail in &mails {
|
||||
let node = graph.add_node(mail);
|
||||
nodes.insert(mail, node);
|
||||
nodes_inv.insert(node, mail);
|
||||
}
|
||||
for mail in &mails {
|
||||
for value in mail.get_header_values("In-Reply-To") {
|
||||
for mid in value.split(' ').map(ToOwned::to_owned) {
|
||||
if let Some(other_mail) = mails_by_id.get(&mid) {
|
||||
graph.add_edge(nodes[other_mail], nodes[mail], ());
|
||||
} else {
|
||||
let pseudomail = Box::leak(Box::new(EasyMail::new_pseudo(mid.clone())));
|
||||
let node = graph.add_node(pseudomail);
|
||||
nodes.insert(pseudomail, node);
|
||||
nodes_inv.insert(node, pseudomail);
|
||||
graph.add_edge(node, nodes[mail], ());
|
||||
mails_by_id.insert(mid, pseudomail);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut roots = graph.node_references().filter(|x| graph.neighbors_directed(x.0, EdgeDirection::Incoming).count() == 0).collect_vec();
|
||||
roots.sort_unstable_by_key(|x| x.1.date);
|
||||
let mails_printed = RefCell::new(HashSet::new());
|
||||
|
||||
// recursive lambda
|
||||
struct PrintThread<'a> {
|
||||
f: &'a dyn Fn(&PrintThread, NodeIndex, usize)
|
||||
}
|
||||
let print_thread = |this: &PrintThread, node, depth| {
|
||||
let mail = nodes_inv[&node];
|
||||
if mails_printed.borrow().contains(mail) && depth == 0 {
|
||||
return;
|
||||
}
|
||||
println!("{}{}", " ".repeat(depth), mail.subject);
|
||||
mails_printed.borrow_mut().insert(mail);
|
||||
let mut replies = graph.neighbors_directed(node, EdgeDirection::Outgoing).collect_vec();
|
||||
replies.sort_unstable_by_key(|&idx| {
|
||||
let mut maximum = &nodes_inv[&idx].date;
|
||||
let mut dfs = Dfs::new(&graph, idx);
|
||||
while let Some(idx) = dfs.next(&graph) {
|
||||
let other = nodes_inv[&idx];
|
||||
maximum = cmp::max(maximum, &other.date);
|
||||
}
|
||||
maximum
|
||||
});
|
||||
for r in replies {
|
||||
(this.f)(this, r, depth + 1);
|
||||
}
|
||||
};
|
||||
let print_thread = PrintThread { f: &print_thread };
|
||||
|
||||
for root in roots {
|
||||
(print_thread.f)(&print_thread, root.0, 0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
use std::{array::IntoIter, cell::RefCell, cmp, collections::{HashMap, HashSet}, env, fs};
|
||||
use std::{array::IntoIter, collections::HashSet, env, fs};
|
||||
|
||||
use ascii_table::{Align, AsciiTable, Column};
|
||||
use inboxid::*;
|
||||
use itertools::Itertools;
|
||||
use mailparse::ParsedMail;
|
||||
use petgraph::{EdgeDirection, dot::Dot, graph::{DiGraph, NodeIndex}, visit::{Dfs, IntoNodeReferences, Walker}};
|
||||
use rustyline::{Editor, error::ReadlineError};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@ -44,87 +43,6 @@ fn show_listing(mailbox: &str) -> Result<()> {
|
||||
rows.push(IntoIter::new([(mails.len() - i).to_string(), flags_display, mail.from.clone(), mail.subject.clone(), mail.date_iso.clone()]));
|
||||
}
|
||||
|
||||
let mut mails_by_id = HashMap::new();
|
||||
let mut threads: HashMap<_, Vec<_>> = HashMap::new();
|
||||
for mail in &mails {
|
||||
let mid = mail.get_header("Message-ID");
|
||||
threads.entry(mid.clone()).or_default().push(mail);
|
||||
if mails_by_id.insert(mid, mail).is_some() {
|
||||
println!("error: missing/duplicate Message-ID");
|
||||
return Ok(());
|
||||
}
|
||||
for value in mail.get_header_values("References") {
|
||||
for mid in value.split(' ').map(ToOwned::to_owned) {
|
||||
threads.entry(mid).or_default().push(mail);
|
||||
}
|
||||
}
|
||||
for value in mail.get_header_values("In-Reply-To") {
|
||||
for mid in value.split(' ').map(ToOwned::to_owned) {
|
||||
threads.entry(mid).or_default().push(mail);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut threads = threads.into_iter().collect_vec();
|
||||
threads.sort_unstable_by_key(|(_, mails)| mails.len());
|
||||
threads.reverse();
|
||||
let mut graph = DiGraph::new();
|
||||
let mut nodes = HashMap::new();
|
||||
let mut nodes_inv = HashMap::new();
|
||||
for mail in &mails {
|
||||
let node = graph.add_node(mail);
|
||||
nodes.insert(mail, node);
|
||||
nodes_inv.insert(node, mail);
|
||||
}
|
||||
for mail in &mails {
|
||||
for value in mail.get_header_values("In-Reply-To") {
|
||||
for mid in value.split(' ').map(ToOwned::to_owned) {
|
||||
if let Some(other_mail) = mails_by_id.get(&mid) {
|
||||
graph.add_edge(nodes[other_mail], nodes[mail], ());
|
||||
} else {
|
||||
let pseudomail = Box::leak(Box::new(EasyMail::new_pseudo(mid.clone())));
|
||||
let node = graph.add_node(pseudomail);
|
||||
nodes.insert(pseudomail, node);
|
||||
nodes_inv.insert(node, pseudomail);
|
||||
graph.add_edge(node, nodes[mail], ());
|
||||
mails_by_id.insert(mid, pseudomail);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut roots = graph.node_references().filter(|x| graph.neighbors_directed(x.0, EdgeDirection::Incoming).count() == 0).collect_vec();
|
||||
roots.sort_unstable_by_key(|x| x.1.date);
|
||||
let mails_printed = RefCell::new(HashSet::new());
|
||||
|
||||
struct PrintThread<'a> {
|
||||
f: &'a dyn Fn(&PrintThread, NodeIndex, usize)
|
||||
}
|
||||
let print_thread = |this: &PrintThread, node, depth| {
|
||||
let mail = nodes_inv[&node];
|
||||
if mails_printed.borrow().contains(mail) && depth == 0 {
|
||||
return;
|
||||
}
|
||||
println!("{}{}", " ".repeat(depth), mail.subject);
|
||||
mails_printed.borrow_mut().insert(mail);
|
||||
let mut replies = graph.neighbors_directed(node, EdgeDirection::Outgoing).collect_vec();
|
||||
replies.sort_unstable_by_key(|&idx| {
|
||||
let mut maximum = &nodes_inv[&idx].date;
|
||||
let mut dfs = Dfs::new(&graph, idx);
|
||||
while let Some(idx) = dfs.next(&graph) {
|
||||
let other = nodes_inv[&idx];
|
||||
maximum = cmp::max(maximum, &other.date);
|
||||
}
|
||||
maximum
|
||||
});
|
||||
for r in replies {
|
||||
(this.f)(this, r, depth + 1);
|
||||
}
|
||||
};
|
||||
let print_thread = PrintThread { f: &print_thread };
|
||||
|
||||
for root in roots {
|
||||
(print_thread.f)(&print_thread, root.0, 0);
|
||||
}
|
||||
|
||||
let mut ascii_table = AsciiTable::default();
|
||||
ascii_table.draw_lines = false;
|
||||
ascii_table.max_width = usize::MAX;
|
||||
|
Loading…
Reference in New Issue
Block a user