From 3f7d962abd4c63afdc10fdba2e8f66ca839de993 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sun, 8 Sep 2019 11:05:25 +0300 Subject: [PATCH] melib: remove ThreadTree, use ThreadNodes for root_set Remove ThreadTree index in Threads {} struct. Keep a Vec for root_set state of mailbox instead of rebuilding ThreadTrees every time. --- melib/src/async_workers.rs | 55 +- melib/src/collection.rs | 80 +- melib/src/mailbox.rs | 13 +- melib/src/thread.rs | 958 +++++++--------------- ui/src/components/mail/listing/compact.rs | 2 +- ui/src/conf/accounts.rs | 181 ++-- ui/src/execute.rs | 2 +- ui/src/execute/actions.rs | 2 +- ui/src/workers.rs | 63 +- 9 files changed, 536 insertions(+), 820 deletions(-) diff --git a/melib/src/async_workers.rs b/melib/src/async_workers.rs index 91a263b98..fc0a16fe5 100644 --- a/melib/src/async_workers.rs +++ b/melib/src/async_workers.rs @@ -72,21 +72,36 @@ impl fmt::Debug for AsyncStatus { } /// A builder object for `Async` -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct AsyncBuilder { + payload_hook: Option () + Send + Sync>>, tx: chan::Sender>, rx: chan::Receiver>, } -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct Async { - value: Option, + pub value: Option, work: Work, active: bool, + payload_hook: Option () + Send + Sync>>, + link: Option, tx: chan::Sender>, rx: chan::Receiver>, } +impl std::fmt::Debug for Async { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "Async<{}> {{ active: {}, payload_hook: {} }}", + stringify!(T), + self.active, + self.payload_hook.is_some() + ) + } +} + impl Default for AsyncBuilder { fn default() -> Self { AsyncBuilder::::new() @@ -102,6 +117,7 @@ where AsyncBuilder { tx: sender, rx: receiver, + payload_hook: None, } } /// Returns the sender object of the promise's channel. @@ -119,9 +135,19 @@ where value: None, tx: self.tx, rx: self.rx, + link: None, + payload_hook: None, active: false, } } + + pub fn add_payload_hook( + &mut self, + payload_hook: Option () + Send + Sync>>, + ) -> &mut Self { + self.payload_hook = payload_hook; + self + } } impl Async @@ -144,6 +170,10 @@ where pub fn tx(&mut self) -> chan::Sender> { self.tx.clone() } + /// Returns the receiver object of the promise's channel. + pub fn rx(&mut self) -> chan::Receiver> { + self.rx.clone() + } /// Polls worker thread and returns result. pub fn poll(&mut self) -> Result, ()> { if self.value.is_some() { @@ -171,6 +201,10 @@ where }, }; self.value = Some(result); + if let Some(hook) = self.payload_hook.as_ref() { + hook(); + } + Ok(AsyncStatus::Finished) } /// Blocks until thread joins. @@ -193,4 +227,19 @@ where } self.value = Some(result); } + + pub fn link(&mut self, other: Async) -> &mut Self { + let Async { + rx, + tx, + work, + value, + .. + } = other; + self.rx = rx; + self.tx = tx; + self.work = work; + self.value = value; + self + } } diff --git a/melib/src/collection.rs b/melib/src/collection.rs index bc88b0e2c..7feb4f20b 100644 --- a/melib/src/collection.rs +++ b/melib/src/collection.rs @@ -71,12 +71,12 @@ impl Collection { self.threads .entry(folder_hash) .or_default() - .remove(envelope_hash, &mut self.envelopes); + .remove(envelope_hash); for (h, t) in self.threads.iter_mut() { if *h == folder_hash { continue; } - t.remove(envelope_hash, &mut self.envelopes); + t.remove(envelope_hash); } } @@ -99,7 +99,7 @@ impl Collection { .threads .entry(folder_hash) .or_default() - .update_envelope(old_hash, new_hash, &self.envelopes) + .update_envelope(old_hash, new_hash) .is_ok() { return; @@ -114,9 +114,7 @@ impl Collection { if *h == folder_hash { continue; } - t.update_envelope(old_hash, new_hash, &self.envelopes) - .ok() - .take(); + t.update_envelope(old_hash, new_hash).ok().take(); } } @@ -124,13 +122,13 @@ impl Collection { /// Returns a list of already existing folders whose threads were updated pub fn merge( &mut self, - mut envelopes: FnvHashMap, + mut new_envelopes: FnvHashMap, folder_hash: FolderHash, mailbox: &mut Mailbox, sent_folder: Option, ) -> Option> { self.sent_folder = sent_folder; - envelopes.retain(|&h, e| { + new_envelopes.retain(|&h, e| { if self.message_ids.contains_key(e.message_id().raw()) { /* skip duplicates until a better way to handle them is found. */ //FIXME @@ -141,11 +139,7 @@ impl Collection { true } }); - let mut new_threads = Threads::new(&mut envelopes); - for (h, e) in envelopes { - self.envelopes.insert(h, e); - } let &mut Collection { ref mut threads, ref mut envelopes, @@ -153,10 +147,36 @@ impl Collection { .. } = self; + if !threads.contains_key(&folder_hash) { + threads.insert(folder_hash, Threads::new(&mut new_envelopes)); + for (h, e) in new_envelopes { + envelopes.insert(h, e); + } + } else { + threads.entry(folder_hash).and_modify(|t| { + let mut ordered_hash_set = + new_envelopes.keys().cloned().collect::>(); + ordered_hash_set.sort_by(|a, b| { + new_envelopes[a] + .date() + .partial_cmp(&new_envelopes[b].date()) + .unwrap() + }); + for h in ordered_hash_set { + envelopes.insert(h, new_envelopes.remove(&h).unwrap()); + t.insert(envelopes, h); + } + }); + } + let mut ret = StackVec::new(); - for (t_fh, t) in threads.iter_mut() { + let keys = threads.keys().cloned().collect::>(); + for t_fh in keys { + if t_fh == folder_hash { + continue; + } if sent_folder.map(|f| f == folder_hash).unwrap_or(false) { - let mut ordered_hash_set = new_threads + let mut ordered_hash_set = threads[&folder_hash] .hash_set .iter() .cloned() @@ -169,28 +189,37 @@ impl Collection { }); let mut updated = false; for h in ordered_hash_set { - updated |= t.insert_reply(envelopes, h); + updated |= threads.entry(t_fh).or_default().insert_reply(envelopes, h); } if updated { - ret.push(*t_fh); + ret.push(t_fh); } continue; } - if sent_folder.map(|f| f == *t_fh).unwrap_or(false) { - let mut ordered_hash_set = - t.hash_set.iter().cloned().collect::>(); + if sent_folder.map(|f| f == t_fh).unwrap_or(false) { + let mut ordered_hash_set = threads[&t_fh] + .hash_set + .iter() + .cloned() + .collect::>(); ordered_hash_set.sort_by(|a, b| { envelopes[a] .date() .partial_cmp(&envelopes[b].date()) .unwrap() }); + let mut updated = false; for h in ordered_hash_set { - new_threads.insert_reply(envelopes, h); + updated |= threads + .entry(folder_hash) + .or_default() + .insert_reply(envelopes, h); + } + if updated { + ret.push(folder_hash); } } } - threads.insert(folder_hash, new_threads); if ret.is_empty() { None } else { @@ -206,8 +235,7 @@ impl Collection { self.envelopes.insert(new_hash, envelope); if self.sent_folder.map(|f| f == folder_hash).unwrap_or(false) { for (_, t) in self.threads.iter_mut() { - t.update_envelope(old_hash, new_hash, &self.envelopes) - .unwrap_or(()); + t.update_envelope(old_hash, new_hash).unwrap_or(()); } } { @@ -215,7 +243,7 @@ impl Collection { .threads .entry(folder_hash) .or_default() - .update_envelope(old_hash, new_hash, &self.envelopes) + .update_envelope(old_hash, new_hash) .is_ok() { return; @@ -230,9 +258,7 @@ impl Collection { if *h == folder_hash { continue; } - t.update_envelope(old_hash, new_hash, &self.envelopes) - .ok() - .take(); + t.update_envelope(old_hash, new_hash).ok().take(); } } diff --git a/melib/src/mailbox.rs b/melib/src/mailbox.rs index 3846d7c18..f32d706fc 100644 --- a/melib/src/mailbox.rs +++ b/melib/src/mailbox.rs @@ -26,14 +26,10 @@ */ use crate::backends::Folder; -pub use crate::email::*; -use crate::thread::ThreadHash; - -pub use crate::thread::{SortField, SortOrder, ThreadNode, Threads}; - pub use crate::collection::*; - +pub use crate::email::*; use fnv::{FnvHashMap, FnvHashSet}; + /// `Mailbox` represents a folder of mail. #[derive(Debug, Deserialize, Serialize, Clone, Default)] pub struct Mailbox { @@ -41,7 +37,6 @@ pub struct Mailbox { pub folder: Folder, name: String, pub envelopes: FnvHashSet, - pub thread_root_set: FnvHashSet, has_sent: bool, } @@ -57,6 +52,10 @@ impl Mailbox { } } + pub fn merge(&mut self, envelopes: &FnvHashMap) { + self.envelopes.extend(envelopes.keys().cloned()); + } + pub fn name(&self) -> &str { &self.name } diff --git a/melib/src/thread.rs b/melib/src/thread.rs index 63db84251..b0c5d8f64 100644 --- a/melib/src/thread.rs +++ b/melib/src/thread.rs @@ -25,11 +25,11 @@ * bloated code that's necessary for the crap modern e-mail is. Quoted comments (/* " .. " */) are * taken almost verbatim from the algorithm. * - * The entry point of this module is the `Threads` struct and its `new` method. It contains `ThreadNodes` which are the - * nodes in the thread trees that might have messages associated with them. The root nodes (first - * messages in each thread) are stored in `root_set` and `tree` vectors. The `ThreadTree` struct - * contains the sorted threads. `Threads` has inner mutability since we need to sort without the - * user having mutable ownership. + * The entry point of this module is the `Threads` struct and its `new` method. It contains + * `ThreadNodes` which are the nodes in the thread trees that might have messages associated with + * them. The root nodes (first messages in each thread) are stored in `root_set` and `tree` + * vectors. `Threads` has inner mutability since we need to sort without the user having mutable + * ownership. */ use crate::email::parser::BytesExt; @@ -40,7 +40,6 @@ use uuid::Uuid; use fnv::{FnvHashMap, FnvHashSet}; use std::cell::{Ref, RefCell}; -use std::cmp; use std::cmp::Ordering; use std::fmt; use std::iter::FromIterator; @@ -138,7 +137,15 @@ macro_rules! make { * if children exists */ $buf.entry($p).and_modify(|e| e.children.push($c)); } - $buf.entry($c).and_modify(|e| e.parent = Some($p)); + let child_date = $buf[&$c].date; + let child_len = $buf[&$c].len; + $buf.entry($c).and_modify(|e| { + e.parent = Some($p); + }); + $buf.entry($p).and_modify(|e| { + e.len += child_len + 1; + e.date = std::cmp::max(e.date, child_date); + }); union($buf, $c, $p); prev_parent }}; @@ -147,7 +154,7 @@ macro_rules! make { /* Strip common prefixes from subjects */ trait SubjectPrefix { fn is_a_reply(&self) -> bool; - fn strip_prefixes(&mut self); + fn strip_prefixes(&mut self) -> &mut Self; } impl SubjectPrefix for &[u8] { @@ -158,7 +165,7 @@ impl SubjectPrefix for &[u8] { || self.starts_with(b"Fw: ") } - fn strip_prefixes(&mut self) { + fn strip_prefixes(&mut self) -> &mut Self { let result = { let mut slice = self.trim(); loop { @@ -197,6 +204,7 @@ impl SubjectPrefix for &[u8] { slice }; *self = result; + self } } @@ -248,96 +256,6 @@ impl FromStr for SortOrder { } } -/* - * The thread tree holds the sorted state of the thread nodes */ - -#[derive(Clone, Debug, Deserialize, Serialize)] -struct ThreadTree { - id: ThreadHash, - children: Vec, -} - -impl ThreadTree { - fn new(id: ThreadHash) -> Self { - ThreadTree { - id, - children: Vec::new(), - } - } - fn insert_child( - vec: &mut Vec, - child: ThreadTree, - sort: (SortField, SortOrder), - buf: &FnvHashMap, - envelopes: &Envelopes, - ) -> usize { - let pos = match sort { - (SortField::Date, SortOrder::Asc) => { - match vec.binary_search_by(|probe| buf[&probe.id].date.cmp(&buf[&child.id].date)) { - Ok(p) => p, - Err(p) => p, - } - } - (SortField::Date, SortOrder::Desc) => { - match vec.binary_search_by(|probe| { - buf[&probe.id].date.cmp(&buf[&child.id].date).reverse() - }) { - Ok(p) => p, - Err(p) => p, - } - } - (SortField::Subject, SortOrder::Asc) => { - match vec.binary_search_by(|probe| { - match ( - buf.get(&probe.id) - .map(|n| n.message.as_ref()) - .unwrap_or(None), - buf.get(&child.id) - .map(|n| n.message.as_ref()) - .unwrap_or(None), - ) { - (Some(p), Some(c)) => envelopes[p] - .subject() - .split_graphemes() - .cmp(&envelopes[c].subject().split_graphemes()), - (Some(_), None) => Ordering::Greater, - (None, Some(_)) => Ordering::Less, - (None, None) => Ordering::Equal, - } - }) { - Ok(p) => p, - Err(p) => p, - } - } - (SortField::Subject, SortOrder::Desc) => { - match vec.binary_search_by(|probe| { - match ( - buf.get(&probe.id) - .map(|n| n.message.as_ref()) - .unwrap_or(None), - buf.get(&child.id) - .map(|n| n.message.as_ref()) - .unwrap_or(None), - ) { - (Some(p), Some(c)) => envelopes[c] - .subject() - .split_graphemes() - .cmp(&envelopes[p].subject().split_graphemes()), - (Some(_), None) => Ordering::Less, - (None, Some(_)) => Ordering::Greater, - (None, None) => Ordering::Equal, - } - }) { - Ok(p) => p, - Err(p) => p, - } - } - }; - vec.insert(pos, child); - pos - } -} - /* `ThreadsIterator` returns messages according to the sorted order. For example, for the following * threads: * @@ -356,15 +274,16 @@ impl ThreadTree { pub struct ThreadsIterator<'a> { pos: usize, stack: StackVec, - tree: Ref<'a, Vec>, + root_tree: Ref<'a, Vec>, + thread_nodes: &'a FnvHashMap, } impl<'a> Iterator for ThreadsIterator<'a> { type Item = (usize, ThreadHash, bool); fn next(&mut self) -> Option<(usize, ThreadHash, bool)> { { - let mut tree = &(*self.tree); + let mut tree = &(*self.root_tree); for i in self.stack.iter() { - tree = &tree[*i].children; + tree = &self.thread_nodes[&tree[*i]].children; } if self.pos == tree.len() { if let Some(p) = self.stack.pop() { @@ -376,10 +295,10 @@ impl<'a> Iterator for ThreadsIterator<'a> { debug_assert!(self.pos < tree.len()); let ret = ( self.stack.len(), - tree[self.pos].id, + tree[self.pos], !self.stack.is_empty() && (self.pos < (tree.len() - 1)), ); - if !tree[self.pos].children.is_empty() { + if !self.thread_nodes[&tree[self.pos]].children.is_empty() { self.stack.push(self.pos); self.pos = 0; return Some(ret); @@ -408,15 +327,16 @@ pub struct ThreadIterator<'a> { init_pos: usize, pos: usize, stack: StackVec, - tree: Ref<'a, Vec>, + root_tree: Ref<'a, Vec>, + thread_nodes: &'a FnvHashMap, } impl<'a> Iterator for ThreadIterator<'a> { type Item = (usize, ThreadHash); fn next(&mut self) -> Option<(usize, ThreadHash)> { { - let mut tree = &(*self.tree); + let mut tree = &(*self.root_tree); for i in self.stack.iter() { - tree = &tree[*i].children; + tree = &self.thread_nodes[&tree[*i]].children; } if self.pos == tree.len() || (self.stack.is_empty() && self.pos > self.init_pos) { if self.stack.is_empty() { @@ -425,8 +345,8 @@ impl<'a> Iterator for ThreadIterator<'a> { self.pos = self.stack.pop().unwrap() + 1; } else { debug_assert!(self.pos < tree.len()); - let ret = (self.stack.len(), tree[self.pos].id); - if !tree[self.pos].children.is_empty() { + let ret = (self.stack.len(), tree[self.pos]); + if !self.thread_nodes[&tree[self.pos]].children.is_empty() { self.stack.push(self.pos); self.pos = 0; return Some(ret); @@ -538,13 +458,76 @@ impl ThreadNode { pub fn set_snoozed(&mut self, set: bool) { self.snoozed = set; } + + fn insert_child_pos( + vec: &[ThreadHash], + child: ThreadHash, + sort: (SortField, SortOrder), + buf: &FnvHashMap, + envelopes: &Envelopes, + ) -> usize { + match sort { + (SortField::Date, SortOrder::Asc) => { + match vec.binary_search_by(|probe| buf[&probe].date.cmp(&buf[&child].date)) { + Ok(p) => p, + Err(p) => p, + } + } + (SortField::Date, SortOrder::Desc) => { + match vec + .binary_search_by(|probe| buf[&probe].date.cmp(&buf[&child].date).reverse()) + { + Ok(p) => p, + Err(p) => p, + } + } + (SortField::Subject, SortOrder::Asc) => { + match vec.binary_search_by(|probe| { + match ( + buf.get(&probe).map(|n| n.message.as_ref()).unwrap_or(None), + buf.get(&child).map(|n| n.message.as_ref()).unwrap_or(None), + ) { + (Some(p), Some(c)) => envelopes[p] + .subject() + .split_graphemes() + .cmp(&envelopes[c].subject().split_graphemes()), + (Some(_), None) => Ordering::Greater, + (None, Some(_)) => Ordering::Less, + (None, None) => Ordering::Equal, + } + }) { + Ok(p) => p, + Err(p) => p, + } + } + (SortField::Subject, SortOrder::Desc) => { + match vec.binary_search_by(|probe| { + match ( + buf.get(&probe).map(|n| n.message.as_ref()).unwrap_or(None), + buf.get(&child).map(|n| n.message.as_ref()).unwrap_or(None), + ) { + (Some(p), Some(c)) => envelopes[c] + .subject() + .split_graphemes() + .cmp(&envelopes[p].subject().split_graphemes()), + (Some(_), None) => Ordering::Less, + (None, Some(_)) => Ordering::Greater, + (None, None) => Ordering::Equal, + } + }) { + Ok(p) => p, + Err(p) => p, + } + } + } + } } #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Threads { pub thread_nodes: FnvHashMap, root_set: RefCell>, - tree: RefCell>, + tree_index: RefCell>, message_ids: FnvHashMap, ThreadHash>, pub message_ids_set: FnvHashSet>, @@ -565,7 +548,8 @@ impl PartialEq for ThreadNode { pub struct RootIterator<'a> { pos: usize, - root_tree: Ref<'a, Vec>, + root_tree: Ref<'a, Vec>, + thread_nodes: &'a FnvHashMap, } impl<'a> Iterator for RootIterator<'a> { @@ -575,11 +559,20 @@ impl<'a> Iterator for RootIterator<'a> { if self.pos == self.root_tree.len() { return None; } + let mut ret = self.root_tree[self.pos]; self.pos += 1; - Some(self.root_tree[self.pos - 1].id) + let thread_node = &self.thread_nodes[&ret]; + if thread_node.message().is_none() { + ret = thread_node.children()[0]; + while self.thread_nodes[&ret].message().is_none() { + ret = self.thread_nodes[&ret].children()[0]; + } + } + Some(ret) } } } + fn find_ref(buf: &FnvHashMap, h: ThreadHash) -> ThreadHash { if buf[&h].thread_group == h { return h; @@ -651,83 +644,6 @@ impl Threads { } x_root } - fn prune_empty_nodes(&mut self, root_set: &mut Vec) { - fn prune( - thread_nodes: &mut FnvHashMap, - idx: ThreadHash, - root_set: &mut Vec, - ) -> bool { - /* "If it is an empty container with no children, nuke it." */ - if !thread_nodes[&idx].has_message() && thread_nodes[&idx].children.is_empty() { - remove_from_parent!(thread_nodes, idx); - thread_nodes.entry(idx).and_modify(|n| n.pruned = true); - return true; - } - - /* - if !thread_nodes[&idx].has_message() && !thread_nodes[&idx].has_parent() { - if thread_nodes[&idx].children.len() == 1 { - /* "Do not promote the children if doing so would promote them to the root set - * -- unless there is only one child, in which case, do." */ - let child = thread_nodes[&idx].children[0]; - root_set.push(child); - remove_from_parent!(thread_nodes, child); - thread_nodes.entry(idx).and_modify(|n| { - n.pruned = true; - n.children.push(child); - }); - return true; // Pruned - } - } else if let Some(p) = thread_nodes[&idx].parent { - if !thread_nodes[&idx].has_message() { - let orphans = thread_nodes[&idx].children.clone(); - for c in &orphans { - make!((p) parent of (*c), thread_nodes); - } - remove_from_parent!(thread_nodes, idx); - /* Keep children in case we happen upon them later and mark it as pruned */ - thread_nodes.entry(idx).and_modify(|n| { - n.pruned = true; - n.children = orphans; - }); - return true; // Pruned - } - } - */ - - /* Recurse to children, but keep in mind more children can be added in each iteration - */ - let mut c_idx = 0; - loop { - if c_idx >= thread_nodes[&idx].children.len() { - break; - } - let c = thread_nodes[&idx].children[c_idx]; - if !prune(thread_nodes, c, root_set) { - c_idx += 1; - } - } - thread_nodes[&idx].pruned - } - - let mut idx = 0; - loop { - if idx >= root_set.len() { - break; - } - if prune(&mut self.thread_nodes, root_set[idx], root_set) { - root_set.remove(idx); - } else { - idx += 1; - } - } - } - - pub fn prune_tree(&self) { - self.tree - .borrow_mut() - .retain(|c| !self.thread_nodes[&c.id].is_empty()); - } pub fn new(envelopes: &mut Envelopes) -> Threads { /* To reconstruct thread information from the mails we need: */ @@ -749,7 +665,7 @@ impl Threads { let hash_set: FnvHashSet = FnvHashSet::with_capacity_and_hasher(envelopes.len(), Default::default()); - let mut t = Threads { + Threads { thread_nodes, message_ids, message_ids_set, @@ -758,78 +674,15 @@ impl Threads { subsort: RefCell::new((SortField::Subject, SortOrder::Desc)), ..Default::default() - }; - /* Add each message to message_ids and threads, and link them together according to the - * References / In-Reply-To headers */ - t.link_threads(envelopes); - - t.create_root_set(envelopes); - t.build_envelopes(envelopes); - //for (i, _t) in t.thread_nodes.iter().enumerate() { - // if !_t.has_parent() && _t.children.is_empty() && !_t.has_message() { - // continue; - // } - // debug!("--------------------------"); - // if let Some(m) = _t.message { - // debug!( - // "\tmessage: {}\t{}", - // envelopes[&m].subject(), - // envelopes[&m].message_id() - // ); - // } else { - // debug!("\tNo message"); - // } - // debug!( - // "Thread #{}, children {}:\n\t{:#?}", - // i, - // _t.children.len(), - // _t - // ); - // if !_t.children.is_empty() { - // debug!("{:?}", _t.children); - // } - //} - //for (i, _t) in t.tree.borrow().iter().enumerate() { - // debug!("Tree #{} id {}, children {}", i, _t.id, _t.children.len()); - // if let Some(m) = t.thread_nodes[_t.id].message { - // debug!("\tmessage: {}", envelopes[&m].subject()); - // } else { - // debug!("\tNo message"); - // } - //} - t - } - - fn create_root_set(&mut self, envelopes: &Envelopes) { - /* Walk over the elements of message_ids, and gather a list of the ThreadNode objects that - * have no parents. These are the root messages of each thread */ - let mut root_set: Vec = Vec::with_capacity(envelopes.len()); - - /* Find the root set */ - for v in self.message_ids.values() { - if self.thread_nodes[v].parent.is_none() { - root_set.push(*v); - } - } - - /* Prune empty thread nodes */ - self.prune_empty_nodes(&mut root_set); - - self.root_set = RefCell::new(root_set); - } - - pub fn print_tree(&self, envelopes: &Envelopes) { - let len = self.tree.borrow().len(); - for (i, t) in self.tree.borrow().iter().enumerate() { - debug!("tree #{}/{}", i + 1, len); - print_threadnodes(t.id, &self.thread_nodes, envelopes); } } + pub fn threads_iter(&self) -> ThreadsIterator { ThreadsIterator { pos: 0, stack: StackVec::new(), - tree: self.tree.borrow(), + root_tree: self.tree_index.borrow(), + thread_nodes: &self.thread_nodes, } } @@ -838,7 +691,8 @@ impl Threads { init_pos: index, pos: index, stack: StackVec::new(), - tree: self.tree.borrow(), + root_tree: self.tree_index.borrow(), + thread_nodes: &self.thread_nodes, } } @@ -846,83 +700,46 @@ impl Threads { &mut self, old_hash: EnvelopeHash, new_hash: EnvelopeHash, - envelopes: &Envelopes, ) -> Result<(), ()> { /* must update: * - hash_set * - message fields in thread_nodes */ - let thread_hash = if let Some((key, node)) = self + if let Some(node) = self .thread_nodes - .iter_mut() - .find(|(_, n)| n.message.map(|n| n == old_hash).unwrap_or(false)) + .values_mut() + .find(|n| n.message.map(|n| n == old_hash).unwrap_or(false)) { node.message = Some(new_hash); - *key } else { return Err(()); }; self.hash_set.remove(&old_hash); self.hash_set.insert(new_hash); - self.rebuild_thread(thread_hash, envelopes); Ok(()) } #[inline] - pub fn remove(&mut self, envelope_hash: EnvelopeHash, envelopes: &mut Envelopes) { + pub fn remove(&mut self, envelope_hash: EnvelopeHash) { self.hash_set.remove(&envelope_hash); - //{ - // let pos = self - // .thread_nodes - // .iter() - // .position(|n| n.message.map(|n| n == envelope_hash).unwrap_or(false)) - // .unwrap(); - // debug!("DEBUG: {} in threads is idx= {}", envelope_hash, pos); - //} - let t_id: ThreadHash; + let t_id: ThreadHash = if let Some((pos, n)) = self + .thread_nodes + .iter_mut() + .find(|(_, n)| n.message.map(|n| n == envelope_hash).unwrap_or(false)) { - if let Some((pos, n)) = self - .thread_nodes - .iter_mut() - .find(|(_, n)| n.message.map(|n| n == envelope_hash).unwrap_or(false)) - { - t_id = *pos; - n.message = None; - } else { - /* else it was deleted during a thread_rebuild or others */ - return; + n.message = None; + *pos + } else { + return; + }; + + if self.thread_nodes[&t_id].parent.is_none() { + let mut tree_index = self.tree_index.borrow_mut(); + if let Some(i) = tree_index.iter().position(|t| *t == t_id) { + tree_index.remove(i); } } - - let mut node_idx = t_id; - - /* Trace path back to root ThreadNode */ - while let Some(p) = &self.thread_nodes[&node_idx].parent { - node_idx = *p; - } - { - let tree = self.tree.get_mut(); - if let Some(pos) = tree.iter().position(|t| t.id == node_idx) { - tree[pos].children.clear(); - if node_idx == t_id { - tree.remove(pos); - } else { - node_build( - &mut tree[pos], - node_idx, - *(self.sort.borrow()), - &mut self.thread_nodes, - 1, - envelopes, - ); - } - } - } - - let mut root_set: Vec = self.tree.borrow().iter().map(|t| t.id).collect(); - self.prune_empty_nodes(&mut root_set); - self.tree.borrow_mut().retain(|t| root_set.contains(&t.id)); } pub fn amend(&mut self, envelopes: &mut Envelopes) { @@ -931,7 +748,7 @@ impl Threads { let difference: Vec = self.hash_set.difference(&new_hash_set).cloned().collect(); for h in difference { - self.remove(h, envelopes); + self.remove(h); } let difference: Vec = @@ -940,33 +757,112 @@ impl Threads { debug!("inserting {}", envelopes[&h].subject()); self.insert(envelopes, h); } - self.create_root_set(envelopes); + } - let mut root_set: Vec = self.tree.borrow().iter().map(|t| t.id).collect(); - self.prune_empty_nodes(&mut root_set); - let tree = self.tree.get_mut(); - tree.retain(|t| root_set.contains(&t.id)); + /// Update show_subject details of ThreadNode + pub fn update_node(&mut self, id: ThreadHash, env_hash: EnvelopeHash, envelopes: &Envelopes) { + let mut subject = envelopes[&env_hash].subject(); + let mut subject = subject.to_mut().as_bytes(); + let stripped_subject = subject.strip_prefixes(); + if let Some(parent_id) = self.thread_nodes[&id].parent { + if let Some(parent_hash) = self.thread_nodes[&parent_id].message { + debug_assert!(envelopes.contains_key(&parent_hash)); + /* decide if the subject should be shown in the UI. + * If parent subject is Foobar and reply is `Re: Foobar` + * then showing the reply's subject is reduntant + */ + let mut parent_subject = envelopes[&parent_hash].subject(); + let mut parent_subject = parent_subject.to_mut().as_bytes(); + parent_subject.strip_prefixes(); + if stripped_subject == &parent_subject { + self.thread_nodes.entry(id).and_modify(|e| { + e.show_subject = false; + }); + } + } + } + for i in 0..self.thread_nodes[&id].children.len() { + let child_hash = self.thread_nodes[&id].children[i]; + if let Some(child_env_hash) = self.thread_nodes[&child_hash].message() { + let mut child_subject = envelopes[&child_env_hash].subject(); + let mut child_subject = child_subject.to_mut().as_bytes(); + child_subject.strip_prefixes(); + if stripped_subject == &child_subject { + self.thread_nodes.entry(child_hash).and_modify(|e| { + e.show_subject = false; + }); + } + } + } } pub fn insert(&mut self, envelopes: &mut Envelopes, env_hash: EnvelopeHash) { - self.link_envelope(envelopes.get_mut(&env_hash).unwrap()); + if self + .message_ids + .contains_key(envelopes[&env_hash].message_id().raw()) + && !self + .missing_message_ids + .contains(envelopes[&env_hash].message_id().raw()) { - let id = self.message_ids[envelopes[&env_hash].message_id().raw()]; - self.rebuild_thread(id, envelopes); + return; } + + let reply_to_id: Option = envelopes[&env_hash] + .in_reply_to() + .map(crate::email::StrBuild::raw) + .and_then(|r| self.message_ids.get(r).cloned()); + let new_id = ThreadHash::new(); + self.thread_nodes.insert( + new_id, + ThreadNode { + message: Some(env_hash), + parent: reply_to_id, + date: envelopes[&env_hash].date(), + ..ThreadNode::new(new_id) + }, + ); + self.message_ids + .insert(envelopes[&env_hash].message_id().raw().to_vec(), new_id); + self.message_ids_set + .insert(envelopes[&env_hash].message_id().raw().to_vec().to_vec()); + self.missing_message_ids + .remove(envelopes[&env_hash].message_id().raw()); + envelopes.get_mut(&env_hash).unwrap().set_thread(new_id); + self.hash_set.insert(env_hash); + if let Some(reply_to_id) = reply_to_id { + self.union(reply_to_id, new_id); + make!((reply_to_id) parent of (new_id), &mut self.thread_nodes); + } else { + if let Some(r) = envelopes[&env_hash] + .in_reply_to() + .map(crate::email::StrBuild::raw) + { + let reply_to_id = ThreadHash::new(); + self.thread_nodes.insert( + reply_to_id, + ThreadNode { + date: envelopes[&env_hash].date(), + thread_group: reply_to_id, + ..ThreadNode::new(reply_to_id) + }, + ); + self.union(reply_to_id, new_id); + make!((reply_to_id) parent of (new_id), &mut self.thread_nodes); + self.missing_message_ids.insert(r.to_vec()); + self.message_ids.insert(r.to_vec(), reply_to_id); + self.message_ids_set.insert(r.to_vec().to_vec()); + } + self.tree_insert_root(new_id, envelopes); + } + self.update_node(new_id, env_hash, envelopes); } /* Insert or update */ pub fn insert_reply(&mut self, envelopes: &mut Envelopes, env_hash: EnvelopeHash) -> bool { - let reply_to_id: Option = self - .message_ids - .get( - envelopes[&env_hash] - .in_reply_to() - .map(crate::email::StrBuild::raw) - .unwrap_or(&[]), - ) - .cloned(); + let reply_to_id: Option = envelopes[&env_hash] + .in_reply_to() + .map(crate::email::StrBuild::raw) + .and_then(|r| self.message_ids.get(r).cloned()); if let Some(id) = self .message_ids .get(envelopes[&env_hash].message_id().raw()) @@ -989,7 +885,6 @@ impl Threads { } } - self.rebuild_thread(reply_to_id.unwrap_or(id), envelopes); self.message_ids .insert(envelopes[&env_hash].message_id().raw().to_vec(), id); self.message_ids_set @@ -998,6 +893,18 @@ impl Threads { .remove(envelopes[&env_hash].message_id().raw()); envelopes.get_mut(&env_hash).unwrap().set_thread(id); self.hash_set.insert(env_hash); + if self.thread_nodes[&id].parent.is_none() { + self.tree_insert_root(id, envelopes); + } + { + let mut tree_index = self.tree_index.borrow_mut(); + for c in &self.thread_nodes[&id].children { + if let Some(i) = tree_index.iter().position(|t| *t == *c) { + tree_index.remove(i); + } + } + } + self.update_node(id, env_hash, envelopes); true } else if let Some(reply_to_id) = reply_to_id { let new_id = ThreadHash::new(); @@ -1020,206 +927,38 @@ impl Threads { self.hash_set.insert(env_hash); self.union(reply_to_id, new_id); make!((reply_to_id) parent of (new_id), &mut self.thread_nodes); - self.rebuild_thread(reply_to_id, envelopes); + self.update_node(new_id, env_hash, envelopes); true } else { - /* - let new_id = ThreadHash::new(); - self.thread_nodes.insert( - new_id, - ThreadNode { - message: Some(env_hash), - parent: None, - date: envelopes[&env_hash].date(), - ..ThreadNode::new(new_id) - }, - ); - self.message_ids - .insert(envelopes[&env_hash].message_id().raw().to_vec(), new_id); - self.message_ids_set - .insert(envelopes[&env_hash].message_id().raw().to_vec().to_vec()); - self.missing_message_ids - .remove(envelopes[&env_hash].message_id().raw()); - envelopes.get_mut(&env_hash).unwrap().set_thread(new_id); - self.hash_set.insert(env_hash); - self.rebuild_thread(new_id, envelopes); - */ false } } - /* Update thread tree information on envelope insertion */ - fn rebuild_thread(&mut self, id: ThreadHash, envelopes: &Envelopes) { - let mut node_idx = id; - let mut stack = StackVec::new(); - - { - let tree = self.tree.get_mut(); - for &c in &self.thread_nodes[&id].children { - if let Some(pos) = tree.iter().position(|t| t.id == c) { - tree.remove(pos); - } - } - } - let no_parent: bool = if let Some(node) = self.thread_nodes.get(&node_idx) { - if let (None, None, 0) = (node.parent, node.message, node.children.len()) { - return; - } - node.parent.is_none() - } else { - panic!(format!( - "node_idx = {:?} not found in self.thread_nodes", - node_idx - )); - }; - - if no_parent { - let tree = self.tree.get_mut(); - if let Some(pos) = tree.iter().position(|t| t.id == id) { - tree[pos] = ThreadTree::new(id); - node_build( - &mut tree[pos], - id, - *(self.sort.borrow()), - &mut self.thread_nodes, - 1, - envelopes, - ); - return; - } else { - for &c in &self.thread_nodes[&id].children { - if let Some(pos) = tree.iter().position(|t| t.id == c) { - tree.remove(pos); - } - } - } - let mut new_tree = ThreadTree::new(id); - node_build( - &mut new_tree, - id, - *(self.sort.borrow()), - &mut self.thread_nodes, - 1, - envelopes, - ); - ThreadTree::insert_child( - tree, - new_tree, - *(self.sort.borrow()), - &self.thread_nodes, - envelopes, - ); - return; - } - - /* Trace path back to root ThreadNode */ - while let Some(p) = &self.thread_nodes[&node_idx].parent { - node_idx = *p; - stack.push(node_idx); - } - - { - /* Trace path from root ThreadTree to the envelope's parent */ - let mut tree = self.tree.get_mut(); - for &s in stack.iter().rev() { - /* Borrow checker is being a tad silly here, so the following - * is basically this: - * - * let tree = &mut tree[s].children; - */ - let temp_tree = tree; - if let Some(pos) = temp_tree.iter().position(|v| v.id == s) { - tree = &mut temp_tree[pos].children; - } else { - let mut tree_node = ThreadTree::new(s); - node_build( - &mut tree_node, - s, - *(self.sort.borrow()), - &mut self.thread_nodes, - 1, - envelopes, - ); - let new_id = ThreadTree::insert_child( - temp_tree, - tree_node, - *(self.sort.borrow()), - &self.thread_nodes, - envelopes, - ); - tree = &mut temp_tree[new_id].children; - } - } - let pos = if let Some(pos) = tree.iter().position(|v| v.id == id) { - pos - } else { - /* Add new child */ - let tree_node = ThreadTree::new(id); - ThreadTree::insert_child( - tree, - tree_node, - *(self.sort.borrow()), - &self.thread_nodes, - envelopes, - ) - }; - node_build( - &mut tree[pos], - id, - *(self.sort.borrow()), - &mut self.thread_nodes, - 1, - envelopes, - ); - } - } - - /* - * Finalize instance by building the thread tree, set show subject and thread lengths etc. */ - fn build_envelopes(&mut self, envelopes: &Envelopes) { - { - let tree = self.tree.get_mut(); - tree.clear(); - for i in self.root_set.borrow().iter() { - let mut tree_node = ThreadTree::new(*i); - node_build( - &mut tree_node, - *i, - *(self.sort.borrow()), - &mut self.thread_nodes, - 0, /* indentation */ - envelopes, - ); - ThreadTree::insert_child( - tree, - tree_node, - *(self.sort.borrow()), - &self.thread_nodes, - envelopes, - ); - } - } - self.inner_sort_by(*self.sort.borrow(), envelopes); - self.inner_subsort_by(*self.subsort.borrow(), envelopes); - } - - fn inner_subsort_by(&self, subsort: (SortField, SortOrder), envelopes: &Envelopes) { - let tree = &mut self.tree.borrow_mut(); + fn inner_subsort_by(&self, _subsort: (SortField, SortOrder), _envelopes: &Envelopes) { + //FIXME: self\.thread_nodes needs interior mutability */ + return; + /* + let Threads { + ref tree_index, + ref thread_nodes, + .. + } = self; + let tree = &mut tree_index.borrow_mut(); for t in tree.iter_mut() { - t.children.sort_by(|a, b| match subsort { + thread_nodes[t].children.sort_by(|a, b| match subsort { (SortField::Date, SortOrder::Desc) => { - let a = &self.thread_nodes[&a.id]; - let b = &self.thread_nodes[&b.id]; + let a = &thread_nodes[&a]; + let b = &thread_nodes[&b]; b.date.cmp(&a.date) } (SortField::Date, SortOrder::Asc) => { - let a = &self.thread_nodes[&a.id]; - let b = &self.thread_nodes[&b.id]; + let a = &thread_nodes[&a]; + let b = &thread_nodes[&b]; a.date.cmp(&b.date) } (SortField::Subject, SortOrder::Desc) => { - let a = &self.thread_nodes[&a.id].message(); - let b = &self.thread_nodes[&b.id].message(); + let a = &thread_nodes[&a].message(); + let b = &thread_nodes[&b].message(); match (a, b) { (Some(_), Some(_)) => {} @@ -1238,8 +977,8 @@ impl Threads { ma.subject().cmp(&mb.subject()) } (SortField::Subject, SortOrder::Asc) => { - let a = &self.thread_nodes[&a.id].message(); - let b = &self.thread_nodes[&b.id].message(); + let a = &thread_nodes[&a].message(); + let b = &thread_nodes[&b].message(); match (a, b) { (Some(_), Some(_)) => {} @@ -1259,24 +998,25 @@ impl Threads { } }); } + */ } fn inner_sort_by(&self, sort: (SortField, SortOrder), envelopes: &Envelopes) { - let tree = &mut self.tree.borrow_mut(); - tree.sort_by(|a, b| match sort { + let tree = &mut self.tree_index.borrow_mut(); + tree.sort_by(|b, a| match sort { (SortField::Date, SortOrder::Desc) => { - let a = &self.thread_nodes[&a.id]; - let b = &self.thread_nodes[&b.id]; + let a = &self.thread_nodes[&a]; + let b = &self.thread_nodes[&b]; b.date.cmp(&a.date) } (SortField::Date, SortOrder::Asc) => { - let a = &self.thread_nodes[&a.id]; - let b = &self.thread_nodes[&b.id]; + let a = &self.thread_nodes[&a]; + let b = &self.thread_nodes[&b]; a.date.cmp(&b.date) } (SortField::Subject, SortOrder::Desc) => { - let a = &self.thread_nodes[&a.id].message(); - let b = &self.thread_nodes[&b.id].message(); + let a = &self.thread_nodes[&a].message(); + let b = &self.thread_nodes[&b].message(); match (a, b) { (Some(_), Some(_)) => {} @@ -1297,8 +1037,8 @@ impl Threads { .cmp(&mb.subject().split_graphemes()) } (SortField::Subject, SortOrder::Asc) => { - let a = &self.thread_nodes[&a.id].message(); - let b = &self.thread_nodes[&b.id].message(); + let a = &self.thread_nodes[&a].message(); + let b = &self.thread_nodes[&b].message(); match (a, b) { (Some(_), Some(_)) => {} @@ -1352,34 +1092,18 @@ impl Threads { } pub fn root_len(&self) -> usize { - self.tree.borrow().len() + self.tree_index.borrow().len() } pub fn root_set(&self, idx: usize) -> ThreadHash { - self.tree.borrow()[idx].id + self.tree_index.borrow()[idx] } pub fn root_iter(&self) -> RootIterator { - self.prune_tree(); RootIterator { pos: 0, - root_tree: self.tree.borrow(), - } - } - - pub fn has_sibling(&self, h: ThreadHash) -> bool { - if let Some(parent) = self[&h].parent { - let children = &self[&parent].children; - if children.is_empty() { - return false; - } - let pos = children - .iter() - .position(|&x| x == h) - .expect("Did not find node in parent!"); - pos != children.len() - 1 - } else { - false + root_tree: self.tree_index.borrow(), + thread_nodes: &self.thread_nodes, } } @@ -1491,23 +1215,35 @@ impl Threads { } ref_ptr = parent_id; } - let mut tree = self.tree.borrow_mut(); - let mut i = 0; - while i < tree.len() { - // Evaluate if useless - let node = &self.thread_nodes[&tree[i].id]; - if let (None, None, 0) = (node.parent, node.message, node.children.len()) { - tree.remove(i); - continue; - } - i += 1; - } } - fn link_threads(&mut self, envelopes: &mut Envelopes) { - for e in envelopes.values_mut() { - self.link_envelope(e); + fn tree_insert_root(&mut self, new_id: ThreadHash, envelopes: &Envelopes) { + debug_assert!( + self.thread_nodes[&new_id].parent.is_none() + || self.thread_nodes[self.thread_nodes[&new_id].parent.as_ref().unwrap()] + .message + .is_none() + ); + /* Index of reply_to_id in self.trees */ + let Threads { + ref mut tree_index, + ref thread_nodes, + .. + } = self; + let mut tree_index = tree_index.borrow_mut(); + for c in &thread_nodes[&new_id].children { + if let Some(i) = tree_index.iter().position(|t| *t == *c) { + tree_index.remove(i); + } } + let pos = ThreadNode::insert_child_pos( + &tree_index, + new_id, + *self.sort.borrow(), + &self.thread_nodes, + envelopes, + ); + tree_index.insert(pos, new_id); } } @@ -1521,100 +1257,7 @@ impl Index<&ThreadHash> for Threads { } } -fn node_build( - tree: &mut ThreadTree, - idx: ThreadHash, - sort: (SortField, SortOrder), - thread_nodes: &mut FnvHashMap, - indentation: usize, - envelopes: &Envelopes, -) { - if let Some(hash) = thread_nodes[&idx].message { - if !envelopes.contains_key(&hash) { - /* invalidate node */ - // thread_nodes[&idx].message = None; - } else if let Some(parent_id) = thread_nodes[&idx].parent { - if let Some(parent_hash) = thread_nodes[&parent_id].message { - if !envelopes.contains_key(&parent_hash) { - /* invalidate node */ - // thread_nodes[&parent_id].message = None; - } else { - /* decide if the subject should be shown in the UI. - * If parent subject is Foobar and reply is `Re: Foobar` - * then showing the reply's subject can be reduntant - */ - let mut subject = envelopes[&hash].subject(); - let mut subject = subject.to_mut().as_bytes(); - subject.strip_prefixes(); - let mut parent_subject = envelopes[&parent_hash].subject(); - let mut parent_subject = parent_subject.to_mut().as_bytes(); - parent_subject.strip_prefixes(); - if subject == parent_subject { - thread_nodes.entry(idx).and_modify(|e| { - e.show_subject = false; - }); - } - } - } - } - } else if let Some(node) = thread_nodes.get(&idx) { - if let (None, None, 0) = (node.parent, node.message, node.children.len()) { - return; - } - } - - let indentation = if thread_nodes[&idx].has_message() { - thread_nodes - .entry(idx) - .and_modify(|e| e.indentation = indentation); - indentation + 1 - } else if indentation > 0 { - indentation - } else { - indentation + 1 - }; - - let mut has_unseen = if let Some(msg) = thread_nodes[&idx].message { - !envelopes[&msg].is_seen() - } else { - false - }; - - thread_nodes.entry(idx).and_modify(|e| { - e.len = e.children.len(); - e.indentation = indentation - }); - - let mut child_vec: Vec = Vec::new(); - /* No child/parent relationship is mutated at any point and no nodes are added or removed. Only - * each node's fields change, so the following is safe. - */ - let children = &thread_nodes[&idx].children as *const Vec; - for &c in unsafe { &(*children) } { - let mut new_tree = ThreadTree::new(c); - node_build( - &mut new_tree, - c, - sort, - thread_nodes, - indentation + 1, - envelopes, - ); - let _c = (thread_nodes[&c].len, thread_nodes[&c].date); - thread_nodes.entry(idx).and_modify(|e| { - e.len += _c.0; - e.date = cmp::max(e.date, _c.1); - }); - - has_unseen |= thread_nodes[&c].has_unseen; - ThreadTree::insert_child(&mut child_vec, new_tree, sort, thread_nodes, envelopes); - } - tree.children = child_vec; - thread_nodes.entry(idx).and_modify(|e| { - e.has_unseen = has_unseen; - }); -} - +/* fn print_threadnodes( node_hash: ThreadHash, nodes: &FnvHashMap, @@ -1644,3 +1287,4 @@ fn print_threadnodes( } help(0, node_hash, nodes, envelopes); } +*/ diff --git a/ui/src/components/mail/listing/compact.rs b/ui/src/components/mail/listing/compact.rs index d5607b995..72a7a1fa5 100644 --- a/ui/src/components/mail/listing/compact.rs +++ b/ui/src/components/mail/listing/compact.rs @@ -301,7 +301,7 @@ impl ListingTrait for CompactListing { grid, ( pos_inc(upper_left, (0, r)), - (flag_x - 1, get_y(upper_left) + r), + (flag_x.saturating_sub(1), get_y(upper_left) + r), ), fg_color, bg_color, diff --git a/ui/src/conf/accounts.rs b/ui/src/conf/accounts.rs index 925559427..b879ea341 100644 --- a/ui/src/conf/accounts.rs +++ b/ui/src/conf/accounts.rs @@ -32,7 +32,7 @@ use melib::backends::{ }; use melib::error::{MeliError, Result}; use melib::mailbox::*; -use melib::thread::ThreadHash; +use melib::thread::{ThreadHash, ThreadNode, Threads}; use melib::AddressBook; use melib::StackVec; @@ -45,7 +45,7 @@ use std::ops::{Index, IndexMut}; use std::result; use std::sync::Arc; -pub type Worker = Option, Mailbox)>>>; +pub type Worker = Option>>>; macro_rules! mailbox { ($idx:expr, $folders:expr) => { @@ -58,9 +58,13 @@ pub enum MailboxEntry { Available(Mailbox), Failed(MeliError), /// first argument is done work, and second is total work - Parsing(usize, usize), - /// first argument is done work, and second is total work - Threading(usize, usize), + Parsing(Mailbox, usize, usize), +} + +impl Default for MailboxEntry { + fn default() -> Self { + MailboxEntry::Parsing(Mailbox::default(), 0, 0) + } } impl std::fmt::Display for MailboxEntry { @@ -71,12 +75,9 @@ impl std::fmt::Display for MailboxEntry { match self { MailboxEntry::Available(ref m) => m.name().to_string(), MailboxEntry::Failed(ref e) => e.to_string(), - MailboxEntry::Parsing(done, total) => { + MailboxEntry::Parsing(_, done, total) => { format!("Parsing messages. [{}/{}]", done, total) } - MailboxEntry::Threading(done, total) => { - format!("Calculating threads. [{}/{}]", done, total) - } } ) } @@ -90,7 +91,7 @@ impl MailboxEntry { } } pub fn is_parsing(&self) -> bool { - if let MailboxEntry::Parsing(_, _) = self { + if let MailboxEntry::Parsing(_, _, _) = self { true } else { false @@ -99,12 +100,14 @@ impl MailboxEntry { pub fn unwrap_mut(&mut self) -> &mut Mailbox { match self { MailboxEntry::Available(ref mut m) => m, + MailboxEntry::Parsing(ref mut m, _, _) => m, e => panic!(format!("mailbox is not available! {:#}", e)), } } pub fn unwrap(&self) -> &Mailbox { match self { MailboxEntry::Available(ref m) => m, + MailboxEntry::Parsing(ref m, _, _) => m, e => panic!(format!("mailbox is not available! {:#}", e)), } } @@ -247,7 +250,10 @@ impl Account { } } } - folders.insert(*h, MailboxEntry::Parsing(0, 0)); + folders.insert( + *h, + MailboxEntry::Parsing(Mailbox::new(f.clone(), &FnvHashMap::default()), 0, 0), + ); workers.insert( *h, Account::new_worker(f.clone(), &mut backend, notify_fn.clone()), @@ -309,29 +315,45 @@ impl Account { ) -> Worker { let mailbox_handle = backend.get(&folder); let mut builder = AsyncBuilder::new(); - let tx = builder.tx(); - Some(builder.build(Box::new(move || { - let mut handle = mailbox_handle.clone(); - let folder = folder.clone(); - let work = handle.work().unwrap(); - work.compute(); - handle.join(); - let envelopes: Result> = handle.extract().map(|v| { - v.into_iter() - .map(|e| (e.hash(), e)) - .collect::>() - }); - let hash = folder.hash(); - if envelopes.is_err() { - tx.send(AsyncStatus::Payload(Err(envelopes.unwrap_err()))); - notify_fn.notify(hash); - return; + let our_tx = builder.tx(); + let folder_hash = folder.hash(); + let w = builder.build(Box::new(move || { + let mut mailbox_handle = mailbox_handle.clone(); + let work = mailbox_handle.work().unwrap(); + let rx = mailbox_handle.rx(); + let tx = mailbox_handle.tx(); + + std::thread::Builder::new() + .spawn(move || { + work.compute(); + }) + .unwrap(); + + loop { + debug!("looping"); + chan_select! { + rx.recv() -> r => { + debug!("got {:?}", r); + match r { + Some(s @ AsyncStatus::Payload(_)) => { + our_tx.send(s); + debug!("notifying for {}", folder_hash); + notify_fn.notify(folder_hash); + } + Some(AsyncStatus::Finished) => { + debug!("exiting"); + return; + } + Some(s) => { + our_tx.send(s); + } + None => return, + } + } + } } - let envelopes = envelopes.unwrap(); - let m = Mailbox::new(folder, &envelopes); - tx.send(AsyncStatus::Payload(Ok((envelopes, m)))); - notify_fn.notify(hash); - }))) + })); + Some(w) } pub fn reload(&mut self, event: RefreshEvent, folder_hash: FolderHash) -> Option { if !self.folders[&folder_hash].is_available() { @@ -460,27 +482,32 @@ impl Account { &mut self.workers } - fn load_mailbox( - &mut self, - folder_hash: FolderHash, - payload: (Result<(FnvHashMap, Mailbox)>), - ) { + fn load_mailbox(&mut self, folder_hash: FolderHash, payload: Result>) { if payload.is_err() { self.folders .insert(folder_hash, MailboxEntry::Failed(payload.unwrap_err())); return; } - let (envelopes, mut mailbox) = payload.unwrap(); - if let Some(updated_folders) = - self.collection - .merge(envelopes, folder_hash, &mut mailbox, self.sent_folder) - { - for f in updated_folders { - self.notify_fn.notify(f); + let envelopes = payload + .unwrap() + .into_iter() + .map(|e| (e.hash(), e)) + .collect::>(); + match self.folders.entry(folder_hash).or_default() { + MailboxEntry::Failed(_) => {} + MailboxEntry::Parsing(ref mut m, _, _) | MailboxEntry::Available(ref mut m) => { + m.merge(&envelopes); + if let Some(updated_folders) = + self.collection + .merge(envelopes, folder_hash, m, self.sent_folder) + { + for f in updated_folders { + self.notify_fn.notify(f); + } + } } } - self.folders - .insert(folder_hash, MailboxEntry::Available(mailbox)); + self.notify_fn.notify(folder_hash); } pub fn status(&mut self, folder_hash: FolderHash) -> result::Result<(), usize> { @@ -488,31 +515,50 @@ impl Account { None => { return Ok(()); } - Some(ref mut w) if self.folders[&folder_hash].is_parsing() => match w.poll() { + Some(ref mut w) => match w.poll() { Ok(AsyncStatus::NoUpdate) => { - return Err(0); + //return Err(0); + } + Ok(AsyncStatus::Payload(envs)) => { + debug!("got payload in status for {}", folder_hash); + self.load_mailbox(folder_hash, envs); + } + Ok(AsyncStatus::Finished) if w.value.is_none() => { + debug!("got finished in status for {}", folder_hash); + self.folders.entry(folder_hash).and_modify(|f| { + let m = if let MailboxEntry::Parsing(m, _, _) = f { + std::mem::replace(m, Mailbox::default()) + } else { + return; + }; + *f = MailboxEntry::Available(m); + }); + + self.workers.insert(folder_hash, None); + } + Ok(AsyncStatus::Finished) if w.value.is_some() => { + let envs = w.value.take().unwrap(); + debug!("got payload in status for {}", folder_hash); + self.load_mailbox(folder_hash, envs); } - Ok(AsyncStatus::Finished) => {} Ok(AsyncStatus::ProgressReport(n)) => { self.folders.entry(folder_hash).and_modify(|f| { - if let MailboxEntry::Parsing(ref mut d, _) = f { + if let MailboxEntry::Parsing(_, ref mut d, _) = f { *d += n; } }); - return Err(n); + //return Err(n); } _ => { - return Err(0); + //return Err(0); } }, Some(_) => return Ok(()), }; - let m = mem::replace(self.workers.get_mut(&folder_hash).unwrap(), None) - .unwrap() - .extract(); - self.workers.insert(folder_hash, None); - self.load_mailbox(folder_hash, m); - if self.folders[&folder_hash].is_available() { + if self.folders[&folder_hash].is_available() + || (self.folders[&folder_hash].is_parsing() + && self.collection.threads.contains_key(&folder_hash)) + { Ok(()) } else { Err(0) @@ -558,15 +604,18 @@ impl Account { } pub fn operation(&self, h: EnvelopeHash) -> Box { for mailbox in self.folders.values() { - if let MailboxEntry::Available(ref m) = mailbox { - if m.envelopes.contains(&h) { - let operation = self.backend.operation(h, m.folder.hash()); - if self.settings.account.read_only() { - return ReadOnlyOp::new(operation); - } else { - return operation; + match mailbox { + MailboxEntry::Available(ref m) | MailboxEntry::Parsing(ref m, _, _) => { + if m.envelopes.contains(&h) { + let operation = self.backend.operation(h, m.folder.hash()); + if self.settings.account.read_only() { + return ReadOnlyOp::new(operation); + } else { + return operation; + } } } + _ => {} } } debug!("didn't find {}", h); diff --git a/ui/src/execute.rs b/ui/src/execute.rs index 499698c12..1db34cf91 100644 --- a/ui/src/execute.rs +++ b/ui/src/execute.rs @@ -22,7 +22,7 @@ /*! A parser module for user commands passed through the Ex mode. */ use melib::backends::FolderOperation; -pub use melib::mailbox::{SortField, SortOrder}; +pub use melib::thread::{SortField, SortOrder}; use nom::{digit, not_line_ending}; use std; pub mod actions; diff --git a/ui/src/execute/actions.rs b/ui/src/execute/actions.rs index fae20e99b..ec62db907 100644 --- a/ui/src/execute/actions.rs +++ b/ui/src/execute/actions.rs @@ -25,8 +25,8 @@ use crate::components::Component; use melib::backends::FolderOperation; -pub use melib::mailbox::{SortField, SortOrder}; use melib::thread::ThreadHash; +pub use melib::thread::{SortField, SortOrder}; use melib::{Draft, EnvelopeHash}; extern crate uuid; diff --git a/ui/src/workers.rs b/ui/src/workers.rs index 7279f49d1..1ea7f131d 100644 --- a/ui/src/workers.rs +++ b/ui/src/workers.rs @@ -19,19 +19,19 @@ impl WorkController { } } -/* impl Drop for WorkController { fn drop(&mut self) { for _ in 0..self.threads.len() { self.thread_end_tx.send(true); } + /* let threads = mem::replace(&mut self.threads, Vec::new()); for handle in threads { handle.join().unwrap(); } + */ } } -*/ // We need a way to keep track of what work needs to be done. // This is a multi-source, multi-consumer queue which we call a @@ -194,16 +194,20 @@ impl WorkController { let mut work_done = 0; 'work_loop: loop { + debug!("Waiting for work"); // Loop while there's expected to be work, looking for work. chan_select! { thread_end_rx.recv() -> _ => { + debug!("received thread_end_rx, quitting"); break 'work_loop; }, new_jobs_rx.recv() -> _ => { // If work is available, do that work. while let Some(work) = thread_queue.get_work() { + debug!("Got some work"); // Do some work. work.compute(); + debug!("finished work"); // Record that some work was done. work_done += 1; @@ -243,58 +247,3 @@ impl WorkController { } } } -/* -pub fn add_jobkk - - println!("Adding jobs to the queue."); - // Variables to keep track of the number of jobs we expect to do. - let mut jobs_remaining = 0; - let mut jobs_total = 0; - - // Just add some numbers to the queue. - // These numbers will be passed into fib(), so they need to stay pretty - // small. - for work in 0..90 { - // Add each one several times. - for _ in 0..100 { - jobs_remaining = queue.add_work(work); - jobs_total += 1; - } - } - - - // Report that some jobs were inserted, and how many are left to be done. - // This is interesting because the workers have been taking jobs out of the queue - // the whole time the control thread has been putting them in! - // - // Try removing the use of std::thread::yield_now() in the thread closure. - // You'll probably (depending on your system) notice that the number remaining - // after insertion goes way up. That's because the operating system is usually - // (not always, but usually) fairly conservative about interrupting a thread - // that is actually doing work. - // - // Similarly, if you add a call to yield_now() in the loop above, you'll see the - // number remaining probably drop to 1 or 2. This can also change depending on - // how optimized the output code is - try `cargo run --release` vs `cargo run`. - // - // This inconsistency should drive home to you that you as the programmer can't - // make any assumptions at all about when and in what order things will happen - // in parallel code unless you use thread control primatives as demonstrated - // in this program. - println!("Total of {} jobs inserted into the queue ({} remaining at this time).", - jobs_total, - jobs_remaining); - - - // Get completed work from the channel while there's work to be done. - while jobs_total > 0 { - match results_rx.recv() { - // If the control thread successfully receives, a job was completed. - Ok(_) => { jobs_total -= 1 }, - // If the control thread is the one left standing, that's pretty - // problematic. - Err(_) => {panic!("All workers died unexpectedly.");} - } - } - -*/