|
|
@ -47,6 +47,8 @@ use std::ops::Index; |
|
|
|
use std::result::Result as StdResult;
|
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
|
|
type Envelopes = FnvHashMap<EnvelopeHash, Envelope>;
|
|
|
|
|
|
|
|
/* Helper macros to avoid repeating ourselves */
|
|
|
|
|
|
|
|
fn rec_change_root_parent(b: &mut Vec<ThreadNode>, idx: usize, new_root: usize) {
|
|
|
@ -221,7 +223,7 @@ impl ThreadTree { |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* `ThreadIterator` returns messages according to the sorted order. For example, for the following
|
|
|
|
/* `ThreadsIterator` returns messages according to the sorted order. For example, for the following
|
|
|
|
* threads: |
|
|
|
*
|
|
|
|
* ```
|
|
|
@ -236,6 +238,53 @@ impl ThreadTree { |
|
|
|
* the iterator returns them as `A, B, C, D, E, F`
|
|
|
|
*/
|
|
|
|
|
|
|
|
pub struct ThreadsIterator<'a> {
|
|
|
|
pos: usize,
|
|
|
|
stack: Vec<usize>,
|
|
|
|
tree: Ref<'a, Vec<ThreadTree>>,
|
|
|
|
}
|
|
|
|
impl<'a> Iterator for ThreadsIterator<'a> {
|
|
|
|
type Item = (usize, usize);
|
|
|
|
fn next(&mut self) -> Option<(usize, usize)> {
|
|
|
|
{
|
|
|
|
let mut tree = &(*self.tree);
|
|
|
|
for i in &self.stack {
|
|
|
|
tree = &tree[*i].children;
|
|
|
|
}
|
|
|
|
if self.pos == tree.len() {
|
|
|
|
if let Some(p) = self.stack.pop() {
|
|
|
|
self.pos = p + 1;
|
|
|
|
} else {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
debug_assert!(self.pos < tree.len());
|
|
|
|
let ret = (self.stack.len(), tree[self.pos].id);
|
|
|
|
if !tree[self.pos].children.is_empty() {
|
|
|
|
self.stack.push(self.pos);
|
|
|
|
self.pos = 0;
|
|
|
|
return Some(ret);
|
|
|
|
}
|
|
|
|
self.pos += 1;
|
|
|
|
return Some(ret);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.next()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* `ThreadIterator` returns messages of a specific thread according to the sorted order. For example, for the following
|
|
|
|
* thread: |
|
|
|
*
|
|
|
|
* ```
|
|
|
|
* A_
|
|
|
|
* |_ B
|
|
|
|
* |_C
|
|
|
|
* |_D
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* the iterator returns them as `A, B, C, D`
|
|
|
|
*/
|
|
|
|
|
|
|
|
pub struct ThreadIterator<'a> {
|
|
|
|
init_pos: usize,
|
|
|
|
pos: usize,
|
|
|
@ -355,7 +404,7 @@ pub struct Threads { |
|
|
|
tree: RefCell<Vec<ThreadTree>>,
|
|
|
|
|
|
|
|
message_ids: FnvHashMap<Vec<u8>, usize>,
|
|
|
|
hash_set: FnvHashSet<EnvelopeHash>,
|
|
|
|
pub hash_set: FnvHashSet<EnvelopeHash>,
|
|
|
|
sort: RefCell<(SortField, SortOrder)>,
|
|
|
|
subsort: RefCell<(SortField, SortOrder)>,
|
|
|
|
}
|
|
|
@ -504,8 +553,7 @@ impl Threads { |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: Split this function
|
|
|
|
pub fn new(collection: &mut FnvHashMap<EnvelopeHash, Envelope>) -> Threads {
|
|
|
|
pub fn new(collection: &mut Envelopes) -> Threads {
|
|
|
|
/* To reconstruct thread information from the mails we need: */
|
|
|
|
|
|
|
|
/* a vector to hold thread members */
|
|
|
@ -529,20 +577,46 @@ impl Threads { |
|
|
|
* References / In-Reply-To headers */
|
|
|
|
t.link_threads(collection);
|
|
|
|
|
|
|
|
t.create_root_set(collection);
|
|
|
|
t.build_collection(collection);
|
|
|
|
for (i, _t) in t.thread_nodes.iter().enumerate() {
|
|
|
|
eprintln!("Thread #{}, children {}", i, _t.children.len());
|
|
|
|
if !_t.children.is_empty() {
|
|
|
|
eprintln!("{:?}", _t.children);
|
|
|
|
}
|
|
|
|
if let Some(m) = _t.message {
|
|
|
|
eprintln!("\tmessage: {}", collection[&m].subject());
|
|
|
|
} else {
|
|
|
|
eprintln!("\tNo message");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
eprintln!("\n");
|
|
|
|
for (i, _t) in t.tree.borrow().iter().enumerate() {
|
|
|
|
eprintln!("Tree #{} id {}, children {}", i, _t.id, _t.children.len());
|
|
|
|
if let Some(m) = t.thread_nodes[_t.id].message {
|
|
|
|
eprintln!("\tmessage: {}", collection[&m].subject());
|
|
|
|
} else {
|
|
|
|
eprintln!("\tNo message");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
t
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_root_set(&mut self, collection: &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<usize> = Vec::with_capacity(collection.len());
|
|
|
|
|
|
|
|
/* Find the root set */
|
|
|
|
'root_set: for v in t.message_ids.values() {
|
|
|
|
if t.thread_nodes[*v].parent.is_none() {
|
|
|
|
'root_set: for v in self.message_ids.values() {
|
|
|
|
if self.thread_nodes[*v].parent.is_none() {
|
|
|
|
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);
|
|
|
|
self.prune_empty_nodes(&mut root_set);
|
|
|
|
|
|
|
|
/* "Group root set by subject."
|
|
|
|
*
|
|
|
@ -553,19 +627,19 @@ impl Threads { |
|
|
|
let mut subject_table: FnvHashMap<Vec<u8>, (bool, usize)> =
|
|
|
|
FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default());
|
|
|
|
|
|
|
|
for r in &root_set {
|
|
|
|
for (i, &r) in root_set.iter().enumerate() {
|
|
|
|
/* "Find the subject of that sub-tree": */
|
|
|
|
let (mut subject, mut is_re): (_, bool) = if t.thread_nodes[*r].message.is_some() {
|
|
|
|
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 = t.thread_nodes[*r].message.unwrap();
|
|
|
|
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 = t.thread_nodes[t.thread_nodes[*r].children[0]]
|
|
|
|
let mut msg_idx = self.thread_nodes[self.thread_nodes[r].children[0]]
|
|
|
|
.message
|
|
|
|
.unwrap();
|
|
|
|
let envelope = &collection[&msg_idx];
|
|
|
@ -591,17 +665,17 @@ impl Threads { |
|
|
|
* "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 (!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),
|
|
|
|
(is_re, r),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* "There is no container in the table with this subject" */
|
|
|
|
subject_table.insert(stripped_subj.to_vec(), (is_re, *r));
|
|
|
|
subject_table.insert(stripped_subj.to_vec(), (is_re, r));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
@ -609,13 +683,14 @@ impl Threads { |
|
|
|
* 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 t.thread_nodes[r].message.is_some() {
|
|
|
|
let msg_idx = t.thread_nodes[r].message.unwrap();
|
|
|
|
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 = t.thread_nodes[t.thread_nodes[r].children[0]]
|
|
|
|
let msg_idx = self.thread_nodes[self.thread_nodes[r].children[0]]
|
|
|
|
.message
|
|
|
|
.unwrap();
|
|
|
|
let envelope = &collection[&msg_idx];
|
|
|
@ -630,7 +705,7 @@ impl Threads { |
|
|
|
|
|
|
|
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 {
|
|
|
|
if !self.thread_nodes[other_idx].has_message() || other_idx == r {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
@ -641,10 +716,10 @@ impl Threads { |
|
|
|
* "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 = t.thread_nodes[r].children.clone();
|
|
|
|
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 t.thread_nodes);
|
|
|
|
make!((other_idx) parent of (c), &mut self.thread_nodes);
|
|
|
|
}
|
|
|
|
roots_to_remove.push(i);
|
|
|
|
|
|
|
@ -652,14 +727,18 @@ impl Threads { |
|
|
|
* 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), &mut t.thread_nodes);
|
|
|
|
} 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 !t.thread_nodes[r].has_message() && t.thread_nodes[other_idx].has_message() {
|
|
|
|
make!((r) parent of (other_idx), &mut t.thread_nodes);
|
|
|
|
} 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);
|
|
|
|
}
|
|
|
@ -667,8 +746,8 @@ impl Threads { |
|
|
|
* "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), &mut t.thread_nodes);
|
|
|
|
} 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
|
|
|
@ -677,8 +756,8 @@ impl Threads { |
|
|
|
* 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), &mut t.thread_nodes);
|
|
|
|
} 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);
|
|
|
|
}
|
|
|
@ -688,11 +767,11 @@ impl Threads { |
|
|
|
* hierarchical relationship which might not be true."
|
|
|
|
*/
|
|
|
|
} else {
|
|
|
|
t.thread_nodes.push(Default::default());
|
|
|
|
let new_id = t.thread_nodes.len() - 1;
|
|
|
|
t.thread_nodes[new_id].thread_group = new_id;
|
|
|
|
make!((new_id) parent of (r), &mut t.thread_nodes);
|
|
|
|
make!((new_id) parent of (other_idx), &mut t.thread_nodes);
|
|
|
|
self.thread_nodes.push(Default::default());
|
|
|
|
let new_id = self.thread_nodes.len() - 1;
|
|
|
|
self.thread_nodes[new_id].thread_group = 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);
|
|
|
@ -705,41 +784,118 @@ impl Threads { |
|
|
|
root_set.remove(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
t.root_set = RefCell::new(root_set);
|
|
|
|
t.build_collection(&collection);
|
|
|
|
t
|
|
|
|
self.root_set = RefCell::new(root_set);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn threads_iter(&self) -> ThreadsIterator {
|
|
|
|
ThreadsIterator {
|
|
|
|
pos: 0,
|
|
|
|
stack: Vec::with_capacity(4),
|
|
|
|
tree: self.tree.borrow(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn thread_iter(&self, index: usize) -> ThreadIterator {
|
|
|
|
ThreadIterator {
|
|
|
|
init_pos: index,
|
|
|
|
pos: index,
|
|
|
|
stack: Vec::new(),
|
|
|
|
stack: Vec::with_capacity(4),
|
|
|
|
tree: self.tree.borrow(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn update_envelope(&mut self, old_hash: EnvelopeHash, envelope: &Envelope) {
|
|
|
|
pub fn update_envelope(
|
|
|
|
&mut self,
|
|
|
|
old_hash: EnvelopeHash,
|
|
|
|
new_hash: EnvelopeHash,
|
|
|
|
) -> Result<(), ()> {
|
|
|
|
/* must update:
|
|
|
|
* - hash_set
|
|
|
|
* - message fields in thread_nodes
|
|
|
|
*/
|
|
|
|
self.hash_set.remove(&old_hash);
|
|
|
|
self.hash_set.insert(envelope.hash());
|
|
|
|
let node = self
|
|
|
|
if let Some(node) = self
|
|
|
|
.thread_nodes
|
|
|
|
.iter_mut()
|
|
|
|
.find(|n| n.message.map(|n| n == old_hash).unwrap_or(false))
|
|
|
|
.unwrap();
|
|
|
|
node.message = Some(envelope.hash());
|
|
|
|
{
|
|
|
|
node.message = Some(new_hash);
|
|
|
|
} else {
|
|
|
|
return Err(());
|
|
|
|
}
|
|
|
|
self.hash_set.insert(new_hash);
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn update(&mut self, collection: &mut FnvHashMap<EnvelopeHash, Envelope>) {
|
|
|
|
#[inline]
|
|
|
|
pub fn remove(&mut self, envelope_hash: EnvelopeHash, collection: &mut Envelopes) {
|
|
|
|
self.hash_set.remove(&envelope_hash);
|
|
|
|
//{
|
|
|
|
// let pos = self
|
|
|
|
// .thread_nodes
|
|
|
|
// .iter()
|
|
|
|
// .position(|n| n.message.map(|n| n == envelope_hash).unwrap_or(false))
|
|
|
|
// .unwrap();
|
|
|
|
// eprintln!("DEBUG: {} in threads is idx= {}", envelope_hash, pos);
|
|
|
|
//}
|
|
|
|
|
|
|
|
let t_id: usize;
|
|
|
|
{
|
|
|
|
if let Some(pos) = self
|
|
|
|
.thread_nodes
|
|
|
|
.iter()
|
|
|
|
.position(|n| n.message.map(|n| n == envelope_hash).unwrap_or(false))
|
|
|
|
{
|
|
|
|
t_id = pos;
|
|
|
|