From 42654410e3229797abcc3a4ddd7330e756eb7567 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Sun, 26 May 2019 02:34:03 +0300 Subject: [PATCH] ui: move Collection to Account Each account had one mailbox per folder, which had one associated collection. Now each Account has one Collection for all folders and each Mailbox object holds only the hashes of each message. Collection also gets Threads for each folder in order to mix messages (ie from/to Sent folder). Insert Sent emails in chronological order if inserted unsorted, mails a, b with a happened-before b, might never get added. Fix multiple insertions in ThreadTree upon insert_reply insert_reply was creating multiple copies in threading --- melib/src/mailbox.rs | 77 +- melib/src/mailbox/collection.rs | 183 +++-- melib/src/mailbox/thread.rs | 819 ++++++++++++++-------- ui/src/components/mail/accounts.rs | 11 +- ui/src/components/mail/compose.rs | 34 +- ui/src/components/mail/listing.rs | 8 +- ui/src/components/mail/listing/compact.rs | 53 +- ui/src/components/mail/listing/plain.rs | 38 +- ui/src/components/mail/listing/thread.rs | 40 +- ui/src/components/mail/view.rs | 71 +- ui/src/components/mail/view/thread.rs | 50 +- ui/src/components/utilities.rs | 15 +- ui/src/conf/accounts.rs | 139 +++- ui/src/execute/actions.rs | 3 +- 14 files changed, 915 insertions(+), 626 deletions(-) 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), }