From 51813510b1ed7acdba9275d1c04b67810cd77d9e Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Mon, 16 Jul 2018 15:26:06 +0300 Subject: [PATCH] Spawn watch threads on account creation --- src/bin.rs | 41 +++++------------ src/mailbox/backends/maildir.rs | 10 +++-- src/ui/components/mail.rs | 79 ++++++++++++++++++++------------- src/ui/components/mod.rs | 10 ----- src/ui/components/utilities.rs | 2 +- src/ui/mod.rs | 12 ++++- 6 files changed, 78 insertions(+), 76 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index e8f8a25f..45abefb2 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -1,7 +1,7 @@ /* * meli - bin.rs * - * Copyright 2017 Manos Pitsidianakis + * Copyright 2017-2018 Manos Pitsidianakis * * This file is part of meli. * @@ -27,7 +27,6 @@ pub mod ui; use ui::*; pub use melib::*; -use std::sync::mpsc::{sync_channel, SyncSender, Receiver}; use std::thread; use std::io::{stdout, stdin, }; @@ -49,10 +48,11 @@ fn main() { let mut _stderr = _stderr.lock(); */ - - + /* Catch SIGWINCH to handle terminal resizing */ let signal = chan_signal::notify(&[Signal::WINCH]); + /* Create a channel to communicate with other threads. The main process is the sole receiver. + * */ let (sender, receiver) = chan::sync(::std::mem::size_of::()); { @@ -62,47 +62,30 @@ fn main() { })}).unwrap(); } - /* - let folder_length = set.accounts["test_account"].folders.len(); - let mut account = Account::new("test_account".to_string(), set.accounts["test_account"].clone(), backends); - - { - let sender = sender.clone(); - account.watch(RefreshEventConsumer::new(Box::new(move |r| { - sender.send(ThreadEvent::from(r)).unwrap(); - }))); - } - */ - let mut state = State::new(_stdout); + /* Create the application State. This is the 'System' part of an ECS architecture */ + let mut state = State::new(_stdout, sender); + /* Register some reasonably useful interfaces */ let menu = Entity {component: Box::new(AccountMenu::new(&state.context.accounts)) }; - let listing = MailListing::new(Mailbox::new_dummy()); + let listing = MailListing::new(); let b = Entity { component: Box::new(listing) }; let window = Entity { component: Box::new(VSplit::new(menu,b,90)) }; let status_bar = Entity { component: Box::new(StatusBar::new(window)) }; state.register_entity(status_bar); - /* - let mut idxa = 0; - let mut idxm = 0; - let account_length = state.context.accounts.len(); - */ + /* Keep track of the input mode. See ui::UIMode for details */ let mut mode: UIMode = UIMode::Normal; 'main: loop { - /* - state.refresh_mailbox(idxa,idxm); - */ - /* - let folder_length = state.context.accounts[idxa].len(); - */ state.render(); 'inner: loop { + /* Check if any entities have sent reply events to State. */ let events: Vec = state.context.get_replies(); for e in events { state.rcv_event(e); } state.redraw(); + /* Poll on all channels. Currently we have the input channel for stdin, watching events and the signal watcher. */ chan_select! { receiver.recv() -> r => { match r.unwrap() { @@ -122,7 +105,6 @@ fn main() { state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Input(key)}); state.redraw(); }, - _ => {} } }, UIMode::Execute => { @@ -142,6 +124,7 @@ fn main() { } }, ThreadEvent::RefreshMailbox { name : n } => { + /* Don't handle this yet. */ eprintln!("Refresh mailbox {}", n); }, ThreadEvent::UIEventType(e) => { diff --git a/src/mailbox/backends/maildir.rs b/src/mailbox/backends/maildir.rs index 86dacb95..21124917 100644 --- a/src/mailbox/backends/maildir.rs +++ b/src/mailbox/backends/maildir.rs @@ -27,7 +27,7 @@ use conf::Folder; extern crate notify; -use self::notify::{Watcher, RecursiveMode, watcher}; +use self::notify::{Watcher, RecursiveMode, watcher, DebouncedEvent}; use std::time::Duration; use std::sync::mpsc::channel; @@ -164,12 +164,16 @@ impl MailBackend for MaildirType { 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) }); + match event { + DebouncedEvent::Create(pathbuf) => { + sender.send(RefreshEvent { folder: format!("{}", pathbuf.parent().unwrap().to_str().unwrap()) }); + }, + _ => {}, + } } Err(e) => eprintln!("watch error: {:?}", e), } diff --git a/src/ui/components/mail.rs b/src/ui/components/mail.rs index 9d783d1c..7831e21a 100644 --- a/src/ui/components/mail.rs +++ b/src/ui/components/mail.rs @@ -14,6 +14,7 @@ pub struct MailListing { // TODO: sorting /// Cache current view. content: CellBuffer, + /// If we must redraw on next redraw event dirty: bool, /// If `self.pager` exists or not. unfocused: bool, @@ -21,6 +22,8 @@ pub struct MailListing { } impl MailListing { + /// Helper function to format entry strings for MailListing */ + /* TODO: Make this configurable */ fn make_entry_string(e: &Envelope, idx: usize) -> String { format!("{} {} {:.85}",idx,&e.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string(),e.get_subject()) } @@ -28,7 +31,7 @@ impl MailListing { - pub fn new(mailbox: Mailbox) -> Self { + pub fn new() -> Self { let mut content = CellBuffer::new(0, 0, Cell::with_char(' ')); MailListing { cursor_pos: (0, 1, 0), @@ -40,13 +43,19 @@ impl MailListing { pager: None, } } + /// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has + /// chosen. fn refresh_mailbox(&mut self, context: &mut Context) { self.dirty = true; self.cursor_pos.2 = 0; self.new_cursor_pos.2 = 0; self.cursor_pos.1 = self.new_cursor_pos.1; + + // Get mailbox as a reference. let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap(); + // Inform State that we changed the current folder view. context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::RefreshMailbox(mailbox.clone()) }); + self.length = mailbox.len(); let mut content = CellBuffer::new(MAX_COLS, self.length+1, Cell::with_char(' ')); if self.length == 0 { @@ -59,9 +68,13 @@ impl MailListing { self.content = content; return; } + + // Populate `CellBuffer` with every entry. + // TODO: Lazy load? let mut idx = 0; for y in 0..=self.length { if idx >= self.length { + /* No more entries left, so fill the rest of the area with empty space */ clear_area(&mut content, ((0, y), (MAX_COLS-1, self.length))); break; @@ -82,10 +95,10 @@ impl MailListing { Color::Default }; let x = write_string_to_grid(&MailListing::make_entry_string(envelope, idx), - &mut content, - fg_color, - bg_color, - ((0, y) , (MAX_COLS-1, y))); + &mut content, + fg_color, + bg_color, + ((0, y) , (MAX_COLS-1, y))); for x in x..MAX_COLS { content[(x,y)].set_ch(' '); @@ -120,7 +133,7 @@ impl MailListing { change_colors(grid, area, fg_color, bg_color); } - /// Draw only the list of `Envelope`s. + /// Draw the list of `Envelope`s. fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { if self.cursor_pos.1 != self.new_cursor_pos.1 { self.refresh_mailbox(context); @@ -167,10 +180,10 @@ impl MailListing { /// Create a pager for the `Envelope` currently under the cursor. fn draw_mail_view(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { { - let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap(); - let envelope: &Envelope = &mailbox.collection[self.cursor_pos.2]; + let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1].as_ref().unwrap().as_ref().unwrap(); + let envelope: &Envelope = &mailbox.collection[self.cursor_pos.2]; - self.pager = Some(Pager::new(envelope)); + self.pager = Some(Pager::new(envelope)); } self.pager.as_mut().map(|p| p.draw(grid, area, context)); } @@ -194,10 +207,12 @@ impl Component for MailListing { clear_area(grid, area); context.dirty_areas.push_back(area); } + // TODO: Make this configurable. User should be able to choose what headers to display, + // and toggle between full header view and custom header view like in mutt. let headers_rows: usize = 6; + /* Render the mail body in a pager, basically copy what HSplit does */ let total_rows = get_y(bottom_right) - get_y(upper_left); - /* TODO: define ratio in Configuration file */ let pager_ratio = context.settings.pager.pager_ratio; let mut bottom_entity_rows = (pager_ratio*total_rows )/100; if bottom_entity_rows < headers_rows + 2 { @@ -215,7 +230,7 @@ impl Component for MailListing { p.draw(grid, ((get_x(upper_left), get_y(upper_left) + mid + headers_rows + 1), bottom_right), context); - } + } return; } self.dirty = false; @@ -244,50 +259,50 @@ impl Component for MailListing { let envelope: &Envelope = &mailbox.collection[self.cursor_pos.2]; let x = write_string_to_grid(&format!("Date: {}", envelope.get_date_as_str()), - grid, - Color::Byte(33), - Color::Default, - (set_y(upper_left, mid+1), set_y(bottom_right, mid+1))); + grid, + Color::Byte(33), + Color::Default, + (set_y(upper_left, mid+1), set_y(bottom_right, mid+1))); for x in x..=get_x(bottom_right) { grid[(x, mid+1)].set_ch(' '); grid[(x, mid+1)].set_bg(Color::Default); grid[(x, mid+1)].set_fg(Color::Default); } let x = write_string_to_grid(&format!("From: {}", envelope.get_from()), - grid, - Color::Byte(33), - Color::Default, - (set_y(upper_left, mid+2), set_y(bottom_right, mid+2))); + grid, + Color::Byte(33), + Color::Default, + (set_y(upper_left, mid+2), set_y(bottom_right, mid+2))); for x in x..=get_x(bottom_right) { grid[(x, mid+2)].set_ch(' '); grid[(x, mid+2)].set_bg(Color::Default); grid[(x, mid+2)].set_fg(Color::Default); } let x = write_string_to_grid(&format!("To: {}", envelope.get_to()), - grid, - Color::Byte(33), - Color::Default, - (set_y(upper_left, mid+3), set_y(bottom_right, mid+3))); + grid, + Color::Byte(33), + Color::Default, + (set_y(upper_left, mid+3), set_y(bottom_right, mid+3))); for x in x..=get_x(bottom_right) { grid[(x, mid+3)].set_ch(' '); grid[(x, mid+3)].set_bg(Color::Default); grid[(x, mid+3)].set_fg(Color::Default); } let x = write_string_to_grid(&format!("Subject: {}", envelope.get_subject()), - grid, - Color::Byte(33), - Color::Default, - (set_y(upper_left, mid+4), set_y(bottom_right, mid+4))); + grid, + Color::Byte(33), + Color::Default, + (set_y(upper_left, mid+4), set_y(bottom_right, mid+4))); for x in x..=get_x(bottom_right) { grid[(x, mid+4)].set_ch(' '); grid[(x, mid+4)].set_bg(Color::Default); grid[(x, mid+4)].set_fg(Color::Default); } let x = write_string_to_grid(&format!("Message-ID: {}", envelope.get_message_id_raw()), - grid, - Color::Byte(33), - Color::Default, - (set_y(upper_left, mid+5), set_y(bottom_right, mid+5))); + grid, + Color::Byte(33), + Color::Default, + (set_y(upper_left, mid+5), set_y(bottom_right, mid+5))); for x in x..=get_x(bottom_right) { grid[(x, mid+5)].set_ch(' '); grid[(x, mid+5)].set_bg(Color::Default); @@ -366,7 +381,7 @@ impl Component for MailListing { }, } }, - UIEventType::RefreshMailbox(ref m) => { + UIEventType::RefreshMailbox(_) => { self.dirty = true; self.pager = None; }, diff --git a/src/ui/components/mod.rs b/src/ui/components/mod.rs index ea7925d4..29247e0f 100644 --- a/src/ui/components/mod.rs +++ b/src/ui/components/mod.rs @@ -26,9 +26,6 @@ use super::*; pub use utilities::*; pub use mail::*; -use std::fmt; - - use super::cells::{Color, CellBuffer}; use super::position::{Area, }; use super::{UIEvent, UIEventType, Key}; @@ -69,12 +66,6 @@ impl Entity { } } -impl fmt::Debug for Entity { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Entity", ) - } -} - /// Types implementing this Trait can draw on the terminal and receive events. /// If a type wants to skip drawing if it has not changed anything, it can hold some flag in its /// fields (eg self.dirty = false) and act upon that in their `draw` implementation. @@ -163,4 +154,3 @@ fn clear_area(grid: &mut CellBuffer, area: Area) { } } } - diff --git a/src/ui/components/utilities.rs b/src/ui/components/utilities.rs index b7c33fb8..ca443b0c 100644 --- a/src/ui/components/utilities.rs +++ b/src/ui/components/utilities.rs @@ -160,7 +160,7 @@ impl Component for Pager { let bottom_right = bottom_right!(area); self.dirty = false; - if self.height == 0 || self.height == self.cursor_pos { + if self.height == 0 || self.height == self.cursor_pos || self.width == 0 { return; } clear_area(grid, diff --git a/src/ui/mod.rs b/src/ui/mod.rs index f349df27..fb4636a2 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -44,6 +44,7 @@ use termion::{clear, style, cursor}; use termion::raw::IntoRawMode; use termion::event::{Key as TermionKey, }; +use chan::Sender; use std::io::{Write, }; @@ -145,6 +146,7 @@ pub struct State { grid: CellBuffer, stdout: termion::raw::RawTerminal, + sender: Sender, entities: Vec, pub context: Context, } @@ -158,7 +160,7 @@ impl Drop for State { } impl State { - pub fn new(stdout: W) -> Self { + pub fn new(stdout: W, sender: Sender) -> Self { let settings = Settings::new(); let backends = Backends::new(); @@ -172,6 +174,7 @@ impl State { rows: rows, grid: CellBuffer::new(cols, rows, Cell::with_char(' ')), stdout: stdout.into_raw_mode().unwrap(), + sender: sender, entities: Vec::with_capacity(1), context: Context { @@ -184,6 +187,13 @@ impl State { }; write!(s.stdout, "{}{}{}", cursor::Hide, clear::All, cursor::Goto(1,1)).unwrap(); s.stdout.flush().unwrap(); + for account in &mut s.context.accounts { + let sender = s.sender.clone(); + account.watch(RefreshEventConsumer::new(Box::new(move |r| { + sender.send(ThreadEvent::from(r)); + }))); + + } s } pub fn update_size(&mut self) {