diff --git a/benches/maildir.rs b/benches/maildir.rs index a5377323e..8a0c2c5a1 100644 --- a/benches/maildir.rs +++ b/benches/maildir.rs @@ -1,6 +1,5 @@ #![feature(test)] extern crate melib; -use melib::mailbox::backends::MailBackend; use melib::mailbox::backends::maildir::*; extern crate test; diff --git a/benches/parse.rs b/benches/parse.rs index 96448114f..c1fced803 100644 --- a/benches/parse.rs +++ b/benches/parse.rs @@ -1,7 +1,7 @@ #![feature(test)] extern crate melib; -use melib::mailbox::email::Mail; +use melib::mailbox::email::Envelope; use melib::mailbox::backends::BackendOpGenerator; use melib::mailbox::backends::maildir::MaildirOp; @@ -10,6 +10,6 @@ use self::test::Bencher; #[bench] fn mail_parse(b: &mut Bencher) { - b.iter(|| Mail::from(Box::new(BackendOpGenerator::new(Box::new(move || { + b.iter(|| Envelope::from(Box::new(BackendOpGenerator::new(Box::new(move || { Box::new(MaildirOp::new("test/attachment_test".to_string()))})))) ); } diff --git a/src/bin.rs b/src/bin.rs index c8d527b7d..bc21d1f1d 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -41,11 +41,11 @@ fn main() { ncurses::touchwin(ncurses::stdscr()); ncurses::mv(0,0); let mailbox = &mut account[j]; - let mut index: Box = match mailbox.as_ref().unwrap() { - &Ok(ref v) => { + let mut index: Box = match *mailbox.as_ref().unwrap() { + Ok(ref v) => { Box::new(Index::new(v)) }, - &Err(ref v) => { + Err(ref v) => { Box::new(ErrorWindow::new((*v).clone())) } }; diff --git a/src/mailbox/accounts.rs b/src/mailbox/accounts.rs index 2d047109e..a040ad23b 100644 --- a/src/mailbox/accounts.rs +++ b/src/mailbox/accounts.rs @@ -37,7 +37,7 @@ pub struct Account { impl Account { pub fn new(name: String, settings: AccountSettings) -> Self { eprintln!("new acc" ); - let sent_folder = settings.folders.iter().position(|ref x| **x == settings.sent_folder); + let sent_folder = settings.folders.iter().position(|x| *x == settings.sent_folder); let mut folders = Vec::with_capacity(settings.folders.len()); for _ in 0..settings.folders.len() { folders.push(None); diff --git a/src/mailbox/backends/maildir.rs b/src/mailbox/backends/maildir.rs index e855e7e37..1fc9a01b3 100644 --- a/src/mailbox/backends/maildir.rs +++ b/src/mailbox/backends/maildir.rs @@ -19,7 +19,7 @@ * along with meli. If not, see . */ -use mailbox::email::Mail; +use mailbox::email::Envelope; use error::{MeliError, Result}; use mailbox::backends::{MailBackend, BackendOp, BackendOpGenerator}; use mailbox::email::parser; @@ -81,7 +81,7 @@ impl BackendOp for MaildirOp { impl MailBackend for MaildirType { - fn get(&self) -> Result> { + fn get(&self) -> Result> { self.get_multicore(4) /* @@ -124,7 +124,7 @@ impl MaildirType { } Ok(()) } - pub fn get_multicore(&self, cores: usize) -> Result> { + pub fn get_multicore(&self, cores: usize) -> Result> { MaildirType::is_valid(&self.path)?; let mut path = PathBuf::from(&self.path); path.push("cur"); @@ -167,10 +167,10 @@ panic!("didn't parse"); }, }; for chunk in files.chunks(chunk_size) { let s = scope.spawn(move || { - let mut local_r:Vec = Vec::with_capacity(chunk.len()); + let mut local_r:Vec = Vec::with_capacity(chunk.len()); for e in chunk { let e_copy = e.to_string(); - if let Some(e) = Mail::from(Box::new(BackendOpGenerator::new(Box::new(move || { + if let Some(e) = Envelope::from(Box::new(BackendOpGenerator::new(Box::new(move || { Box::new(MaildirOp::new(e_copy.clone())) } )))) { local_r.push(e); diff --git a/src/mailbox/backends/mod.rs b/src/mailbox/backends/mod.rs index 1dbc11f5c..33696ee58 100644 --- a/src/mailbox/backends/mod.rs +++ b/src/mailbox/backends/mod.rs @@ -20,18 +20,18 @@ */ pub mod maildir; -use mailbox::email::Mail; +use mailbox::email::Envelope; use error::Result; use std::fmt; pub trait MailBackend { - fn get(&self) -> Result>; + fn get(&self) -> Result>; } -/// A BackendOp manages common operations for the various mail backends. They only live for the -/// duration of the operation. They are generated by BackendOpGenerator on demand. +/// A `BackendOp` manages common operations for the various mail backends. They only live for the +/// duration of the operation. They are generated by `BackendOpGenerator` on demand. /// /// # Motivation /// @@ -75,8 +75,8 @@ pub trait BackendOp: ::std::fmt::Debug + ::std::marker::Send { fn fetch_body(&mut self) -> Result<&[u8]>; } -/// BackendOpGenerator is a wrapper for a closure that returns a BackendOp object -/// See BackendOp for details. +/// `BackendOpGenerator` is a wrapper for a closure that returns a `BackendOp` object +/// See `BackendOp` for details. /* * I know this sucks, but that's the best way I found that rustc deems safe. * */ diff --git a/src/mailbox/email/mod.rs b/src/mailbox/email/mod.rs index 0764238b1..d4c420d97 100644 --- a/src/mailbox/email/mod.rs +++ b/src/mailbox/email/mod.rs @@ -96,7 +96,7 @@ struct References { use std::sync::Arc; /* A very primitive mail object */ #[derive(Debug, Clone)] -pub struct Mail +pub struct Envelope { date: String, from: Option, @@ -115,7 +115,7 @@ pub struct Mail } -impl Mail +impl Envelope { pub fn get_date(&self) -> i64 { self.timestamp @@ -146,7 +146,7 @@ impl Mail Err(_) => { let operation = self.operation_token.generate(); eprintln!("error in parsing mail\n{}", operation.description()); - panic!(); + panic!() }, }; let mut builder = AttachmentBuilder::new(body); @@ -233,7 +233,7 @@ impl Mail Some(ref mut s) => { if s.refs.contains(&new_ref) { if s.refs[s.refs.len() - 1] != new_ref { - if let Some(index) = s.refs.iter().position(|ref x| **x == new_ref) { + if let Some(index) = s.refs.iter().position(|x| *x == new_ref) { s.refs.remove(index); } else { panic!(); @@ -282,13 +282,10 @@ impl Mail self.datetime = new_val; if let Some(v) = self.datetime { self.timestamp = v.timestamp(); - if self.timestamp == 1485962960 { - eprintln!("it's {:?}", self); - } } } pub fn new(token: Box) -> Self { - Mail { + Envelope { date: "".to_string(), from: None, to: None, @@ -307,7 +304,7 @@ impl Mail } } - pub fn from(operation_token: Box) -> Option { + pub fn from(operation_token: Box) -> Option { let mut operation = operation_token.generate(); let headers = match parser::headers(operation.fetch_headers().unwrap()).to_full_result() { Ok(v) => { @@ -320,7 +317,7 @@ impl Mail }, }; - let mut mail = Mail::new(operation_token); + let mut mail = Envelope::new(operation_token); let mut in_reply_to = None; let mut datetime = None; @@ -383,20 +380,20 @@ impl Mail } } -impl Eq for Mail {} -impl Ord for Mail { - fn cmp(&self, other: &Mail) -> Ordering { +impl Eq for Envelope {} +impl Ord for Envelope { + fn cmp(&self, other: &Envelope) -> Ordering { self.get_datetime().cmp(&other.get_datetime()) } } -impl PartialOrd for Mail { - fn partial_cmp(&self, other: &Mail) -> Option { +impl PartialOrd for Envelope { + fn partial_cmp(&self, other: &Envelope) -> Option { Some(self.cmp(other)) } } -impl PartialEq for Mail { - fn eq(&self, other: &Mail) -> bool { +impl PartialEq for Envelope { + fn eq(&self, other: &Envelope) -> bool { self.get_message_id_raw() == other.get_message_id_raw() } } diff --git a/src/mailbox/email/parser.rs b/src/mailbox/email/parser.rs index 5ded035d8..81720b9d6 100644 --- a/src/mailbox/email/parser.rs +++ b/src/mailbox/email/parser.rs @@ -30,9 +30,7 @@ use encoding::{Encoding, DecoderTrap}; fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8],u8> { if input.is_empty() || input.len() < 3 { IResult::Incomplete(Needed::Size(1)) - } else if input[0] != b'=' { - IResult::Error(error_code!(ErrorKind::Custom(43))) - } else if is_hex_digit(input[1]) && is_hex_digit(input[2]) { + } else if input[0] == b'=' && is_hex_digit(input[1]) && is_hex_digit(input[2]) { let a = if input[1] < b':' { input[1] - 48 } else if input[1] < b'[' { @@ -47,7 +45,6 @@ fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8],u8> { } else { input[2] - 87 }; - IResult::Done(&input[3..], a*16+b) } else { IResult::Error(error_code!(ErrorKind::Custom(43))) @@ -350,8 +347,8 @@ fn message_id_peek(input: &[u8]) -> IResult<&[u8],&str> { } else if input_length == 2 || input[0] != b'<' { IResult::Error(error_code!(ErrorKind::Custom(43))) } else { - for i in 1..input_length { - if input[i] == b'>' { + for (i, &x) in input.iter().take(input_length).enumerate().skip(1) { + if x == b'>' { return IResult::Done(&input[i+1..], from_utf8(&input[0..i+1]).unwrap()); } } diff --git a/src/mailbox/mod.rs b/src/mailbox/mod.rs index 6c2ba1ed6..6ceec1e24 100644 --- a/src/mailbox/mod.rs +++ b/src/mailbox/mod.rs @@ -28,386 +28,29 @@ use mailbox::backends::maildir; use error::Result; pub mod accounts; pub use mailbox::accounts::Account; +mod thread; +use mailbox::thread::{Container, build_threads}; -extern crate fnv; - -use self::fnv::FnvHashMap; use std::option::Option; -use std; -type UnixTimestamp = i64; /*a Mailbox represents a folder of mail. Currently only Maildir is supported.*/ #[derive(Debug,Clone)] pub struct Mailbox{ pub path: String, - pub collection: Vec, + pub collection: Vec, pub threaded_collection: Vec, - threads: Vec, + threads: Vec, length: usize, } -/* a Thread struct is needed to describe the Thread tree forest during creation - * of threads. Because of Rust's memory model, we store indexes of other node - * instead of references and every reference is passed through the Thread owner - * (a Vec). - * - * message refers to a Mail entry in a Vec. If it's empty, the Thread is - * nonexistent in our Mailbox but we know it exists (for example we have a copy - * of a reply to a mail but we don't have its copy. - */ -#[derive(Clone, Copy, Debug)] -pub struct Thread { - id: usize, - message: Option, - parent: Option, - first_child: Option, - next_sibling: Option, - date: UnixTimestamp, - indentation: usize, - show_subject: bool, -} - -impl Thread { - pub fn get_message(&self) -> Option { - self.message - } - pub fn get_parent(&self) -> Option { - self.parent - } - pub fn has_parent(&self) -> bool { - self.parent.is_some() - } - pub fn get_first_child(&self) -> Option { - self.first_child - } - pub fn get_next_sibling(&self) -> Option { - self.next_sibling - } - pub fn has_children(&self) -> bool { - self.first_child.is_some() - } - pub fn has_sibling(&self) -> bool { - self.next_sibling.is_some() - } - pub fn has_message(&self) -> bool { - self.message.is_some() - } - fn set_indentation(&mut self, i: usize) { - self.indentation = i; - } - pub fn get_indentation(&self) -> usize { - self.indentation - } - fn is_descendant(&self, threads: &[Thread], other: &Thread) -> bool { - if self == other { - return true; - } - - if let Some(v) = self.first_child { - if threads[v].is_descendant(threads, other) { - return true; - } - }; - if let Some(v) = self.next_sibling { - if threads[v].is_descendant(threads, other) { - return true; - } - }; - false - } - fn set_show_subject(&mut self, set: bool) -> () { - self.show_subject = set; - } - pub fn get_show_subject(&self) -> bool { - self.show_subject - } -} - -impl PartialEq for Thread { - fn eq(&self, other: &Thread) -> bool { - match (self.message, other.message) { - (Some(s), Some(o)) => { - s == o - }, - _ => { - self.id == other.id - } - } - } -} - -fn build_collection(threads: &mut Vec, id_table: &mut FnvHashMap, collection: &mut [Mail]) -> () -{ - for (i, x) in collection.iter_mut().enumerate() { - let x_index; /* x's index in threads */ - let m_id = x.get_message_id_raw().to_string(); - if id_table.contains_key(&m_id) { - let t = id_table[&m_id]; - /* the already existing Thread should be empty, since we're - * seeing this message for the first time */ - if threads[t].message.is_some() { - /* skip duplicate message-id, but this should be handled instead */ - continue; - } - x_index = t; - /* Store this message in the Thread's message slot. */ - threads[t].date = x.get_date(); - x.set_thread(t); - threads[t].message = Some(i); - } else { - /* Create a new Thread object holding this message */ - x_index = threads.len(); - threads.push( - Thread { - message: Some(i), - id: x_index, - parent: None, - first_child: None, - next_sibling: None, - date: x.get_date(), - indentation: 0, - show_subject: true, - }); - x.set_thread(x_index); - id_table.insert(m_id, x_index); - } - /* For each element in the message's References field: - * - * Find a Thread object for the given Message-ID: - * If there's one in id_table use that; - * Otherwise, make (and index) one with a null Message - * - * Link the References field's Threads 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 curr_ref = x_index; - let mut iasf = 0; - for &r in x.get_references().iter().rev() { - if iasf == 1 { - continue; - } - iasf += 1; - let parent_id = - if id_table.contains_key(r.get_raw()) { - let p = id_table[r.get_raw()]; - if !(threads[p].is_descendant(threads, &threads[curr_ref]) || - threads[curr_ref].is_descendant(threads, &threads[p])) { - threads[curr_ref].parent = Some(p); - if threads[p].first_child.is_none() { - threads[p].first_child = Some(curr_ref); - } else { - let mut child_iter = threads[p].first_child.unwrap(); - while threads[child_iter].next_sibling.is_some() { - threads[child_iter].parent = Some(p); - child_iter = threads[child_iter].next_sibling.unwrap(); - } - threads[child_iter].next_sibling = Some(curr_ref); - threads[child_iter].parent = Some(p); - } - } - p - } else { - let idx = threads.len(); - threads.push( - Thread { - message: None, - id: idx, - parent: None, - first_child: Some(curr_ref), - next_sibling: None, - date: x.get_date(), - indentation: 0, - show_subject: true, - }); - if threads[curr_ref].parent.is_none() { - threads[curr_ref].parent = Some(idx); - } - id_table.insert(r.get_raw().to_string(), idx); - idx - }; - /* update thread date */ - let mut parent_iter = parent_id; - 'date: loop { - let p = &mut threads[parent_iter]; - if p.date < x.get_date() { - p.date = x.get_date(); - } - match p.parent { - Some(p) => { parent_iter = p; }, - None => { break 'date; }, - } - } - curr_ref = parent_id; - } - } -} impl Mailbox { pub fn new(path: &str, sent_folder: &Option>) -> Result { - let mut collection: Vec = maildir::MaildirType::new(path).get()?; - /* To reconstruct thread information from the mails we need: */ - - /* a vector to hold thread members */ - let mut threads: Vec = Vec::with_capacity((collection.len() as f64 * 1.2) as usize); - /* A hash table of Message IDs */ - let mut id_table: FnvHashMap = FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default()); - + let mut collection: Vec = maildir::MaildirType::new(path).get()?; collection.sort_by(|a, b| a.get_date().cmp(&b.get_date())); - /* Add each message to id_table and threads, and link them together according to the - * References / In-Reply-To headers */ - build_collection(&mut threads, &mut id_table, &mut collection); - let mut idx = collection.len(); - let mut tidx = threads.len(); - /* Link messages from Sent folder if they are relevant to this folder. - * This means that - * - if a message from Sent is a reply to a message in this folder, we will - * add it to the threading (but not the collection; non-threading users shouldn't care - * about this) - * - if a message in this folder is a reply to a message we sent, we will add it to the - * threading - */ - - if let &Some(ref sent_box) = sent_folder { - if sent_box.is_ok() { - let sent_mailbox = sent_box.as_ref(); - let sent_mailbox = sent_mailbox.unwrap();; - - for ref x in &sent_mailbox.collection { - if id_table.contains_key(x.get_message_id_raw()) || - (!x.get_in_reply_to_raw().is_empty() && id_table.contains_key(x.get_in_reply_to_raw())) { - let mut x: Mail = (*x).clone(); - if id_table.contains_key(x.get_message_id_raw()) { - let c = id_table[x.get_message_id_raw()]; - if threads[c].message.is_some() { - /* skip duplicate message-id, but this should be handled instead */ - continue; - } - threads[c].message = Some(idx); - assert!(threads[c].has_children()); - threads[c].date = x.get_date(); - x.set_thread(c); - } else if !x.get_in_reply_to_raw().is_empty() && id_table.contains_key(x.get_in_reply_to_raw()) { - let p = id_table[x.get_in_reply_to_raw()]; - let c = if id_table.contains_key(x.get_message_id_raw()) { - id_table[x.get_message_id_raw()] - } else { - threads.push( - Thread { - message: Some(idx), - id: tidx, - parent: Some(p), - first_child: None, - next_sibling: None, - date: x.get_date(), - indentation: 0, - show_subject: true, - }); - id_table.insert(x.get_message_id_raw().to_string(), tidx); - x.set_thread(tidx); - tidx += 1; - tidx - 1 - }; - threads[c].parent = Some(p); - if threads[p].is_descendant(&threads, &threads[c]) || - threads[c].is_descendant(&threads, &threads[p]) { - continue; - } - if threads[p].first_child.is_none() { - threads[p].first_child = Some(c); - } else { - let mut fc = threads[p].first_child.unwrap(); - while threads[fc].next_sibling.is_some() { - threads[fc].parent = Some(p); - fc = threads[fc].next_sibling.unwrap(); - } - threads[fc].next_sibling = Some(c); - threads[fc].parent = Some(p); - } - /* update thread date */ - let mut parent_iter = p; - 'date: loop { - let p = &mut threads[parent_iter]; - if p.date < x.get_date() { - p.date = x.get_date(); - } - match p.parent { - Some(p) => { parent_iter = p; }, - None => { break 'date; }, - } - } - } - collection.push(x); - idx += 1; - } - } - } - } - /* Walk over the elements of id_table, and gather a list of the Thread objects that have - * no parents. These are the root messages of each thread */ - let mut root_set = Vec::with_capacity(collection.len()); - 'root_set: for v in id_table.values() { - if threads[*v].parent.is_none() { - if !threads[*v].has_message() && threads[*v].has_children() && !threads[threads[*v].first_child.unwrap()].has_sibling() { - /* 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(threads[*v].first_child.unwrap()); - continue 'root_set; - } - root_set.push(*v); - } - } - root_set.sort_by(|a, b| threads[*b].date.cmp(&threads[*a].date)); - - /* Group messages together by thread in a collection so we can print them together */ - let mut threaded_collection: Vec = Vec::with_capacity(collection.len()); - fn build_threaded(threads: &mut Vec, indentation: usize, threaded: &mut Vec, i: usize, root_subject_idx: usize, collection: &[Mail]) - { - let thread = threads[i]; - if threads[root_subject_idx].has_message() { - let root_subject = collection[threads[root_subject_idx].get_message().unwrap()].get_subject(); - /* If the Container has no Message, but does have children, remove this container but - * promote its children to this level (that is, splice them in to the current child - * list.) */ - if indentation > 0 && thread.has_message() { - let subject = collection[thread.get_message().unwrap()].get_subject(); - if subject == root_subject || subject.starts_with("Re: ") && subject.ends_with(root_subject) { - threads[i].set_show_subject(false); - } - } - } - if thread.has_parent() && !threads[thread.get_parent().unwrap()].has_message() { - threads[i].parent = None; - } - let indentation = - if thread.has_message() { - threads[i].set_indentation(indentation); - if !threaded.contains(&i) { - threaded.push(i); - } - indentation + 1 - } else if indentation > 0 { - indentation - } else { - indentation + 1 - }; - if thread.has_children() { - let mut fc = thread.get_first_child().unwrap(); - loop { - build_threaded(threads, indentation, threaded, fc, i, collection); - let thread_ = threads[fc]; - if !thread_.has_sibling() { - break; - } - fc = thread_.get_next_sibling().unwrap(); - } - } - } - for i in &root_set { - build_threaded(&mut threads, 0, &mut threaded_collection, *i, *i, &collection); - } + let (threads, threaded_collection) = build_threads(&mut collection, sent_folder); let length = collection.len(); @@ -426,12 +69,12 @@ impl Mailbox let thread = self.threads[self.threaded_collection[i]]; thread.get_message().unwrap() } - pub fn get_mail_and_thread(&mut self, i: usize) -> (&mut Mail, Thread) { + pub fn get_mail_and_thread(&mut self, i: usize) -> (&mut Envelope, Container) { let x = &mut self.collection.as_mut_slice()[i]; let thread = self.threads[x.get_thread()]; (x, thread) } - pub fn get_thread(&self, i: usize) -> &Thread { + pub fn get_thread(&self, i: usize) -> &Container { &self.threads[i] } } diff --git a/src/mailbox/thread.rs b/src/mailbox/thread.rs new file mode 100644 index 000000000..1e931e5d2 --- /dev/null +++ b/src/mailbox/thread.rs @@ -0,0 +1,395 @@ +/* + * meli - mailbox threading module. + * + * Copyright 2017 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ + +/* a Container struct is needed to describe the Thread tree forest during creation + * of threads. Because of Rust's memory model, we store indexes of other node + * instead of references and every reference is passed through the Container owner + * (a Vec). + * + * message refers to a Envelope entry in a Vec. If it's empty, the Container is + * nonexistent in our Mailbox but we know it exists (for example we have a copy + * of a reply to a mail but we don't have its copy. + */ +use mailbox::email::*; +use mailbox::Mailbox; +use error::Result; + +extern crate fnv; +use self::fnv::FnvHashMap; +use std; + + + +type UnixTimestamp = i64; + +#[derive(Clone, Copy, Debug)] +pub struct Container { + id: usize, + message: Option, + parent: Option, + first_child: Option, + next_sibling: Option, + date: UnixTimestamp, + indentation: usize, + show_subject: bool, +} + +impl Container { + pub fn get_message(&self) -> Option { + self.message + } + pub fn get_parent(&self) -> Option { + self.parent + } + pub fn has_parent(&self) -> bool { + self.parent.is_some() + } + pub fn get_first_child(&self) -> Option { + self.first_child + } + pub fn get_next_sibling(&self) -> Option { + self.next_sibling + } + pub fn has_children(&self) -> bool { + self.first_child.is_some() + } + pub fn has_sibling(&self) -> bool { + self.next_sibling.is_some() + } + pub fn has_message(&self) -> bool { + self.message.is_some() + } + fn set_indentation(&mut self, i: usize) { + self.indentation = i; + } + pub fn get_indentation(&self) -> usize { + self.indentation + } + fn is_descendant(&self, threads: &[Container], other: &Container) -> bool { + if self == other { + return true; + } + + if let Some(v) = self.first_child { + if threads[v].is_descendant(threads, other) { + return true; + } + }; + if let Some(v) = self.next_sibling { + if threads[v].is_descendant(threads, other) { + return true; + } + }; + false + } + fn set_show_subject(&mut self, set: bool) -> () { + self.show_subject = set; + } + pub fn get_show_subject(&self) -> bool { + self.show_subject + } +} + +impl PartialEq for Container { + fn eq(&self, other: &Container) -> bool { + match (self.message, other.message) { + (Some(s), Some(o)) => { + s == o + }, + _ => { + self.id == other.id + } + } + } +} + +fn build_collection(threads: &mut Vec, id_table: &mut FnvHashMap, collection: &mut [Envelope]) -> () +{ + for (i, x) in collection.iter_mut().enumerate() { + let x_index; /* x's index in threads */ + let m_id = x.get_message_id_raw().to_string(); + if id_table.contains_key(&m_id) { + let t = id_table[&m_id]; + /* the already existing Container should be empty, since we're + * seeing this message for the first time */ + if threads[t].message.is_some() { + /* skip duplicate message-id, but this should be handled instead */ + continue; + } + x_index = t; + /* Store this message in the Container's message slot. */ + threads[t].date = x.get_date(); + x.set_thread(t); + threads[t].message = Some(i); + } else { + /* Create a new Container object holding this message */ + x_index = threads.len(); + threads.push( + Container { + message: Some(i), + id: x_index, + parent: None, + first_child: None, + next_sibling: None, + date: x.get_date(), + indentation: 0, + show_subject: true, + }); + x.set_thread(x_index); + id_table.insert(m_id, x_index); + } + /* For each element in the message's References field: + * + * Find a Container object for the given Message-ID: + * If there's one in id_table use that; + * Otherwise, make (and index) one with a null Message + * + * Link the References field's Container 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 curr_ref = x_index; + let mut iasf = 0; + for &r in x.get_references().iter().rev() { + if iasf == 1 { + continue; + } + iasf += 1; + let parent_id = + if id_table.contains_key(r.get_raw()) { + let p = id_table[r.get_raw()]; + if !(threads[p].is_descendant(threads, &threads[curr_ref]) || + threads[curr_ref].is_descendant(threads, &threads[p])) { + threads[curr_ref].parent = Some(p); + if threads[p].first_child.is_none() { + threads[p].first_child = Some(curr_ref); + } else { + let mut child_iter = threads[p].first_child.unwrap(); + while threads[child_iter].next_sibling.is_some() { + threads[child_iter].parent = Some(p); + child_iter = threads[child_iter].next_sibling.unwrap(); + } + threads[child_iter].next_sibling = Some(curr_ref); + threads[child_iter].parent = Some(p); + } + } + p + } else { + let idx = threads.len(); + threads.push( + Container { + message: None, + id: idx, + parent: None, + first_child: Some(curr_ref), + next_sibling: None, + date: x.get_date(), + indentation: 0, + show_subject: true, + }); + if threads[curr_ref].parent.is_none() { + threads[curr_ref].parent = Some(idx); + } + id_table.insert(r.get_raw().to_string(), idx); + idx + }; + /* update thread date */ + let mut parent_iter = parent_id; + 'date: loop { + let p = &mut threads[parent_iter]; + if p.date < x.get_date() { + p.date = x.get_date(); + } + match p.parent { + Some(p) => { parent_iter = p; }, + None => { break 'date; }, + } + } + curr_ref = parent_id; + } + } +} + + +pub fn build_threads(collection: &mut Vec, sent_folder: &Option>) -> (Vec, Vec) { + /* To reconstruct thread information from the mails we need: */ + + /* a vector to hold thread members */ + let mut threads: Vec = Vec::with_capacity((collection.len() as f64 * 1.2) as usize); + /* A hash table of Message IDs */ + let mut id_table: FnvHashMap = FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default()); + + /* Add each message to id_table and threads, and link them together according to the + * References / In-Reply-To headers */ + build_collection(&mut threads, &mut id_table, collection); + let mut idx = collection.len(); + let mut tidx = threads.len(); + /* Link messages from Sent folder if they are relevant to this folder. + * This means that + * - if a message from Sent is a reply to a message in this folder, we will + * add it to the threading (but not the collection; non-threading users shouldn't care + * about this) + * - if a message in this folder is a reply to a message we sent, we will add it to the + * threading + */ + + if let Some(ref sent_box) = *sent_folder { + if sent_box.is_ok() { + let sent_mailbox = sent_box.as_ref(); + let sent_mailbox = sent_mailbox.unwrap();; + + for x in &sent_mailbox.collection { + if id_table.contains_key(x.get_message_id_raw()) || + (!x.get_in_reply_to_raw().is_empty() && id_table.contains_key(x.get_in_reply_to_raw())) { + let mut x: Envelope = (*x).clone(); + if id_table.contains_key(x.get_message_id_raw()) { + let c = id_table[x.get_message_id_raw()]; + if threads[c].message.is_some() { + /* skip duplicate message-id, but this should be handled instead */ + continue; + } + threads[c].message = Some(idx); + assert!(threads[c].has_children()); + threads[c].date = x.get_date(); + x.set_thread(c); + } else if !x.get_in_reply_to_raw().is_empty() && id_table.contains_key(x.get_in_reply_to_raw()) { + let p = id_table[x.get_in_reply_to_raw()]; + let c = if id_table.contains_key(x.get_message_id_raw()) { + id_table[x.get_message_id_raw()] + } else { + threads.push( + Container { + message: Some(idx), + id: tidx, + parent: Some(p), + first_child: None, + next_sibling: None, + date: x.get_date(), + indentation: 0, + show_subject: true, + }); + id_table.insert(x.get_message_id_raw().to_string(), tidx); + x.set_thread(tidx); + tidx += 1; + tidx - 1 + }; + threads[c].parent = Some(p); + if threads[p].is_descendant(&threads, &threads[c]) || + threads[c].is_descendant(&threads, &threads[p]) { + continue; + } + if threads[p].first_child.is_none() { + threads[p].first_child = Some(c); + } else { + let mut fc = threads[p].first_child.unwrap(); + while threads[fc].next_sibling.is_some() { + threads[fc].parent = Some(p); + fc = threads[fc].next_sibling.unwrap(); + } + threads[fc].next_sibling = Some(c); + threads[fc].parent = Some(p); + } + /* update thread date */ + let mut parent_iter = p; + 'date: loop { + let p = &mut threads[parent_iter]; + if p.date < x.get_date() { + p.date = x.get_date(); + } + match p.parent { + Some(p) => { parent_iter = p; }, + None => { break 'date; }, + } + } + } + collection.push(x); + idx += 1; + } + } + } + } + /* Walk over the elements of id_table, and gather a list of the Container objects that have + * no parents. These are the root messages of each thread */ + let mut root_set = Vec::with_capacity(collection.len()); + 'root_set: for v in id_table.values() { + if threads[*v].parent.is_none() { + if !threads[*v].has_message() && threads[*v].has_children() && !threads[threads[*v].first_child.unwrap()].has_sibling() { + /* 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(threads[*v].first_child.unwrap()); + continue 'root_set; + } + root_set.push(*v); + } + } + root_set.sort_by(|a, b| threads[*b].date.cmp(&threads[*a].date)); + + /* Group messages together by thread in a collection so we can print them together */ + let mut threaded_collection: Vec = Vec::with_capacity(collection.len()); + fn build_threaded(threads: &mut Vec, indentation: usize, threaded: &mut Vec, i: usize, root_subject_idx: usize, collection: &[Envelope]) + { + let thread = threads[i]; + if threads[root_subject_idx].has_message() { + let root_subject = collection[threads[root_subject_idx].get_message().unwrap()].get_subject(); + /* If the Container has no Message, but does have children, remove this container but + * promote its children to this level (that is, splice them in to the current child + * list.) */ + if indentation > 0 && thread.has_message() { + let subject = collection[thread.get_message().unwrap()].get_subject(); + if subject == root_subject || subject.starts_with("Re: ") && subject.ends_with(root_subject) { + threads[i].set_show_subject(false); + } + } + } + if thread.has_parent() && !threads[thread.get_parent().unwrap()].has_message() { + threads[i].parent = None; + } + let indentation = + if thread.has_message() { + threads[i].set_indentation(indentation); + if !threaded.contains(&i) { + threaded.push(i); + } + indentation + 1 + } else if indentation > 0 { + indentation + } else { + indentation + 1 + }; + if thread.has_children() { + let mut fc = thread.get_first_child().unwrap(); + loop { + build_threaded(threads, indentation, threaded, fc, i, collection); + let thread_ = threads[fc]; + if !thread_.has_sibling() { + break; + } + fc = thread_.get_next_sibling().unwrap(); + } + } + } + for i in &root_set { + build_threaded(&mut threads, 0, &mut threaded_collection, *i, *i, collection); + } + + + (threads, threaded_collection) +} diff --git a/src/ui/index.rs b/src/ui/index.rs index 1377f46c9..69cd703e3 100644 --- a/src/ui/index.rs +++ b/src/ui/index.rs @@ -18,7 +18,7 @@ * You should have received a copy of the GNU General Public License * along with meli. If not, see . */ -use mailbox::email::Mail; +use mailbox::email::Envelope; use mailbox::*; use error::MeliError; use std::error::Error; @@ -356,7 +356,7 @@ impl Index { /* draw_entry() doesn't take &mut self because borrow checker complains if it's called from * another method. */ - fn draw_entry(win: ncurses::WINDOW, mail: &Mail, i: usize, indent: usize, + fn draw_entry(win: ncurses::WINDOW, mail: &Envelope, i: usize, indent: usize, has_sibling: bool, has_parent: bool, highlight: bool, show_subject: bool, indentations: Option<&Vec>) { /* TODO: use addchstr */ @@ -428,7 +428,7 @@ impl Index { } ncurses::getmaxyx(self.win, &mut self.screen_height, &mut self.screen_width); - let x: &mut Mail = if self.threaded { + let x: &mut Envelope = if self.threaded { let i = self.mailbox.get_threaded_mail(self.cursor_idx); &mut self.mailbox.collection[i] } else { diff --git a/src/ui/pager.rs b/src/ui/pager.rs index a2187816b..9cff7a0b4 100644 --- a/src/ui/pager.rs +++ b/src/ui/pager.rs @@ -36,7 +36,7 @@ pub struct Pager { impl Pager { pub fn new(parent: ncurses::WINDOW, - entry: &mut mailbox::Mail) -> Pager { + entry: &mut mailbox::Envelope) -> Pager { let mut screen_height = 0; let mut screen_width = 0; ncurses::getmaxyx(parent, &mut screen_height, &mut screen_width); @@ -136,7 +136,7 @@ impl Pager { w - 1, ); } - fn print_entry_headers(win: ncurses::WINDOW, mail: &mut mailbox::Mail) -> i32 { + fn print_entry_headers(win: ncurses::WINDOW, mail: &mut mailbox::Envelope) -> i32 { let mut i = 0; ncurses::wattron(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_HEADERS)); ncurses::waddstr(win, "Date: "); @@ -195,7 +195,7 @@ impl Pager { } fn print_entry_content( win: ncurses::WINDOW, - mail: &mut mailbox::Mail, + mail: &mut mailbox::Envelope, height: i32) -> (ncurses::WINDOW, i32, i32) { let mut h = 0; let mut w = 0; @@ -231,7 +231,7 @@ impl Pager { } fn print_entry( win: ncurses::WINDOW, - mail: &mut mailbox::Mail) -> (ncurses::WINDOW, i32, i32) { + mail: &mut mailbox::Envelope) -> (ncurses::WINDOW, i32, i32) { let header_height = Pager::print_entry_headers(win, mail); Pager::print_entry_content(win, mail, header_height + 2) }