diff options
Diffstat (limited to 'src/views/maillist.rs')
-rw-r--r-- | src/views/maillist.rs | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/src/views/maillist.rs b/src/views/maillist.rs new file mode 100644 index 0000000..dc13bf3 --- /dev/null +++ b/src/views/maillist.rs @@ -0,0 +1,240 @@ +use std::path::PathBuf; +use std::ops::Deref; + +use anyhow::Result; +use anyhow::Context; +use cursive::Cursive; +use cursive::Printer; +use cursive::Rect; +use cursive::View; +use cursive::XY; +use cursive::direction::Direction; +use cursive::view::Nameable; +use cursive::view::Selector; +use cursive::event::Event; +use cursive::event::EventResult; +use cursive::views::NamedView; +use cursive_table_view::TableView; +use cursive_table_view::TableViewItem; +use chrono::naive::NaiveDateTime; +use notmuch::Message; +use notmuch::MessageOwner; +use getset::Getters; +use cursive::views::ResizedView; +use cursive::view::SizeConstraint; + +use crate::views::main::MainView; +use crate::views::mail::MailView; + +pub struct MaillistView { + view: ResizedView<TableView<MailListingData, MailListingColumn>>, +} + +impl Deref for MaillistView { + type Target = TableView<MailListingData, MailListingColumn>; + fn deref(&self) -> &Self::Target { + self.view.get_inner() + } +} + +impl MaillistView { + pub fn create_for(database_path: PathBuf, query: &str, name: String) -> Result<NamedView<Self>> { + debug!("Getting '{}' from '{}'", query, 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 = notmuch::Database::open(&database_path, notmuch::DatabaseMode::ReadOnly) + .context(format!("Opening database {}", database_path.display()))? + .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::<Result<Vec<_>>>() + .context(format!("Creating MaillinglistView for '{}' on {}", query, database_path.display()))?; + + debug!("Found {} entries", items.len()); + let n = name.clone(); + let db_path = database_path.clone(); + let view = TableView::<MailListingData, MailListingColumn>::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(&n, move |table: &mut MaillistView| { + table.view + .get_inner_mut() + .borrow_item(row) + .map(|data| { + debug!("Opening: {:?}", data); + (data.mail_id.clone(), data.filename.clone()) + }) + }) + .unwrap() + .unwrap(); + + debug!("Showing mail {}", mail_id); + + // Why do I have to do this? This is UGLY! + let n = n.clone(); + let db_path = db_path.clone(); + let name = format!("{}-{}", n, mail_id); + let mv = MailView::create_for(db_path, mail_id, filename, name).unwrap(); + + siv.call_on_name(crate::views::main::MAIN_MUX_NAME, move |mux: &mut cursive_multiplex::Mux| { + mux.add_right_of(mv, mux.root().build().unwrap()); + }); + + // use the mail ID to get the whole thread and open it as a table item + }); + + Ok(MaillistView { view: ResizedView::new(SizeConstraint::Full, SizeConstraint::Full, view )}.with_name(name)) + } + + pub fn borrow_item(&mut self, idx: usize) -> Option<&MailListingData> { + self.view.get_inner_mut().borrow_item(idx) + } +} + +impl View for MaillistView { + fn draw(&self, printer: &Printer) { + self.view.draw(printer) + } + + fn layout(&mut self, xy: XY<usize>) { + self.view.layout(xy) + } + + fn needs_relayout(&self) -> bool { + self.view.needs_relayout() + } + + fn required_size(&mut self, constraint: XY<usize>) -> XY<usize> { + self.view.required_size(constraint) + } + + fn on_event(&mut self, e: Event) -> EventResult { + self.view.on_event(e) + } + + fn call_on_any<'a>(&mut self, s: &Selector, tpl: &'a mut (dyn FnMut(&mut (dyn View + 'static)) + 'a)) { + self.view.call_on_any(s, tpl); + } + + fn focus_view(&mut self, s: &Selector) -> Result<(), ()> { + self.view.focus_view(s) + } + + fn take_focus(&mut self, source: Direction) -> bool { + self.view.take_focus(source) + } + + fn important_area(&self, view_size: XY<usize>) -> Rect { + self.view.important_area(view_size) + } + + fn type_name(&self) -> &'static str { + self.view.type_name() + } + +} + +#[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<String>, + + #[getset(get = "pub")] + date: String, + + #[getset(get = "pub")] + from: String, + + #[getset(get = "pub")] + to: String, + + #[getset(get = "pub")] + subject: String, +} + +impl TableViewItem<MailListingColumn> 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), + } + } + +} + |