Browse Source

threads

embed
Manos Pitsidianakis 6 years ago
parent
commit
9946fbcbe0
Signed by untrusted user: epilys GPG Key ID: 73627C2F690DF710
  1. 12
      Cargo.toml
  2. 136
      src/conf/mod.rs
  3. 37
      src/error.rs
  4. 371
      src/mailbox/email.rs
  5. 92
      src/mailbox/maildir.rs
  6. 494
      src/mailbox/mod.rs
  7. 176
      src/mailbox/parser.rs
  8. 110
      src/main.rs
  9. 348
      src/ui/index.rs
  10. 17
      src/ui/mod.rs
  11. 115
      src/ui/pager.rs

12
Cargo.toml

@ -4,11 +4,19 @@ version = "0.1.0"
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
[dependencies]
mailparse = "0.5.1"
maildir = "0.1.1"
chrono = "0.4"
xdg = "2.1.0"
config = "0.6"
serde_derive = "^1.0.8"
serde = "^1.0.8"
nom = "3.2.0"
memmap = "*"
base64 = "*"
[dependencies.ncurses]
features = ["wide"]
optional = false
version = "5.86.0"
[profile.release]
lto = true

136
src/conf/mod.rs

@ -0,0 +1,136 @@
/*
* meli - configuration 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/>.
*/
extern crate xdg;
extern crate config;
use std::collections::HashMap;
use std::io;
use std::fs;
use std::path::{PathBuf, Path};
#[derive(Debug)]
enum MailFormat {
Maildir
}
impl MailFormat {
pub fn from_str(x: &str) -> MailFormat {
match x {
"maildir" | "Maildir" |
"MailDir" => { MailFormat::Maildir },
_ => { panic!("Unrecognizable mail format");}
}
}
}
#[derive(Debug, Deserialize)]
struct FileAccount {
folders: String,
format: String,
sent_folder: String,
threaded : bool,
}
#[derive(Debug, Deserialize, Default)]
struct FileSettings {
accounts: HashMap<String, FileAccount>,
}
#[derive(Debug)]
pub struct Account {
pub folders: Vec<String>,
format: MailFormat,
pub sent_folder: String,
threaded : bool,
}
#[derive(Debug)]
pub struct Settings {
pub accounts: HashMap<String, Account>,
}
use self::config::{Config, File, FileFormat};
impl FileSettings {
pub fn new() -> FileSettings {
let xdg_dirs = xdg::BaseDirectories::with_prefix("meli").unwrap();
let config_path = xdg_dirs.place_config_file("config")
.expect("cannot create configuration directory");
//let setts = Config::default().merge(File::new(config_path.to_str().unwrap_or_default(), config::FileFormat::Toml)).unwrap();
let mut s = Config::new();
let s = s.merge(File::new(config_path.to_str().unwrap(), FileFormat::Toml));
match s.is_ok() { //.unwrap_or(Settings { });
true => { s.unwrap().deserialize().unwrap() },
false => {
eprintln!("{:?}",s.err().unwrap());
let mut buf = String::new();
io::stdin().read_line(&mut buf).expect("Failed to read line");
FileSettings { ..Default::default() } },
}
}
}
impl Settings {
pub fn new() -> Settings {
let fs = FileSettings::new();
let mut s: HashMap<String, Account> = HashMap::new();
for (id, x) in fs.accounts {
let mut folders = Vec::new();
fn recurse_folders<P: AsRef<Path>>(folders: &mut Vec<String>, p: P) {
for mut f in fs::read_dir(p).unwrap() {
for f in f.iter_mut().next() {
{
let path = f.path();
if path.ends_with("cur") || path.ends_with("new") ||
path.ends_with("tmp") {
continue;
}
if path.is_dir() {
folders.push(path.to_str().unwrap().to_string());
recurse_folders(folders, path);
}
}
}
}
};
let path = PathBuf::from(&x.folders);
if path.is_dir() {
folders.push(path.to_str().unwrap().to_string());
}
recurse_folders(&mut folders, &x.folders);
s.insert(id.clone(), Account {
folders: folders,
format: MailFormat::from_str(&x.format),
sent_folder: x.sent_folder.clone(),
threaded: x.threaded,
});
}
Settings { accounts: s }
}
}

