Add watcher and input threads

embed
Manos Pitsidianakis 2017-09-28 18:06:35 +03:00
parent 5ed4c37e52
commit 577889f7da
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
11 changed files with 219 additions and 93 deletions

View File

@ -24,6 +24,7 @@ crossbeam = "^0.3.0"
fnv = "1.0.3"
encoding = "0.2.33"
bitflags = "1.0"
notify = "4.0.1"
[dependencies.ncurses]
features = ["wide"]

8
README
View File

@ -1,10 +1,10 @@
__
__/ \__
/ \__/ \__ .
\__/ \__/ \ , _ , _ ___ | '
/ \__ \__/ |' `|' `. .' ` | |
\__/ \__/ \ | | | |----' | |
\__/ \__/ / ' / `.___, /\__ /
\__/ \__/ \ , _ , _ ___ '
/ \__ \__/ │' `│ `┒ .' ` │ │
\__/ \__/ \ │ │ │ |────' │ │
\__/ \__/ / `.___, /\__ /
\__/
terminal mail user agent

View File

@ -21,22 +21,47 @@
mod ui;
use ui::index::*;
use ui::ThreadEvent;
extern crate melib;
use melib::*;
use mailbox::*;
use conf::*;
extern crate ncurses;
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
use std::thread;
fn main() {
let locale_conf = ncurses::LcCategory::all;
ncurses::setlocale(locale_conf, "en_US.UTF-8");
let set = Settings::new();
let ui = ui::TUI::initialize();
let (sender, receiver): (SyncSender<ThreadEvent>, Receiver<ThreadEvent>) =
sync_channel(::std::mem::size_of::<ThreadEvent>());
{
let sender = sender.clone();
let mut ch = None;
thread::Builder::new()
.name("input-thread".to_string())
.spawn(move || loop {
ch = ncurses::get_wch();
if let Some(k) = ch {
sender.send(ThreadEvent::Input(k)).unwrap();
}
})
.unwrap();
}
let mut j = 0;
let folder_length = set.accounts["norn"].folders.len();
let mut account = Account::new("norn".to_string(), set.accounts["norn"].clone());
{
let sender = sender.clone();
account.watch(RefreshEventConsumer::new(Box::new(move |r| {
sender.send(ThreadEvent::from(r)).unwrap();
})));
}
'main: loop {
ncurses::touchwin(ncurses::stdscr());
ncurses::mv(0, 0);
@ -50,37 +75,48 @@ fn main() {
index.draw();
let mut ch;
'inner: loop {
ch = ncurses::get_wch();
match ch {
Some(ncurses::WchResult::KeyCode(k @ ncurses::KEY_UP)) |
Some(ncurses::WchResult::KeyCode(k @ ncurses::KEY_DOWN)) => {
index.handle_input(k);
continue;
}
Some(ncurses::WchResult::Char(k @ 10)) => {
index.handle_input(k as i32);
continue;
}
Some(ncurses::WchResult::KeyCode(ncurses::KEY_F1)) |
Some(ncurses::WchResult::Char(113)) => {
break 'main;
}
Some(ncurses::WchResult::Char(74)) => if j < folder_length - 1 {
j += 1;
break 'inner;
match receiver.recv().unwrap() {
ThreadEvent::Input(k) => match k {
ncurses::WchResult::KeyCode(k @ ncurses::KEY_UP)
| ncurses::WchResult::KeyCode(k @ ncurses::KEY_DOWN) => {
index.handle_input(k);
continue;
}
ncurses::WchResult::Char(k @ 10) => {
index.handle_input(k as i32);
continue;
}
ncurses::WchResult::KeyCode(k @ ncurses::KEY_F1) => {
if !index.handle_input(k) {
break 'main;
}
}
ncurses::WchResult::Char(113) => {
break 'main;
}
ncurses::WchResult::Char(74) => {
if j < folder_length - 1 {
j += 1;
break 'inner;
}
}
ncurses::WchResult::Char(75) => {
if j > 0 {
j -= 1;
break 'inner;
}
}
ncurses::WchResult::KeyCode(ncurses::KEY_RESIZE) => {
eprintln!("key_resize");
index.redraw();
continue;
}
_ => {}
},
Some(ncurses::WchResult::Char(75)) => if j > 0 {
j -= 1;
break 'inner;
},
Some(ncurses::WchResult::KeyCode(ncurses::KEY_RESIZE)) => {
eprintln!("key_resize");
index.redraw();
continue;
ThreadEvent::RefreshMailbox { name: n } => {
eprintln!("Refresh mailbox {}", n);
}
_ => {}
}
}
}

View File

@ -35,3 +35,8 @@ extern crate encoding;
#[macro_use]
extern crate bitflags;
pub use mailbox::*;
pub use conf::*;
pub use mailbox::backends::RefreshEventConsumer;

View File

@ -20,6 +20,7 @@
*/
use mailbox::*;
use mailbox::backends::RefreshEventConsumer;
use conf::AccountSettings;
use std::ops::{Index, IndexMut};
#[derive(Debug)]
@ -30,9 +31,11 @@ pub struct Account {
sent_folder: Option<usize>,
settings: AccountSettings,
pub backend: Box<MailBackend>,
}
use mailbox::backends::maildir::MaildirType;
impl Account {
pub fn new(name: String, settings: AccountSettings) -> Self {
eprintln!("new acc");
@ -44,6 +47,7 @@ impl Account {
for _ in 0..settings.folders.len() {
folders.push(None);
}
let backend = Box::new(MaildirType::new(""));
Account {
name: name,
folders: folders,
@ -51,8 +55,12 @@ impl Account {
sent_folder: sent_folder,
settings: settings,
backend: backend,
}
}
pub fn watch(&self, r: RefreshEventConsumer) -> () {
self.backend.watch(r, &self.settings.folders);
}
}
impl Index<usize> for Account {

View File

@ -21,18 +21,23 @@
use mailbox::email::{Envelope, Flag};
use error::{MeliError, Result};
use mailbox::backends::{BackendOp, BackendOpGenerator, MailBackend};
use mailbox::backends::{BackendOp, BackendOpGenerator, MailBackend, RefreshEvent, RefreshEventConsumer};
use mailbox::email::parser;
extern crate notify;
use self::notify::{Watcher, RecursiveMode, watcher};
use std::time::Duration;
use std::sync::mpsc::channel;
//use std::sync::mpsc::sync_channel;
//use std::sync::mpsc::SyncSender;
//use std::time::Duration;
use std::thread;
extern crate crossbeam;
use std::path::PathBuf;
use memmap::{Mmap, Protection};
pub struct MaildirType {
path: String,
}
#[derive(Debug, Default)]
pub struct MaildirOp {
path: String,
@ -64,9 +69,10 @@ impl BackendOp for MaildirOp {
fn as_bytes(&mut self) -> Result<&[u8]> {
if self.slice.is_none() {
self.slice = Some(
Mmap::open_path(self.path.to_string(), Protection::Read).unwrap(),
Mmap::open_path(self.path.to_string(), Protection::Read)?,
);
}
/* Unwrap is safe since we use ? above. */
Ok(unsafe { self.slice.as_ref().unwrap().as_slice() })
}
fn fetch_headers(&mut self) -> Result<&[u8]> {
@ -107,6 +113,12 @@ impl BackendOp for MaildirOp {
}
#[derive(Debug)]
pub struct MaildirType {
path: String,
}
impl MailBackend for MaildirType {
fn get(&self) -> Result<Vec<Envelope>> {
self.get_multicore(4)
@ -132,6 +144,34 @@ impl MailBackend for MaildirType {
Ok(r)
*/
}
fn watch(&self, sender: RefreshEventConsumer, folders: &[String]) -> () {
let folders = folders.to_vec();
thread::Builder::new().name("folder watch".to_string()).spawn(move || {
let (tx, rx) = channel();
let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
for f in folders {
if MaildirType::is_valid(&f).is_err() {
continue;
}
let mut p = PathBuf::from(&f);
p.push("cur");
watcher.watch(&p, RecursiveMode::NonRecursive).unwrap();
p.pop();
p.push("new");
watcher.watch(&p, RecursiveMode::NonRecursive).unwrap();
eprintln!("watching {:?}", f);
}
loop {
match rx.recv() {
Ok(event) => {
sender.send(RefreshEvent { folder: format!("{:?}", event) });
}
Err(e) => eprintln!("watch error: {:?}", e),
}
}
}).unwrap();
}
}
impl MaildirType {
@ -198,9 +238,10 @@ panic!("didn't parse"); },
let mut local_r: Vec<Envelope> = Vec::with_capacity(chunk.len());
for e in chunk {
let e_copy = e.to_string();
if let Some(e) = Envelope::from(Box::new(BackendOpGenerator::new(
if let Some(mut e) = Envelope::from(Box::new(BackendOpGenerator::new(
Box::new(move || Box::new(MaildirOp::new(e_copy.clone()))),
))) {
e.populate_headers();
local_r.push(e);
}
}

View File

@ -25,10 +25,26 @@ use error::Result;
use std::fmt;
pub trait MailBackend {
fn get(&self) -> Result<Vec<Envelope>>;
pub struct RefreshEvent {
pub folder: String,
}
pub struct RefreshEventConsumer(Box<Fn(RefreshEvent) -> ()>);
unsafe impl Send for RefreshEventConsumer {}
unsafe impl Sync for RefreshEventConsumer {}
impl RefreshEventConsumer {
pub fn new(b: Box<Fn(RefreshEvent) -> ()>) -> Self {
RefreshEventConsumer(b)
}
pub fn send(&self, r: RefreshEvent) -> () {
self.0(r);
}
}
pub trait MailBackend: ::std::fmt::Debug {
fn get(&self) -> Result<Vec<Envelope>>;
fn watch(&self, sender:RefreshEventConsumer, folders: &[String]) -> ();
//fn new(folders: &Vec<String>) -> Box<Self>;
}
/// A `BackendOp` manages common operations for the various mail backends. They only live for the
/// duration of the operation. They are generated by `BackendOpGenerator` on demand.

View File

@ -156,6 +156,7 @@ impl AttachmentBuilder {
self
}
fn decode(&self) -> String {
// TODO: Use charset for decoding
match self.content_transfer_encoding {
ContentTransferEncoding::Base64 => {
match ::base64::decode(&::std::str::from_utf8(&self.raw)
@ -178,8 +179,8 @@ impl AttachmentBuilder {
}
}
ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_text(&self.raw)
.to_full_result()
.unwrap(),
.to_full_result()
.unwrap(),
ContentTransferEncoding::_7Bit |
ContentTransferEncoding::_8Bit |
ContentTransferEncoding::Other { .. } => {

View File

@ -41,31 +41,25 @@ pub struct Mailbox {
pub collection: Vec<Envelope>,
pub threaded_collection: Vec<usize>,
threads: Vec<Container>,
length: usize,
}
impl Mailbox {
pub fn new(path: &str, sent_folder: &Option<Result<Mailbox>>) -> Result<Mailbox> {
let mut collection: Vec<Envelope> = maildir::MaildirType::new(path).get()?;
for e in &mut collection {
e.populate_headers();
}
collection.sort_by(|a, b| a.get_date().cmp(&b.get_date()));
let (threads, threaded_collection) = build_threads(&mut collection, sent_folder);
let length = collection.len();
Ok(Mailbox {
path: path.to_string(),
collection: collection,
threads: threads,
length: length,
threaded_collection: threaded_collection,
})
}
pub fn get_length(&self) -> usize {
self.length
self.collection.len()
}
pub fn get_threaded_mail(&self, i: usize) -> usize {
let thread = self.threads[self.threaded_collection[i]];

View File

@ -21,6 +21,8 @@
use mailbox::email::Envelope;
use mailbox::*;
use error::MeliError;
use super::pager::Pager;
use std::error::Error;
extern crate ncurses;
@ -28,7 +30,7 @@ extern crate ncurses;
pub trait Window {
fn draw(&mut self) -> ();
fn redraw(&mut self) -> ();
fn handle_input(&mut self, input: i32) -> ();
fn handle_input(&mut self, input: i32) -> bool;
}
pub struct ErrorWindow {
@ -46,7 +48,7 @@ impl Window for ErrorWindow {
ncurses::waddstr(self.win, &self.description);
ncurses::wrefresh(self.win);
}
fn handle_input(&mut self, _: i32) -> () {}
fn handle_input(&mut self, _: i32) -> bool { false }
}
impl ErrorWindow {
pub fn new(err: MeliError) -> Self {
@ -78,12 +80,14 @@ pub struct Index {
threaded: bool,
cursor_idx: usize,
pager: Option<Pager>,
}
impl Window for Index {
fn draw(&mut self) {
if self.mailbox.get_length() == 0 {
if self.get_length() == 0 {
return;
}
let mut x = 0;
@ -199,7 +203,7 @@ impl Window for Index {
fn redraw(&mut self) -> () {
ncurses::wnoutrefresh(self.win);
ncurses::doupdate();
if self.mailbox.get_length() == 0 {
if self.get_length() == 0 {
return;
}
/* Draw newly highlighted entry */
@ -224,13 +228,31 @@ impl Window for Index {
);
ncurses::wrefresh(self.win);
}
fn handle_input(&mut self, motion: i32) {
if self.mailbox.get_length() == 0 {
return;
fn handle_input(&mut self, motion: i32) -> bool {
if self.get_length() == 0 {
return false;
}
ncurses::getmaxyx(self.win, &mut self.screen_height, &mut self.screen_width);
if self.screen_height == 0 {
return;
return false;
}
if self.pager.is_some() {
match motion {
m @ ncurses::KEY_UP |
m @ ncurses::KEY_DOWN |
m @ ncurses::KEY_NPAGE |
m @ ncurses::KEY_PPAGE => {
self.pager.as_mut().unwrap().scroll(m);
return true;
},
ncurses::KEY_F1 => {
self.pager = None;
self.redraw();
return true;
},
_ => {}
}
return false;
}
let mut x = 0;
let mut y = 0;
@ -240,19 +262,19 @@ impl Window for Index {
ncurses::KEY_UP => if self.cursor_idx > 0 {
self.cursor_idx -= 1;
} else {
return;
return false;
},
ncurses::KEY_DOWN => if self.cursor_idx < self.mailbox.get_length() - 1 {
ncurses::KEY_DOWN => if self.cursor_idx < self.get_length() - 1 {
self.cursor_idx += 1;
} else {
return;
return false;
},
10 => {
self.show_pager();
self.redraw();
return true;
}
_ => {
return;
return false;
}
}
@ -318,10 +340,10 @@ impl Window for Index {
* i-1
*/
if pminrow != pminrow_prev &&
pminrow + self.screen_height > self.mailbox.get_length() as i32
pminrow + self.screen_height > self.get_length() as i32
{
/* touch dead entries in index (tell ncurses to redraw the empty lines next refresh) */
let live_entries = self.mailbox.get_length() as i32 - pminrow;
let live_entries = self.get_length() as i32 - pminrow;
ncurses::wredrawln(self.win, live_entries, self.screen_height);
ncurses::wrefresh(self.win);
}
@ -334,6 +356,7 @@ impl Window for Index {
self.screen_height - 1,
self.screen_width - 1,
);
return true;
}
}
impl Index {
@ -365,15 +388,6 @@ impl Index {
ncurses::printw(&format!("Mailbox {} is empty.\n", mailbox.path));
ncurses::refresh();
}
let mut color = true;
let mut thread_color = Vec::with_capacity(mailbox_length);
for i in &mailbox.threaded_collection {
let container = mailbox.get_thread(*i);
if !container.has_parent() {
color = !color;
}
thread_color.push(color);
}
Index {
mailbox: mailbox,
win: win,
@ -382,9 +396,16 @@ impl Index {
screen_height: 0,
threaded: true,
cursor_idx: 0,
pager: None,
}
}
fn get_length(&self) -> usize {
if self.threaded {
self.mailbox.threaded_collection.len()
} else {
self.mailbox.get_length()
}
}
/* draw_entry() doesn't take &mut self because borrow checker complains if it's called from
* another method. */
fn draw_entry(
@ -465,7 +486,7 @@ impl Index {
ncurses::wattroff(win, attr);
}
fn show_pager(&mut self) {
if self.mailbox.get_length() == 0 {
if self.get_length() == 0 {
return;
}
ncurses::getmaxyx(self.win, &mut self.screen_height, &mut self.screen_width);
@ -475,22 +496,11 @@ impl Index {
} else {
&mut self.mailbox.collection[self.cursor_idx]
};
let mut pager = super::pager::Pager::new(self.win, x);
let mut pager = Pager::new(self.win, x);
/* TODO: Fix this: */
pager.scroll(ncurses::KEY_DOWN);
pager.scroll(ncurses::KEY_UP);
let mut ch = ncurses::getch();
while ch != ncurses::KEY_F(1) {
match ch {
m @ ncurses::KEY_UP |
m @ ncurses::KEY_DOWN |
m @ ncurses::KEY_NPAGE |
m @ ncurses::KEY_PPAGE => {
pager.scroll(m);
}
_ => {}
}
ch = ncurses::getch();
}
self.pager = Some(pager);
}
}
impl Drop for Index {

View File

@ -23,6 +23,8 @@ pub mod index;
pub mod pager;
extern crate ncurses;
extern crate melib;
use melib::mailbox::backends::RefreshEvent;
/* Color pairs; foreground && background. */
pub static COLOR_PAIR_DEFAULT: i16 = 1;
@ -64,3 +66,15 @@ impl Drop for TUI {
ncurses::endwin();
}
}
pub enum ThreadEvent {
Input(ncurses::WchResult),
RefreshMailbox{ name: String },
//Decode { _ }, // For gpg2 signature check
}
impl From<RefreshEvent> for ThreadEvent {
fn from(event: RefreshEvent) -> Self {
ThreadEvent::RefreshMailbox { name: event.folder }
}
}