From 710920c67bc12d21777a8dda039661c692140c81 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Thu, 16 Aug 2018 21:20:53 +0300 Subject: [PATCH] Detect new mail events and pass them as notifications to State concerns #26 --- melib/src/mailbox/accounts.rs | 74 +++++++++++++++++++++++--- melib/src/mailbox/backends/maildir.rs | 35 ++++++------ src/bin.rs | 11 ++-- ui/src/state.rs | 76 ++++++++++++++++++--------- 4 files changed, 138 insertions(+), 58 deletions(-) diff --git a/melib/src/mailbox/accounts.rs b/melib/src/mailbox/accounts.rs index e943aa29..533a9c5c 100644 --- a/melib/src/mailbox/accounts.rs +++ b/melib/src/mailbox/accounts.rs @@ -29,6 +29,12 @@ use mailbox::backends::{Backends, RefreshEventConsumer}; use mailbox::*; use std::ops::{Index, IndexMut}; use std::result; +use std::mem; + +pub struct NewMailEvent { + pub folder: u64, + pub index: Vec, +} pub type Worker = Option>>>; @@ -93,13 +99,32 @@ impl Account { pub fn workers(&mut self) -> &mut Vec { &mut self.workers } - fn load_mailbox(&mut self, index: usize, envelopes: Result>) -> () { + fn load_mailbox(&mut self, index: usize, envelopes: Result>) -> Option { + let mut ret: Option = None; + + // TODO: Cleanup this function let folders = self.backend.folders(); let folder = &folders[index]; if self.sent_folder.is_some() { let id = self.sent_folder.unwrap(); if id == index { - self.folders[index] = Some(Mailbox::new(folder, &None, envelopes)); + /* ======================== */ + let old_m = mem::replace(&mut self.folders[index], Some(Mailbox::new(folder, &None, envelopes))); + if let Some(old_m) = old_m { + if self.folders[index].is_some() && old_m.is_ok() { + let diff = self.folders[index].as_ref().map(|v| v.as_ref().unwrap().collection.len()).unwrap_or(0).saturating_sub(old_m.as_ref().map(|v| v.collection.len()).unwrap_or(0)); + if diff > 0 { + let mut index = old_m.as_ref().unwrap().collection.iter().zip(&self.folders[index].as_ref().unwrap().as_ref().unwrap().collection).count(); + ret = Some(NewMailEvent { + folder: folder.hash(), + index: (index.saturating_sub(1)..(self.folders[index].as_ref().unwrap().as_ref().unwrap().collection.len().saturating_sub(1))).collect(), + }); + + } + + } + } + /* ======================== */ } else { let (sent, cur) = { let ptr = self.folders.as_mut_ptr(); @@ -115,17 +140,51 @@ impl Account { if sent[0].is_none() { sent[0] = Some(Mailbox::new(sent_path, &None, envelopes.clone())); } - cur[0] = Some(Mailbox::new(folder, &sent[0], envelopes)); + /* ======================== */ + let old_m = mem::replace(&mut cur[0], Some(Mailbox::new(folder, &sent[0], envelopes))); + if let Some(old_m) = old_m { + if cur[0].is_some() && old_m.is_ok() { + let diff = cur[0].as_ref().map(|v| v.as_ref().unwrap().collection.len()).unwrap_or(0).saturating_sub(old_m.as_ref().map(|v| v.collection.len()).unwrap_or(0)); + if diff > 0 { + let mut index = old_m.as_ref().unwrap().collection.iter().zip(&cur[0].as_ref().unwrap().as_ref().unwrap().collection).count(); + ret = Some(NewMailEvent { + folder: folder.hash(), + index: (index.saturating_sub(1)..(cur[0].as_ref().unwrap().as_ref().unwrap().collection.len()).saturating_sub(1)).collect(), + }); + + } + + } + } + /* ======================== */ } } else { - self.folders[index] = Some(Mailbox::new(folder, &None, envelopes)); + /* ======================== */ + let old_m = mem::replace(&mut self.folders[index], Some(Mailbox::new(folder, &None, envelopes))); + if let Some(old_m) = old_m { + if self.folders[index].is_some() && old_m.is_ok() { + let diff = self.folders[index].as_ref().map(|v| v.as_ref().unwrap().collection.len()).unwrap_or(0).saturating_sub(old_m.as_ref().map(|v| v.collection.len()).unwrap_or(0)); + if diff > 0 { + let mut index = old_m.as_ref().unwrap().collection.iter().zip(&self.folders[index].as_ref().unwrap().as_ref().unwrap().collection).count(); + ret = Some(NewMailEvent { + folder: folder.hash(), + index: (index.saturating_sub(1)..(self.folders[index].as_ref().unwrap().as_ref().unwrap().collection.len().saturating_sub(1))).collect(), + }); + + } + + } + } + /* ======================== */ }; + + ret } - pub fn status(&mut self, index: usize) -> result::Result { + pub fn status(&mut self, index: usize) -> result::Result, usize> { match self.workers[index].as_mut() { None => { - return Ok(false); + return Ok(None); } Some(ref mut w) => match w.poll() { Ok(AsyncStatus::NoUpdate) => { @@ -142,9 +201,8 @@ impl Account { }, }; let m = self.workers[index].take().unwrap().extract(); - self.load_mailbox(index, m); self.workers[index] = None; - Ok(true) + Ok(self.load_mailbox(index, m)) } } diff --git a/melib/src/mailbox/backends/maildir.rs b/melib/src/mailbox/backends/maildir.rs index 6e9cb8ab..64ca6820 100644 --- a/melib/src/mailbox/backends/maildir.rs +++ b/melib/src/mailbox/backends/maildir.rs @@ -49,7 +49,7 @@ use std::fs; use std::io; use std::io::Read; use std::sync::{Mutex, Arc}; -use std::hash::Hasher; +use std::hash::{Hasher, Hash}; use std::path::{Path, PathBuf}; extern crate fnv; use self::fnv::FnvHashMap; @@ -213,21 +213,20 @@ impl MailBackend for MaildirType { Ok(event) => match event { DebouncedEvent::Create(mut pathbuf) | DebouncedEvent::Remove(mut pathbuf) => { - let path = if pathbuf.is_dir() { + if pathbuf.is_dir() { if pathbuf.ends_with("cur") | pathbuf.ends_with("new") { pathbuf.pop(); } - pathbuf.to_str().unwrap() } else { pathbuf.pop(); - pathbuf.parent().unwrap().to_str().unwrap() + pathbuf.pop(); }; - eprintln!(" got event in {}", path); + eprintln!(" got event in {}", pathbuf.display()); let mut hasher = DefaultHasher::new(); - hasher.write(path.as_bytes()); + pathbuf.hash(&mut hasher); sender.send(RefreshEvent { - folder: format!("{}", path), + folder: format!("{}", pathbuf.display()), hash: hasher.finish(), }); } @@ -306,16 +305,15 @@ impl MaildirType { let tx = w.tx(); // TODO: Avoid clone let folder: &MaildirFolder = &self.folders[self.owned_folder_idx(folder)]; - let path = folder.path().to_string(); + let mut path: PathBuf = folder.path().into(); let name = format!("parsing {:?}", folder.name()); let map = self.hash_index.clone(); let map2 = self.hash_index.clone(); thread::Builder::new() - .name(name) + .name(name.clone()) .spawn(move || { let cache_dir = cache_dir.clone(); - let mut path = PathBuf::from(path); path.push("cur"); let iter = path.read_dir()?; let count = path.read_dir()?.count(); @@ -341,7 +339,7 @@ impl MaildirType { let cache_dir = cache_dir.clone(); let mut tx = tx.clone(); let map = map.clone(); - let s = scope.spawn(move || { + let s = scope.builder().name(name.clone()).spawn(move || { let len = chunk.len(); let size = if len <= 100 { 100 } else { (len / 100) * 100 }; let mut local_r: Vec = Vec::with_capacity(chunk.len()); @@ -419,7 +417,7 @@ impl MaildirType { } local_r }); - threads.push(s); + threads.push(s.unwrap()); } }); } @@ -447,26 +445,27 @@ impl MaildirType { pub struct MaildirFolder { hash: u64, name: String, - path: String, + path: PathBuf, children: Vec, } impl MaildirFolder { pub fn new(path: String, file_name: String, children: Vec) -> Result { + let pathbuf = PathBuf::from(path); let mut h = DefaultHasher::new(); - h.write(&path.as_bytes()); + pathbuf.hash(&mut h); let ret = MaildirFolder { hash: h.finish(), name: file_name, - path: path, + path: pathbuf, children: children, }; ret.is_valid()?; Ok(ret) } - pub fn path(&self) -> &str { - &self.path + pub fn path(&self) -> &Path { + self.path.as_path() } fn is_valid(&self) -> Result<()> { let path = self.path(); @@ -476,7 +475,7 @@ impl MaildirFolder { if !p.is_dir() { return Err(MeliError::new(format!( "{} is not a valid maildir folder", - path + path.display() ))); } p.pop(); diff --git a/src/bin.rs b/src/bin.rs index c41787ac..bafd223f 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -66,7 +66,7 @@ fn main() { let menu = Entity { component: Box::new(AccountMenu::new(&state.context.accounts)), }; - let listing = CompactListing::new(); + let listing = MailListing::new(); let b = Entity { component: Box::new(listing), }; @@ -150,7 +150,6 @@ fn main() { }, ThreadEvent::RefreshMailbox { hash : h } => { state.hash_to_folder(h); - state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Notification(String::from("Update in mailbox"))}); state.redraw(); }, ThreadEvent::UIEvent(UIEventType::ChangeMode(f)) => { @@ -160,10 +159,10 @@ fn main() { ThreadEvent::UIEvent(UIEventType::StartupCheck) => { let mut flag = false; let mut render_flag = false; - for account in &mut state.context.accounts { - let len = account.len(); - for i in 0..len { - match account.status(i) { + for idx_a in 0..state.context.accounts.len() { + let len = state.context.accounts[idx_a].len(); + for idx_m in 0..len { + match state.context.account_status(idx_a, idx_m) { Ok(true) => { render_flag = true; }, diff --git a/ui/src/state.rs b/ui/src/state.rs index 109f9a3f..0fe0413e 100644 --- a/ui/src/state.rs +++ b/ui/src/state.rs @@ -33,6 +33,7 @@ use chan::{Receiver, Sender}; use fnv::FnvHashMap; use std::io::Write; use std::thread; +use std::result; use std::time; use termion::raw::IntoRawMode; use termion::screen::AlternateScreen; @@ -97,6 +98,24 @@ impl Context { pub fn restore_input(&self) { self.input.restore(self.sender.clone()); } + pub fn account_status(&mut self, idx_a: usize, idx_m: usize) -> result::Result { + let s = self.accounts[idx_a].status(idx_m)?; + if let Some(event) = s { + eprintln!("setting up notification"); + let (idx_a, idx_m) = self.mailbox_hashes[&event.folder]; + let subjects = { + let mut ret = Vec::with_capacity(event.index.len()); + eprintln!("index is {:?}", &event.index); + for &i in &event.index { + ret.push(self.accounts[idx_a][idx_m].as_ref().unwrap().collection[i].subject()); + } + ret + }; + self.replies.push_back(UIEvent { id: 0, event_type: UIEventType::Notification(format!("Update in {}/{}, indexes {:?}", self.accounts[idx_a].name(), self.accounts[idx_a][idx_m].as_ref().unwrap().folder.name(), subjects)) }); + return Ok(true); + } + Ok(false) + } } /// A State object to manage and own components and entities of the UI. `State` is responsible for @@ -224,8 +243,10 @@ impl State { cursor::Goto(1, 1) ).unwrap(); s.flush(); + eprintln!("DEBUG: inserting mailbox hashes:"); for (x, account) in s.context.accounts.iter_mut().enumerate() { for (y, folder) in account.backend.folders().iter().enumerate() { + eprintln!("{:?}", folder); s.context.mailbox_hashes.insert(folder.hash(), (x, y)); } let sender = s.context.sender.clone(); @@ -242,35 +263,38 @@ impl State { * and startup a thread to remind us to poll it every now and then till it's finished. */ pub fn hash_to_folder(&mut self, hash: u64) { - let (idxa, idxm) = self.context.mailbox_hashes[&hash]; - self.context.accounts[idxa].reload(idxm); - let (startup_tx, startup_rx) = chan::async(); - let startup_thread = { - let sender = self.context.sender.clone(); - let startup_rx = startup_rx.clone(); + if let Some(&(idxa, idxm)) = self.context.mailbox_hashes.get(&hash) { + self.context.accounts[idxa].reload(idxm); + let (startup_tx, startup_rx) = chan::async(); + let startup_thread = { + let sender = self.context.sender.clone(); + let startup_rx = startup_rx.clone(); - thread::Builder::new() - .name("startup-thread".to_string()) - .spawn(move || { - let dur = time::Duration::from_millis(100); - loop { - chan_select! { - default => {}, - startup_rx.recv() -> _ => { - sender.send(ThreadEvent::UIEvent(UIEventType::MailboxUpdate((idxa,idxm)))); - sender.send(ThreadEvent::ThreadJoin(thread::current().id())); - return; + thread::Builder::new() + .name("startup-thread".to_string()) + .spawn(move || { + let dur = time::Duration::from_millis(100); + loop { + chan_select! { + default => {}, + startup_rx.recv() -> _ => { + sender.send(ThreadEvent::UIEvent(UIEventType::MailboxUpdate((idxa,idxm)))); + sender.send(ThreadEvent::ThreadJoin(thread::current().id())); + return; + } } + sender.send(ThreadEvent::UIEvent(UIEventType::StartupCheck)); + thread::sleep(dur); } - sender.send(ThreadEvent::UIEvent(UIEventType::StartupCheck)); - thread::sleep(dur); - } - }) - .unwrap() - }; - self.startup_thread = Some(startup_tx.clone()); - self.threads - .insert(startup_thread.thread().id(), (startup_tx, startup_thread)); + }) + .expect("Failed to spawn startup-thread in hash_to_folder()") + }; + self.startup_thread = Some(startup_tx.clone()); + self.threads + .insert(startup_thread.thread().id(), (startup_tx, startup_thread)); + } else { + eprintln!("BUG: mailbox with hash {} not found in mailbox_hashes.", hash); + } } /// If an owned thread returns a `ThreadEvent::ThreadJoin` event to `State` then it must remove