fix lints, rename types, add thread module
Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>embed
parent
655b5a6ea7
commit
cddea885f2
|
@ -1,6 +1,5 @@
|
||||||
#![feature(test)]
|
#![feature(test)]
|
||||||
extern crate melib;
|
extern crate melib;
|
||||||
use melib::mailbox::backends::MailBackend;
|
|
||||||
use melib::mailbox::backends::maildir::*;
|
use melib::mailbox::backends::maildir::*;
|
||||||
|
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
#![feature(test)]
|
#![feature(test)]
|
||||||
extern crate melib;
|
extern crate melib;
|
||||||
|
|
||||||
use melib::mailbox::email::Mail;
|
use melib::mailbox::email::Envelope;
|
||||||
use melib::mailbox::backends::BackendOpGenerator;
|
use melib::mailbox::backends::BackendOpGenerator;
|
||||||
use melib::mailbox::backends::maildir::MaildirOp;
|
use melib::mailbox::backends::maildir::MaildirOp;
|
||||||
|
|
||||||
|
@ -10,6 +10,6 @@ use self::test::Bencher;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn mail_parse(b: &mut Bencher) {
|
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()))})))) );
|
Box::new(MaildirOp::new("test/attachment_test".to_string()))})))) );
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,11 +41,11 @@ fn main() {
|
||||||
ncurses::touchwin(ncurses::stdscr());
|
ncurses::touchwin(ncurses::stdscr());
|
||||||
ncurses::mv(0,0);
|
ncurses::mv(0,0);
|
||||||
let mailbox = &mut account[j];
|
let mailbox = &mut account[j];
|
||||||
let mut index: Box<Window> = match mailbox.as_ref().unwrap() {
|
let mut index: Box<Window> = match *mailbox.as_ref().unwrap() {
|
||||||
&Ok(ref v) => {
|
Ok(ref v) => {
|
||||||
Box::new(Index::new(v))
|
Box::new(Index::new(v))
|
||||||
},
|
},
|
||||||
&Err(ref v) => {
|
Err(ref v) => {
|
||||||
Box::new(ErrorWindow::new((*v).clone()))
|
Box::new(ErrorWindow::new((*v).clone()))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -37,7 +37,7 @@ pub struct Account {
|
||||||
impl Account {
|
impl Account {
|
||||||
pub fn new(name: String, settings: AccountSettings) -> Self {
|
pub fn new(name: String, settings: AccountSettings) -> Self {
|
||||||
eprintln!("new acc" );
|
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());
|
let mut folders = Vec::with_capacity(settings.folders.len());
|
||||||
for _ in 0..settings.folders.len() {
|
for _ in 0..settings.folders.len() {
|
||||||
folders.push(None);
|
folders.push(None);
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use mailbox::email::Mail;
|
use mailbox::email::Envelope;
|
||||||
use error::{MeliError, Result};
|
use error::{MeliError, Result};
|
||||||
use mailbox::backends::{MailBackend, BackendOp, BackendOpGenerator};
|
use mailbox::backends::{MailBackend, BackendOp, BackendOpGenerator};
|
||||||
use mailbox::email::parser;
|
use mailbox::email::parser;
|
||||||
|
@ -81,7 +81,7 @@ impl BackendOp for MaildirOp {
|
||||||
|
|
||||||
|
|
||||||
impl MailBackend for MaildirType {
|
impl MailBackend for MaildirType {
|
||||||
fn get(&self) -> Result<Vec<Mail>> {
|
fn get(&self) -> Result<Vec<Envelope>> {
|
||||||
self.get_multicore(4)
|
self.get_multicore(4)
|
||||||
/*
|
/*
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ impl MaildirType {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn get_multicore(&self, cores: usize) -> Result<Vec<Mail>> {
|
pub fn get_multicore(&self, cores: usize) -> Result<Vec<Envelope>> {
|
||||||
MaildirType::is_valid(&self.path)?;
|
MaildirType::is_valid(&self.path)?;
|
||||||
let mut path = PathBuf::from(&self.path);
|
let mut path = PathBuf::from(&self.path);
|
||||||
path.push("cur");
|
path.push("cur");
|
||||||
|
@ -167,10 +167,10 @@ panic!("didn't parse"); },
|
||||||
};
|
};
|
||||||
for chunk in files.chunks(chunk_size) {
|
for chunk in files.chunks(chunk_size) {
|
||||||
let s = scope.spawn(move || {
|
let s = scope.spawn(move || {
|
||||||
let mut local_r:Vec<Mail> = Vec::with_capacity(chunk.len());
|
let mut local_r:Vec<Envelope> = Vec::with_capacity(chunk.len());
|
||||||
for e in chunk {
|
for e in chunk {
|
||||||
let e_copy = e.to_string();
|
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()))
|
Box::new(MaildirOp::new(e_copy.clone()))
|
||||||
} )))) {
|
} )))) {
|
||||||
local_r.push(e);
|
local_r.push(e);
|
||||||
|
|
|
@ -20,18 +20,18 @@
|
||||||
*/
|
*/
|
||||||
pub mod maildir;
|
pub mod maildir;
|
||||||
|
|
||||||
use mailbox::email::Mail;
|
use mailbox::email::Envelope;
|
||||||
use error::Result;
|
use error::Result;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
pub trait MailBackend {
|
pub trait MailBackend {
|
||||||
fn get(&self) -> Result<Vec<Mail>>;
|
fn get(&self) -> Result<Vec<Envelope>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A BackendOp manages common operations for the various mail backends. They only live for the
|
/// 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.
|
/// duration of the operation. They are generated by `BackendOpGenerator` on demand.
|
||||||
///
|
///
|
||||||
/// # Motivation
|
/// # Motivation
|
||||||
///
|
///
|
||||||
|
@ -75,8 +75,8 @@ pub trait BackendOp: ::std::fmt::Debug + ::std::marker::Send {
|
||||||
fn fetch_body(&mut self) -> Result<&[u8]>;
|
fn fetch_body(&mut self) -> Result<&[u8]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// BackendOpGenerator is a wrapper for a closure that returns a BackendOp object
|
/// `BackendOpGenerator` is a wrapper for a closure that returns a `BackendOp` object
|
||||||
/// See BackendOp for details.
|
/// See `BackendOp` for details.
|
||||||
/*
|
/*
|
||||||
* I know this sucks, but that's the best way I found that rustc deems safe.
|
* I know this sucks, but that's the best way I found that rustc deems safe.
|
||||||
* */
|
* */
|
||||||
|
|
|
@ -96,7 +96,7 @@ struct References {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
/* A very primitive mail object */
|
/* A very primitive mail object */
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Mail
|
pub struct Envelope
|
||||||
{
|
{
|
||||||
date: String,
|
date: String,
|
||||||
from: Option<String>,
|
from: Option<String>,
|
||||||
|
@ -115,7 +115,7 @@ pub struct Mail
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Mail
|
impl Envelope
|
||||||
{
|
{
|
||||||
pub fn get_date(&self) -> i64 {
|
pub fn get_date(&self) -> i64 {
|
||||||
self.timestamp
|
self.timestamp
|
||||||
|
@ -146,7 +146,7 @@ impl Mail
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
let operation = self.operation_token.generate();
|
let operation = self.operation_token.generate();
|
||||||
eprintln!("error in parsing mail\n{}", operation.description());
|
eprintln!("error in parsing mail\n{}", operation.description());
|
||||||
panic!();
|
panic!()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let mut builder = AttachmentBuilder::new(body);
|
let mut builder = AttachmentBuilder::new(body);
|
||||||
|
@ -233,7 +233,7 @@ impl Mail
|
||||||
Some(ref mut s) => {
|
Some(ref mut s) => {
|
||||||
if s.refs.contains(&new_ref) {
|
if s.refs.contains(&new_ref) {
|
||||||
if s.refs[s.refs.len() - 1] != 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);
|
s.refs.remove(index);
|
||||||
} else {
|
} else {
|
||||||
panic!();
|
panic!();
|
||||||
|
@ -282,13 +282,10 @@ impl Mail
|
||||||
self.datetime = new_val;
|
self.datetime = new_val;
|
||||||
if let Some(v) = self.datetime {
|
if let Some(v) = self.datetime {
|
||||||
self.timestamp = v.timestamp();
|
self.timestamp = v.timestamp();
|
||||||
if self.timestamp == 1485962960 {
|
|
||||||
eprintln!("it's {:?}", self);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn new(token: Box<BackendOpGenerator>) -> Self {
|
pub fn new(token: Box<BackendOpGenerator>) -> Self {
|
||||||
Mail {
|
Envelope {
|
||||||
date: "".to_string(),
|
date: "".to_string(),
|
||||||
from: None,
|
from: None,
|
||||||
to: None,
|
to: None,
|
||||||
|
@ -307,7 +304,7 @@ impl Mail
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn from(operation_token: Box<BackendOpGenerator>) -> Option<Mail> {
|
pub fn from(operation_token: Box<BackendOpGenerator>) -> Option<Envelope> {
|
||||||
let mut operation = operation_token.generate();
|
let mut operation = operation_token.generate();
|
||||||
let headers = match parser::headers(operation.fetch_headers().unwrap()).to_full_result() {
|
let headers = match parser::headers(operation.fetch_headers().unwrap()).to_full_result() {
|
||||||
Ok(v) => {
|
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 in_reply_to = None;
|
||||||
let mut datetime = None;
|
let mut datetime = None;
|
||||||
|
|
||||||
|
@ -383,20 +380,20 @@ impl Mail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for Mail {}
|
impl Eq for Envelope {}
|
||||||
impl Ord for Mail {
|
impl Ord for Envelope {
|
||||||
fn cmp(&self, other: &Mail) -> Ordering {
|
fn cmp(&self, other: &Envelope) -> Ordering {
|
||||||
self.get_datetime().cmp(&other.get_datetime())
|
self.get_datetime().cmp(&other.get_datetime())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl PartialOrd for Mail {
|
impl PartialOrd for Envelope {
|
||||||
fn partial_cmp(&self, other: &Mail) -> Option<Ordering> {
|
fn partial_cmp(&self, other: &Envelope) -> Option<Ordering> {
|
||||||
Some(self.cmp(other))
|
Some(self.cmp(other))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for Mail {
|
impl PartialEq for Envelope {
|
||||||
fn eq(&self, other: &Mail) -> bool {
|
fn eq(&self, other: &Envelope) -> bool {
|
||||||
self.get_message_id_raw() == other.get_message_id_raw()
|
self.get_message_id_raw() == other.get_message_id_raw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,9 +30,7 @@ use encoding::{Encoding, DecoderTrap};
|
||||||
fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8],u8> {
|
fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8],u8> {
|
||||||
if input.is_empty() || input.len() < 3 {
|
if input.is_empty() || input.len() < 3 {
|
||||||
IResult::Incomplete(Needed::Size(1))
|
IResult::Incomplete(Needed::Size(1))
|
||||||
} else if input[0] != b'=' {
|
} else if input[0] == b'=' && is_hex_digit(input[1]) && is_hex_digit(input[2]) {
|
||||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
||||||
} else if is_hex_digit(input[1]) && is_hex_digit(input[2]) {
|
|
||||||
let a = if input[1] < b':' {
|
let a = if input[1] < b':' {
|
||||||
input[1] - 48
|
input[1] - 48
|
||||||
} else if input[1] < b'[' {
|
} else if input[1] < b'[' {
|
||||||
|
@ -47,7 +45,6 @@ fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8],u8> {
|
||||||
} else {
|
} else {
|
||||||
input[2] - 87
|
input[2] - 87
|
||||||
};
|
};
|
||||||
|
|
||||||
IResult::Done(&input[3..], a*16+b)
|
IResult::Done(&input[3..], a*16+b)
|
||||||
} else {
|
} else {
|
||||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
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'<' {
|
} else if input_length == 2 || input[0] != b'<' {
|
||||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||||
} else {
|
} else {
|
||||||
for i in 1..input_length {
|
for (i, &x) in input.iter().take(input_length).enumerate().skip(1) {
|
||||||
if input[i] == b'>' {
|
if x == b'>' {
|
||||||
return IResult::Done(&input[i+1..], from_utf8(&input[0..i+1]).unwrap());
|
return IResult::Done(&input[i+1..], from_utf8(&input[0..i+1]).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,386 +28,29 @@ use mailbox::backends::maildir;
|
||||||
use error::Result;
|
use error::Result;
|
||||||
pub mod accounts;
|
pub mod accounts;
|
||||||
pub use mailbox::accounts::Account;
|
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::option::Option;
|
||||||
use std;
|
|
||||||
|
|
||||||
type UnixTimestamp = i64;
|
|
||||||
|
|
||||||
/*a Mailbox represents a folder of mail. Currently only Maildir is supported.*/
|
/*a Mailbox represents a folder of mail. Currently only Maildir is supported.*/
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug,Clone)]
|
||||||
pub struct Mailbox{
|
pub struct Mailbox{
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub collection: Vec<Mail>,
|
pub collection: Vec<Envelope>,
|
||||||
pub threaded_collection: Vec<usize>,
|
pub threaded_collection: Vec<usize>,
|
||||||
threads: Vec<Thread>,
|
threads: Vec<Container>,
|
||||||
length: usize,
|
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<Thread>).
|
|
||||||
*
|
|
||||||
* 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<usize>,
|
|
||||||
parent: Option<usize>,
|
|
||||||
first_child: Option<usize>,
|
|
||||||
next_sibling: Option<usize>,
|
|
||||||
date: UnixTimestamp,
|
|
||||||
indentation: usize,
|
|
||||||
show_subject: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Thread {
|
|
||||||
pub fn get_message(&self) -> Option<usize> {
|
|
||||||
self.message
|
|
||||||
}
|
|
||||||
pub fn get_parent(&self) -> Option<usize> {
|
|
||||||
self.parent
|
|
||||||
}
|
|
||||||
pub fn has_parent(&self) -> bool {
|
|
||||||
self.parent.is_some()
|
|
||||||
}
|
|
||||||
pub fn get_first_child(&self) -> Option<usize> {
|
|
||||||
self.first_child
|
|
||||||
}
|
|
||||||
pub fn get_next_sibling(&self) -> Option<usize> {
|
|
||||||
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<Thread>, id_table: &mut FnvHashMap<std::string::String, usize>, 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
|
impl Mailbox
|
||||||
{
|
{
|
||||||
pub fn new(path: &str, sent_folder: &Option<Result<Mailbox>>) -> Result<Mailbox> {
|
pub fn new(path: &str, sent_folder: &Option<Result<Mailbox>>) -> Result<Mailbox> {
|
||||||
let mut collection: Vec<Mail> = maildir::MaildirType::new(path).get()?;
|
let mut collection: Vec<Envelope> = maildir::MaildirType::new(path).get()?;
|
||||||
/* To reconstruct thread information from the mails we need: */
|
|
||||||
|
|
||||||
/* a vector to hold thread members */
|
|
||||||
let mut threads: Vec<Thread> = Vec::with_capacity((collection.len() as f64 * 1.2) as usize);
|
|
||||||
/* A hash table of Message IDs */
|
|
||||||
let mut id_table: FnvHashMap<std::string::String, usize> = FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default());
|
|
||||||
|
|
||||||
collection.sort_by(|a, b| a.get_date().cmp(&b.get_date()));
|
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
|
let (threads, threaded_collection) = build_threads(&mut collection, sent_folder);
|
||||||
* 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<usize> = Vec::with_capacity(collection.len());
|
|
||||||
fn build_threaded(threads: &mut Vec<Thread>, indentation: usize, threaded: &mut Vec<usize>, 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 length = collection.len();
|
let length = collection.len();
|
||||||
|
|
||||||
|
@ -426,12 +69,12 @@ impl Mailbox
|
||||||
let thread = self.threads[self.threaded_collection[i]];
|
let thread = self.threads[self.threaded_collection[i]];
|
||||||
thread.get_message().unwrap()
|
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 x = &mut self.collection.as_mut_slice()[i];
|
||||||
let thread = self.threads[x.get_thread()];
|
let thread = self.threads[x.get_thread()];
|
||||||
(x, thread)
|
(x, thread)
|
||||||
}
|
}
|
||||||
pub fn get_thread(&self, i: usize) -> &Thread {
|
pub fn get_thread(&self, i: usize) -> &Container {
|
||||||
&self.threads[i]
|
&self.threads[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* 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<Container>).
|
||||||
|
*
|
||||||
|
* 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<usize>,
|
||||||
|
parent: Option<usize>,
|
||||||
|
first_child: Option<usize>,
|
||||||
|
next_sibling: Option<usize>,
|
||||||
|
date: UnixTimestamp,
|
||||||
|
indentation: usize,
|
||||||
|
show_subject: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Container {
|
||||||
|
pub fn get_message(&self) -> Option<usize> {
|
||||||
|
self.message
|
||||||
|
}
|
||||||
|
pub fn get_parent(&self) -> Option<usize> {
|
||||||
|
self.parent
|
||||||
|
}
|
||||||
|
pub fn has_parent(&self) -> bool {
|
||||||
|
self.parent.is_some()
|
||||||
|
}
|
||||||
|
pub fn get_first_child(&self) -> Option<usize> {
|
||||||
|
self.first_child
|
||||||
|
}
|
||||||
|
pub fn get_next_sibling(&self) -> Option<usize> {
|
||||||
|
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<Container>, id_table: &mut FnvHashMap<std::string::String, usize>, 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<Envelope>, sent_folder: &Option<Result<Mailbox>>) -> (Vec<Container>, Vec<usize>) {
|
||||||
|
/* To reconstruct thread information from the mails we need: */
|
||||||
|
|
||||||
|
/* a vector to hold thread members */
|
||||||
|
let mut threads: Vec<Container> = Vec::with_capacity((collection.len() as f64 * 1.2) as usize);
|
||||||
|
/* A hash table of Message IDs */
|
||||||
|
let mut id_table: FnvHashMap<std::string::String, usize> = 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<usize> = Vec::with_capacity(collection.len());
|
||||||
|
fn build_threaded(threads: &mut Vec<Container>, indentation: usize, threaded: &mut Vec<usize>, 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)
|
||||||
|
}
|
|
@ -18,7 +18,7 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
use mailbox::email::Mail;
|
use mailbox::email::Envelope;
|
||||||
use mailbox::*;
|
use mailbox::*;
|
||||||
use error::MeliError;
|
use error::MeliError;
|
||||||
use std::error::Error;
|
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
|
/* draw_entry() doesn't take &mut self because borrow checker complains if it's called from
|
||||||
* another method. */
|
* 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,
|
has_sibling: bool, has_parent: bool, highlight: bool,
|
||||||
show_subject: bool, indentations: Option<&Vec<bool>>) {
|
show_subject: bool, indentations: Option<&Vec<bool>>) {
|
||||||
/* TODO: use addchstr */
|
/* TODO: use addchstr */
|
||||||
|
@ -428,7 +428,7 @@ impl Index {
|
||||||
}
|
}
|
||||||
ncurses::getmaxyx(self.win,
|
ncurses::getmaxyx(self.win,
|
||||||
&mut self.screen_height, &mut self.screen_width);
|
&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);
|
let i = self.mailbox.get_threaded_mail(self.cursor_idx);
|
||||||
&mut self.mailbox.collection[i]
|
&mut self.mailbox.collection[i]
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -36,7 +36,7 @@ pub struct Pager {
|
||||||
|
|
||||||
impl Pager {
|
impl Pager {
|
||||||
pub fn new(parent: ncurses::WINDOW,
|
pub fn new(parent: ncurses::WINDOW,
|
||||||
entry: &mut mailbox::Mail) -> Pager {
|
entry: &mut mailbox::Envelope) -> Pager {
|
||||||
let mut screen_height = 0;
|
let mut screen_height = 0;
|
||||||
let mut screen_width = 0;
|
let mut screen_width = 0;
|
||||||
ncurses::getmaxyx(parent, &mut screen_height, &mut screen_width);
|
ncurses::getmaxyx(parent, &mut screen_height, &mut screen_width);
|
||||||
|
@ -136,7 +136,7 @@ impl Pager {
|
||||||
w - 1,
|
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;
|
let mut i = 0;
|
||||||
ncurses::wattron(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_HEADERS));
|
ncurses::wattron(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_HEADERS));
|
||||||
ncurses::waddstr(win, "Date: ");
|
ncurses::waddstr(win, "Date: ");
|
||||||
|
@ -195,7 +195,7 @@ impl Pager {
|
||||||
}
|
}
|
||||||
fn print_entry_content(
|
fn print_entry_content(
|
||||||
win: ncurses::WINDOW,
|
win: ncurses::WINDOW,
|
||||||
mail: &mut mailbox::Mail,
|
mail: &mut mailbox::Envelope,
|
||||||
height: i32) -> (ncurses::WINDOW, i32, i32) {
|
height: i32) -> (ncurses::WINDOW, i32, i32) {
|
||||||
let mut h = 0;
|
let mut h = 0;
|
||||||
let mut w = 0;
|
let mut w = 0;
|
||||||
|
@ -231,7 +231,7 @@ impl Pager {
|
||||||
}
|
}
|
||||||
fn print_entry(
|
fn print_entry(
|
||||||
win: ncurses::WINDOW,
|
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);
|
let header_height = Pager::print_entry_headers(win, mail);
|
||||||
Pager::print_entry_content(win, mail, header_height + 2)
|
Pager::print_entry_content(win, mail, header_height + 2)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue