summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMatthias Beyer <mail@beyermatthias.de>2020-07-23 20:36:52 +0200
committerMatthias Beyer <mail@beyermatthias.de>2020-08-03 17:22:41 +0200
commit632725d650d4c6975cc7c3563627e09662667709 (patch)
tree55ab934d10f0b4294a0132c43e8357be6c6852b0
parentbdeb6d82f81860ed836c7c40bcb3c4e2ba292f5b (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.toml1
-rw-r--r--src/mail_view.rs108
-rw-r--r--src/maillist_view.rs72
-rw-r--r--src/main.rs1
-rw-r--r--src/main_view.rs5
5 files changed, 164 insertions, 23 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 4b7ffc4..78d8006 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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));