use std::path::PathBuf; use std::rc::Rc; use anyhow::Context; use anyhow::Result; use chrono::naive::NaiveDateTime; use cursive::Cursive; use cursive::view::SizeConstraint; use cursive::views::ResizedView; use cursive_table_view::TableView; use cursive_table_view::TableViewItem; use getset::Getters; use notmuch::Message; use notmuch::MessageOwner; use crate::views::mail::MailView; use crate::runtime::Runtime; pub struct MaillistView { rt: Rc, view: ResizedView>, } impl MaillistView { pub fn for_query(rt: Rc, query: &str) -> Result { debug!("Getting '{}' from '{}'", query, rt.config().notmuch_database_path().display()); fn get_header_field_save<'o, O: MessageOwner + 'o>(msg: &Message<'o, O>, field: &str) -> String { match msg.header(field) { Err(e) => { error!("Failed getting '{}' of '{}': {}", field, msg.id(), e); String::from("---") }, Ok(None) => format!("No Value for {}", field), Ok(Some(f)) => f.to_string(), } } let items = rt.database() .create_query(query) .context("Creating the search query")? .search_messages() .context(format!("Searching for messages with '{}'", query))? .map(|msg| { let mail_id = msg.id().to_string(); let filename = msg.filename(); let tags = msg.tags().collect(); let date = NaiveDateTime::from_timestamp_opt(msg.date(), 0) .map(|ndt| ndt.to_string()) .ok_or_else(|| { error!("Failed to parse timestamp: {}", msg.date()); anyhow!("Failed to parse timestamp: {}", msg.date()) }) .context(format!("Getting the date of message {}", msg.id()))?; let from = get_header_field_save(&msg, "From"); let to = get_header_field_save(&msg, "To"); let subject = get_header_field_save(&msg, "Subject"); Ok(MailListingData { mail_id, filename, tags, date, from, to, subject, }) }) .collect::>>() .context(format!("Creating MaillinglistView for '{}' on {}", query, rt.config().notmuch_database_path().display()))?; debug!("Found {} entries", items.len()); let mailviewrt = rt.clone(); let view = TableView::::new() .column(MailListingColumn::Date, "Date", |c| c.width(20)) .column(MailListingColumn::Tags, "Tags", |c| c.width(20)) .column(MailListingColumn::From, "From", |c| c) .column(MailListingColumn::To, "To", |c| c) .column(MailListingColumn::Subject, "Subject", |c| c) .default_column(MailListingColumn::Date) .items(items) .selected_item(0) .on_submit(move |siv: &mut Cursive, row: usize, _: usize| { let (mail_id, filename) = siv.call_on_name(crate::views::main::MAIN_VIEW_NAME, move |main: &mut crate::views::main::MainView| { main.get_current_mux() .map(|mux| { if let Some(table) = mux.downcast_ref::>() { table .borrow_item(row) .map(|data| { debug!("Opening: {:?}", data); (data.mail_id.clone(), data.filename.clone()) }) } else { unimplemented!() } }) }) .unwrap() .unwrap() .unwrap(); debug!("Showing mail {}", mail_id); let mv = MailView::create_for(mailviewrt.clone(), mail_id, filename).unwrap(); siv.call_on_name(crate::views::main::MAIN_VIEW_NAME , move |main: &mut crate::views::main::MainView| { main.get_current_tab_mut() .map(|mux: &mut cursive_multiplex::Mux| { mux.add_right_of(mv, mux.focus()); }) }); // use the mail ID to get the whole thread and open it as a table item }); Ok({ MaillistView { rt, view: ResizedView::new(SizeConstraint::Full, SizeConstraint::Full, view) } }) } } impl cursive::view::ViewWrapper for MaillistView { type V = ResizedView>; fn with_view(&self, f: F) -> Option where F: FnOnce(&Self::V) -> R { Some(f(&self.view)) } fn with_view_mut(&mut self, f: F) -> Option where F: FnOnce(&mut Self::V) -> R { Some(f(&mut self.view)) } } #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum MailListingColumn { Date, Tags, From, To, Subject, } #[derive(Clone, Debug, Getters)] pub struct MailListingData { #[getset(get = "pub")] mail_id: String, #[getset(get = "pub")] filename: PathBuf, #[getset(get = "pub")] tags: Vec, #[getset(get = "pub")] date: String, #[getset(get = "pub")] from: String, #[getset(get = "pub")] to: String, #[getset(get = "pub")] subject: String, } impl TableViewItem for MailListingData { fn to_column(&self, column: MailListingColumn) -> String { match column { MailListingColumn::Date => self.date.clone(), MailListingColumn::Tags => self.tags.join(", "), MailListingColumn::From => self.from.clone(), MailListingColumn::To => self.to.clone(), MailListingColumn::Subject => self.subject.clone(), } } fn cmp(&self, other: &Self, column: MailListingColumn) -> std::cmp::Ordering where Self: Sized { match column { MailListingColumn::Date => self.date.cmp(&other.date), MailListingColumn::Tags => self.tags.cmp(&other.tags), MailListingColumn::From => self.from.cmp(&other.from), MailListingColumn::To => self.to.cmp(&other.to), MailListingColumn::Subject => self.subject.cmp(&other.subject), } } }