diff options
author | Matthias Beyer <mail@beyermatthias.de> | 2020-07-23 20:36:52 +0200 |
---|---|---|
committer | Matthias Beyer <mail@beyermatthias.de> | 2020-08-03 17:22:41 +0200 |
commit | 632725d650d4c6975cc7c3563627e09662667709 (patch) | |
tree | 55ab934d10f0b4294a0132c43e8357be6c6852b0 | |
parent | bdeb6d82f81860ed836c7c40bcb3c4e2ba292f5b (diff) |
Add viewing of a mail
Right now this only reads the mail without parsing it.
Signed-off-by: Matthias Beyer <mail@beyermatthias.de>
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/mail_view.rs | 108 | ||||
-rw-r--r-- | src/maillist_view.rs | 72 | ||||
-rw-r--r-- | src/main.rs | 1 | ||||
-rw-r--r-- | src/main_view.rs | 5 |
5 files changed, 164 insertions, 23 deletions
@@ -22,6 +22,7 @@ serde = { version = "1.0", features = ["derive"] } walkdir = "2" cursive = "0.15" +cursive_core = "0.1" [dependencies.cursive_tree_view] git = "https://github.com/matthiasbeyer/cursive_tree_view" diff --git a/src/mail_view.rs b/src/mail_view.rs new file mode 100644 index 0000000..eb446ce --- /dev/null +++ b/src/mail_view.rs @@ -0,0 +1,108 @@ +use std::path::PathBuf; +use anyhow::Result; +use anyhow::Error; + +use cursive::Cursive; +use cursive::Printer; +use cursive::Rect; +use cursive::View; +use cursive::XY; +use cursive::direction::Direction; +use cursive::event::Event; +use cursive::event::EventResult; +use cursive::view::Nameable; +use cursive::view::Selector; +use cursive::view::Scrollable; +use cursive::views::NamedView; +use cursive::views::ResizedView; +use cursive::views::TextView; +use cursive::views::LinearLayout; +use cursive::views::ScrollView; + +pub struct MailView { + database_path: PathBuf, + view: ScrollView<LinearLayout>, +} + +impl MailView { + + pub fn create_for(database_path: PathBuf, id: String, mailfile: PathBuf, name: String) -> Result<NamedView<Self>> { + let query = format!("id:{}", id); + let view = notmuch::Database::open(&database_path, notmuch::DatabaseMode::ReadOnly)? + .create_query(&query)? + .search_messages()? + .map(|msg| { + debug!("Constructing textview for '{}'", msg.filename().display()); + MailView::path_to_textview(msg.filename()) + }) + .fold(Ok(LinearLayout::vertical()), |r, el| { + el.and_then(|e| { + r.map(|lv| lv.child(e)) + }) + })?; + + let view = if view.len() == 0 { + debug!("Falling back to mailfile parsing"); + LinearLayout::vertical() + .child(MailView::path_to_textview(mailfile)?) + } else { + view + }; + + Ok(MailView { database_path, view: view.scrollable() }.with_name(name)) + } + + fn path_to_textview(pb: PathBuf) -> Result<TextView> { + let s = std::fs::read(&pb) + .map_err(Error::from) + .and_then(|b| String::from_utf8(b).map_err(Error::from))?; + + debug!("Found {} bytes from {}", s.bytes().len(), pb.display()); + + Ok(TextView::new(s)) + } + +} + +impl View for MailView { + 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() + } + +} diff --git a/src/maillist_view.rs b/src/maillist_view.rs index aaf36c4..d20c37d 100644 --- a/src/maillist_view.rs +++ b/src/maillist_view.rs @@ -8,20 +8,28 @@ 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::ResizedView; +use cursive::views::NamedView; use cursive_table_view::TableView; use cursive_table_view::TableViewItem; use chrono::naive::NaiveDateTime; use notmuch::Message; use notmuch::MessageOwner; -pub struct MaillistView(TableView<MailListingData, MailListingColumn>); +use crate::main_view::MainView; +use crate::mail_view::MailView; + +pub struct MaillistView { + view: TableView<MailListingData, MailListingColumn>, + database_path: PathBuf, +} impl MaillistView { - pub fn create_for(database_path: &PathBuf, query: &str, name: String) -> Result<Self> { + 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 { @@ -36,16 +44,17 @@ impl MaillistView { } } - let items = notmuch::Database::open(database_path, notmuch::DatabaseMode::ReadOnly) + 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 tags = msg.tags().collect(); - let date = NaiveDateTime::from_timestamp_opt(msg.date(), 0) + 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()); @@ -59,6 +68,7 @@ impl MaillistView { Ok(MailListingData { mail_id, + filename, tags, date, from, @@ -70,6 +80,8 @@ impl MaillistView { .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 tab = TableView::<MailListingData, MailListingColumn>::new() .column(MailListingColumn::Date, "Date", |c| c.width(20)) .column(MailListingColumn::Tags, "Tags", |c| c.width(20)) @@ -80,58 +92,77 @@ impl MaillistView { .items(items) .selected_item(0) .on_submit(move |siv: &mut Cursive, row: usize, _: usize| { - let mail_id = siv.call_on_name(&name, move |table: &mut ResizedView<TableView<MailListingData, MailListingColumn>>| { - table.get_inner_mut() + let (mail_id, filename) = siv.call_on_name(&n, move |table: &mut MaillistView| { + table.view .borrow_item(row) - .map(|data| data.mail_id.clone()) + .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(); + + siv.call_on_name(crate::main_view::MAIN_VIEW_NAME, move |main_view: &mut MainView| { + let name = format!("{}-{}", n, mail_id); + debug!("Creating MailView '{}' for {} ({}) in {}", name, mail_id, filename.display(), db_path.display()); + let mv = MailView::create_for(db_path, mail_id, filename, name.clone()).unwrap(); + + main_view.add_tab(name, mv); }); // use the mail ID to get the whole thread and open it as a table item }); - Ok(MaillistView(tab)) + Ok(MaillistView{ view: tab, database_path }.with_name(name)) } } impl View for MaillistView { fn draw(&self, printer: &Printer) { - self.0.draw(printer) + self.view.draw(printer) } fn layout(&mut self, xy: XY<usize>) { - self.0.layout(xy) + self.view.layout(xy) } fn needs_relayout(&self) -> bool { - self.0.needs_relayout() + self.view.needs_relayout() } fn required_size(&mut self, constraint: XY<usize>) -> XY<usize> { - self.0.required_size(constraint) + self.view.required_size(constraint) } fn on_event(&mut self, e: Event) -> EventResult { - self.0.on_event(e) + 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.0.call_on_any(s, tpl); + self.view.call_on_any(s, tpl); } fn focus_view(&mut self, s: &Selector) -> Result<(), ()> { - self.0.focus_view(s) + self.view.focus_view(s) } fn take_focus(&mut self, source: Direction) -> bool { - self.0.take_focus(source) + self.view.take_focus(source) } fn important_area(&self, view_size: XY<usize>) -> Rect { - self.0.important_area(view_size) + self.view.important_area(view_size) } fn type_name(&self) -> &'static str { - self.0.type_name() + self.view.type_name() } } @@ -148,6 +179,7 @@ pub enum MailListingColumn { #[derive(Clone, Debug)] pub struct MailListingData { mail_id: String, + filename: PathBuf, tags: Vec<String>, date: String, from: String, diff --git a/src/main.rs b/src/main.rs index 9ea680c..57a1012 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ use cursive_flexi_logger_view::FlexiLoggerView; use flexi_logger::{Logger, LogTarget}; mod main_view; +mod mail_view; mod maillist_view; mod configuration; mod util; diff --git a/src/main_view.rs b/src/main_view.rs index 868efb1..ef21e85 100644 --- a/src/main_view.rs +++ b/src/main_view.rs @@ -102,8 +102,7 @@ impl MainView { .with_tab(config.notmuch_default_query().clone(), { ResizedView::new(cursive::view::SizeConstraint::Full, cursive::view::SizeConstraint::Full, - MaillistView::create_for(config.notmuch_database_path(), config.notmuch_default_query(), MAIN_MAIL_LIST_NAME.to_string())? - .with_name(MAIN_MAIL_LIST_NAME)) + MaillistView::create_for(config.notmuch_database_path().clone(), config.notmuch_default_query(), MAIN_MAIL_LIST_NAME.to_string())?) }); Ok(MainView { config, tabs }.with_name(MAIN_VIEW_NAME)) @@ -129,7 +128,7 @@ impl MainView { .map(|dbpath| { use cursive::view::SizeConstraint as SC; - let t = MaillistView::create_for(&dbpath, query, query.to_string())? + let t = MaillistView::create_for(dbpath, query, query.to_string())? .full_screen() .with_name(format!("{}-view", query)); |