diff --git a/melib/src/mailbox/backends/maildir/backend.rs b/melib/src/mailbox/backends/maildir/backend.rs index 1d23b980f..c589d99f8 100644 --- a/melib/src/mailbox/backends/maildir/backend.rs +++ b/melib/src/mailbox/backends/maildir/backend.rs @@ -183,7 +183,7 @@ impl MailBackend for MaildirType { ) { sender.send(RefreshEvent { hash: get_path_hash!(pathbuf), - kind: Create(env), + kind: Create(Box::new(env)), }); } else { continue; @@ -216,7 +216,7 @@ impl MailBackend for MaildirType { ) { sender.send(RefreshEvent { hash: get_path_hash!(pathbuf), - kind: Create(env), + kind: Create(Box::new(env)), }); } return; @@ -233,7 +233,7 @@ impl MailBackend for MaildirType { sender.send(RefreshEvent { hash: get_path_hash!(pathbuf), - kind: Update(old_hash, env), + kind: Update(old_hash, Box::new(env)), }); } } diff --git a/melib/src/mailbox/backends/mod.rs b/melib/src/mailbox/backends/mod.rs index a125d5952..e2fda5774 100644 --- a/melib/src/mailbox/backends/mod.rs +++ b/melib/src/mailbox/backends/mod.rs @@ -82,8 +82,8 @@ impl Backends { #[derive(Debug)] pub enum RefreshEventKind { - Update(EnvelopeHash, Envelope), - Create(Envelope), + Update(EnvelopeHash, Box), + Create(Box), Remove(FolderHash), Rescan, } diff --git a/melib/src/mailbox/collection.rs b/melib/src/mailbox/collection.rs index 6b2ed9d9e..9587fc9e2 100644 --- a/melib/src/mailbox/collection.rs +++ b/melib/src/mailbox/collection.rs @@ -63,10 +63,8 @@ impl Collection { self.threads.insert(&mut envelope); self.envelopes.insert(hash, envelope); } - pub(crate) fn insert_reply(&mut self, hash: EnvelopeHash, mut envelope: Envelope) { - if self.threads.insert_reply(&mut envelope) { - self.envelopes.insert(hash, envelope); - } + pub(crate) fn insert_reply(&mut self, envelope: Envelope) { + self.threads.insert_reply(envelope, &mut self.envelopes); } } diff --git a/melib/src/mailbox/email/compose/mod.rs b/melib/src/mailbox/email/compose/mod.rs index a4badd0e1..e8445c869 100644 --- a/melib/src/mailbox/email/compose/mod.rs +++ b/melib/src/mailbox/email/compose/mod.rs @@ -1,6 +1,7 @@ use super::*; use chrono::{DateTime, Local}; use data_encoding::BASE64_MIME; +use std::str; mod random; @@ -41,8 +42,38 @@ impl Default for Draft { } } +impl str::FromStr for Draft { + type Err = MeliError; + fn from_str(s: &str) -> Result { + if s.is_empty() { + return Err(MeliError::new("sadfsa")); + } + + let (headers, _) = parser::mail(s.as_bytes()).to_full_result()?; + let mut ret = Draft::default(); + + for (k, v) in headers { + if ignore_header(k) { + continue; + } + ret.headers.insert( + String::from_utf8(k.to_vec())?, + String::from_utf8(v.to_vec())?, + ); + } + + let body = Envelope::new(0).body_bytes(s.as_bytes()); + + ret.body = String::from_utf8(decode(&body, None))?; + + //ret.attachments = body.attachments(); + + Ok(ret) + } +} + impl Draft { - pub fn as_reply(envelope: &Envelope, bytes: &[u8]) -> Self { + pub fn new_reply(envelope: &Envelope, bytes: &[u8]) -> Self { let mut ret = Draft::default(); ret.headers_mut().insert( "References".into(), @@ -58,11 +89,11 @@ impl Draft { acc.push_str(&x.to_string()); acc }), - envelope.message_id() + envelope.message_id_display() ), ); ret.headers_mut() - .insert("In-Reply-To".into(), envelope.message_id().into()); + .insert("In-Reply-To".into(), envelope.message_id_display().into()); ret.headers_mut() .insert("To".into(), envelope.field_from_to_string()); ret.headers_mut() @@ -137,33 +168,6 @@ impl Draft { Ok(ret) } - - pub fn from_str(s: &str) -> Result { - if s.is_empty() { - return Err(MeliError::new("sadfsa")); - } - - let (headers, _) = parser::mail(s.as_bytes()).to_full_result()?; - let mut ret = Draft::default(); - - for (k, v) in headers { - if ignore_header(k) { - continue; - } - ret.headers.insert( - String::from_utf8(k.to_vec())?, - String::from_utf8(v.to_vec())?, - ); - } - - let body = Envelope::new(0).body_bytes(s.as_bytes()); - - ret.body = String::from_utf8(decode(&body, None))?; - - //ret.attachments = body.attachments(); - - Ok(ret) - } } fn ignore_header(header: &[u8]) -> bool { diff --git a/melib/src/mailbox/email/mod.rs b/melib/src/mailbox/email/mod.rs index 1d860db2e..cd95f17d3 100644 --- a/melib/src/mailbox/email/mod.rs +++ b/melib/src/mailbox/email/mod.rs @@ -423,6 +423,19 @@ impl Envelope { h.write(bytes); self.set_message_id(format!("<{:x}>", h.finish()).as_bytes()); } + if self.references.is_some() { + if let Some(pos) = self + .references + .as_ref() + .map(|r| &r.refs) + .unwrap() + .iter() + .position(|r| r == self.message_id.as_ref().unwrap()) + { + self.references.as_mut().unwrap().refs.remove(pos); + } + } + Ok(()) } @@ -546,7 +559,10 @@ impl Envelope { _ => Cow::from(String::new()), } } - pub fn message_id(&self) -> Cow { + pub fn message_id(&self) -> &MessageID { + self.message_id.as_ref().unwrap() + } + pub fn message_id_display(&self) -> Cow { match self.message_id { Some(ref s) => String::from_utf8_lossy(s.val()), _ => Cow::from(String::new()), diff --git a/melib/src/mailbox/mod.rs b/melib/src/mailbox/mod.rs index e25759456..b9965d696 100644 --- a/melib/src/mailbox/mod.rs +++ b/melib/src/mailbox/mod.rs @@ -128,8 +128,7 @@ impl Mailbox { } fn insert_reply(&mut self, envelope: Envelope) { - let hash = envelope.hash(); - self.collection.insert_reply(hash, envelope); + self.collection.insert_reply(envelope); } pub fn remove(&mut self, envelope_hash: EnvelopeHash) { diff --git a/melib/src/mailbox/thread.rs b/melib/src/mailbox/thread.rs index ee7f1600e..fe2419027 100644 --- a/melib/src/mailbox/thread.rs +++ b/melib/src/mailbox/thread.rs @@ -20,7 +20,15 @@ */ /*! - * Threading algorithm + * This module implements Jamie Zawinski's (threading algorithm) + * [https://www.jwz.org/doc/threading.html]. It is a bit of a beast, so prepare for a lot of + * bloated code that's necessary for the crap modern e-mail is. + * + * The entry point of this module is the `Threads` struct and its `new` method. It contains `ThreadNodes` which are the + * nodes in the thread trees that might have messages associated with them. The root nodes (first + * messages in each thread) are stored in `root_set` and `tree` vectors. The `ThreadTree` struct + * contains the sorted threads. `Threads` has inner mutability since we need to sort without the + * user having mutable ownership. */ use mailbox::email::*; @@ -30,10 +38,94 @@ use self::fnv::{FnvHashMap, FnvHashSet}; use std::cell::{Ref, RefCell}; use std::cmp::Ordering; use std::iter::FromIterator; +use std::mem; use std::ops::Index; use std::result::Result as StdResult; use std::str::FromStr; +/* Helper macros to avoid repeating ourselves */ + +macro_rules! remove_from_parent { + ($t:expr, $i:expr) => { + if let Some(p) = $t[$i].parent { + if let Some(pos) = $t[p].children.iter().position(|c| *c == $i) { + $t[p].children.remove(pos); + } + } + $t[$i].parent = None; + }; +} + +macro_rules! make { + (($p:expr)parent of($c:expr), $t:expr) => { + remove_from_parent!($t, $c); + if !($t[$p]).children.contains(&$c) { + $t[$p].children.push($c); + } + $t[$c].parent = Some($p); + }; +} + +/* Strip common prefixes from subjects */ +trait SubjectPrefix { + fn strip_prefixes(&mut self) -> bool; +} + +impl SubjectPrefix for String { + fn strip_prefixes(&mut self) -> bool { + let mut ret: bool = false; + let result = { + let mut slice = self.trim(); + let mut end_prefix_idx = 0; + loop { + if slice.starts_with("RE: ") + || slice.starts_with("Re: ") + || slice.starts_with("FW: ") + || slice.starts_with("Fw: ") + { + if end_prefix_idx == 0 { + ret = true; + } + slice = &slice[3..]; + end_prefix_idx += 3; + continue; + } + if slice.starts_with("FWD: ") + || slice.starts_with("Fwd: ") + || slice.starts_with("fwd: ") + { + slice = &slice[4..]; + end_prefix_idx += 4; + continue; + } + if slice.starts_with(' ') || slice.starts_with('\t') || slice.starts_with('\r') { + slice = &slice[1..]; + end_prefix_idx += 1; + continue; + } + if slice.starts_with('[') + && !(slice.starts_with("[PATCH") || slice.starts_with("[RFC")) + { + if let Some(pos) = slice.chars().position(|c| c == ']') { + end_prefix_idx += pos; + slice = &slice[pos..]; + continue; + } + slice = &slice[1..]; + end_prefix_idx += 1; + continue; + } + break; + } + slice.to_string() + }; + mem::replace(self, result); + ret + } +} + +/* Sorting states. */ + #[derive(Debug, Clone, PartialEq, Copy, Deserialize)] pub enum SortOrder { Asc, @@ -80,6 +172,9 @@ impl FromStr for SortOrder { } } +/* + * The thread tree holds the sorted state of the thread nodes */ + #[derive(Clone, Debug, Deserialize)] struct ThreadTree { id: usize, @@ -95,6 +190,21 @@ impl ThreadTree { } } +/* `ThreadIterator` returns messages according to the sorted order. For example, for the following + * threads: + * + * ``` + * A_ + * |_ B + * |_C + * D + * E_ + * |_F + * ``` + * + * the iterator returns them as `A, B, C, D, E, F` + */ + pub struct ThreadIterator<'a> { init_pos: usize, pos: usize, @@ -106,7 +216,7 @@ impl<'a> Iterator for ThreadIterator<'a> { fn next(&mut self) -> Option<(usize, usize)> { { let mut tree = &(*self.tree); - for i in self.stack.iter() { + for i in &self.stack { tree = &tree[*i].children; } if self.pos == tree.len() || (self.stack.is_empty() && self.pos > self.init_pos) { @@ -141,6 +251,8 @@ pub struct ThreadNode { len: usize, has_unseen: bool, + + thread_group: usize, } impl Default for ThreadNode { @@ -155,6 +267,7 @@ impl Default for ThreadNode { len: 0, has_unseen: false, + thread_group: 0, } } } @@ -172,18 +285,10 @@ impl ThreadNode { self.len } - fn is_descendant(&self, thread_nodes: &[ThreadNode], other: &ThreadNode) -> bool { - if self == other { - return true; - } - - for v in &self.children { - if thread_nodes[*v].is_descendant(thread_nodes, other) { - return true; - } - } - return false; + pub fn is_empty(&self) -> bool { + self.len == 0 } + pub fn message(&self) -> Option { self.message } @@ -243,69 +348,250 @@ impl<'a> Iterator for RootIterator<'a> { return None; } self.pos += 1; - return Some(self.root_tree[self.pos - 1].id); + Some(self.root_tree[self.pos - 1].id) } } } impl Threads { + fn find(&mut self, i: usize) -> usize { + if self.thread_nodes[i].thread_group == i { + return i; + } + let p = self.thread_nodes[i].thread_group; + self.thread_nodes[i].thread_group = self.find(p); + self.thread_nodes[i].thread_group + } + fn union(&mut self, x: usize, y: usize) -> usize { + let x_root = self.find(x); + let y_root = self.find(y); + + // x and y are already in the same set + if x_root == y_root { + return x_root; + } + + // x and y are not in same set, so we merge them + self.thread_nodes[y].thread_group = x_root; + self.thread_nodes[x].thread_group = x_root; + x_root + } + + // FIXME: Split this function pub fn new(collection: &mut FnvHashMap) -> Threads { /* To reconstruct thread information from the mails we need: */ /* a vector to hold thread members */ - let mut thread_nodes: Vec = + let thread_nodes: Vec = Vec::with_capacity((collection.len() as f64 * 1.2) as usize); /* A hash table of Message IDs */ - let mut message_ids: FnvHashMap = + let message_ids: FnvHashMap = FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default()); - let mut hash_set: FnvHashSet = + let hash_set: FnvHashSet = FnvHashSet::with_capacity_and_hasher(collection.len(), Default::default()); - /* Add each message to message_ids and threads, and link them together according to the - * References / In-Reply-To headers */ - link_threads( - &mut thread_nodes, - &mut message_ids, - &mut hash_set, - collection, - ); - /* 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()); - - 'root_set: for v in message_ids.values() { - /* update length */ - fn set_length(id: usize, thread_nodes: &mut Vec) -> usize { - let mut length = thread_nodes[id].children.len(); - let children: Vec = thread_nodes[id].children.iter().cloned().collect(); - for c in children { - length += set_length(c, thread_nodes); - } - thread_nodes[id].len = length; - return length; - } - set_length(*v, &mut thread_nodes); - - if thread_nodes[*v].parent.is_none() { - if !thread_nodes[*v].has_message() && thread_nodes[*v].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. */ - root_set.push(thread_nodes[*v].children[0]); - - continue 'root_set; - } - root_set.push(*v); - } - } let mut t = Threads { thread_nodes, - root_set: RefCell::new(root_set), message_ids, hash_set, subsort: RefCell::new((SortField::Subject, SortOrder::Desc)), ..Default::default() }; + /* Add each message to message_ids and threads, and link them together according to the + * References / In-Reply-To headers */ + t.link_threads(collection); + + /* 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()); + + 'root_set: for v in t.message_ids.values() { + /* update length */ + fn set_length(mut id: usize, thread_nodes: &mut Vec) -> usize { + let mut length = thread_nodes[id].children.len(); + let children: Vec = thread_nodes[id].children.clone(); + + /* Do some sanitization first */ + if length == 0 && !thread_nodes[id].has_message() { + /* Drop empty node. */ + remove_from_parent!(thread_nodes, id); + return 0; + } + if thread_nodes[id].has_parent() && !thread_nodes[id].has_message() { + /* Node is empty so transfer its children to its parent */ + let orphan_children: Vec = + mem::replace(&mut thread_nodes[id].children, Vec::new()); + let parent_id = thread_nodes[id].parent.unwrap(); + remove_from_parent!(thread_nodes, id); + for c in orphan_children { + make!((parent_id) parent of (c), thread_nodes); + } + id = parent_id; + length = thread_nodes[id].len; + } + + for c in children { + length += set_length(c, thread_nodes); + } + thread_nodes[id].len = length; + length + } + set_length(*v, &mut t.thread_nodes); + + if t.thread_nodes[*v].parent.is_none() { + if !t.thread_nodes[*v].has_message() { + if t.thread_nodes[*v].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. */ + root_set.push(t.thread_nodes[*v].children[0]); + + continue 'root_set; + } else if t.thread_nodes[*v].children.is_empty() { + /* Drop empty node. */ + continue; + } + } + root_set.push(*v); + } + } + + let mut subject_table: FnvHashMap = + FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default()); + + for r in &root_set { + let (mut subject, mut is_re): (String, bool) = if t.thread_nodes[*r].message.is_some() { + let msg_idx = t.thread_nodes[*r].message.unwrap(); + let envelope = &collection[&msg_idx]; + ( + envelope.subject().to_string(), + !envelope.references().is_empty(), + ) + } else { + let msg_idx = t.thread_nodes[t.thread_nodes[*r].children[0]] + .message + .unwrap(); + let envelope = &collection[&msg_idx]; + ( + envelope.subject().to_string(), + !envelope.references().is_empty(), + ) + }; + + is_re |= subject.strip_prefixes(); + + if subject.is_empty() { + continue; + } + + if subject_table.contains_key(&subject) { + let (other_is_re, id) = subject_table[&subject]; + /* "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." */ + if (!t.thread_nodes[id].has_message() && t.thread_nodes[*r].has_message()) + || (other_is_re && !is_re) + { + mem::replace(subject_table.entry(subject).or_default(), (is_re, *r)); + } + } else { + subject_table.insert(subject, (is_re, *r)); + } + } + + let mut roots_to_remove: Vec = Vec::with_capacity(root_set.len()); + + for i in 0..root_set.len() { + let r = root_set[i]; + let (mut subject, mut is_re): (String, bool) = if t.thread_nodes[r].message.is_some() { + let msg_idx = t.thread_nodes[r].message.unwrap(); + let envelope = &collection[&msg_idx]; + ( + envelope.subject().to_string(), + !envelope.references().is_empty(), + ) + } else { + let msg_idx = t.thread_nodes[t.thread_nodes[r].children[0]] + .message + .unwrap(); + let envelope = &collection[&msg_idx]; + ( + envelope.subject().to_string(), + !envelope.references().is_empty(), + ) + }; + + is_re |= subject.strip_prefixes(); + + if !subject_table.contains_key(&subject) || subject_table[&subject].1 == r { + continue; + } + + let (other_is_re, other_idx) = subject_table[&subject]; + /* + * "If both are dummies, append one's children to the other, and remove the now-empty + * container." + */ + if !t.thread_nodes[r].has_message() && !t.thread_nodes[other_idx].has_message() { + let children = mem::replace(&mut t.thread_nodes[r].children, Vec::new()); + t.thread_nodes[other_idx] + .children + .extend(children.into_iter()); + 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 t.thread_nodes[r].has_message() && !t.thread_nodes[other_idx].has_message() { + make!((other_idx) parent of (r), t.thread_nodes); + roots_to_remove.push(i); + } else if !t.thread_nodes[r].has_message() && t.thread_nodes[other_idx].has_message() { + make!((r) parent of (other_idx), t.thread_nodes); + if let Some(pos) = root_set.iter().position(|r| *r == 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 t.thread_nodes[other_idx].has_message() && !other_is_re && is_re { + make!((other_idx) parent of (r), t.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 t.thread_nodes[other_idx].has_message() && other_is_re && !is_re { + make!((r) parent of (other_idx), t.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 { + t.thread_nodes.push(Default::default()); + let new_id = t.thread_nodes.len() - 1; + make!((new_id) parent of (r), t.thread_nodes); + make!((new_id) parent of (other_idx), t.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); + } + + t.root_set = RefCell::new(root_set); t.build_collection(&collection); t } @@ -330,37 +616,57 @@ impl Threads { } pub fn insert(&mut self, envelope: &mut Envelope) { - link_envelope( - &mut self.thread_nodes, - &mut self.message_ids, - &mut self.hash_set, - envelope, - ); + self.link_envelope(envelope); } - pub fn insert_reply(&mut self, envelope: &mut Envelope) -> bool { + pub fn insert_reply( + &mut self, + envelope: Envelope, + collection: &mut FnvHashMap, + ) -> bool { { let in_reply_to = envelope.in_reply_to_raw(); if !self.message_ids.contains_key(in_reply_to.as_ref()) { return false; } } - /* FIXME: This does not update show_subject and len which is done in node_build upon - * creation */ - link_envelope( - &mut self.thread_nodes, - &mut self.message_ids, - &mut self.hash_set, - envelope, - ); - self.rebuild_thread(envelope); - return true; + let hash: EnvelopeHash = envelope.hash(); + collection.insert(hash, envelope); + { + let envelope: &mut Envelope = collection.entry(hash).or_default(); + + /* FIXME: This does not update show_subject and len which is done in node_build upon + * creation */ + self.link_envelope(envelope); + } + let envelope: &Envelope = &collection[&hash]; + { + let in_reply_to = envelope.in_reply_to_raw(); + let parent_id = self.message_ids[in_reply_to.as_ref()]; + let msg_id = envelope.message_id_raw(); + let msg_idx = self.message_ids[msg_id.as_ref()]; + for c in self.thread_nodes[parent_id].children.clone() { + if let Some(message_hash) = self.thread_nodes[c].message { + if collection.contains_key(&message_hash) + && collection[&message_hash] + .references() + .last() + .map(|r| **r == *envelope.message_id()) + .unwrap_or(false) + { + make!((msg_idx) parent of (c), self.thread_nodes); + } + } + } + make!((parent_id) parent of (msg_idx), self.thread_nodes); + self.rebuild_thread(parent_id, collection); + } + true } /* Update thread tree information on envelope insertion */ - fn rebuild_thread(&mut self, envelope: &Envelope) { - let m_id = envelope.message_id_raw(); - let mut node_idx = self.message_ids[m_id.as_ref()]; + fn rebuild_thread(&mut self, id: usize, collection: &FnvHashMap) { + let mut node_idx = id; let mut stack = Vec::with_capacity(32); /* Trace path back to root ThreadNode */ @@ -368,9 +674,6 @@ impl Threads { node_idx = *p; stack.push(node_idx); } - for &p in stack.iter() { - self.thread_nodes[p].len += 1; - } /* Trace path from root ThreadTree to the envelope's parent */ let mut tree = self.tree.get_mut(); @@ -389,7 +692,15 @@ impl Threads { } } /* Add new child */ - tree.push(ThreadTree::new(self.message_ids[m_id.as_ref()])); + let pos = tree.iter().position(|v| v.id == id).unwrap(); + node_build( + &mut tree[pos], + id, + &mut self.thread_nodes, + 1, + id, + collection, + ); } /* @@ -526,14 +837,14 @@ impl Threads { } pub fn root_len(&self) -> usize { - self.root_set.borrow().len() + self.tree.borrow().len() } pub fn root_set(&self, idx: usize) -> usize { self.tree.borrow()[idx].id } - pub fn root_iter<'a>(&'a self) -> RootIterator<'a> { + pub fn root_iter(&self) -> RootIterator { RootIterator { pos: 0, root_tree: self.tree.borrow(), @@ -547,127 +858,101 @@ impl Threads { false } } -} -fn link_envelope( - thread_nodes: &mut Vec, - message_ids: &mut FnvHashMap, - hash_set: &mut FnvHashSet, - envelope: &mut Envelope, -) { - let m_id = envelope.message_id_raw().to_string(); + fn link_envelope(&mut self, envelope: &mut Envelope) { + let m_id = envelope.message_id_raw().to_string(); - /* The index of this message's ThreadNode in thread_nodes */ + /* The index of this message's ThreadNode in thread_nodes */ - let t_idx: usize = if message_ids.get(&m_id).is_some() { - let node_idx = message_ids[&m_id]; - /* the already existing ThreadNote should be empty, since we're - * seeing this message for the first time. otherwise it's a - * duplicate. */ - if thread_nodes[node_idx].message.is_some() { - return; - } - thread_nodes[node_idx].date = envelope.date(); - thread_nodes[node_idx].message = Some(envelope.hash()); - envelope.set_thread(node_idx); - - node_idx - } else { - /* Create a new ThreadNode object holding this message */ - thread_nodes.push(ThreadNode { - message: Some(envelope.hash()), - date: envelope.date(), - ..Default::default() - }); - envelope.set_thread(thread_nodes.len() - 1); - message_ids.insert(m_id, thread_nodes.len() - 1); - hash_set.insert(envelope.hash()); - thread_nodes.len() - 1 - }; - - /* For each element in the message's References field: - * - * Find a ThreadNode object for the given Message-ID: - * If there's one in message_ids use that; - * Otherwise, make (and index) one with a null Message - * - * Link the References field's ThreadNode together in the order implied - * by the References header. - * If they are already linked, don't change the existing links. - * Do not add a link if adding that link would introduce a loop: that - * is, before asserting A->B, search down the children of B to see if A - * is reachable, and also search down the children of A to see if B is - * reachable. If either is already reachable as a child of the other, - * don't add the link. - */ - - /* The index of the reference we are currently examining, start from current message */ - let mut ref_ptr = t_idx; - - for &refn in envelope.references().iter().rev() { - let r_id = String::from_utf8_lossy(refn.raw()).into(); - let parent_id = if message_ids.contains_key(&r_id) { - let parent_id = message_ids[&r_id]; - if !(thread_nodes[parent_id].is_descendant(thread_nodes, &thread_nodes[ref_ptr]) - || thread_nodes[ref_ptr].is_descendant(thread_nodes, &thread_nodes[parent_id])) - { - thread_nodes[ref_ptr].parent = Some(parent_id); - if !thread_nodes[parent_id].children.contains(&ref_ptr) { - thread_nodes[parent_id].children.push(ref_ptr); - } - - let (left, right) = thread_nodes.split_at_mut(parent_id); - let (parent, right) = right.split_first_mut().unwrap(); - for &c in &parent.children { - if c > parent_id { - right[c - parent_id - 1].parent = Some(parent_id); - } else { - left[c].parent = Some(parent_id); - } - } + let t_idx: usize = if self.message_ids.get(&m_id).is_some() { + let node_idx = self.message_ids[&m_id]; + /* 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() { + return; } - parent_id + node_idx } else { - /* Create a new ThreadNode object holding this reference */ - thread_nodes.push(ThreadNode { - message: None, - children: vec![ref_ptr; 1], + /* Create a new ThreadNode object holding this message */ + self.thread_nodes.push(ThreadNode { + message: Some(envelope.hash()), date: envelope.date(), ..Default::default() }); - if thread_nodes[ref_ptr].parent.is_none() { - thread_nodes[ref_ptr].parent = Some(thread_nodes.len() - 1); - } - message_ids.insert(r_id, thread_nodes.len() - 1); - thread_nodes.len() - 1 + let new_id = self.thread_nodes.len() - 1; + self.thread_nodes[new_id].thread_group = new_id; + self.message_ids.insert(m_id, new_id); + new_id }; + self.thread_nodes[t_idx].date = envelope.date(); + self.thread_nodes[t_idx].message = Some(envelope.hash()); + envelope.set_thread(t_idx); + self.hash_set.insert(envelope.hash()); - /* Update thread's date */ + /* For each element in the message's References field: + * + * Find a ThreadNode object for the given Message-ID: + * If there's one in message_ids use that; + * Otherwise, make (and index) one with a null Message + * + * Link the References field's ThreadNode together in the order implied + * by the References header. + * If they are already linked, don't change the existing links. + * Do not add a link if adding that link would introduce a loop: that + * is, before asserting A->B, search down the children of B to see if A + * is reachable, and also search down the children of A to see if B is + * reachable. If either is already reachable as a child of the other, + * don't add the link. + */ - let mut parent_iter = parent_id; - 'date: loop { - let p: &mut ThreadNode = &mut thread_nodes[parent_iter]; - if p.date < envelope.date() { - p.date = envelope.date(); - } - if let Some(p) = p.parent { - parent_iter = p; - } else { - break 'date; - } + /* The index of the reference we are currently examining, start from current message */ + let mut ref_ptr = t_idx; + if self.thread_nodes[t_idx].has_parent() { + remove_from_parent!(self.thread_nodes, t_idx); } - ref_ptr = parent_id; - } -} -fn link_threads( - thread_nodes: &mut Vec, - message_ids: &mut FnvHashMap, - hash_set: &mut FnvHashSet, - collection: &mut FnvHashMap, -) { - for v in collection.values_mut() { - link_envelope(thread_nodes, message_ids, hash_set, v); + for &refn in envelope.references().iter().rev() { + let r_id = String::from_utf8_lossy(refn.raw()).into(); + let parent_id = if self.message_ids.contains_key(&r_id) { + self.message_ids[&r_id] + } else { + /* Create a new ThreadNode object holding this reference */ + self.thread_nodes.push(ThreadNode { + ..Default::default() + }); + let new_id = self.thread_nodes.len() - 1; + self.thread_nodes[new_id].thread_group = new_id; + self.message_ids.insert(r_id, new_id); + new_id + }; + if self.find(ref_ptr) != self.find(parent_id) { + self.union(ref_ptr, parent_id); + make!((parent_id) parent of (ref_ptr), self.thread_nodes); + } + + /* Update thread's date */ + + let mut parent_iter = parent_id; + 'date: loop { + let p: &mut ThreadNode = &mut self.thread_nodes[parent_iter]; + if p.date < envelope.date() { + p.date = envelope.date(); + } + if let Some(p) = p.parent { + parent_iter = p; + } else { + break 'date; + } + } + ref_ptr = parent_id; + } + } + + fn link_threads(&mut self, collection: &mut FnvHashMap) { + for v in collection.values_mut() { + self.link_envelope(v); + } } } @@ -716,17 +1001,10 @@ fn node_build( let mut has_unseen = thread_nodes[idx].has_unseen; let mut child_vec: Vec = Vec::new(); - for c in thread_nodes[idx].children.clone().iter() { - let mut new_tree = ThreadTree::new(*c); - node_build( - &mut new_tree, - *c, - thread_nodes, - indentation, - idx, - collection, - ); - has_unseen |= thread_nodes[*c].has_unseen; + for c in thread_nodes[idx].children.clone() { + let mut new_tree = ThreadTree::new(c); + node_build(&mut new_tree, c, thread_nodes, indentation, idx, collection); + has_unseen |= thread_nodes[c].has_unseen; child_vec.push(new_tree); } tree.children = child_vec; diff --git a/src/bin.rs b/src/bin.rs index e7e544f49..7894b6461 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -139,7 +139,7 @@ fn main() { } }, ThreadEvent::RefreshMailbox(event) => { - state.refresh_event(event); + state.refresh_event(*event); state.redraw(); }, ThreadEvent::UIEvent(UIEventType::ChangeMode(f)) => { diff --git a/ui/src/components/mail/compose.rs b/ui/src/components/mail/compose.rs index f044078e5..d432de8cc 100644 --- a/ui/src/components/mail/compose.rs +++ b/ui/src/components/mail/compose.rs @@ -22,6 +22,7 @@ use super::*; use melib::Draft; +use std::str::FromStr; #[derive(Debug)] pub struct Composer { @@ -116,7 +117,7 @@ impl Composer { .operation(parent_message.hash()); let parent_bytes = op.as_bytes(); - ret.draft = Draft::as_reply(parent_message, parent_bytes.unwrap()); + ret.draft = Draft::new_reply(parent_message, parent_bytes.unwrap()); ret.draft.headers_mut().insert( "Subject".into(), if p.show_subject() { @@ -394,7 +395,7 @@ impl Component for Composer { ('x', ViewMode::Discard(u)) => { context.replies.push_back(UIEvent { id: 0, - event_type: UIEventType::Action(Tab(Kill(u.clone()))), + event_type: UIEventType::Action(Tab(Kill(*u))), }); return true; } diff --git a/ui/src/components/mail/listing/plain.rs b/ui/src/components/mail/listing/plain.rs index 42369fc61..0e49d6f84 100644 --- a/ui/src/components/mail/listing/plain.rs +++ b/ui/src/components/mail/listing/plain.rs @@ -134,7 +134,7 @@ impl PlainListing { break; } /* Write an entire line for each envelope entry. */ - self.local_collection = mailbox.collection.keys().map(|v| *v).collect(); + self.local_collection = mailbox.collection.keys().cloned().collect(); let sort = self.sort; self.local_collection.sort_by(|a, b| match sort { (SortField::Date, SortOrder::Desc) => { diff --git a/ui/src/components/mail/view/thread.rs b/ui/src/components/mail/view/thread.rs index 4bd1eac60..7c3919071 100644 --- a/ui/src/components/mail/view/thread.rs +++ b/ui/src/components/mail/view/thread.rs @@ -75,15 +75,13 @@ impl ThreadView { new_cursor_pos: 0, ..Default::default() }; - let mut line = 0; - for (ind, idx) in thread_iter { + for (line, (ind, idx)) in thread_iter.enumerate() { let entry = if let Some(msg_idx) = threads.thread_nodes()[idx].message() { view.make_entry((ind, idx, line), msg_idx) } else { continue; }; view.entries.push(entry); - line += 1; match expanded_idx { Some(expanded_idx) if expanded_idx == idx => { view.new_expanded_pos = view.entries.len().saturating_sub(1); diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs index cd24635d2..239b93109 100644 --- a/ui/src/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -719,10 +719,7 @@ pub struct Tabbed { impl Tabbed { pub fn new(children: Vec>) -> Self { Tabbed { - children: children - .into_iter() - .map(|x: Box| Entity::from(x)) - .collect(), + children: children.into_iter().map(Entity::from).collect(), cursor_pos: 0, } } @@ -811,7 +808,7 @@ impl Component for Tabbed { return true; } UIEventType::Action(Tab(Close)) => { - let uuid = self.children[self.cursor_pos].uuid().clone(); + let uuid = *self.children[self.cursor_pos].uuid(); self.children[self.cursor_pos].kill(uuid); return true; } diff --git a/ui/src/types/accounts.rs b/ui/src/types/accounts.rs index d60adf180..c2e9c63f7 100644 --- a/ui/src/types/accounts.rs +++ b/ui/src/types/accounts.rs @@ -83,10 +83,10 @@ impl Account { let mailbox: &mut Mailbox = self.folders[idx].as_mut().unwrap().as_mut().unwrap(); match kind { RefreshEventKind::Update(old_hash, envelope) => { - mailbox.update(old_hash, envelope); + mailbox.update(old_hash, *envelope); } RefreshEventKind::Create(envelope) => { - let env: &Envelope = mailbox.insert(envelope); + let env: &Envelope = mailbox.insert(*envelope); let ref_folders: Vec = self.backend.folders(); return Some(Notification( Some("New mail".into()), @@ -107,7 +107,7 @@ impl Account { self.workers[idx] = Some(handle); } } - return None; + None } pub fn watch(&self, r: RefreshEventConsumer) -> () { self.backend.watch(r).unwrap(); @@ -212,7 +212,8 @@ impl Account { }; let m = self.workers[index].take().unwrap().extract(); self.workers[index] = None; - Ok(self.load_mailbox(index, m)) + self.load_mailbox(index, m); + Ok(()) } } diff --git a/ui/src/types/mod.rs b/ui/src/types/mod.rs index 29f908e3d..2efe55cc5 100644 --- a/ui/src/types/mod.rs +++ b/ui/src/types/mod.rs @@ -57,14 +57,14 @@ pub enum ThreadEvent { /// User input. Input(Key), /// A watched folder has been refreshed. - RefreshMailbox(RefreshEvent), + RefreshMailbox(Box), UIEvent(UIEventType), //Decode { _ }, // For gpg2 signature check } impl From for ThreadEvent { fn from(event: RefreshEvent) -> Self { - ThreadEvent::RefreshMailbox(event) + ThreadEvent::RefreshMailbox(Box::new(event)) } }