summaryrefslogtreecommitdiffstats
path: root/src/views/maillist.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/views/maillist.rs')
-rw-r--r--src/views/maillist.rs240
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),
+ }
+ }
+
+}
+