summaryrefslogtreecommitdiffstats
path: root/src/views/mail.rs
blob: 93c781a927bfe011215d87e08f395ac24f93f307 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use std::path::PathBuf;
use anyhow::Result;
use anyhow::Error;

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::TextView;
use cursive::views::LinearLayout;
use cursive::views::ScrollView;
use result_inspect::ResultInspect;
use mailparse::ParsedMail;

pub struct MailView {
    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());
                let buf = std::fs::read(msg.filename())?;
                debug!("Found {} bytes from {}", buf.len(), msg.filename().display());

                let parsed = mailparse::parse_mail(buf)?;
                MailView::parsed_mail_to_list_of_textviews(&parsed)
            })
            .fold(Ok(LinearLayout::vertical()), |ll: Result<_>, views: Result<Vec<TextView>>| {
                ll.and_then(|mut l| {
                    views?.into_iter().for_each(|e| l.add_child(e));
                    Ok(l)
                })
            })?;

        let view = if view.len() == 0 {
            debug!("Falling back to mailfile parsing");
            LinearLayout::vertical().child({
                std::fs::read(&mailfile)
                    .map_err(Error::from)
                    .and_then(|b| String::from_utf8(b).map_err(Error::from))
                    .inspect(|s| debug!("Found {} bytes from {}", s.bytes().len(), mailfile.display()))
                    .map(TextView::new)?
            })
        } else {
            view
        };

        Ok(MailView { view: view.scrollable() }.with_name(name))
    }

    fn parsed_mail_to_list_of_textviews<'a>(pm: &'a ParsedMail) -> Result<Vec<TextView>> {
        fn collect_into<'a>(v: &mut Vec<TextView>, pm: &'a ParsedMail) -> Result<()> {
            v.push(pm.get_body().map(TextView::new)?);

            pm.subparts
                .iter()
                .map(|subp| collect_into(v, subp))
                .collect::<Result<Vec<_>>>()
                .map(|_| ())
        }

        let mut vec = Vec::new();
        collect_into(&mut vec, pm).map(|_| vec)
    }

}

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()
    }

}