diff --git a/melib/src/mailbox.rs b/melib/src/mailbox.rs index c593bc8a3..773d226df 100644 --- a/melib/src/mailbox.rs +++ b/melib/src/mailbox.rs @@ -41,26 +41,30 @@ pub use self::collection::*; use std::option::Option; +use fnv::{FnvHashMap, FnvHashSet}; /// `Mailbox` represents a folder of mail. #[derive(Debug, Deserialize, Serialize, Clone, Default)] pub struct Mailbox { #[serde(skip_serializing, skip_deserializing)] pub folder: Folder, name: String, - pub collection: Collection, + pub envelopes: FnvHashSet, + pub thread_root_set: FnvHashSet, has_sent: bool, } impl Mailbox { - pub fn new(folder: Folder, envelopes: Result>) -> Result { - let mut envelopes: Vec = envelopes?; - envelopes.sort_by(|a, b| a.date().cmp(&b.date())); - let collection = Collection::new(envelopes, &folder); + pub fn new( + folder: Folder, + envelopes: Result<&FnvHashMap>, + ) -> Result { + let envelopes = envelopes?; let name = folder.name().into(); + let envelopes = envelopes.keys().cloned().collect(); Ok(Mailbox { folder, - collection, name, + envelopes, ..Default::default() }) } @@ -70,65 +74,20 @@ impl Mailbox { } pub fn is_empty(&self) -> bool { - self.collection.is_empty() + self.envelopes.is_empty() } pub fn len(&self) -> usize { - self.collection.len() + self.envelopes.len() } - pub fn thread_to_mail_mut(&mut self, h: ThreadHash) -> &mut Envelope { - self.collection - .envelopes - .entry(self.collection.threads.thread_to_mail(h)) - .or_default() + pub fn insert(&mut self, h: EnvelopeHash) { + self.envelopes.insert(h); } - pub fn thread_to_mail(&self, h: ThreadHash) -> &Envelope { - &self.collection.envelopes[&self.collection.threads.thread_to_mail(h)] - } - pub fn threaded_mail(&self, h: ThreadHash) -> EnvelopeHash { - self.collection.threads.thread_to_mail(h) - } - pub fn mail_and_thread(&mut self, i: EnvelopeHash) -> (&mut Envelope, &ThreadNode) { - let thread; - { - let x = &mut self.collection.envelopes.entry(i).or_default(); - thread = &self.collection.threads[&x.thread()]; - } - (self.collection.envelopes.entry(i).or_default(), thread) - } - pub fn thread(&self, h: ThreadHash) -> &ThreadNode { - &self.collection.threads.thread_nodes()[&h] - } - - pub fn insert_sent_folder(&mut self, _sent: &Mailbox) { - /*if !self.has_sent { - for envelope in sent.collection.envelopes.values() { - self.insert_reply(envelope); - } - self.has_sent = true; - }*/ - } - pub fn rename(&mut self, old_hash: EnvelopeHash, new_hash: EnvelopeHash) { - self.collection.rename(old_hash, new_hash); - } + self.envelopes.remove(&old_hash); - pub fn update(&mut self, old_hash: EnvelopeHash, envelope: Envelope) { - self.collection.update_envelope(old_hash, envelope); + self.envelopes.insert(new_hash); } - - pub fn insert(&mut self, envelope: Envelope) -> &Envelope { - let hash = envelope.hash(); - self.collection.insert(envelope); - &self.collection[&hash] - } - - pub fn insert_reply(&mut self, envelope: &Envelope) { - debug!("mailbox insert reply {}", self.name); - self.collection.insert_reply(envelope); - } - - pub fn remove(&mut self, envelope_hash: EnvelopeHash) { - self.collection.remove(envelope_hash); - // debug!("envelope_hash: {}\ncollection:\n{:?}", envelope_hash, self.collection); + pub fn remove(&mut self, h: EnvelopeHash) { + self.envelopes.remove(&h); } } diff --git a/melib/src/mailbox/collection.rs b/melib/src/mailbox/collection.rs index b53987eaf..9298d3ea5 100644 --- a/melib/src/mailbox/collection.rs +++ b/melib/src/mailbox/collection.rs @@ -1,4 +1,5 @@ use super::*; +use crate::mailbox::backends::FolderHash; use std::collections::BTreeMap; use std::fs; use std::io; @@ -6,22 +7,20 @@ use std::ops::{Deref, DerefMut}; use fnv::FnvHashMap; -/// `Mailbox` represents a folder of mail. #[derive(Debug, Clone, Deserialize, Default, Serialize)] pub struct Collection { - #[serde(skip_serializing, skip_deserializing)] - folder: Folder, pub envelopes: FnvHashMap, + message_ids: FnvHashMap, EnvelopeHash>, date_index: BTreeMap, subject_index: Option>, - pub threads: Threads, + pub threads: FnvHashMap, + sent_folder: Option, } impl Drop for Collection { fn drop(&mut self) { - let cache_dir = - xdg::BaseDirectories::with_profile("meli", format!("{}_Thread", self.folder.hash())) - .unwrap(); + let cache_dir: xdg::BaseDirectories = + xdg::BaseDirectories::with_profile("meli", "threads".to_string()).unwrap(); if let Ok(cached) = cache_dir.place_cache_file("threads") { /* place result in cache directory */ let f = match fs::File::create(cached) { @@ -37,47 +36,24 @@ impl Drop for Collection { } impl Collection { - pub fn new(vec: Vec, folder: &Folder) -> Collection { - let mut envelopes: FnvHashMap = - FnvHashMap::with_capacity_and_hasher(vec.len(), Default::default()); - for e in vec { - envelopes.insert(e.hash(), e); - } + pub fn new(envelopes: FnvHashMap) -> Collection { let date_index = BTreeMap::new(); let subject_index = None; + let message_ids = FnvHashMap::with_capacity_and_hasher(2048, Default::default()); /* Scrap caching for now. When a cached threads file is loaded, we must remove/rehash the * thread nodes that shouldn't exist anymore (e.g. because their file moved from /new to * /cur, or it was deleted). */ - let threads = Threads::new(&mut envelopes); - - /*let cache_dir = - xdg::BaseDirectories::with_profile("meli", format!("{}_Thread", folder.hash())) - .unwrap(); - if let Some(cached) = cache_dir.find_cache_file("threads") { - let reader = io::BufReader::new(fs::File::open(cached).unwrap()); - let result: result::Result = bincode::deserialize_from(reader); - let ret = if let Ok(mut cached_t) = result { - use std::iter::FromIterator; - debug!("loaded cache, our hash set is {:?}\n and the cached one is {:?}", FnvHashSet::from_iter(envelopes.keys().cloned()), cached_t.hash_set); - cached_t.amend(&mut envelopes); - cached_t - } else { - Threads::new(&mut envelopes) - }; - ret - } else { - Threads::new(&mut envelopes) - }; - */ + let threads = FnvHashMap::with_capacity_and_hasher(16, Default::default()); Collection { - folder: folder.clone(), envelopes, date_index, + message_ids, subject_index, threads, + sent_folder: None, } } @@ -89,22 +65,34 @@ impl Collection { self.envelopes.is_empty() } - pub fn remove(&mut self, envelope_hash: EnvelopeHash) { + pub fn remove(&mut self, envelope_hash: EnvelopeHash, folder_hash: FolderHash) { debug!("DEBUG: Removing {}", envelope_hash); self.envelopes.remove(&envelope_hash); - self.threads.remove(envelope_hash, &mut self.envelopes); + self.threads + .entry(folder_hash) + .or_default() + .remove(envelope_hash, &mut self.envelopes); } - pub fn rename(&mut self, old_hash: EnvelopeHash, new_hash: EnvelopeHash) { + pub fn rename( + &mut self, + old_hash: EnvelopeHash, + new_hash: EnvelopeHash, + folder_hash: FolderHash, + ) { if !self.envelopes.contains_key(&old_hash) { return; } let mut env = self.envelopes.remove(&old_hash).unwrap(); env.set_hash(new_hash); + self.message_ids + .insert(env.message_id().raw().to_vec(), new_hash); self.envelopes.insert(new_hash, env); { if self .threads + .entry(folder_hash) + .or_default() .update_envelope(old_hash, new_hash, &self.envelopes) .is_ok() { @@ -114,17 +102,102 @@ impl Collection { /* envelope is not in threads, so insert it */ let env = self.envelopes.entry(new_hash).or_default() as *mut Envelope; unsafe { - self.threads.insert(&mut (*env), &self.envelopes); + self.threads + .entry(folder_hash) + .or_default() + .insert(&mut (*env), &self.envelopes); } } - pub fn update_envelope(&mut self, old_hash: EnvelopeHash, envelope: Envelope) { + pub fn merge( + &mut self, + mut envelopes: FnvHashMap, + folder_hash: FolderHash, + mailbox: &mut Result, + sent_folder: Option, + ) { + self.sent_folder = sent_folder; + 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 + if let Ok(mailbox) = mailbox.as_mut() { + mailbox.remove(h); + } + false + } else { + self.message_ids.insert(e.message_id().raw().to_vec(), h); + true + } + }); + let mut threads = Threads::new(&mut envelopes); + + for (h, e) in envelopes { + self.envelopes.insert(h, e); + } + for (t_fh, t) in self.threads.iter_mut() { + if self.sent_folder.map(|f| f == folder_hash).unwrap_or(false) { + let mut ordered_hash_set = threads + .hash_set + .iter() + .cloned() + .collect::>(); + unsafe { + /* FIXME NLL + * Sorting ordered_hash_set triggers a borrow which should not happen with NLL + * probably */ + let envelopes = &self.envelopes as *const FnvHashMap; + ordered_hash_set.sort_by(|a, b| { + (*envelopes)[a] + .date() + .partial_cmp(&(*(envelopes))[b].date()) + .unwrap() + }); + } + for h in ordered_hash_set { + t.insert_reply(&mut self.envelopes, h); + } + continue; + } + if self.sent_folder.map(|f| f == *t_fh).unwrap_or(false) { + let mut ordered_hash_set = + t.hash_set.iter().cloned().collect::>(); + unsafe { + /* FIXME NLL + * Sorting ordered_hash_set triggers a borrow which should not happen with NLL + * probably */ + let envelopes = &self.envelopes as *const FnvHashMap; + ordered_hash_set.sort_by(|a, b| { + (*envelopes)[a] + .date() + .partial_cmp(&(*(envelopes))[b].date()) + .unwrap() + }); + } + for h in ordered_hash_set { + threads.insert_reply(&mut self.envelopes, h); + } + } + } + self.threads.insert(folder_hash, threads); + } + + pub fn update(&mut self, old_hash: EnvelopeHash, envelope: Envelope, folder_hash: FolderHash) { self.envelopes.remove(&old_hash); let new_hash = envelope.hash(); + self.message_ids + .insert(envelope.message_id().raw().to_vec(), new_hash); 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); + } + } { if self .threads + .entry(folder_hash) + .or_default() .update_envelope(old_hash, new_hash, &self.envelopes) .is_ok() { @@ -134,26 +207,28 @@ impl Collection { /* envelope is not in threads, so insert it */ let env = self.envelopes.entry(new_hash).or_default() as *mut Envelope; unsafe { - self.threads.insert(&mut (*env), &self.envelopes); + self.threads + .entry(folder_hash) + .or_default() + .insert(&mut (*env), &self.envelopes); } } - pub fn insert(&mut self, envelope: Envelope) { + pub fn insert(&mut self, envelope: Envelope, folder_hash: FolderHash) -> &Envelope { let hash = envelope.hash(); - debug!("DEBUG: Inserting hash {} in {}", hash, self.folder.name()); + self.message_ids + .insert(envelope.message_id().raw().to_vec(), hash); self.envelopes.insert(hash, envelope); - let env = self.envelopes.entry(hash).or_default() as *mut Envelope; - unsafe { - self.threads.insert(&mut (*env), &self.envelopes); - } + self.threads + .entry(folder_hash) + .or_default() + .insert_reply(&mut self.envelopes, hash); + &self.envelopes[&hash] } - pub(crate) fn insert_reply(&mut self, _envelope: &Envelope) { - return; - /* - //self.insert(envelope); - debug!("insert_reply in collections"); - self.threads.insert_reply(envelope, &mut self.envelopes); - */ + pub fn insert_reply(&mut self, env_hash: EnvelopeHash) { + for (_, t) in self.threads.iter_mut() { + t.insert_reply(&mut self.envelopes, env_hash); + } } } diff --git a/melib/src/mailbox/thread.rs b/melib/src/mailbox/thread.rs index ded6b943d..79cb16003 100644 --- a/melib/src/mailbox/thread.rs +++ b/melib/src/mailbox/thread.rs @@ -32,6 +32,7 @@ * user having mutable ownership. */ +use crate::grapheme_clusters::*; use crate::mailbox::email::parser::BytesExt; use crate::mailbox::email::*; use uuid::Uuid; @@ -49,9 +50,21 @@ use std::str::FromStr; type Envelopes = FnvHashMap; -#[derive(PartialEq, Hash, Eq, Debug, Copy, Clone, Serialize, Deserialize, Default)] +#[derive(PartialEq, Hash, Eq, Copy, Clone, Serialize, Deserialize, Default)] pub struct ThreadHash(Uuid); +impl fmt::Debug for ThreadHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0.to_string()) + } +} + +impl fmt::Display for ThreadHash { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.0.to_string()) + } +} + impl ThreadHash { fn new() -> Self { ThreadHash(Uuid::new_v4()) @@ -80,18 +93,24 @@ fn rec_change_children( idx: ThreadHash, new_root: ThreadHash, ) { - let entry = b.entry(idx).or_default(); - entry.thread_group = new_root; + b.entry(idx).and_modify(|e| { + e.thread_group = new_root; + }); - for c in entry.children.clone() { + let mut ctr = 0; + while ctr < b[&idx].children.len() { + let c = b[&idx].children[ctr]; rec_change_children(b, c, new_root); + ctr += 1; } } macro_rules! remove_from_parent { ($buf:expr, $idx:expr) => {{ + let mut parent: Option = None; let entry = $buf.entry($idx).or_default(); if let Some(p) = entry.parent { + parent = Some(p); if let Some(pos) = $buf[&p].children.iter().position(|c| *c == $idx) { $buf.entry(p).and_modify(|e| { e.children.remove(pos); @@ -102,20 +121,22 @@ macro_rules! remove_from_parent { $buf.entry($idx).and_modify(|e| e.parent = None); rec_change_children($buf, $idx, $idx); $buf.entry($idx).and_modify(|e| e.thread_group = $idx); + parent }}; } macro_rules! make { - (($p:expr)parent of($c:expr), $buf:expr) => { - remove_from_parent!($buf, $c); + (($p:expr)parent of($c:expr), $buf:expr) => {{ + let prev_parent = remove_from_parent!($buf, $c); if !($buf[&$p]).children.contains(&$c) { + /* Pruned nodes keep their children in case they show up in a later merge, so do not panic + * if children exists */ $buf.entry($p).and_modify(|e| e.children.push($c)); - } else { - panic!(); } $buf.entry($c).and_modify(|e| e.parent = Some($p)); union($buf, $c, $p); - }; + prev_parent + }}; } /* Strip common prefixes from subjects */ @@ -225,18 +246,12 @@ impl FromStr for SortOrder { /* * The thread tree holds the sorted state of the thread nodes */ -#[derive(Clone, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] struct ThreadTree { id: ThreadHash, children: Vec, } -impl fmt::Debug for ThreadTree { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "") - } -} - impl ThreadTree { fn new(id: ThreadHash) -> Self { ThreadTree { @@ -244,6 +259,78 @@ impl ThreadTree { 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 @@ -285,7 +372,7 @@ impl<'a> Iterator for ThreadsIterator<'a> { let ret = ( self.stack.len(), tree[self.pos].id, - !tree.is_empty() && !self.stack.is_empty() && (self.pos != (tree.len() - 1)), + !self.stack.is_empty() && (self.pos < (tree.len() - 1)), ); if !tree[self.pos].children.is_empty() { self.stack.push(self.pos); @@ -355,6 +442,7 @@ pub struct ThreadNode { date: UnixTimestamp, indentation: usize, show_subject: bool, + pruned: bool, len: usize, has_unseen: bool, @@ -375,6 +463,7 @@ impl Default for ThreadNode { date: UnixTimestamp::default(), indentation: 0, show_subject: true, + pruned: false, len: 0, has_unseen: false, @@ -406,7 +495,7 @@ impl ThreadNode { } pub fn is_empty(&self) -> bool { - self.len == 0 + self.parent.is_none() && self.message.is_none() && self.children.is_empty() } pub fn message(&self) -> Option { @@ -453,6 +542,8 @@ pub struct Threads { tree: RefCell>, message_ids: FnvHashMap, ThreadHash>, + pub message_ids_set: FnvHashSet>, + pub missing_message_ids: FnvHashSet>, pub hash_set: FnvHashSet, sort: RefCell<(SortField, SortOrder)>, subsort: RefCell<(SortField, SortOrder)>, @@ -564,31 +655,40 @@ impl Threads { /* "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); - 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); + /* + 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 + } } - remove_from_parent!(thread_nodes, idx); - thread_nodes.entry(idx).and_modify(|e| { - e.children.clear(); - }); - return true; // Pruned - } - } + */ /* Recurse to children, but keep in mind more children can be added in each iteration */ @@ -602,12 +702,12 @@ impl Threads { c_idx += 1; } } - !thread_nodes[&idx].has_message() && thread_nodes[&idx].children.is_empty() + thread_nodes[&idx].pruned } let mut idx = 0; loop { - if idx == root_set.len() { + if idx >= root_set.len() { break; } if prune(&mut self.thread_nodes, root_set[idx], root_set) { @@ -618,23 +718,37 @@ impl Threads { } } - pub fn new(collection: &mut Envelopes) -> Threads { + 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: */ /* a vector to hold thread members */ let thread_nodes: FnvHashMap = FnvHashMap::with_capacity_and_hasher( - (collection.len() as f64 * 1.2) as usize, + (envelopes.len() as f64 * 1.2) as usize, Default::default(), ); /* A hash table of Message IDs */ let message_ids: FnvHashMap, ThreadHash> = - FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default()); + FnvHashMap::with_capacity_and_hasher(envelopes.len(), Default::default()); + /* A hash set of Message IDs we haven't encountered yet as an Envelope */ + let missing_message_ids: FnvHashSet> = + FnvHashSet::with_capacity_and_hasher(envelopes.len(), Default::default()); + /* A hash set of Message IDs we have encountered as a MessageID */ + let message_ids_set: FnvHashSet> = + FnvHashSet::with_capacity_and_hasher(envelopes.len(), Default::default()); let hash_set: FnvHashSet = - FnvHashSet::with_capacity_and_hasher(collection.len(), Default::default()); + FnvHashSet::with_capacity_and_hasher(envelopes.len(), Default::default()); let mut t = Threads { thread_nodes, message_ids, + message_ids_set, + missing_message_ids, hash_set, subsort: RefCell::new((SortField::Subject, SortOrder::Desc)), @@ -642,10 +756,10 @@ impl Threads { }; /* Add each message to message_ids and threads, and link them together according to the * References / In-Reply-To headers */ - t.link_threads(collection); + t.link_threads(envelopes); - t.create_root_set(collection); - t.build_collection(collection); + 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; @@ -654,8 +768,8 @@ impl Threads { // if let Some(m) = _t.message { // debug!( // "\tmessage: {}\t{}", - // collection[&m].subject(), - // collection[&m].message_id() + // envelopes[&m].subject(), + // envelopes[&m].message_id() // ); // } else { // debug!("\tNo message"); @@ -673,7 +787,7 @@ impl Threads { //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: {}", collection[&m].subject()); + // debug!("\tmessage: {}", envelopes[&m].subject()); // } else { // debug!("\tNo message"); // } @@ -681,10 +795,10 @@ impl Threads { t } - fn create_root_set(&mut self, collection: &Envelopes) { + 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(collection.len()); + let mut root_set: Vec = Vec::with_capacity(envelopes.len()); /* Find the root set */ for v in self.message_ids.values() { @@ -693,181 +807,19 @@ impl Threads { } } - let mut roots_to_remove: Vec = Vec::with_capacity(root_set.len()); /* Prune empty thread nodes */ self.prune_empty_nodes(&mut root_set); - /* "Group root set by subject." - * - * "If any two members of the root set have the same subject, merge them. This is so that - * messages which don't have References headers at all still get threaded (to the extent - * possible, at least.)" - */ - let mut subject_table: FnvHashMap, (bool, ThreadHash)> = - FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default()); - - for &r in root_set.iter() { - /* "Find the subject of that sub-tree": */ - let (mut subject, mut is_re): (_, bool) = if self.thread_nodes[&r].message.is_some() { - /* "If there is a message in the Container, the subject is the subject of that - * message. " */ - let msg_idx = self.thread_nodes[&r].message.unwrap(); - let envelope = &collection[&msg_idx]; - (envelope.subject(), !envelope.references().is_empty()) - } else { - /* "If there is no message in the Container, then the Container will have at least - * one child Container, and that Container will have a message. Use the subject of - * that message instead." */ - let msg_idx = self.thread_nodes[&self.thread_nodes[&r].children[0]] - .message - .unwrap(); - let envelope = &collection[&msg_idx]; - (envelope.subject(), !envelope.references().is_empty()) - }; - - /* "Strip ``Re:'', ``RE:'', ``RE[5]:'', ``Re: Re[4]: Re:'' and so on." */ - /* References of this envelope can be empty but if the subject contains a ``Re:`` - * prefix, it's a reply */ - let mut stripped_subj = subject.to_mut().as_bytes(); - is_re |= stripped_subj.is_a_reply(); - stripped_subj.strip_prefixes(); - - if stripped_subj.is_empty() { - continue; - } - - /* "Add this Container to the subject_table if:" */ - if subject_table.contains_key(stripped_subj) { - let (other_is_re, id) = subject_table[stripped_subj]; - /* "This one is an empty container and the old one is not: the empty one is more - * interesting as a root, so put it in the table instead." - * or - * "The container in the table has a ``Re:'' version of this subject, and this - * container has a non-``Re:'' version of this subject. The non-re version is the - * more interesting of the two." */ - if (!self.thread_nodes[&id].has_message() && self.thread_nodes[&r].has_message()) - || (other_is_re && !is_re) - { - mem::replace( - subject_table.entry(stripped_subj.to_vec()).or_default(), - (is_re, r), - ); - } - } else { - /* "There is no container in the table with this subject" */ - subject_table.insert(stripped_subj.to_vec(), (is_re, r)); - } - } - - /* "Now the subject_table is populated with one entry for each subject which occurs in the - * root set. Now iterate over the root set, and gather together the difference." */ - for i in 0..root_set.len() { - let r = root_set[i]; - - /* "Find the subject of this Container (as above.)" */ - let (mut subject, mut is_re): (_, bool) = if self.thread_nodes[&r].message.is_some() { - let msg_idx = self.thread_nodes[&r].message.unwrap(); - let envelope = &collection[&msg_idx]; - (envelope.subject(), !envelope.references().is_empty()) - } else { - let msg_idx = self.thread_nodes[&self.thread_nodes[&r].children[0]] - .message - .unwrap(); - let envelope = &collection[&msg_idx]; - (envelope.subject(), !envelope.references().is_empty()) - }; - - let mut subject = subject.to_mut().as_bytes(); - is_re |= subject.is_a_reply(); - subject.strip_prefixes(); - if subject.is_empty() { - continue; - } - - let (other_is_re, other_idx) = subject_table[subject]; - /* "If it is null, or if it is this container, continue." */ - if !self.thread_nodes[&other_idx].has_message() || other_idx == r { - continue; - } - - /* "Otherwise, we want to group together this Container and the one in the table. There - * are a few possibilities:" */ - - /* - * "If both are dummies, append one's children to the other, and remove the now-empty - * container." - */ - if !self.thread_nodes[&r].has_message() && !self.thread_nodes[&other_idx].has_message() - { - let children = self.thread_nodes[&r].children.clone(); - for c in children { - make!((other_idx) parent of (c), &mut self.thread_nodes); - } - roots_to_remove.push(i); - - /* "If one container is a empty and the other is not, make the non-empty one be a child - * of the empty, and a sibling of the other ``real'' messages with the same subject - * (the empty's children.)" - */ - } else if self.thread_nodes[&r].has_message() - && !self.thread_nodes[&other_idx].has_message() - { - make!((other_idx) parent of (r), &mut self.thread_nodes); - if !root_set.contains(&other_idx) { - root_set.push(other_idx); - } - roots_to_remove.push(i); - } else if !self.thread_nodes[&r].has_message() - && self.thread_nodes[&other_idx].has_message() - { - make!((r) parent of (other_idx), &mut self.thread_nodes); - if let Some(pos) = root_set.iter().position(|&i| i == other_idx) { - roots_to_remove.push(pos); - } - /* - * "If that container is a non-empty, and that message's subject does not begin with ``Re:'', but this - * message's subject does, then make this be a child of the other." - */ - } else if self.thread_nodes[&other_idx].has_message() && !other_is_re && is_re { - make!((other_idx) parent of (r), &mut self.thread_nodes); - roots_to_remove.push(i); - - /* "If that container is a non-empty, and that message's subject begins with ``Re:'', but this - * message's subject does not, then make that be a child of this one -- they were misordered. (This - * happens somewhat implicitly, since if there are two messages, one with Re: and one without, the one - * without will be in the hash table, regardless of the order in which they were - * seen.)" - */ - } else if self.thread_nodes[&other_idx].has_message() && other_is_re && !is_re { - make!((r) parent of (other_idx), &mut self.thread_nodes); - if let Some(pos) = root_set.iter().position(|r| *r == other_idx) { - roots_to_remove.push(pos); - } - - /* "Otherwise, make a new empty container and make both msgs be a child of it. This catches the - * both-are-replies and neither-are-replies cases, and makes them be siblings instead of asserting a - * hierarchical relationship which might not be true." - */ - } else { - let new_id = ThreadHash::new(); - self.thread_nodes.insert(new_id, ThreadNode::new(new_id)); - make!((new_id) parent of (r), &mut self.thread_nodes); - make!((new_id) parent of (other_idx), &mut self.thread_nodes); - root_set[i] = new_id; - if let Some(pos) = root_set.iter().position(|r| *r == other_idx) { - roots_to_remove.push(pos); - } - } - } - roots_to_remove.sort_unstable(); - roots_to_remove.dedup(); - for r in roots_to_remove.into_iter().rev() { - root_set.remove(r); - } - 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, @@ -889,7 +841,7 @@ impl Threads { &mut self, old_hash: EnvelopeHash, new_hash: EnvelopeHash, - collection: &Envelopes, + envelopes: &Envelopes, ) -> Result<(), ()> { /* must update: * - hash_set @@ -907,12 +859,12 @@ impl Threads { }; self.hash_set.remove(&old_hash); self.hash_set.insert(new_hash); - self.rebuild_thread(thread_hash, collection); + self.rebuild_thread(thread_hash, envelopes); Ok(()) } #[inline] - pub fn remove(&mut self, envelope_hash: EnvelopeHash, collection: &mut Envelopes) { + pub fn remove(&mut self, envelope_hash: EnvelopeHash, envelopes: &mut Envelopes) { self.hash_set.remove(&envelope_hash); //{ // let pos = self @@ -954,9 +906,10 @@ impl Threads { node_build( &mut tree[pos], node_idx, + *(self.sort.borrow()), &mut self.thread_nodes, 1, - collection, + envelopes, ); } } @@ -967,27 +920,27 @@ impl Threads { self.tree.borrow_mut().retain(|t| root_set.contains(&t.id)); } - pub fn amend(&mut self, collection: &mut Envelopes) { - let new_hash_set = FnvHashSet::from_iter(collection.keys().cloned()); + pub fn amend(&mut self, envelopes: &mut Envelopes) { + let new_hash_set = FnvHashSet::from_iter(envelopes.keys().cloned()); let difference: Vec = self.hash_set.difference(&new_hash_set).cloned().collect(); for h in difference { - self.remove(h, collection); + self.remove(h, envelopes); } let difference: Vec = new_hash_set.difference(&self.hash_set).cloned().collect(); for h in difference { - debug!("inserting {}", collection[&h].subject()); - let env = collection.entry(h).or_default() as *mut Envelope; + debug!("inserting {}", envelopes[&h].subject()); + let env = envelopes.entry(h).or_default() as *mut Envelope; unsafe { - // `collection` is borrowed immutably and `insert` only changes the envelope's + // `envelopes` is borrowed immutably and `insert` only changes the envelope's // `thread` field. - self.insert(&mut (*env), collection); + self.insert(&mut (*env), envelopes); } } - self.create_root_set(collection); + 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); @@ -995,16 +948,84 @@ impl Threads { tree.retain(|t| root_set.contains(&t.id)); } - pub fn insert(&mut self, envelope: &mut Envelope, collection: &Envelopes) { + pub fn insert(&mut self, envelope: &mut Envelope, envelopes: &Envelopes) { self.link_envelope(envelope); { let id = self.message_ids[envelope.message_id().raw()]; - self.rebuild_thread(id, collection); + self.rebuild_thread(id, envelopes); } } - pub fn insert_reply(&mut self, envelope: &Envelope, collection: &mut Envelopes) -> bool { - //return false; + /* 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(|mgid| mgid.raw()) + .unwrap_or(&[]), + ) + .map(|h| *h); + if let Some(id) = self + .message_ids + .get(envelopes[&env_hash].message_id().raw()) + .map(|h| *h) + { + self.thread_nodes.entry(id).and_modify(|n| { + n.message = Some(env_hash); + n.date = envelopes[&env_hash].date(); + n.pruned = false; + if n.parent.is_none() { + if let Some(reply_to_id) = reply_to_id { + n.parent = Some(reply_to_id); + } + } + }); + if let Some(reply_to_id) = reply_to_id { + if !self.thread_nodes[&reply_to_id].children.contains(&id) { + make!((reply_to_id) parent of (id), &mut self.thread_nodes); + self.union(id, reply_to_id); + } + } + + 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 + .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(id); + self.hash_set.insert(env_hash); + true + } else if let Some(reply_to_id) = reply_to_id { + let new_id = ThreadHash::new(); + self.thread_nodes.insert( + new_id, + ThreadNode { + message: Some(env_hash), + parent: Some(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); + 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); + true + } else { + false + } + /* { if let Some(in_reply_to) = envelope.in_reply_to() { if !self.message_ids.contains_key(in_reply_to.raw()) { @@ -1015,42 +1036,89 @@ impl Threads { } } let hash: EnvelopeHash = envelope.hash(); - collection.insert(hash, envelope.clone()); + envelopes.insert(hash, envelope.clone()); { - let envelope = collection.entry(hash).or_default() as *mut Envelope; + let envelope = envelopes.entry(hash).or_default() as *mut Envelope; unsafe { /* Safe because insert only changes envelope's fields and nothing more */ - self.insert(&mut (*envelope), &collection); + self.insert(&mut (*envelope), &envelopes); } } - let envelope: &Envelope = &collection[&hash]; + let envelope: &Envelope = &envelopes[&hash]; { let in_reply_to = envelope.in_reply_to().unwrap().raw(); let parent_id = self.message_ids[in_reply_to]; - self.rebuild_thread(parent_id, collection); + self.rebuild_thread(parent_id, envelopes); } true + */ } /* Update thread tree information on envelope insertion */ - fn rebuild_thread(&mut self, id: ThreadHash, collection: &Envelopes) { + fn rebuild_thread(&mut self, id: ThreadHash, envelopes: &Envelopes) { let mut node_idx = id; let mut stack = Vec::with_capacity(32); + { + 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) { + match (node.parent, node.message, node.children.len()) { + (None, None, 0) => { + return; + } + _ => {} + } node.parent.is_none() } else { - false + panic!(format!( + "node_idx = {:?} not found in self.thread_nodes", + node_idx + )); }; if no_parent { let tree = self.tree.get_mut(); if let Some(tree) = tree.iter_mut().find(|t| t.id == id) { *tree = ThreadTree::new(id); - node_build(tree, id, &mut self.thread_nodes, 1, collection); + node_build( + tree, + 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); + } + } } - tree.push(ThreadTree::new(id)); + 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 */ @@ -1072,9 +1140,22 @@ impl Threads { if let Some(pos) = temp_tree.iter().position(|v| v.id == s) { tree = &mut temp_tree[pos].children; } else { - let tree_node = ThreadTree::new(s); - temp_tree.push(tree_node); - let new_id = temp_tree.len() - 1; + 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; } } @@ -1083,19 +1164,28 @@ impl Threads { } else { /* Add new child */ let tree_node = ThreadTree::new(id); - tree.push(tree_node); - tree.len() - 1 + ThreadTree::insert_child( + tree, + tree_node, + *(self.sort.borrow()), + &self.thread_nodes, + envelopes, + ) }; - node_build(&mut tree[pos], id, &mut self.thread_nodes, 1, collection); + node_build( + &mut tree[pos], + id, + *(self.sort.borrow()), + &mut self.thread_nodes, + 1, + envelopes, + ); } - // FIXME: use insertion according to self.sort etc instead of sorting everytime - self.inner_sort_by(*self.sort.borrow(), collection); - self.inner_subsort_by(*self.subsort.borrow(), collection); } /* * Finalize instance by building the thread tree, set show subject and thread lengths etc. */ - fn build_collection(&mut self, collection: &Envelopes) { + fn build_envelopes(&mut self, envelopes: &Envelopes) { { let tree = self.tree.get_mut(); tree.clear(); @@ -1104,18 +1194,25 @@ impl Threads { node_build( &mut tree_node, *i, + *(self.sort.borrow()), &mut self.thread_nodes, 0, /* indentation */ - collection, + envelopes, + ); + ThreadTree::insert_child( + tree, + tree_node, + *(self.sort.borrow()), + &self.thread_nodes, + envelopes, ); - tree.push(tree_node); } } - self.inner_sort_by(*self.sort.borrow(), collection); - self.inner_subsort_by(*self.subsort.borrow(), collection); + self.inner_sort_by(*self.sort.borrow(), envelopes); + self.inner_subsort_by(*self.subsort.borrow(), envelopes); } - fn inner_subsort_by(&self, subsort: (SortField, SortOrder), collection: &Envelopes) { + fn inner_subsort_by(&self, subsort: (SortField, SortOrder), envelopes: &Envelopes) { let tree = &mut self.tree.borrow_mut(); for t in tree.iter_mut() { t.children.sort_by(|a, b| match subsort { @@ -1133,29 +1230,47 @@ impl Threads { let a = &self.thread_nodes[&a.id].message(); let b = &self.thread_nodes[&b.id].message(); - if a.is_none() || b.is_none() { - return Ordering::Equal; + match (a, b) { + (Some(_), Some(_)) => {} + (Some(_), None) => { + return Ordering::Greater; + } + (None, Some(_)) => { + return Ordering::Less; + } + (None, None) => { + return Ordering::Equal; + } } - let ma = &collection[&a.unwrap()]; - let mb = &collection[&b.unwrap()]; + let ma = &envelopes[&a.unwrap()]; + let mb = &envelopes[&b.unwrap()]; 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(); - if a.is_none() || b.is_none() { - return Ordering::Equal; + match (a, b) { + (Some(_), Some(_)) => {} + (Some(_), None) => { + return Ordering::Less; + } + (None, Some(_)) => { + return Ordering::Greater; + } + (None, None) => { + return Ordering::Equal; + } } - let ma = &collection[&a.unwrap()]; - let mb = &collection[&b.unwrap()]; + let ma = &envelopes[&a.unwrap()]; + let mb = &envelopes[&b.unwrap()]; mb.subject().cmp(&ma.subject()) } }); } } - fn inner_sort_by(&self, sort: (SortField, SortOrder), collection: &Envelopes) { + fn inner_sort_by(&self, sort: (SortField, SortOrder), envelopes: &Envelopes) { let tree = &mut self.tree.borrow_mut(); tree.sort_by(|a, b| match sort { (SortField::Date, SortOrder::Desc) => { @@ -1172,23 +1287,46 @@ impl Threads { let a = &self.thread_nodes[&a.id].message(); let b = &self.thread_nodes[&b.id].message(); - if a.is_none() || b.is_none() { - return Ordering::Equal; + match (a, b) { + (Some(_), Some(_)) => {} + (Some(_), None) => { + return Ordering::Greater; + } + (None, Some(_)) => { + return Ordering::Less; + } + (None, None) => { + return Ordering::Equal; + } } - let ma = &collection[&a.unwrap()]; - let mb = &collection[&b.unwrap()]; - ma.subject().cmp(&mb.subject()) + let ma = &envelopes[&a.unwrap()]; + let mb = &envelopes[&b.unwrap()]; + ma.subject() + .split_graphemes() + .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(); - if a.is_none() || b.is_none() { - return Ordering::Equal; + match (a, b) { + (Some(_), Some(_)) => {} + (Some(_), None) => { + return Ordering::Less; + } + (None, Some(_)) => { + return Ordering::Greater; + } + (None, None) => { + return Ordering::Equal; + } } - let ma = &collection[&a.unwrap()]; - let mb = &collection[&b.unwrap()]; - mb.subject().cmp(&ma.subject()) + let ma = &envelopes[&a.unwrap()]; + let mb = &envelopes[&b.unwrap()]; + mb.subject() + .as_ref() + .split_graphemes() + .cmp(&ma.subject().split_graphemes()) } }); } @@ -1197,14 +1335,14 @@ impl Threads { &self, sort: (SortField, SortOrder), subsort: (SortField, SortOrder), - collection: &Envelopes, + envelopes: &Envelopes, ) { if *self.sort.borrow() != sort { - self.inner_sort_by(sort, collection); + self.inner_sort_by(sort, envelopes); *self.sort.borrow_mut() = sort; } if *self.subsort.borrow() != subsort { - self.inner_subsort_by(subsort, collection); + self.inner_subsort_by(subsort, envelopes); *self.subsort.borrow_mut() = subsort; } } @@ -1231,6 +1369,7 @@ impl Threads { } pub fn root_iter(&self) -> RootIterator { + self.prune_tree(); RootIterator { pos: 0, root_tree: self.tree.borrow(), @@ -1270,9 +1409,10 @@ impl Threads { /* the already existing ThreadNote should be empty, since we're * seeing this message for the first time. otherwise it's a * duplicate. */ - if self.thread_nodes[&node_idx].message.is_some() { + if !self.missing_message_ids.contains(m_id) { return; } + self.missing_message_ids.remove(m_id); node_idx } else { /* Create a new ThreadNode object holding this message */ @@ -1287,6 +1427,7 @@ impl Threads { self.thread_nodes.insert(new_id, node); self.message_ids.insert(m_id.to_vec(), new_id); + self.message_ids_set.insert(m_id.to_vec()); new_id } }; @@ -1321,19 +1462,31 @@ impl Threads { self.thread_nodes.insert( new_id, ThreadNode { + date: envelope.date(), thread_group: new_id, ..Default::default() }, ); self.message_ids.insert(r_id.to_vec(), new_id); + self.missing_message_ids.insert(r_id.to_vec()); + self.message_ids_set.insert(r_id.to_vec()); new_id }; - /* If they are already linked, don't change the existing links. */ + + /* If they are already linked, don't change the existing links. if self.thread_nodes[&ref_ptr].has_parent() && self.thread_nodes[&ref_ptr].parent.unwrap() != parent_id { ref_ptr = parent_id; continue; + } */ + if !self.thread_nodes[&ref_ptr].parent.is_none() { + if self.thread_nodes[&parent_id].parent == Some(ref_ptr) { + eprintln!("ALARM"); + remove_from_parent!(&mut self.thread_nodes, parent_id); + } + ref_ptr = parent_id; + continue; } /* Do not add a link if adding that link would introduce a loop: that is, before @@ -1347,11 +1500,25 @@ 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]; + match (node.parent, node.message, node.children.len()) { + (None, None, 0) => { + tree.remove(i); + continue; + } + _ => {} + } + i += 1; + } } - fn link_threads(&mut self, collection: &mut Envelopes) { - for v in collection.values_mut() { - self.link_envelope(v); + fn link_threads(&mut self, envelopes: &mut Envelopes) { + for e in envelopes.values_mut() { + self.link_envelope(e); } } } @@ -1369,17 +1536,18 @@ impl Index<&ThreadHash> for Threads { fn node_build( tree: &mut ThreadTree, idx: ThreadHash, + sort: (SortField, SortOrder), thread_nodes: &mut FnvHashMap, indentation: usize, - collection: &Envelopes, + envelopes: &Envelopes, ) { if let Some(hash) = thread_nodes[&idx].message { - if !collection.contains_key(&hash) { + 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 !collection.contains_key(&parent_hash) { + if !envelopes.contains_key(&parent_hash) { /* invalidate node */ // thread_nodes[&parent_id].message = None; } else { @@ -1387,10 +1555,10 @@ fn node_build( * If parent subject is Foobar and reply is `Re: Foobar` * then showing the reply's subject can be reduntant */ - let mut subject = collection[&hash].subject(); + let mut subject = envelopes[&hash].subject(); let mut subject = subject.to_mut().as_bytes(); subject.strip_prefixes(); - let mut parent_subject = collection[&parent_hash].subject(); + 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 { @@ -1401,6 +1569,15 @@ fn node_build( } } } + } else { + if let Some(node) = thread_nodes.get(&idx) { + match (node.parent, node.message, node.children.len()) { + (None, None, 0) => { + return; + } + _ => {} + } + } } let indentation = if thread_nodes[&idx].has_message() { @@ -1415,23 +1592,31 @@ fn node_build( }; let mut has_unseen = if let Some(msg) = thread_nodes[&idx].message { - !collection[&msg].is_seen() + !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(); - - thread_nodes - .entry(idx) - .and_modify(|e| e.len = e.children.len()); - /* 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, thread_nodes, indentation, collection); + 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; @@ -1439,10 +1624,40 @@ fn node_build( }); has_unseen |= thread_nodes[&c].has_unseen; - child_vec.push(new_tree); + 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, + envelopes: &Envelopes, +) { + fn help( + level: usize, + node_hash: ThreadHash, + nodes: &FnvHashMap, + envelopes: &Envelopes, + ) { + eprint!("{}ThreadNode {}\n{}\tmessage: {}\n{}\tparent: {}\n{}\tthread_group: {}\n{}\tchildren (len: {}):\n", + "\t".repeat(level), + node_hash, + "\t".repeat(level), + nodes[&node_hash].message().as_ref().map(|m| format!("{} - {}\n{}\t\t{}", envelopes[m].message_id_display(), envelopes[m].subject(), "\t".repeat(level), envelopes[m].references().iter().map(|r| r.to_string()).collect::>().join(", "))).unwrap_or_else(|| "None".to_string()), + "\t".repeat(level), + nodes[&node_hash].parent().as_ref().map(|p| p.to_string()).unwrap_or_else(|| "None".to_string()), + "\t".repeat(level), + nodes[&node_hash].thread_group, + "\t".repeat(level), + nodes[&node_hash].children.len(), + ); + for c in &nodes[&node_hash].children { + help(level + 2, *c, nodes, envelopes); + } + } + help(0, node_hash, nodes, envelopes); +} diff --git a/ui/src/components/mail/accounts.rs b/ui/src/components/mail/accounts.rs index 8c7a24d6e..034e7f0fc 100644 --- a/ui/src/components/mail/accounts.rs +++ b/ui/src/components/mail/accounts.rs @@ -163,12 +163,11 @@ impl AccountsPanel { write_string_to_grid( &format!( "total {}", - a.iter_mailboxes().fold(0, |mut acc, m| { - acc += m - .map(|m| m.collection.values().filter(|e| !e.is_seen()).count()) - .unwrap_or(0); - acc - }) + a.collection + .envelopes + .values() + .filter(|e| !e.is_seen()) + .count() ), &mut self.content, Color::Default, diff --git a/ui/src/components/mail/compose.rs b/ui/src/components/mail/compose.rs index 554fc8960..de73c790f 100644 --- a/ui/src/components/mail/compose.rs +++ b/ui/src/components/mail/compose.rs @@ -127,21 +127,14 @@ impl Composer { * msg: index of message we reply to in thread_nodes * context: current context */ - pub fn edit(coordinates: (usize, usize, usize), msg: ThreadHash, context: &Context) -> Self { - let mailbox = &context.accounts[coordinates.0][coordinates.1] - .as_ref() - .unwrap(); - let threads = &mailbox.collection.threads; - let thread_nodes = &threads.thread_nodes(); + pub fn edit(account_pos: usize, h: EnvelopeHash, context: &Context) -> Self { let mut ret = Composer::default(); - let message = &mailbox.collection[&thread_nodes[&msg].message().unwrap()]; - let op = context.accounts[coordinates.0] - .backend - .operation(message.hash(), mailbox.folder.hash()); + let op = context.accounts[account_pos].operation(&h); + let envelope: &Envelope = context.accounts[account_pos].get_env(&h); - ret.draft = Draft::edit(message, op); + ret.draft = Draft::edit(envelope, op); - ret.account_cursor = coordinates.0; + ret.account_cursor = account_pos; ret } pub fn with_context( @@ -149,17 +142,14 @@ impl Composer { msg: ThreadHash, context: &Context, ) -> Self { - let mailbox = &context.accounts[coordinates.0][coordinates.1] - .as_ref() - .unwrap(); - let threads = &mailbox.collection.threads; + let account = &context.accounts[coordinates.0]; + let mailbox = &account[coordinates.1].as_ref().unwrap(); + let threads = &account.collection.threads[&mailbox.folder.hash()]; let thread_nodes = &threads.thread_nodes(); let mut ret = Composer::default(); let p = &thread_nodes[&msg]; - let parent_message = &mailbox.collection[&p.message().unwrap()]; - let mut op = context.accounts[coordinates.0] - .backend - .operation(parent_message.hash(), mailbox.folder.hash()); + let parent_message = &account.collection[&p.message().unwrap()]; + let mut op = account.operation(&parent_message.hash()); let parent_bytes = op.as_bytes(); ret.draft = Draft::new_reply(parent_message, parent_bytes.unwrap()); @@ -168,10 +158,10 @@ impl Composer { if p.show_subject() { format!( "Re: {}", - mailbox.collection[&p.message().unwrap()].subject().clone() + account.get_env(&p.message().unwrap()).subject().clone() ) } else { - mailbox.collection[&p.message().unwrap()].subject().into() + account.get_env(&p.message().unwrap()).subject().into() }, ); diff --git a/ui/src/components/mail/listing.rs b/ui/src/components/mail/listing.rs index 3522f275b..8c16ac828 100644 --- a/ui/src/components/mail/listing.rs +++ b/ui/src/components/mail/listing.rs @@ -500,11 +500,13 @@ impl Listing { ) { match context.accounts[index].status(entries[&folder_idx].hash()) { Ok(_) => { - let count = context.accounts[index][entries[&folder_idx].hash()] + let account = &context.accounts[index]; + let count = account[entries[&folder_idx].hash()] .as_ref() .unwrap() - .collection - .values() + .envelopes + .iter() + .map(|h| &account.collection[&h]) .filter(|e| !e.is_seen()) .count(); let len = s.len(); diff --git a/ui/src/components/mail/listing/compact.rs b/ui/src/components/mail/listing/compact.rs index 923c43b99..3839deb40 100644 --- a/ui/src/components/mail/listing/compact.rs +++ b/ui/src/components/mail/listing/compact.rs @@ -189,15 +189,15 @@ impl MailboxView { } if old_cursor_pos == self.new_cursor_pos { self.view.update(context); - } else { + } else if self.unfocused { self.view = ThreadView::new(self.new_cursor_pos, None, context); } - let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] - .as_ref() - .unwrap(); + let account = &context.accounts[self.cursor_pos.0]; + let mailbox = account[self.cursor_pos.1].as_ref().unwrap(); - self.length = mailbox.collection.threads.root_len(); + let threads = &account.collection.threads[&mailbox.folder.hash()]; + self.length = threads.root_len(); self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' ')); self.order.clear(); if self.length == 0 { @@ -211,11 +211,10 @@ impl MailboxView { ); return; } - let threads = &mailbox.collection.threads; let mut rows = Vec::with_capacity(1024); let mut min_width = (0, 0, 0); - threads.sort_by(self.sort, self.subsort, &mailbox.collection); + threads.sort_by(self.sort, self.subsort, &account.collection); for (idx, root_idx) in threads.root_iter().enumerate() { let thread_node = &threads.thread_nodes()[&root_idx]; let i = if let Some(i) = thread_node.message() { @@ -227,7 +226,7 @@ impl MailboxView { } threads.thread_nodes()[&iter_ptr].message().unwrap() }; - if !mailbox.collection.contains_key(&i) { + if !context.accounts[self.cursor_pos.0].contains_key(&i) { debug!("key = {}", i); debug!( "name = {} {}", @@ -238,7 +237,7 @@ impl MailboxView { panic!(); } - let root_envelope: &Envelope = &mailbox.collection[&i]; + let root_envelope: &Envelope = &context.accounts[self.cursor_pos.0].get_env(&i); let strings = MailboxView::make_entry_string( root_envelope, @@ -277,14 +276,14 @@ impl MailboxView { } threads.thread_nodes()[&iter_ptr].message().unwrap() }; - if !mailbox.collection.contains_key(&i) { - debug!("key = {}", i); - debug!( - "name = {} {}", - mailbox.name(), - context.accounts[self.cursor_pos.0].name() - ); - debug!("{:#?}", context.accounts); + if !context.accounts[self.cursor_pos.0].contains_key(&i) { + //debug!("key = {}", i); + //debug!( + // "name = {} {}", + // mailbox.name(), + // context.accounts[self.cursor_pos.0].name() + //); + //debug!("{:#?}", context.accounts); panic!(); } @@ -365,13 +364,12 @@ impl MailboxView { context: &Context, ) { if idx == self.cursor_pos.2 || grid.is_none() { - let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] - .as_ref() - .unwrap(); - if mailbox.is_empty() { + if self.length == 0 { return; } - let threads = &mailbox.collection.threads; + let account = &context.accounts[self.cursor_pos.0]; + let mailbox = account[self.cursor_pos.1].as_ref().unwrap(); + let threads = &account.collection.threads[&mailbox.folder.hash()]; let thread_node = threads.root_set(idx); let thread_node = &threads.thread_nodes()[&thread_node]; let i = if let Some(i) = thread_node.message() { @@ -384,7 +382,7 @@ impl MailboxView { threads.thread_nodes()[&iter_ptr].message().unwrap() }; - let root_envelope: &Envelope = &mailbox.collection[&i]; + let root_envelope: &Envelope = &account.get_env(&i); let fg_color = if !root_envelope.is_seen() { Color::Byte(0) } else { @@ -671,11 +669,12 @@ impl Component for MailboxView { Action::ToggleThreadSnooze => { { //FIXME NLL - - let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1] - .as_mut() + let account = &mut context.accounts[self.cursor_pos.0]; + let folder_hash = account[self.cursor_pos.1] + .as_ref() + .map(|m| m.folder.hash()) .unwrap(); - let threads = &mut mailbox.collection.threads; + let threads = account.collection.threads.entry(folder_hash).or_default(); let thread_group = threads.thread_nodes() [&threads.root_set(self.cursor_pos.2)] .thread_group(); diff --git a/ui/src/components/mail/listing/plain.rs b/ui/src/components/mail/listing/plain.rs index b1245594a..83c9eeaf5 100644 --- a/ui/src/components/mail/listing/plain.rs +++ b/ui/src/components/mail/listing/plain.rs @@ -131,9 +131,8 @@ impl PlainListing { return; } } - let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] - .as_ref() - .unwrap(); + let account = &context.accounts[self.cursor_pos.0]; + let mailbox = &account[self.cursor_pos.1].as_ref().unwrap(); self.length = mailbox.len(); self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' ')); @@ -158,31 +157,31 @@ impl PlainListing { break; } /* Write an entire line for each envelope entry. */ - self.local_collection = mailbox.collection.keys().cloned().collect(); + self.local_collection = account.collection.keys().cloned().collect(); let sort = self.sort; self.local_collection.sort_by(|a, b| match sort { (SortField::Date, SortOrder::Desc) => { - let ma = &mailbox.collection[a]; - let mb = &mailbox.collection[b]; + let ma = &account.get_env(a); + let mb = &account.get_env(b); mb.date().cmp(&ma.date()) } (SortField::Date, SortOrder::Asc) => { - let ma = &mailbox.collection[a]; - let mb = &mailbox.collection[b]; + let ma = &account.get_env(a); + let mb = &account.get_env(b); ma.date().cmp(&mb.date()) } (SortField::Subject, SortOrder::Desc) => { - let ma = &mailbox.collection[a]; - let mb = &mailbox.collection[b]; + let ma = &account.get_env(a); + let mb = &account.get_env(b); ma.subject().cmp(&mb.subject()) } (SortField::Subject, SortOrder::Asc) => { - let ma = &mailbox.collection[a]; - let mb = &mailbox.collection[b]; + let ma = &account.get_env(a); + let mb = &account.get_env(b); mb.subject().cmp(&ma.subject()) } }); - let envelope: &Envelope = &mailbox.collection[&self.local_collection[idx]]; + let envelope: &Envelope = &account.get_env(&self.local_collection[idx]); let fg_color = if !envelope.is_seen() { Color::Byte(0) @@ -230,10 +229,8 @@ impl PlainListing { } fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) { - let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] - .as_ref() - .unwrap(); - let envelope: &Envelope = &mailbox.collection[&self.local_collection[idx]]; + let account = &context.accounts[self.cursor_pos.0]; + let envelope: &Envelope = &account.get_env(&self.local_collection[idx]); let fg_color = if !envelope.is_seen() { Color::Byte(0) @@ -367,11 +364,8 @@ impl Component for PlainListing { false } else { let account = &mut context.accounts[self.cursor_pos.0]; - let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap(); - let envelope: &mut Envelope = &mut mailbox - .collection - .entry(self.local_collection[idx]) - .or_default(); + let envelope: &mut Envelope = + &mut account.get_env_mut(&self.local_collection[idx]); !envelope.is_seen() } }; diff --git a/ui/src/components/mail/listing/thread.rs b/ui/src/components/mail/listing/thread.rs index 268480105..dd33040b0 100644 --- a/ui/src/components/mail/listing/thread.rs +++ b/ui/src/components/mail/listing/thread.rs @@ -20,7 +20,6 @@ */ use super::*; -use std::dbg; const MAX_COLS: usize = 500; @@ -129,11 +128,10 @@ impl ThreadListing { return; } } - let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1] - .as_ref() - .unwrap(); + let account = &context.accounts[self.cursor_pos.0]; + let mailbox = account[self.cursor_pos.1].as_ref().unwrap(); - self.length = mailbox.collection.threads.len(); + self.length = account.collection.threads.len(); self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' ')); self.locations.clear(); if self.length == 0 { @@ -151,8 +149,8 @@ impl ThreadListing { let mut indentations: Vec = Vec::with_capacity(6); let mut thread_idx = 0; // needed for alternate thread colors /* Draw threaded view. */ - let threads = &mailbox.collection.threads; - threads.sort_by(self.sort, self.subsort, &mailbox.collection); + let threads = &account.collection.threads[&mailbox.folder.hash()]; + threads.sort_by(self.sort, self.subsort, &account.collection); let thread_nodes: &FnvHashMap = &threads.thread_nodes(); let mut iter = threads.threads_iter().peekable(); /* This is just a desugared for loop so that we can use .peek() */ @@ -164,7 +162,7 @@ impl ThreadListing { thread_idx += 1; } if thread_node.has_message() { - let envelope: &Envelope = &mailbox.collection[&thread_node.message().unwrap()]; + let envelope: &Envelope = &account.get_env(&thread_node.message().unwrap()); self.locations.push(envelope.hash()); let fg_color = if !envelope.is_seen() { Color::Byte(0) @@ -230,7 +228,8 @@ impl ThreadListing { return; } if self.locations[idx] != 0 { - let envelope: &Envelope = &mailbox.collection[&self.locations[idx]]; + let envelope: &Envelope = + &context.accounts[self.cursor_pos.0].get_env(&self.locations[idx]); let fg_color = if !envelope.is_seen() { Color::Byte(0) @@ -262,7 +261,8 @@ impl ThreadListing { } if self.locations[idx] != 0 { - let envelope: &Envelope = &mailbox.collection[&self.locations[idx]]; + let envelope: &Envelope = + &context.accounts[self.cursor_pos.0].get_env(&self.locations[idx]); let fg_color = if !envelope.is_seen() { Color::Byte(0) @@ -469,26 +469,14 @@ impl Component for ThreadListing { } else { let account = &mut context.accounts[self.cursor_pos.0]; let (hash, is_seen) = { - let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap(); - debug!("key is {}", self.locations[dbg!(self.cursor_pos).2]); let envelope: &Envelope = - &mailbox.collection[&self.locations[self.cursor_pos.2]]; + &account.get_env(&self.locations[self.cursor_pos.2]); (envelope.hash(), envelope.is_seen()) }; if !is_seen { - let folder_hash = { - let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap(); - mailbox.folder.hash() - }; - let op = { - let backend = &account.backend; - backend.operation(hash, folder_hash) - }; - let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap(); - let envelope: &mut Envelope = mailbox - .collection - .get_mut(&self.locations[self.cursor_pos.2]) - .unwrap(); + let op = account.operation(&hash); + let envelope: &mut Envelope = + account.get_env_mut(&self.locations[self.cursor_pos.2]); envelope.set_seen(op).unwrap(); true } else { diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs index 1ac321ae8..ba8083dcc 100644 --- a/ui/src/components/mail/view.rs +++ b/ui/src/components/mail/view.rs @@ -89,8 +89,7 @@ impl MailView { ) -> Self { let account = &mut context.accounts[coordinates.0]; let (hash, is_seen) = { - let mailbox = &mut account[coordinates.1].as_mut().unwrap(); - let envelope: &mut Envelope = &mut mailbox.collection.entry(coordinates.2).or_default(); + let envelope: &Envelope = &account.get_env(&coordinates.2); (envelope.hash(), envelope.is_seen()) }; if !is_seen { @@ -102,8 +101,7 @@ impl MailView { let backend = &account.backend; backend.operation(hash, folder_hash) }; - let mailbox = &mut account[coordinates.1].as_mut().unwrap(); - let envelope: &mut Envelope = &mut mailbox.collection.entry(coordinates.2).or_default(); + let envelope: &mut Envelope = &mut account.get_env_mut(&coordinates.2); envelope.set_seen(op).unwrap(); } MailView { @@ -292,16 +290,13 @@ impl Component for MailView { let bottom_right = bottom_right!(area); let y: usize = { - let accounts = &mut context.accounts; - let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1] - .as_ref() - .unwrap(); - if !mailbox.collection.contains_key(&self.coordinates.2) { + let account = &mut context.accounts[self.coordinates.0]; + if !account.contains_key(&self.coordinates.2) { /* The envelope has been renamed or removed, so wait for the appropriate event to * arrive */ return; } - let envelope: &Envelope = &mailbox.collection[&self.coordinates.2]; + let envelope: &Envelope = &account.get_env(&self.coordinates.2); if self.mode == ViewMode::Raw { clear_area(grid, area); @@ -383,14 +378,9 @@ impl Component for MailView { if self.dirty { let body = { - let mailbox_idx = self.coordinates; // coordinates are mailbox idxs - let mailbox = &context.accounts[mailbox_idx.0][mailbox_idx.1] - .as_ref() - .unwrap(); - let envelope: &Envelope = &mailbox.collection[&mailbox_idx.2]; - let op = context.accounts[mailbox_idx.0] - .backend - .operation(envelope.hash(), mailbox.folder.hash()); + let account = &mut context.accounts[self.coordinates.0]; + let envelope: &Envelope = &account.get_env(&self.coordinates.2); + let op = account.operation(&envelope.hash()); envelope.body(op) }; match self.mode { @@ -413,14 +403,9 @@ impl Component for MailView { ViewMode::Subview | ViewMode::ContactSelector(_) => {} ViewMode::Raw => { let text = { - let mailbox_idx = self.coordinates; // coordinates are mailbox idxs - let mailbox = &context.accounts[mailbox_idx.0][mailbox_idx.1] - .as_ref() - .unwrap(); - let envelope: &Envelope = &mailbox.collection[&mailbox_idx.2]; - let mut op = context.accounts[mailbox_idx.0] - .backend - .operation(envelope.hash(), mailbox.folder.hash()); + let account = &mut context.accounts[self.coordinates.0]; + let envelope: &Envelope = &account.get_env(&self.coordinates.2); + let mut op = account.operation(&envelope.hash()); op.as_bytes() .map(|v| String::from_utf8_lossy(v).into_owned()) .unwrap_or_else(|e| e.to_string()) @@ -507,8 +492,7 @@ impl Component for MailView { let account = &mut context.accounts[self.coordinates.0]; let mut results = Vec::new(); { - let mailbox = &account[self.coordinates.1].as_ref().unwrap(); - let envelope: &Envelope = &mailbox.collection[&self.coordinates.2]; + let envelope: &Envelope = &account.get_env(&self.coordinates.2); for c in s.collect() { let c = usize::from_ne_bytes({ [c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]] @@ -536,11 +520,8 @@ impl Component for MailView { } return true; } - let accounts = &context.accounts; - let mailbox = &accounts[self.coordinates.0][self.coordinates.1] - .as_ref() - .unwrap(); - let envelope: &Envelope = &mailbox.collection[&self.coordinates.2]; + let account = &mut context.accounts[self.coordinates.0]; + let envelope: &Envelope = &account.get_env(&self.coordinates.2); let mut entries = Vec::new(); for (idx, env) in envelope @@ -594,15 +575,9 @@ impl Component for MailView { .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); { - let accounts = &context.accounts; - let mailbox = &accounts[self.coordinates.0][self.coordinates.1] - .as_ref() - .unwrap(); - - let envelope: &Envelope = &mailbox.collection[&self.coordinates.2]; - let op = context.accounts[self.coordinates.0] - .backend - .operation(envelope.hash(), mailbox.folder.hash()); + let account = &mut context.accounts[self.coordinates.0]; + let envelope: &Envelope = &account.get_env(&self.coordinates.2); + let op = account.operation(&envelope.hash()); if let Some(u) = envelope.body(op).attachments().get(lidx) { match u.content_type() { ContentType::MessageRfc822 => { @@ -690,16 +665,10 @@ impl Component for MailView { .replies .push_back(UIEvent::StatusEvent(StatusEvent::BufClear)); let url = { - let accounts = &context.accounts; - let mailbox = &accounts[self.coordinates.0][self.coordinates.1] - .as_ref() - .unwrap(); - - let envelope: &Envelope = &mailbox.collection[&self.coordinates.2]; + let account = &mut context.accounts[self.coordinates.0]; + let envelope: &Envelope = &account.get_env(&self.coordinates.2); let finder = LinkFinder::new(); - let op = context.accounts[self.coordinates.0] - .backend - .operation(envelope.hash(), mailbox.folder.hash()); + let op = account.operation(&envelope.hash()); let mut t = envelope.body(op).text().to_string(); let links: Vec = finder.links(&t).collect(); if let Some(u) = links.get(lidx) { diff --git a/ui/src/components/mail/view/thread.rs b/ui/src/components/mail/view/thread.rs index bac5c8e47..ee3531e73 100644 --- a/ui/src/components/mail/view/thread.rs +++ b/ui/src/components/mail/view/thread.rs @@ -146,16 +146,15 @@ impl ThreadView { } fn initiate(&mut self, expanded_hash: Option, context: &Context) { /* stack to push thread messages in order in order to pop and print them later */ - let mailbox = &context.accounts[self.coordinates.0][self.coordinates.1] - .as_ref() - .unwrap(); - let threads = &mailbox.collection.threads; + let account = &context.accounts[self.coordinates.0]; + let mailbox = &account[self.coordinates.1].as_ref().unwrap(); + let threads = &account.collection.threads[&mailbox.folder.hash()]; let thread_iter = threads.thread_iter(self.coordinates.2); self.entries.clear(); for (line, (ind, thread_hash)) in thread_iter.enumerate() { let entry = if let Some(msg_hash) = threads.thread_nodes()[&thread_hash].message() { - let seen: bool = mailbox.collection[&msg_hash].is_seen(); + let seen: bool = account.get_env(&msg_hash).is_seen(); self.make_entry((ind, thread_hash, line), msg_hash, seen) } else { continue; @@ -180,7 +179,7 @@ impl ThreadView { let mut highlight_reply_subjects: Vec> = Vec::with_capacity(self.entries.len()); for e in &mut self.entries { - let envelope: &Envelope = &mailbox.collection[&e.msg_hash]; + let envelope: &Envelope = &context.accounts[self.coordinates.0].get_env(&e.msg_hash); let thread_node = &threads.thread_nodes()[&e.index.1]; let string = if thread_node.show_subject() { let subject = envelope.subject(); @@ -533,10 +532,9 @@ impl ThreadView { /* First draw the thread subject on the first row */ let y = if self.dirty { - let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1] - .as_ref() - .unwrap(); - let threads = &mailbox.collection.threads; + let account = &context.accounts[self.coordinates.0]; + let mailbox = &account[self.coordinates.1].as_ref().unwrap(); + let threads = &account.collection.threads[&mailbox.folder.hash()]; let thread_node = &threads.thread_nodes()[&threads.root_set(self.coordinates.2)]; let i = if let Some(i) = thread_node.message() { i @@ -545,7 +543,7 @@ impl ThreadView { .message() .unwrap() }; - let envelope: &Envelope = &mailbox.collection[&i]; + let envelope: &Envelope = account.get_env(&i); let (x, y) = write_string_to_grid( &envelope.subject(), @@ -613,10 +611,9 @@ impl ThreadView { /* First draw the thread subject on the first row */ let y = { - let mailbox = &context.accounts[self.coordinates.0][self.coordinates.1] - .as_ref() - .unwrap(); - let threads = &mailbox.collection.threads; + let account = &context.accounts[self.coordinates.0]; + let mailbox = &account[self.coordinates.1].as_ref().unwrap(); + let threads = &account.collection.threads[&mailbox.folder.hash()]; let thread_node = &threads.thread_nodes()[&threads.root_set(self.coordinates.2)]; let i = if let Some(i) = thread_node.message() { i @@ -627,7 +624,7 @@ impl ThreadView { } threads.thread_nodes()[&iter_ptr].message().unwrap() }; - let envelope: &Envelope = &mailbox.collection[&i]; + let envelope: &Envelope = account.get_env(&i); let (x, y) = write_string_to_grid( &envelope.subject(), @@ -833,10 +830,9 @@ impl Component for ThreadView { } UIEvent::Input(Key::Char('e')) => { { - let mailbox = &context.accounts[self.coordinates.0][self.coordinates.1] - .as_ref() - .unwrap(); - let threads = &mailbox.collection.threads; + let account = &context.accounts[self.coordinates.0]; + let mailbox = &account[self.coordinates.1].as_ref().unwrap(); + let threads = &account.collection.threads[&mailbox.folder.hash()]; let thread_node = &threads.thread_nodes()[&threads.root_set(self.coordinates.2)]; let i = if let Some(i) = thread_node.message() { @@ -846,20 +842,18 @@ impl Component for ThreadView { .message() .unwrap() }; - let envelope: &Envelope = &mailbox.collection[&i]; - let op = context.accounts[self.coordinates.0] - .backend - .operation(envelope.hash(), mailbox.folder.hash()); + let envelope: &Envelope = &account.get_env(&i); + let op = account.operation(&envelope.hash()); debug!( "sending action edit for {}, {}", envelope.message_id(), op.description() ); + context.replies.push_back(UIEvent::Action(Tab(Edit( + self.coordinates.0, + envelope.hash(), + )))); } - context.replies.push_back(UIEvent::Action(Tab(Edit( - self.coordinates, - self.entries[self.expanded_pos].index.1, - )))); return true; } UIEvent::Input(Key::Up) => { diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs index 326910153..7ea825afc 100644 --- a/ui/src/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -868,13 +868,18 @@ impl Component for StatusBar { return false; } } - let m = &context.accounts[*idx_a][*idx_f].as_ref().unwrap(); + let account = &context.accounts[*idx_a]; + let m = &account[*idx_f].as_ref().unwrap(); self.status = format!( "{} | Mailbox: {}, Messages: {}, New: {}", self.mode, m.folder.name(), - m.collection.len(), - m.collection.values().filter(|e| !e.is_seen()).count() + m.envelopes.len(), + m.envelopes + .iter() + .map(|h| &account.collection[&h]) + .filter(|e| !e.is_seen()) + .count() ); self.dirty = true; } @@ -1254,8 +1259,8 @@ impl Component for Tabbed { self.children[self.cursor_pos].set_dirty(); return true; } - UIEvent::Action(Tab(Edit(coordinates, msg))) => { - self.add_component(Box::new(Composer::edit(coordinates, msg, context))); + UIEvent::Action(Tab(Edit(account_pos, msg))) => { + self.add_component(Box::new(Composer::edit(account_pos, msg, context))); self.cursor_pos = self.children.len() - 1; self.children[self.cursor_pos].set_dirty(); return true; diff --git a/ui/src/conf/accounts.rs b/ui/src/conf/accounts.rs index 02e4e258b..7c78d9ccf 100644 --- a/ui/src/conf/accounts.rs +++ b/ui/src/conf/accounts.rs @@ -31,9 +31,11 @@ use melib::async_workers::{Async, AsyncBuilder, AsyncStatus}; use melib::backends::FolderHash; use melib::error::Result; use melib::mailbox::backends::{ - Backends, Folder, MailBackend, NotifyFn, RefreshEvent, RefreshEventConsumer, RefreshEventKind, + BackendOp, Backends, Folder, MailBackend, NotifyFn, RefreshEvent, RefreshEventConsumer, + RefreshEventKind, }; use melib::mailbox::*; +use melib::thread::ThreadHash; use melib::AddressBook; use std::collections::VecDeque; @@ -45,7 +47,7 @@ use std::result; use std::sync::Arc; use types::UIEvent::{self, EnvelopeRemove, EnvelopeRename, EnvelopeUpdate, Notification}; -pub type Worker = Option>>; +pub type Worker = Option>, Result)>>; macro_rules! mailbox { ($idx:expr, $folders:expr) => { @@ -66,7 +68,8 @@ pub struct Account { pub(crate) folders_order: Vec, folder_names: FnvHashMap, tree: Vec, - sent_folder: Option, + sent_folder: Option, + pub(crate) collection: Collection, pub(crate) address_book: AddressBook, @@ -161,11 +164,15 @@ impl Account { let notify_fn = Arc::new(notify_fn); let mut folder_names = FnvHashMap::default(); + let mut sent_folder = None; for f in ref_folders.values_mut() { let entry = settings .folder_confs .entry(f.name().to_string()) .or_default(); + if f.name().eq_ignore_ascii_case("sent") { + sent_folder = Some(f.hash()); + } if (f.name().eq_ignore_ascii_case("junk") || f.name().eq_ignore_ascii_case("spam") || f.name().eq_ignore_ascii_case("sent") @@ -247,7 +254,8 @@ impl Account { folder_names, tree, address_book, - sent_folder: None, + sent_folder, + collection: Collection::new(Default::default()), workers, settings: settings.clone(), runtime_settings: settings, @@ -271,10 +279,17 @@ impl Account { let work = handle.work().unwrap(); work.compute(); handle.join(); - let envelopes = handle.extract(); + let envelopes: Result> = handle.extract().map(|v| { + v.into_iter() + .map(|e| (e.hash(), e)) + .collect::>() + }); let hash = folder.hash(); - let ret = Mailbox::new(folder, envelopes); - tx.send(AsyncStatus::Payload(ret)); + let m = { + //FIXME NLL + Mailbox::new(folder, envelopes.as_ref().map_err(|e| e.clone())) + }; + tx.send(AsyncStatus::Payload((envelopes, m))); notify_fn.notify(hash); }))) } @@ -291,31 +306,51 @@ impl Account { //let mailbox: &mut Mailbox = self.folders[idx].as_mut().unwrap().as_mut().unwrap(); match kind { RefreshEventKind::Update(old_hash, envelope) => { - mailbox!(&folder_hash, self.folders).update(old_hash, *envelope); + mailbox!(&folder_hash, self.folders).rename(old_hash, envelope.hash()); + self.collection.update(old_hash, *envelope, folder_hash); return Some(EnvelopeUpdate(old_hash)); } RefreshEventKind::Rename(old_hash, new_hash) => { debug!("rename {} to {}", old_hash, new_hash); let mailbox = mailbox!(&folder_hash, self.folders); mailbox.rename(old_hash, new_hash); + self.collection.rename(old_hash, new_hash, folder_hash); return Some(EnvelopeRename(mailbox.folder.hash(), old_hash, new_hash)); } RefreshEventKind::Create(envelope) => { - debug!("create {}", envelope.hash()); - let env_hash: EnvelopeHash = { + let env_hash = envelope.hash(); + { + //FIXME NLL let mailbox = mailbox!(&folder_hash, self.folders); - mailbox.insert(*envelope).hash() - }; + mailbox.insert(env_hash); + self.collection.insert(*envelope, folder_hash); + if self + .sent_folder + .as_ref() + .map(|h| *h == folder_hash) + .unwrap_or(false) + { + self.collection.insert_reply(env_hash); + } + } + let ref_folders: FnvHashMap = self.backend.folders(); - let folder_conf = &self.settings.folder_confs[&self.folder_names[&folder_hash]]; - if folder_conf.ignore.is_true() { - return None; + { + //FIXME NLL + let folder_conf = + &self.settings.folder_confs[&self.folder_names[&folder_hash]]; + if folder_conf.ignore.is_true() { + return None; + } } - let (env, thread_node) = - mailbox!(&folder_hash, self.folders).mail_and_thread(env_hash); - if thread_node.snoozed() { - return None; + { + //FIXME NLL + let (_, thread_node) = self.mail_and_thread(env_hash, folder_hash); + if thread_node.snoozed() { + return None; + } } + let env = self.get_env(&env_hash); return Some(Notification( Some("new mail".into()), format!( @@ -329,6 +364,7 @@ impl Account { } RefreshEventKind::Remove(envelope_hash) => { mailbox!(&folder_hash, self.folders).remove(envelope_hash); + self.collection.remove(envelope_hash, folder_hash); return Some(EnvelopeRemove(envelope_hash)); } RefreshEventKind::Rescan => { @@ -394,7 +430,19 @@ impl Account { &mut self.workers } - fn load_mailbox(&mut self, folder_hash: FolderHash, mailbox: Result) { + fn load_mailbox( + &mut self, + folder_hash: FolderHash, + mailbox: (Result>, Result), + ) { + let (envs, mut mailbox) = mailbox; + if envs.is_err() { + self.folders.insert(folder_hash, None); + return; + } + let envs = envs.unwrap(); + self.collection + .merge(envs, folder_hash, &mut mailbox, self.sent_folder); self.folders.insert(folder_hash, Some(mailbox)); } @@ -440,7 +488,58 @@ impl Account { pos: 0, } } + + pub fn get_env(&self, h: &EnvelopeHash) -> &Envelope { + &self.collection[h] + } + pub fn get_env_mut(&mut self, h: &EnvelopeHash) -> &mut Envelope { + self.collection.entry(*h).or_default() + } + pub fn contains_key(&self, h: &EnvelopeHash) -> bool { + self.collection.contains_key(h) + } + pub fn operation(&self, h: &EnvelopeHash) -> Box { + for mailbox in self.folders.values() { + if let Some(Ok(m)) = mailbox { + if m.envelopes.contains(h) { + return self.backend.operation(*h, m.folder.hash()); + } + } + } + debug!("didn't find {}", *h); + std::dbg!(&self.folders); + std::dbg!(&self.collection.envelopes); + unreachable!() + } + pub fn thread_to_mail_mut(&mut self, h: ThreadHash, f: FolderHash) -> &mut Envelope { + self.collection + .envelopes + .entry(self.collection.threads[&f].thread_to_mail(h)) + .or_default() + } + pub fn thread_to_mail(&self, h: ThreadHash, f: FolderHash) -> &Envelope { + &self.collection.envelopes[&self.collection.threads[&f].thread_to_mail(h)] + } + pub fn threaded_mail(&self, h: ThreadHash, f: FolderHash) -> EnvelopeHash { + self.collection.threads[&f].thread_to_mail(h) + } + pub fn mail_and_thread( + &mut self, + i: EnvelopeHash, + f: FolderHash, + ) -> (&mut Envelope, &ThreadNode) { + let thread; + { + let x = &mut self.collection.envelopes.entry(i).or_default(); + thread = &self.collection.threads[&f][&x.thread()]; + } + (self.collection.envelopes.entry(i).or_default(), thread) + } + pub fn thread(&self, h: ThreadHash, f: FolderHash) -> &ThreadNode { + &self.collection.threads[&f].thread_nodes()[&h] + } } + impl Index for Account { type Output = Result; fn index(&self, index: FolderHash) -> &Result { diff --git a/ui/src/execute/actions.rs b/ui/src/execute/actions.rs index fa9070a37..dd5d3ab30 100644 --- a/ui/src/execute/actions.rs +++ b/ui/src/execute/actions.rs @@ -26,6 +26,7 @@ use components::Component; pub use melib::mailbox::{SortField, SortOrder}; use melib::thread::ThreadHash; +use melib::EnvelopeHash; extern crate uuid; use uuid::Uuid; @@ -43,7 +44,7 @@ pub enum TabAction { NewDraft(usize), Reply((usize, usize, usize), ThreadHash), // thread coordinates (account, mailbox, root_set idx) and thread hash Close, - Edit((usize, usize, usize), ThreadHash), // thread coordinates (account, mailbox, root_set idx) and thread hash + Edit(usize, EnvelopeHash), // account_position, envelope hash Kill(Uuid), }