melib: Prune empty containers while threading
parent
b2e91a83bd
commit
47481e72da
|
@ -22,7 +22,8 @@
|
||||||
/*!
|
/*!
|
||||||
* This module implements Jamie Zawinski's (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
|
* [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.
|
* bloated code that's necessary for the crap modern e-mail is. Quoted comments (/* " .. " */) are
|
||||||
|
* taken almost verbatim from the algorithm.
|
||||||
*
|
*
|
||||||
* The entry point of this module is the `Threads` struct and its `new` method. It contains `ThreadNodes` which are the
|
* 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
|
* nodes in the thread trees that might have messages associated with them. The root nodes (first
|
||||||
|
@ -45,24 +46,45 @@ use std::str::FromStr;
|
||||||
|
|
||||||
/* Helper macros to avoid repeating ourselves */
|
/* Helper macros to avoid repeating ourselves */
|
||||||
|
|
||||||
|
fn rec_change_root_parent(b: &mut Vec<ThreadNode>, idx: usize, new_root: usize) {
|
||||||
|
b[idx].thread_group = new_root;
|
||||||
|
if let Some(p) = b[idx].parent {
|
||||||
|
rec_change_children(b, p, new_root);
|
||||||
|
rec_change_root_parent(b, p, new_root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn rec_change_children(b: &mut Vec<ThreadNode>, idx: usize, new_root: usize) {
|
||||||
|
b[idx].thread_group = new_root;
|
||||||
|
|
||||||
|
for c in b[idx].children.clone() {
|
||||||
|
rec_change_children(b, c, new_root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! remove_from_parent {
|
macro_rules! remove_from_parent {
|
||||||
($t:expr, $i:expr) => {
|
($buf:expr, $idx:expr) => {
|
||||||
if let Some(p) = $t[$i].parent {
|
if let Some(p) = $buf[$idx].parent {
|
||||||
if let Some(pos) = $t[p].children.iter().position(|c| *c == $i) {
|
if let Some(pos) = $buf[p].children.iter().position(|c| *c == $idx) {
|
||||||
$t[p].children.remove(pos);
|
$buf[p].children.remove(pos);
|
||||||
}
|
}
|
||||||
|
rec_change_root_parent($buf, p, p);
|
||||||
}
|
}
|
||||||
$t[$i].parent = None;
|
$buf[$idx].parent = None;
|
||||||
|
rec_change_children($buf, $idx, $idx);
|
||||||
|
$buf[$idx].thread_group = $idx;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! make {
|
macro_rules! make {
|
||||||
(($p:expr)parent of($c:expr), $t:expr) => {
|
(($p:expr)parent of($c:expr), $buf:expr) => {
|
||||||
remove_from_parent!($t, $c);
|
remove_from_parent!($buf, $c);
|
||||||
if !($t[$p]).children.contains(&$c) {
|
if !($buf[$p]).children.contains(&$c) {
|
||||||
$t[$p].children.push($c);
|
$buf[$p].children.push($c);
|
||||||
|
} else {
|
||||||
|
panic!();
|
||||||
}
|
}
|
||||||
$t[$c].parent = Some($p);
|
$buf[$c].parent = Some($p);
|
||||||
|
union($buf, $c, $p);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,7 +128,7 @@ impl SubjectPrefix for String {
|
||||||
if slice.starts_with('[')
|
if slice.starts_with('[')
|
||||||
&& !(slice.starts_with("[PATCH") || slice.starts_with("[RFC"))
|
&& !(slice.starts_with("[PATCH") || slice.starts_with("[RFC"))
|
||||||
{
|
{
|
||||||
if let Some(pos) = slice.chars().position(|c| c == ']') {
|
if let Some(pos) = slice.find(']') {
|
||||||
end_prefix_idx += pos;
|
end_prefix_idx += pos;
|
||||||
slice = &slice[pos..];
|
slice = &slice[pos..];
|
||||||
continue;
|
continue;
|
||||||
|
@ -252,7 +274,9 @@ pub struct ThreadNode {
|
||||||
len: usize,
|
len: usize,
|
||||||
has_unseen: bool,
|
has_unseen: bool,
|
||||||
|
|
||||||
|
/* Union/Find set fields */
|
||||||
thread_group: usize,
|
thread_group: usize,
|
||||||
|
rank: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ThreadNode {
|
impl Default for ThreadNode {
|
||||||
|
@ -268,6 +292,7 @@ impl Default for ThreadNode {
|
||||||
len: 0,
|
len: 0,
|
||||||
has_unseen: false,
|
has_unseen: false,
|
||||||
thread_group: 0,
|
thread_group: 0,
|
||||||
|
rank: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -352,30 +377,123 @@ impl<'a> Iterator for RootIterator<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn find(buf: &mut Vec<ThreadNode>, i: usize) -> usize {
|
||||||
|
if buf[i].thread_group == i {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
let p = buf[i].thread_group;
|
||||||
|
buf[i].thread_group = find(buf, p);
|
||||||
|
buf[i].thread_group
|
||||||
|
}
|
||||||
|
fn union(buf: &mut Vec<ThreadNode>, x: usize, y: usize) -> usize {
|
||||||
|
let mut x_root = find(buf, x);
|
||||||
|
let mut y_root = find(buf, y);
|
||||||
|
|
||||||
|
// x and y are already in the same set
|
||||||
|
if x_root == y_root {
|
||||||
|
return x_root;
|
||||||
|
}
|
||||||
|
|
||||||
|
if buf[x_root].rank < buf[y_root].rank {
|
||||||
|
mem::swap(&mut x_root, &mut y_root);
|
||||||
|
}
|
||||||
|
|
||||||
|
// x and y are not in same set, so we merge them
|
||||||
|
//
|
||||||
|
buf[y_root].thread_group = x_root;
|
||||||
|
if buf[x_root].rank == buf[y_root].rank {
|
||||||
|
buf[x_root].rank += 1;
|
||||||
|
}
|
||||||
|
x_root
|
||||||
|
}
|
||||||
|
|
||||||
impl Threads {
|
impl Threads {
|
||||||
fn find(&mut self, i: usize) -> usize {
|
fn find(&mut self, i: usize) -> usize {
|
||||||
if self.thread_nodes[i].thread_group == i {
|
find(&mut self.thread_nodes, 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 {
|
fn union(&mut self, x: usize, y: usize) -> usize {
|
||||||
let x_root = self.find(x);
|
let mut x_root = self.find(x);
|
||||||
let y_root = self.find(y);
|
let mut y_root = self.find(y);
|
||||||
|
|
||||||
// x and y are already in the same set
|
// x and y are already in the same set
|
||||||
if x_root == y_root {
|
if x_root == y_root {
|
||||||
return x_root;
|
return x_root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.thread_nodes[x_root].rank < self.thread_nodes[y_root].rank {
|
||||||
|
mem::swap(&mut x_root, &mut y_root);
|
||||||
|
}
|
||||||
|
|
||||||
// x and y are not in same set, so we merge them
|
// 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;
|
self.thread_nodes[y_root].thread_group = x_root;
|
||||||
|
if self.thread_nodes[x_root].rank == self.thread_nodes[y_root].rank {
|
||||||
|
self.thread_nodes[x_root].rank += 1;
|
||||||
|
}
|
||||||
x_root
|
x_root
|
||||||
}
|
}
|
||||||
|
fn prune_empty_nodes(&mut self, root_set: &mut Vec<usize>) {
|
||||||
|
fn prune(
|
||||||
|
thread_nodes: &mut Vec<ThreadNode>,
|
||||||
|
idx: usize,
|
||||||
|
root_set: &mut Vec<usize>,
|
||||||
|
) -> bool {
|
||||||
|
/* "If it is an empty container with no children, nuke it." */
|
||||||
|
if !thread_nodes[idx].has_message() && thread_nodes[idx].children.is_empty() {
|
||||||
|
remove_from_parent!(thread_nodes, idx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !thread_nodes[idx].has_message() {
|
||||||
|
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);
|
||||||
|
thread_nodes[idx].children.clear();
|
||||||
|
remove_from_parent!(thread_nodes, idx);
|
||||||
|
remove_from_parent!(thread_nodes, child);
|
||||||
|
return true; // Pruned
|
||||||
|
} else {
|
||||||
|
if let Some(p) = thread_nodes[idx].parent {
|
||||||
|
let orphans = thread_nodes[idx].children.clone();
|
||||||
|
for c in orphans {
|
||||||
|
make!((p) parent of (c), thread_nodes);
|
||||||
|
}
|
||||||
|
remove_from_parent!(thread_nodes, idx);
|
||||||
|
thread_nodes[idx].children.clear();
|
||||||
|
return true; // Pruned
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Recurse to children, but keep in mind more children can be added in each iteration
|
||||||
|
*/
|
||||||
|
let mut c_idx = 0;
|
||||||
|
loop {
|
||||||
|
if c_idx == thread_nodes[idx].children.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let c = thread_nodes[idx].children[c_idx];
|
||||||
|
if !prune(thread_nodes, c, root_set) {
|
||||||
|
c_idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
!thread_nodes[idx].has_message() && thread_nodes[idx].children.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut idx = 0;
|
||||||
|
loop {
|
||||||
|
if idx == root_set.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if prune(&mut self.thread_nodes, root_set[idx], root_set) {
|
||||||
|
root_set.remove(idx);
|
||||||
|
} else {
|
||||||
|
idx += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// FIXME: Split this function
|
// FIXME: Split this function
|
||||||
pub fn new(collection: &mut FnvHashMap<EnvelopeHash, Envelope>) -> Threads {
|
pub fn new(collection: &mut FnvHashMap<EnvelopeHash, Envelope>) -> Threads {
|
||||||
|
@ -406,61 +524,31 @@ impl Threads {
|
||||||
* have no parents. These are the root messages of each thread */
|
* have no parents. These are the root messages of each thread */
|
||||||
let mut root_set: Vec<usize> = Vec::with_capacity(collection.len());
|
let mut root_set: Vec<usize> = Vec::with_capacity(collection.len());
|
||||||
|
|
||||||
|
/* Find the root set */
|
||||||
'root_set: for v in t.message_ids.values() {
|
'root_set: for v in t.message_ids.values() {
|
||||||
/* update length */
|
|
||||||
fn set_length(mut id: usize, thread_nodes: &mut Vec<ThreadNode>) -> usize {
|
|
||||||
let mut length = thread_nodes[id].children.len();
|
|
||||||
let children: Vec<usize> = 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<usize> =
|
|
||||||
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].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);
|
root_set.push(*v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut roots_to_remove: Vec<usize> = Vec::with_capacity(root_set.len());
|
||||||
|
/* Prune empty thread nodes */
|
||||||
|
t.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<String, (bool, usize)> =
|
let mut subject_table: FnvHashMap<String, (bool, usize)> =
|
||||||
FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default());
|
FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default());
|
||||||
|
|
||||||
for r in &root_set {
|
for r in &root_set {
|
||||||
|
/* "Find the subject of that sub-tree": */
|
||||||
let (mut subject, mut is_re): (String, bool) = if t.thread_nodes[*r].message.is_some() {
|
let (mut subject, mut is_re): (String, bool) = if t.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 = t.thread_nodes[*r].message.unwrap();
|
let msg_idx = t.thread_nodes[*r].message.unwrap();
|
||||||
let envelope = &collection[&msg_idx];
|
let envelope = &collection[&msg_idx];
|
||||||
(
|
(
|
||||||
|
@ -468,6 +556,9 @@ impl Threads {
|
||||||
!envelope.references().is_empty(),
|
!envelope.references().is_empty(),
|
||||||
)
|
)
|
||||||
} else {
|
} 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 = t.thread_nodes[t.thread_nodes[*r].children[0]]
|
let msg_idx = t.thread_nodes[t.thread_nodes[*r].children[0]]
|
||||||
.message
|
.message
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -478,30 +569,40 @@ impl Threads {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* "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 */
|
||||||
is_re |= subject.strip_prefixes();
|
is_re |= subject.strip_prefixes();
|
||||||
|
|
||||||
if subject.is_empty() {
|
if subject.is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* "Add this Container to the subject_table if:" */
|
||||||
if subject_table.contains_key(&subject) {
|
if subject_table.contains_key(&subject) {
|
||||||
let (other_is_re, id) = subject_table[&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
|
/* "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." */
|
* 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 (!t.thread_nodes[id].has_message() && t.thread_nodes[*r].has_message())
|
if (!t.thread_nodes[id].has_message() && t.thread_nodes[*r].has_message())
|
||||||
|| (other_is_re && !is_re)
|
|| (other_is_re && !is_re)
|
||||||
{
|
{
|
||||||
mem::replace(subject_table.entry(subject).or_default(), (is_re, *r));
|
mem::replace(subject_table.entry(subject).or_default(), (is_re, *r));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
/* "There is no container in the table with this subject" */
|
||||||
subject_table.insert(subject, (is_re, *r));
|
subject_table.insert(subject, (is_re, *r));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut roots_to_remove: Vec<usize> = Vec::with_capacity(root_set.len());
|
/* "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() {
|
for i in 0..root_set.len() {
|
||||||
let r = root_set[i];
|
let r = root_set[i];
|
||||||
|
/* "Find the subject of this Container (as above.)" */
|
||||||
let (mut subject, mut is_re): (String, bool) = if t.thread_nodes[r].message.is_some() {
|
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 msg_idx = t.thread_nodes[r].message.unwrap();
|
||||||
let envelope = &collection[&msg_idx];
|
let envelope = &collection[&msg_idx];
|
||||||
|
@ -521,21 +622,28 @@ impl Threads {
|
||||||
};
|
};
|
||||||
|
|
||||||
is_re |= subject.strip_prefixes();
|
is_re |= subject.strip_prefixes();
|
||||||
|
if subject.is_empty() {
|
||||||
if !subject_table.contains_key(&subject) || subject_table[&subject].1 == r {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let (other_is_re, other_idx) = subject_table[&subject];
|
let (other_is_re, other_idx) = subject_table[&subject];
|
||||||
|
/* "If it is null, or if it is this container, continue." */
|
||||||
|
if !t.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
|
* "If both are dummies, append one's children to the other, and remove the now-empty
|
||||||
* container."
|
* container."
|
||||||
*/
|
*/
|
||||||
if !t.thread_nodes[r].has_message() && !t.thread_nodes[other_idx].has_message() {
|
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());
|
let children = t.thread_nodes[r].children.clone();
|
||||||
t.thread_nodes[other_idx]
|
for c in children {
|
||||||
.children
|
make!((other_idx) parent of (c), &mut t.thread_nodes);
|
||||||
.extend(children.into_iter());
|
}
|
||||||
roots_to_remove.push(i);
|
roots_to_remove.push(i);
|
||||||
|
|
||||||
/* "If one container is a empty and the other is not, make the non-empty one be a child
|
/* "If one container is a empty and the other is not, make the non-empty one be a child
|
||||||
|
@ -543,11 +651,14 @@ impl Threads {
|
||||||
* (the empty's children.)"
|
* (the empty's children.)"
|
||||||
*/
|
*/
|
||||||
} else if t.thread_nodes[r].has_message() && !t.thread_nodes[other_idx].has_message() {
|
} else if t.thread_nodes[r].has_message() && !t.thread_nodes[other_idx].has_message() {
|
||||||
make!((other_idx) parent of (r), t.thread_nodes);
|
make!((other_idx) parent of (r), &mut t.thread_nodes);
|
||||||
|
if !root_set.contains(&other_idx) {
|
||||||
|
root_set.push(other_idx);
|
||||||
|
}
|
||||||
roots_to_remove.push(i);
|
roots_to_remove.push(i);
|
||||||
} else if !t.thread_nodes[r].has_message() && t.thread_nodes[other_idx].has_message() {
|
} else if !t.thread_nodes[r].has_message() && t.thread_nodes[other_idx].has_message() {
|
||||||
make!((r) parent of (other_idx), t.thread_nodes);
|
make!((r) parent of (other_idx), &mut t.thread_nodes);
|
||||||
if let Some(pos) = root_set.iter().position(|r| *r == other_idx) {
|
if let Some(pos) = root_set.iter().position(|&i| i == other_idx) {
|
||||||
roots_to_remove.push(pos);
|
roots_to_remove.push(pos);
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
@ -555,7 +666,7 @@ impl Threads {
|
||||||
* message's subject does, then make this be a child of the other."
|
* 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 {
|
} else if t.thread_nodes[other_idx].has_message() && !other_is_re && is_re {
|
||||||
make!((other_idx) parent of (r), t.thread_nodes);
|
make!((other_idx) parent of (r), &mut t.thread_nodes);
|
||||||
roots_to_remove.push(i);
|
roots_to_remove.push(i);
|
||||||
|
|
||||||
/* "If that container is a non-empty, and that message's subject begins with ``Re:'', but this
|
/* "If that container is a non-empty, and that message's subject begins with ``Re:'', but this
|
||||||
|
@ -565,7 +676,7 @@ impl Threads {
|
||||||
* seen.)"
|
* seen.)"
|
||||||
*/
|
*/
|
||||||
} else if t.thread_nodes[other_idx].has_message() && other_is_re && !is_re {
|
} else if t.thread_nodes[other_idx].has_message() && other_is_re && !is_re {
|
||||||
make!((r) parent of (other_idx), t.thread_nodes);
|
make!((r) parent of (other_idx), &mut t.thread_nodes);
|
||||||
if let Some(pos) = root_set.iter().position(|r| *r == other_idx) {
|
if let Some(pos) = root_set.iter().position(|r| *r == other_idx) {
|
||||||
roots_to_remove.push(pos);
|
roots_to_remove.push(pos);
|
||||||
}
|
}
|
||||||
|
@ -577,8 +688,9 @@ impl Threads {
|
||||||
} else {
|
} else {
|
||||||
t.thread_nodes.push(Default::default());
|
t.thread_nodes.push(Default::default());
|
||||||
let new_id = t.thread_nodes.len() - 1;
|
let new_id = t.thread_nodes.len() - 1;
|
||||||
make!((new_id) parent of (r), t.thread_nodes);
|
t.thread_nodes[new_id].thread_group = new_id;
|
||||||
make!((new_id) parent of (other_idx), t.thread_nodes);
|
make!((new_id) parent of (r), &mut t.thread_nodes);
|
||||||
|
make!((new_id) parent of (other_idx), &mut t.thread_nodes);
|
||||||
root_set[i] = new_id;
|
root_set[i] = new_id;
|
||||||
if let Some(pos) = root_set.iter().position(|r| *r == other_idx) {
|
if let Some(pos) = root_set.iter().position(|r| *r == other_idx) {
|
||||||
roots_to_remove.push(pos);
|
roots_to_remove.push(pos);
|
||||||
|
@ -624,6 +736,7 @@ impl Threads {
|
||||||
envelope: Envelope,
|
envelope: Envelope,
|
||||||
collection: &mut FnvHashMap<EnvelopeHash, Envelope>,
|
collection: &mut FnvHashMap<EnvelopeHash, Envelope>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
|
return false;
|
||||||
{
|
{
|
||||||
let in_reply_to = envelope.in_reply_to_raw();
|
let in_reply_to = envelope.in_reply_to_raw();
|
||||||
if !self.message_ids.contains_key(in_reply_to.as_ref()) {
|
if !self.message_ids.contains_key(in_reply_to.as_ref()) {
|
||||||
|
@ -643,22 +756,6 @@ impl Threads {
|
||||||
{
|
{
|
||||||
let in_reply_to = envelope.in_reply_to_raw();
|
let in_reply_to = envelope.in_reply_to_raw();
|
||||||
let parent_id = self.message_ids[in_reply_to.as_ref()];
|
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);
|
self.rebuild_thread(parent_id, collection);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
|
@ -692,15 +789,10 @@ impl Threads {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Add new child */
|
/* Add new child */
|
||||||
let pos = tree.iter().position(|v| v.id == id).unwrap();
|
let tree_node = ThreadTree::new(id);
|
||||||
node_build(
|
tree.push(tree_node);
|
||||||
&mut tree[pos],
|
let new_id = tree.len() - 1;
|
||||||
id,
|
node_build(&mut tree[new_id], id, &mut self.thread_nodes, 1, collection);
|
||||||
&mut self.thread_nodes,
|
|
||||||
1,
|
|
||||||
id,
|
|
||||||
collection,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -714,8 +806,7 @@ impl Threads {
|
||||||
&mut tree_node,
|
&mut tree_node,
|
||||||
*i,
|
*i,
|
||||||
&mut self.thread_nodes,
|
&mut self.thread_nodes,
|
||||||
0, /* indentation */
|
0, /* indentation */
|
||||||
*i, /* root_subject_idx */
|
|
||||||
collection,
|
collection,
|
||||||
);
|
);
|
||||||
tree.push(tree_node);
|
tree.push(tree_node);
|
||||||
|
@ -862,8 +953,14 @@ impl Threads {
|
||||||
fn link_envelope(&mut self, envelope: &mut Envelope) {
|
fn link_envelope(&mut self, envelope: &mut Envelope) {
|
||||||
let m_id = envelope.message_id_raw().to_string();
|
let m_id = envelope.message_id_raw().to_string();
|
||||||
|
|
||||||
/* The index of this message's ThreadNode in thread_nodes */
|
/* t_idx: The index of this message's ThreadNode in thread_nodes
|
||||||
|
*
|
||||||
|
* If id_table contains an empty Container for this ID:
|
||||||
|
* Store this message in the Container's message slot.
|
||||||
|
* Else:
|
||||||
|
* Create a new Container object holding this message;
|
||||||
|
* Index the Container by Message-ID in id_table.
|
||||||
|
*/
|
||||||
let t_idx: usize = if self.message_ids.get(&m_id).is_some() {
|
let t_idx: usize = if self.message_ids.get(&m_id).is_some() {
|
||||||
let node_idx = self.message_ids[&m_id];
|
let node_idx = self.message_ids[&m_id];
|
||||||
/* the already existing ThreadNote should be empty, since we're
|
/* the already existing ThreadNote should be empty, since we're
|
||||||
|
@ -880,13 +977,16 @@ impl Threads {
|
||||||
date: envelope.date(),
|
date: envelope.date(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
/* The new thread node's set is just itself */
|
||||||
let new_id = self.thread_nodes.len() - 1;
|
let new_id = self.thread_nodes.len() - 1;
|
||||||
self.thread_nodes[new_id].thread_group = new_id;
|
self.thread_nodes[new_id].thread_group = new_id;
|
||||||
|
|
||||||
self.message_ids.insert(m_id, new_id);
|
self.message_ids.insert(m_id, new_id);
|
||||||
new_id
|
new_id
|
||||||
};
|
};
|
||||||
self.thread_nodes[t_idx].date = envelope.date();
|
self.thread_nodes[t_idx].date = envelope.date();
|
||||||
self.thread_nodes[t_idx].message = Some(envelope.hash());
|
self.thread_nodes[t_idx].message = Some(envelope.hash());
|
||||||
|
self.thread_nodes[t_idx].has_unseen |= !envelope.is_seen();
|
||||||
envelope.set_thread(t_idx);
|
envelope.set_thread(t_idx);
|
||||||
self.hash_set.insert(envelope.hash());
|
self.hash_set.insert(envelope.hash());
|
||||||
|
|
||||||
|
@ -898,18 +998,12 @@ impl Threads {
|
||||||
*
|
*
|
||||||
* Link the References field's ThreadNode together in the order implied
|
* Link the References field's ThreadNode together in the order implied
|
||||||
* by the References header.
|
* 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 */
|
/* The index of the reference we are currently examining, start from current message */
|
||||||
let mut ref_ptr = t_idx;
|
let mut ref_ptr = t_idx;
|
||||||
if self.thread_nodes[t_idx].has_parent() {
|
if self.thread_nodes[t_idx].has_parent() {
|
||||||
remove_from_parent!(self.thread_nodes, t_idx);
|
remove_from_parent!(&mut self.thread_nodes, t_idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
for &refn in envelope.references().iter().rev() {
|
for &refn in envelope.references().iter().rev() {
|
||||||
|
@ -926,13 +1020,23 @@ impl Threads {
|
||||||
self.message_ids.insert(r_id, new_id);
|
self.message_ids.insert(r_id, new_id);
|
||||||
new_id
|
new_id
|
||||||
};
|
};
|
||||||
|
/* If they are already linked, don't change the existing links. */
|
||||||
|
if self.thread_nodes[ref_ptr].has_parent() {
|
||||||
|
ref_ptr = parent_id;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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.
|
||||||
|
*/
|
||||||
if self.find(ref_ptr) != self.find(parent_id) {
|
if self.find(ref_ptr) != self.find(parent_id) {
|
||||||
self.union(ref_ptr, parent_id);
|
self.union(ref_ptr, parent_id);
|
||||||
make!((parent_id) parent of (ref_ptr), self.thread_nodes);
|
make!((parent_id) parent of (ref_ptr), &mut self.thread_nodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Update thread's date */
|
/* Update thread's date */
|
||||||
|
|
||||||
let mut parent_iter = parent_id;
|
let mut parent_iter = parent_id;
|
||||||
'date: loop {
|
'date: loop {
|
||||||
let p: &mut ThreadNode = &mut self.thread_nodes[parent_iter];
|
let p: &mut ThreadNode = &mut self.thread_nodes[parent_iter];
|
||||||
|
@ -971,25 +1075,22 @@ fn node_build(
|
||||||
idx: usize,
|
idx: usize,
|
||||||
thread_nodes: &mut Vec<ThreadNode>,
|
thread_nodes: &mut Vec<ThreadNode>,
|
||||||
indentation: usize,
|
indentation: usize,
|
||||||
root_subject_idx: usize,
|
|
||||||
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
||||||
) {
|
) {
|
||||||
if let Some(msg_idx) = thread_nodes[root_subject_idx].message().as_ref() {
|
if let Some(hash) = thread_nodes[idx].message {
|
||||||
let root_subject = collection[msg_idx].subject();
|
if let Some(parent_id) = thread_nodes[idx].parent {
|
||||||
/* If the ThreadNode has no Message, but does have children, remove this container but
|
if let Some(parent_hash) = thread_nodes[parent_id].message {
|
||||||
* promote its children to this level (that is, splice them in to the current child
|
let mut subject = collection[&hash].subject().to_string();
|
||||||
* list.) */
|
subject.strip_prefixes();
|
||||||
if indentation > 0 && thread_nodes[idx].has_message() {
|
let mut parent_subject = collection[&parent_hash].subject().to_string();
|
||||||
let subject = collection[thread_nodes[idx].message().as_ref().unwrap()].subject();
|
parent_subject.strip_prefixes();
|
||||||
thread_nodes[idx].has_unseen =
|
if subject == parent_subject {
|
||||||
!collection[thread_nodes[idx].message().as_ref().unwrap()].is_seen();
|
thread_nodes[idx].show_subject = false;
|
||||||
if subject == root_subject
|
}
|
||||||
|| subject.starts_with("Re: ") && subject.as_ref().ends_with(root_subject.as_ref())
|
|
||||||
{
|
|
||||||
thread_nodes[idx].show_subject = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let indentation = if thread_nodes[idx].has_message() {
|
let indentation = if thread_nodes[idx].has_message() {
|
||||||
thread_nodes[idx].indentation = indentation;
|
thread_nodes[idx].indentation = indentation;
|
||||||
indentation + 1
|
indentation + 1
|
||||||
|
@ -1001,12 +1102,16 @@ fn node_build(
|
||||||
|
|
||||||
let mut has_unseen = thread_nodes[idx].has_unseen;
|
let mut has_unseen = thread_nodes[idx].has_unseen;
|
||||||
let mut child_vec: Vec<ThreadTree> = Vec::new();
|
let mut child_vec: Vec<ThreadTree> = Vec::new();
|
||||||
|
|
||||||
|
let mut length = thread_nodes[idx].children.len();
|
||||||
for c in thread_nodes[idx].children.clone() {
|
for c in thread_nodes[idx].children.clone() {
|
||||||
let mut new_tree = ThreadTree::new(c);
|
let mut new_tree = ThreadTree::new(c);
|
||||||
node_build(&mut new_tree, c, thread_nodes, indentation, idx, collection);
|
node_build(&mut new_tree, c, thread_nodes, indentation, collection);
|
||||||
|
length += thread_nodes[c].len;
|
||||||
has_unseen |= thread_nodes[c].has_unseen;
|
has_unseen |= thread_nodes[c].has_unseen;
|
||||||
child_vec.push(new_tree);
|
child_vec.push(new_tree);
|
||||||
}
|
}
|
||||||
tree.children = child_vec;
|
tree.children = child_vec;
|
||||||
|
thread_nodes[idx].len = length;
|
||||||
thread_nodes[idx].has_unseen = has_unseen;
|
thread_nodes[idx].has_unseen = has_unseen;
|
||||||
}
|
}
|
||||||
|
|
|
@ -243,6 +243,10 @@ impl State {
|
||||||
pub fn refresh_event(&mut self, event: RefreshEvent) {
|
pub fn refresh_event(&mut self, event: RefreshEvent) {
|
||||||
let hash = event.hash();
|
let hash = event.hash();
|
||||||
if let Some(&(idxa, idxm)) = self.context.mailbox_hashes.get(&hash) {
|
if let Some(&(idxa, idxm)) = self.context.mailbox_hashes.get(&hash) {
|
||||||
|
if self.context.accounts[idxa].status(idxm).is_err() {
|
||||||
|
self.context.replies.push_back(UIEvent::from(event));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if let Some(notification) = self.context.accounts[idxa].reload(event, idxm) {
|
if let Some(notification) = self.context.accounts[idxa].reload(event, idxm) {
|
||||||
self.context.replies.push_back(UIEvent {
|
self.context.replies.push_back(UIEvent {
|
||||||
id: 0,
|
id: 0,
|
||||||
|
|
|
@ -93,6 +93,16 @@ pub enum UIEventType {
|
||||||
MailboxUpdate((usize, usize)), // (account_idx, mailbox_idx)
|
MailboxUpdate((usize, usize)), // (account_idx, mailbox_idx)
|
||||||
EntityKill(Uuid),
|
EntityKill(Uuid),
|
||||||
StartupCheck,
|
StartupCheck,
|
||||||
|
RefreshEvent(Box<RefreshEvent>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RefreshEvent> for UIEvent {
|
||||||
|
fn from(event: RefreshEvent) -> Self {
|
||||||
|
UIEvent {
|
||||||
|
id: 0,
|
||||||
|
event_type: UIEventType::RefreshEvent(Box::new(event)),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An event passed from `State` to its Entities.
|
/// An event passed from `State` to its Entities.
|
||||||
|
|
Loading…
Reference in New Issue