37
src/error.rs

@ -0,0 +1,37 @@
use std::error::Error;
use std::fmt;
use std::result;
use std::io;
pub type Result<T> = result::Result<T, MeliError>;
#[derive(Debug)]
pub struct MeliError {
details: String
}
impl MeliError {
pub fn new<M>(msg: M) -> MeliError where M: Into<String> {
MeliError{details: msg.into()}
}
}
impl fmt::Display for MeliError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,"{}",self.details)
}
}
impl Error for MeliError {
fn description(&self) -> &str {
&self.details
}
}
impl From<io::Error> for MeliError {
#[inline]
fn from(kind: io::Error) -> MeliError {
MeliError::new(kind.description())
}
}

371
src/mailbox/email.rs

@ -0,0 +1,371 @@
use std::string::String;
use memmap::{Mmap, Protection};
use std;
use std::cmp::Ordering;
use std::fmt;
use std::option::Option;
use std::io::prelude::*;
use mailbox::parser::*;
use chrono;
use chrono::TimeZone;
/* Helper struct to return slices from a struct on demand */
#[derive(Clone,Debug)]
struct StrBuilder {
offset: usize,
length: usize,
}
pub trait StrBuild {
fn new(&str, &str) -> Self;
fn get_raw(&self) -> &str;
fn get_val(&self) -> &str;
}
#[derive(Clone)]
pub struct MessageID (String, StrBuilder);
impl StrBuild for MessageID {
fn new(string: &str, slice: &str) -> Self {
let offset = string.find(slice).unwrap();
MessageID (string.to_string(), StrBuilder {
offset: offset,
length: slice.len() + 1,
})
}
fn get_raw(&self) -> &str {
let offset = self.1.offset;
let length = self.1.length;
&self.0[offset..length]
}
fn get_val(&self) -> &str {
&self.0
}
}
#[test]
fn test_strbuilder() {
let m_id = "<20170825132332.6734-1-el13635@mail.ntua.gr>";
let (_, slice) = message_id(m_id.as_bytes()).unwrap();
assert_eq!(MessageID::new(m_id, slice), MessageID (m_id.to_string(), StrBuilder{offset: 1, length: 43}));
}
impl PartialEq for MessageID {
fn eq(&self, other: &MessageID) -> bool {
self.get_raw() == other.get_raw()
}
}
impl fmt::Debug for MessageID {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.get_raw())
}
}
#[derive(Clone,Debug)]
struct References {
raw: String,
refs: Vec<MessageID>,
}
/* A very primitive mail object */
#[derive(Clone,Debug)]
pub struct Mail {
date: String,
from: Option<String>,
to: Option<String>,
body: String,
subject: Option<String>,
message_id: Option<MessageID>,
in_reply_to: Option<MessageID>,
references: Option<References>,
datetime: Option<chrono::DateTime<chrono::FixedOffset>>,
thread: usize,
}
impl Mail {
pub fn get_date(&self) -> i64 {
match self.datetime {
Some(v) => v.timestamp(),
None => 0,
}
}
pub fn get_datetime(&self) -> chrono::DateTime<chrono::FixedOffset> {
self.datetime.unwrap_or(chrono::FixedOffset::west(0).ymd(1970, 1, 1).and_hms(0, 0, 0))
}
pub fn get_date_as_str(&self) -> &str {
&self.date
}
pub fn get_from(&self) -> &str {
match self.from {
Some(ref s) => s,
None => "",
}
}
pub fn get_to(&self) -> &str {
match self.to {
Some(ref s) => s,
None => "",
}
}
pub fn get_body(&self) -> &str {
&self.body
}
pub fn get_subject(&self) -> &str {
match self.subject {
Some(ref s) => s,
_ => ""
}
}
pub fn get_in_reply_to(&self) -> &str {
match self.in_reply_to {
Some(ref s) => s.get_val(),
_ => ""
}
}
pub fn get_in_reply_to_raw(&self) -> &str {
match self.in_reply_to {
Some(ref s) => s.get_raw(),
_ => ""
}
}
pub fn get_message_id(&self) -> &str {
match self.message_id {
Some(ref s) => s.get_val(),
_ => "",
}
}
pub fn get_message_id_raw(&self) -> &str {
match self.message_id {
Some(ref s) => s.get_raw(),
_ => "",
}
}
fn set_date(&mut self, new_val: String) -> () {
self.date = new_val;
}
fn set_from(&mut self, new_val: String) -> () {
self.from = Some(new_val);
}
fn set_to(&mut self, new_val: String) -> () {
self.to = Some(new_val);
}
fn set_in_reply_to(&mut self, new_val: &str) -> () {
let slice = match message_id(new_val.as_bytes()).to_full_result() {
Ok(v) => { v },
Err(v) => { eprintln!("{} {:?}",new_val, v);
self.in_reply_to = None;
return; }
};
self.in_reply_to = Some(MessageID::new(new_val, slice));
}
fn set_subject(&mut self, new_val: String) -> () {
self.subject = Some(new_val);
}
fn set_message_id(&mut self, new_val: &str) -> () {
let slice = match message_id(new_val.as_bytes()).to_full_result() {
Ok(v) => { v },
Err(v) => { eprintln!("{} {:?}",new_val, v);
self.message_id = None;
return; }
};
self.message_id = Some(MessageID::new(new_val, slice));
}
fn push_references(&mut self, new_val: &str) -> () {
let slice = match message_id(new_val.as_bytes()).to_full_result() {
Ok(v) => { v },
Err(v) => { eprintln!("{} {:?}",new_val, v);
return; }
};
let new_ref = MessageID::new(new_val, slice);
match self.references {
Some(ref mut s) => {
if s.refs.contains(&new_ref) {
return;
}
s.refs.push(new_ref);
},
None => {
let mut v = Vec::new();
v.push(new_ref);
self.references = Some(References { raw: "".to_string(), refs: v, });
}
}
}
fn set_references(&mut self, new_val: String) -> () {
match self.references {
Some(ref mut s) => {
s.raw = new_val;
},
None => {
let v = Vec::new();
self.references = Some(References { raw: new_val, refs: v, });
}
}
}
pub fn get_references<'a>(&'a self) -> Vec<&'a MessageID> {
match self.references {
Some(ref s) => s.refs.iter().fold(Vec::with_capacity(s.refs.len()), |mut acc, x| { acc.push(&x); acc }),
None => Vec::new(),
}
}
pub fn set_body(&mut self, new_val: String) -> () {
self.body = new_val;
}
pub fn get_thread(&self) -> usize {
self.thread
}
pub fn set_thread(&mut self, new_val: usize) -> () {
self.thread = new_val;
}
pub fn set_datetime(&mut self, new_val: Option<chrono::DateTime<chrono::FixedOffset>>) -> () {
self.datetime = new_val;
}
pub fn new() -> Self {
Mail {
date: "".to_string(),
from: None,
to: None,
body: "".to_string(),
subject: None,
message_id: None,
in_reply_to: None,
references: None,
datetime: None,
thread: 0,
}
}
pub fn from(path: std::string::String) -> Option<Self> {
let f = Mmap::open_path(path.clone(), Protection::Read).unwrap();
let file = unsafe { f.as_slice() };
let (headers, body) = match mail(file).to_full_result() {
Ok(v) => v,
Err(_) => {
eprintln!("error in parsing");
let path = std::path::PathBuf::from(&path);
let mut czc = std::fs::File::open(path).unwrap();
let mut buffer = Vec::new();
let _ = czc.read_to_end(&mut buffer);
eprintln!("\n-------------------------------");
eprintln!("{}\n", std::string::String::from_utf8_lossy(&buffer));
eprintln!("-------------------------------\n");
return None; }
};
let mut mail = Mail::new();
let mut in_reply_to = None;
let mut datetime = None;
for (name, value) in headers {
if value.len() == 1 && value[0].is_empty() {
continue;
}
match name {
"To" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
let parse_result = subject(value.as_bytes());
let value = match parse_result.is_done() {
true => {
parse_result.to_full_result().unwrap()
},
false => {
"".to_string()
},
};
mail.set_to(value);
},
"From" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
let parse_result = subject(value.as_bytes());
let value = match parse_result.is_done() {
true => {
parse_result.to_full_result().unwrap()
},
false => {
"".to_string()
},
};
mail.set_from(value);
},
"Subject" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(" "); acc.push_str(x); acc });
let parse_result = subject(value.trim().as_bytes());
let value = match parse_result.is_done() {
true => {
parse_result.to_full_result().unwrap()
},
false => {
"".to_string()
},
};
mail.set_subject(value);
},
"Message-ID" | "Message-Id" | "Message-id" | "message-id" => {
mail.set_message_id(&value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc }));
},
"References" => {
let folded_value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
{
let parse_result = references(&folded_value.as_bytes());
match parse_result.is_done() {
true => {
for v in parse_result.to_full_result().unwrap() {
mail.push_references(v);
}
},
_ => {}
}
}
mail.set_references(folded_value);
},
"In-Reply-To" | "In-reply-to" | "In-Reply-to" | "in-reply-to" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
mail.set_in_reply_to(&value);
in_reply_to = Some(value); },
"Date" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
mail.set_date(value.clone());
datetime = Some(value);
},
_ => {},
}
};
match in_reply_to {
Some(ref mut x) => {
mail.push_references(x);
},
None => {},
}
mail.set_body(String::from_utf8_lossy(body).into_owned());
if datetime.is_some() {
mail.set_datetime(date(&datetime.unwrap()));
}
Some(mail)
}
}
impl Eq for Mail {}
impl Ord for Mail {
fn cmp(&self, other: &Mail) -> Ordering {
self.get_datetime().cmp(&other.get_datetime())
}
}
impl PartialOrd for Mail {
fn partial_cmp(&self, other: &Mail) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Mail {
fn eq(&self, other: &Mail) -> bool {
self.get_message_id_raw() == other.get_message_id_raw()
}
}

92
src/mailbox/maildir.rs

@ -0,0 +1,92 @@
/*
* meli - mailbox 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/>.
*/
//use std::io::prelude::*;
//use std::fs::File;
use std::path::PathBuf;
use super::email::Mail;
use error::{MeliError, Result};
pub trait MailBackend {
fn get(&self) -> Result<Vec<Mail>>;
}
pub struct MaildirType {
path: String,
}
impl MailBackend for MaildirType {
fn get(&self) -> Result<Vec<Mail>> {
MaildirType::is_valid(&self.path)?;
let mut path = PathBuf::from(&self.path);
path.push("cur");
let iter = path.read_dir()?;
let count = path.read_dir()?.count();
let mut r = Vec::with_capacity(count);
for e in iter {
//eprintln!("{:?}", e);
let e = e.and_then(|x| {
let path = x.path();
Ok(path.to_str().unwrap().to_string())
})?;
match Mail::from(e) {
Some(e) => {r.push(e);},
None => {}
}
/*
f.read_to_end(&mut buffer)?;
eprintln!("{:?}", String::from_utf8(buffer.clone()).unwrap());
let m = match Email::parse(&buffer) {
Ok((v, rest)) => match rest.len() {
0 => v,
_ =>
{ eprintln!("{:?}", String::from_utf8(rest.to_vec()).unwrap());
panic!("didn't parse"); },
},
Err(v) => panic!(v),
};
r.push(m);
*/
}
Ok(r)
}
}
impl MaildirType {
pub fn new(path: &str) -> Self {
MaildirType {
path: path.to_string()
}
}
fn is_valid(path: &str) -> Result<()> {
let mut p = PathBuf::from(path);
for d in ["cur", "new", "tmp"].iter() {
p.push(d);
if !p.is_dir() {
return Err(MeliError::new(format!("{} is not a valid maildir folder", path)));
}
p.pop();
}
Ok(())
}
}

494
src/mailbox/mod.rs

@ -2,7 +2,7 @@
* meli - mailbox module.
*
* Copyright 2017 Manos Pitsidianakis
*
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
@ -19,27 +19,27 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
extern crate maildir;
extern crate mailparse;
use self::mailparse::*;
use std::cmp::Ordering;
//use std::cmp::Ordering;
//use std::fmt;
use std::option::Option;
use std::collections::HashMap;
use std;
mod maildir;
pub mod email;
mod parser;
pub use self::email::*;
use mailbox::maildir::MailBackend;
use error::Result;
type UnixTimestamp = i64;
pub struct Mail {
entry: maildir::MailEntry,
subject: std::string::String,
pub message_id: std::string::String,
pub references: Vec<std::string::String>,
date: UnixTimestamp,
thread: usize,
}
/*a Mailbox represents a folder of mail. Currently only Maildir is supported.*/
#[derive(Debug)]
pub struct Mailbox{
pub path: String,
pub collection: Box<Vec<Mail>>,
pub threaded_collection: Vec<usize>,
threads: Vec<Thread>,
@ -55,15 +55,16 @@ pub struct Mailbox{
* 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)]
#[derive(Clone, Copy, Debug)]
pub struct Thread {
id: usize,
message: Option<usize>,
message: Option<usize>,
parent: Option<usize>,
first_child: Option<usize>,
next_sibling: Option<usize>,
date: UnixTimestamp,
indentation: usize,
show_subject: bool,
}
impl Thread {
@ -73,6 +74,9 @@ impl Thread {
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
}
@ -88,237 +92,301 @@ impl Thread {
pub fn has_message(&self) -> bool {
self.message.is_some()
}
pub fn set_indentation(&mut self, i: usize) {
fn set_indentation(&mut self, i: usize) {
self.indentation = i;
}
pub fn get_indentation(&self) -> usize {
self.indentation
}
pub fn is_descendant(&self, threads: &Vec<Thread>, other: Thread) -> bool {
fn is_descendant(&self, threads: &Vec<Thread>, other: &Thread) -> bool {
if self == other {
return true;
}
match self.first_child {
Some(v) => {
if threads[v] == other {
return true;
}
if threads[v].clone().is_descendant(&threads, other) {
Some(v) => {
if threads[v].is_descendant(threads, other) {
return true;
}
},
None => {}
}
};
match self.next_sibling {
Some(v) => {
if threads[v] == other {
return true;
}
if threads[v].clone().is_descendant(threads, other) {
Some(v) => {
if threads[v].is_descendant(threads, other) {
return true;
}
},
None => {}
}
};
return 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 {
self.id == other.id
match (self.message, other.message) {
(Some(s), Some(o)) => {
s == o
},
_ => {
self.id == other.id
}
}
}
}
impl Mailbox {
pub fn new(path: &str) -> Mailbox {
let maildir = maildir::Maildir::from(path);
let iter = maildir.list_cur();
let mut collection: Box<Vec<Mail>> = Box::new(Vec::new());
let mut threads: Vec<Thread> = Vec::new();
let mut id_table: HashMap<std::string::String, usize> = HashMap::new();
let mut idx = 0;
for x in iter {
let mut e = x.unwrap();
let d = e.headers().unwrap().get_first_value("Date").unwrap();
let m_id = match e.headers().unwrap().get_first_value("Message-Id") {
Ok(v) => {
match v {
Some(v) => {
v
},
None => idx.to_string()
}
}
Err(_) => {
idx.to_string()
}
};
let mut references: Vec<std::string::String> = Vec::new();
match e.headers().unwrap().get_first_value("References") {
Ok(v) => {
match v {
Some(v) => {
references.append(&mut v.split_whitespace().map(|x| x.to_string()).collect());
}
None => {}
}
}
Err(_) => {
}
};
match e.headers().unwrap().get_first_value("In-Reply-To:") {
Ok(v) => {
match v {
Some(v) => {
references.push(v);
}
None => {}
}
}
Err(_) => {
}
};
let subject = match e.headers().unwrap().get_first_value("Subject") {
Ok(v) => {
match v
{
Some(v) => v.clone(),
None => std::string::String::from("")
}
},
Err(x) => panic!(x)
};
collection.push(
Mail {
entry: e,
subject: subject,
references: references,
message_id: m_id,
date: dateparse(&d.unwrap()).unwrap(),
thread: 0,
});
idx += 1;
}
idx = 0;
collection.sort_by(|a, b| b.date.cmp(&a.date));
for (i, x) in collection.iter_mut().enumerate() {
let x_index;
let m_id = x.message_id.clone();
if id_table.contains_key(&m_id) {
let c = id_table.get(&m_id).unwrap();
/* the already existing Thread should be empty, since we're
* seeing this message for the first time
* Store this message in the Thread's message slot. */
threads[*c].message = Some(i);
threads[*c].date = x.date;
x.thread = *c;
x_index = *c;
} else {
/* Create a new Thread object holding this message */
threads.push(
Thread {
message: Some(i),
id: idx,
parent: None,
first_child: None,
next_sibling: None,
date: x.date,
indentation: 0,
});
x_index = idx;
x.thread = idx;
id_table.insert(m_id, x_index);
idx += 1;
}
/* 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.
*/
if x.references.len() == 0 {
fn build_collection(threads: &mut Vec<Thread>, id_table: &mut HashMap<std::string::String, usize>, collection: &mut Box<Vec<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.get(&m_id).unwrap());
/* 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;
}
let r_to = x.references[x.references.len() - 1].clone();
let parent_id =
if id_table.contains_key(&r_to) {
let p = id_table.get(&r_to).unwrap();
if threads[*p].is_descendant(&threads, threads[x_index]) ||
threads[x_index].is_descendant(&threads, threads[*p]) {
continue;
}
if threads[*p].first_child.is_none() {
threads[*p].first_child = Some(x_index);
} else {
let mut fc = threads[*p].first_child.unwrap();
while threads[fc].next_sibling.is_some() {
fc = threads[fc].next_sibling.unwrap();
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;
'ref_loop: for &r in x.get_references().iter().rev() {
let parent_id =
if id_table.contains_key(r.get_raw()) {
let p = *(id_table.get(r.get_raw()).unwrap());
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);
}
threads[fc].next_sibling = Some(x_index);
threads[fc].parent = Some(*p);
}
*p
p
} else {
let idx = threads.len();
threads.push(
Thread {
message: None,
id: idx,
parent: None,
first_child: Some(x_index),
first_child: Some(curr_ref),
next_sibling: None,
date: x.date,
date: x.get_date(),
indentation: 0,
show_subject: true,
});
id_table.insert(r_to.clone(), idx);
idx += 1;
idx-1
id_table.insert(r.get_raw().to_string(), idx);
idx
};
/* update thread date */
let mut parent_iter = parent_id;
loop {
'date: loop {
let mut p = &mut threads[parent_iter];
p.date = x.date;
if p.parent.is_none() {
break;
} else {
parent_iter = p.get_parent().unwrap();
if p.date < x.get_date() {
p.date = x.get_date();
}
match p.parent {
Some(p) => { parent_iter = p; },
None => { break 'date; },
}
}
if threads[curr_ref].parent.is_none() {
threads[curr_ref].parent = Some(parent_id);
}
curr_ref = parent_id;
}
}
}
impl Mailbox {
pub fn new(path: &str, sent_folder: Option<&str>) -> Result<Mailbox> {
let mut collection: Box<Vec<Mail>> = Box::new(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: HashMap<std::string::String, usize> = HashMap::with_capacity(collection.len());
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 sent_folder.is_some() {
for mut x in maildir::MaildirType::new(sent_folder.unwrap()).get().unwrap() {
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())) {
collection.push(x.clone());
idx += 1;
}
if id_table.contains_key(x.get_message_id_raw()) {
let c = *(id_table.get(x.get_message_id_raw()).unwrap());
if threads[c].message.is_some() {
/* skip duplicate message-id, but this should be handled instead */
continue;
}
threads[c].message = Some(idx-1);
assert!(threads[c].has_children());
threads[c].date = x.get_date();
x.set_thread(c);
}
if !x.get_in_reply_to_raw().is_empty() && id_table.contains_key(x.get_in_reply_to_raw()) {
let p = *(id_table.get(x.get_in_reply_to_raw()).unwrap());
let c = if !id_table.contains_key(x.get_message_id_raw()) {
threads.push(
Thread {
message: Some(idx-1),
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
} else {
*(id_table.get(x.get_message_id_raw()).unwrap())
};
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 mut 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; },
}
}
}
}
threads[x_index].parent = Some(parent_id);
}
/* Walk over the elements of id_table, and gather a list of the Thread objects that have
* no parents. */
let mut root_set = Vec::new();
for (_,v) in id_table.iter() {
* 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.iter() {
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));
let mut threaded_collection: Vec<usize> = Vec::new();
fn build_threaded(threads: &mut Vec<Thread>, indentation: usize, threaded: &mut Vec<usize>, index: usize) {
let thread = threads[index];
if thread.has_message() {
threads[index].set_indentation(indentation);
if !threaded.contains(&index) {
threaded.push(index);
/* 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: &Vec<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 + 1, threaded, fc);
build_threaded(threads, indentation, threaded, fc, i, collection);
let thread_ = threads[fc];
if !thread_.has_sibling() {
break;
@ -328,16 +396,18 @@ impl Mailbox {
}
}
for i in &root_set {
build_threaded(&mut threads, 0, &mut threaded_collection, *i);
build_threaded(&mut threads, 0, &mut threaded_collection, *i, *i, &collection);
}
let length = collection.len();
Mailbox {
Ok(Mailbox {
path: path.to_string(),
collection: collection,
threads: threads,
length: length,
threaded_collection: threaded_collection,
}
})
}
pub fn get_length(&self) -> usize {
self.length
@ -349,43 +419,11 @@ impl Mailbox {
pub fn get_mail_and_thread(&mut self, i: usize) -> (&mut Mail, Thread) {
let ref mut x = self.collection.as_mut_slice()[i];
let thread = self.threads[x.get_thread()].clone();
let thread = self.threads[x.get_thread()];
(x, thread)
}
pub fn get_thread(&self, i: usize) -> Thread {
self.threads[i].clone()
}
}
impl Mail {
pub fn get_entry(&mut self) -> &mut maildir::MailEntry {
&mut self.entry
}
pub fn get_date(&self) -> i64 {
self.date
}
pub fn get_subject(&self) -> &str {
&self.subject
}
pub fn get_thread(&self) -> usize {
self.thread
}
}
impl Eq for Mail {}
impl Ord for Mail {
fn cmp(&self, other: &Mail) -> Ordering {
self.date.cmp(&other.date)
}
}
impl PartialOrd for Mail {
fn partial_cmp(&self, other: &Mail) -> Option<Ordering> {
Some(self.cmp(other))
pub fn get_thread(&self, i: usize) -> &Thread {
&self.threads[i]
}
}
impl PartialEq for Mail {
fn eq(&self, other: &Mail) -> bool {
self.date == other.date
}
}

176
src/mailbox/parser.rs

@ -0,0 +1,176 @@
//use memmap::{Mmap, Protection};
use std;
use base64;
use chrono;
use nom::le_u8;
/* Wow this sucks! */
named!(quoted_printable_byte<u8>, do_parse!(
p: map_res!(preceded!(tag!("="), verify!(complete!(take!(2)), |s: &[u8]| { ::nom::is_hex_digit(s[0]) && ::nom::is_hex_digit(s[1]) })), std::str::from_utf8) >>
( {
u8::from_str_radix(p, 16).unwrap()
} )));
// Parser definition
/* A header can span multiple lines, eg:
*
* Received: from -------------------- (-------------------------)
* by --------------------- (--------------------- [------------------]) (-----------------------)
* with ESMTP id ------------ for <------------------->;
* Tue, 5 Jan 2016 21:30:44 +0100 (CET)
*/
/*
* if a header value is a Vec<&str>, this is the tail of that Vector
*/
named!(valuelist<&str>,
map_res!(delimited!(alt_complete!(tag!("\t") | tag!(" ")), take_until!("\n"), tag!("\n")), std::str::from_utf8)
);
/* Parse the value part of the header -> Vec<&str> */
named!(value<Vec<&str>>,
do_parse!(
head: map_res!(terminated!(take_until!("\n"), tag!("\n")), std::str::from_utf8) >>
tail: many0!(valuelist) >>
( {
let tail_len = tail.len();
let tail: Vec<&str> = tail