Signed-off-by: Manos Pitsidianakis <el13635@mail.ntua.gr>
embed
Manos Pitsidianakis 2017-09-16 15:05:28 +03:00
parent cddea885f2
commit fb745be27f
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
15 changed files with 808 additions and 698 deletions

View File

@ -10,6 +10,9 @@ use self::test::Bencher;
#[bench] #[bench]
fn mail_parse(b: &mut Bencher) { fn mail_parse(b: &mut Bencher) {
b.iter(|| Envelope::from(Box::new(BackendOpGenerator::new(Box::new(move || { b.iter(|| {
Box::new(MaildirOp::new("test/attachment_test".to_string()))})))) ); Envelope::from(Box::new(BackendOpGenerator::new(Box::new(move || {
Box::new(MaildirOp::new("test/attachment_test".to_string()))
}))))
});
} }

View File

@ -37,17 +37,13 @@ fn main() {
let mut j = 0; let mut j = 0;
let folder_length = set.accounts["norn"].folders.len(); let folder_length = set.accounts["norn"].folders.len();
let mut account = Account::new("norn".to_string(), set.accounts["norn"].clone()); let mut account = Account::new("norn".to_string(), set.accounts["norn"].clone());
'main : loop { 'main: loop {
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) => Box::new(ErrorWindow::new((*v).clone())),
},
Err(ref v) => {
Box::new(ErrorWindow::new((*v).clone()))
}
}; };
//eprintln!("{:?}", set); //eprintln!("{:?}", set);
ncurses::refresh(); ncurses::refresh();
@ -55,39 +51,35 @@ fn main() {
index.draw(); index.draw();
let mut ch; let mut ch;
'inner : loop { 'inner: loop {
ch = ncurses::get_wch(); ch = ncurses::get_wch();
match ch { match ch {
Some(ncurses::WchResult::KeyCode(k @ ncurses::KEY_UP)) | Some(ncurses::WchResult::KeyCode(k @ ncurses::KEY_UP)) |
Some(ncurses::WchResult::KeyCode(k @ ncurses::KEY_DOWN)) => { Some(ncurses::WchResult::KeyCode(k @ ncurses::KEY_DOWN)) => {
index.handle_input(k); index.handle_input(k);
continue; continue;
} }
Some(ncurses::WchResult::Char(k @ 10)) => { Some(ncurses::WchResult::Char(k @ 10)) => {
index.handle_input(k as i32); index.handle_input(k as i32);
continue; continue;
} }
Some(ncurses::WchResult::KeyCode(ncurses::KEY_F1)) | Some(ncurses::WchResult::KeyCode(ncurses::KEY_F1)) |
Some(ncurses::WchResult::Char(113)) => { Some(ncurses::WchResult::Char(113)) => {
break 'main; break 'main;
}, }
Some(ncurses::WchResult::Char(74)) => { Some(ncurses::WchResult::Char(74)) => if j < folder_length - 1 {
if j < folder_length - 1 { j += 1;
j += 1; break 'inner;
break 'inner;
}
}, },
Some(ncurses::WchResult::Char(75)) => { Some(ncurses::WchResult::Char(75)) => if j > 0 {
if j > 0 { j -= 1;
j -= 1; break 'inner;
break 'inner;
}
}, },
Some(ncurses::WchResult::KeyCode(ncurses::KEY_RESIZE)) => { Some(ncurses::WchResult::KeyCode(ncurses::KEY_RESIZE)) => {
eprintln!("key_resize"); eprintln!("key_resize");
index.redraw(); index.redraw();
continue; continue;
}, }
_ => {} _ => {}
} }
} }

View File

@ -19,27 +19,23 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
extern crate xdg;
extern crate config; extern crate config;
extern crate xdg;
use std::collections::HashMap; use std::collections::HashMap;
use std::io; use std::io;
use std::fs; use std::fs;
use std::path::{PathBuf, Path}; use std::path::{Path, PathBuf};
#[derive(Debug,Clone)] #[derive(Debug, Clone)]
pub enum MailFormat { pub enum MailFormat {
Maildir Maildir,
} }
impl MailFormat { impl MailFormat {
pub fn from_str(x: &str) -> MailFormat { pub fn from_str(x: &str) -> MailFormat {
match x.to_lowercase().as_ref() { match x.to_lowercase().as_ref() {
"maildir" => { "maildir" => MailFormat::Maildir,
MailFormat::Maildir _ => panic!("Unrecognizable mail format"),
},
_ => {
panic!("Unrecognizable mail format")
}
} }
} }
} }
@ -49,7 +45,7 @@ struct FileAccount {
folders: String, folders: String,
format: String, format: String,
sent_folder: String, sent_folder: String,
threaded : bool, threaded: bool,
} }
#[derive(Debug, Deserialize, Default)] #[derive(Debug, Deserialize, Default)]
@ -57,16 +53,16 @@ struct FileSettings {
accounts: HashMap<String, FileAccount>, accounts: HashMap<String, FileAccount>,
} }
#[derive(Debug,Clone)] #[derive(Debug, Clone)]
pub struct AccountSettings { pub struct AccountSettings {
pub folders: Vec<String>, pub folders: Vec<String>,
format: MailFormat, format: MailFormat,
pub sent_folder: String, pub sent_folder: String,
threaded : bool, threaded: bool,
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Settings { pub struct Settings {
pub accounts: HashMap<String, AccountSettings>, pub accounts: HashMap<String, AccountSettings>,
} }
@ -74,8 +70,9 @@ use self::config::{Config, File, FileFormat};
impl FileSettings { impl FileSettings {
pub fn new() -> FileSettings { pub fn new() -> FileSettings {
let xdg_dirs = xdg::BaseDirectories::with_prefix("meli").unwrap(); let xdg_dirs = xdg::BaseDirectories::with_prefix("meli").unwrap();
let config_path = xdg_dirs.place_config_file("config") let config_path = xdg_dirs
.expect("cannot create configuration directory"); .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 setts = Config::default().merge(File::new(config_path.to_str().unwrap_or_default(), config::FileFormat::Toml)).unwrap();
let mut s = Config::new(); let mut s = Config::new();
let s = s.merge(File::new(config_path.to_str().unwrap(), FileFormat::Toml)); let s = s.merge(File::new(config_path.to_str().unwrap(), FileFormat::Toml));
@ -83,10 +80,14 @@ impl FileSettings {
if s.is_ok() { if s.is_ok() {
s.unwrap().deserialize().unwrap() s.unwrap().deserialize().unwrap()
} else { } else {
eprintln!("{:?}",s.err().unwrap()); eprintln!("{:?}", s.err().unwrap());
let mut buf = String::new(); let mut buf = String::new();
io::stdin().read_line(&mut buf).expect("Failed to read line"); io::stdin()
FileSettings { ..Default::default() } .read_line(&mut buf)
.expect("Failed to read line");
FileSettings {
..Default::default()
}
} }
} }
} }
@ -99,40 +100,39 @@ impl Settings {
for (id, x) in fs.accounts { for (id, x) in fs.accounts {
let mut folders = Vec::new(); let mut folders = Vec::new();
fn recurse_folders<P: AsRef<Path>>(folders: &mut Vec<String>, p: P) { fn recurse_folders<P: AsRef<Path>>(folders: &mut Vec<String>, p: P) {
for mut f in fs::read_dir(p).unwrap() { for mut f in fs::read_dir(p).unwrap() {
for f in f.iter_mut() { for f in f.iter_mut() {
{ {
let path = f.path(); let path = f.path();
if path.ends_with("cur") || path.ends_with("new") || if path.ends_with("cur") || path.ends_with("new") ||
path.ends_with("tmp") { path.ends_with("tmp")
{
continue; continue;
} }
if path.is_dir() { if path.is_dir() {
folders.push(path.to_str().unwrap().to_string()); folders.push(path.to_str().unwrap().to_string());
recurse_folders(folders, path); recurse_folders(folders, path);
}
} }
} }
} }
}
}; };
let path = PathBuf::from(&x.folders); let path = PathBuf::from(&x.folders);
if path.is_dir() { if path.is_dir() {
folders.push(path.to_str().unwrap().to_string()); folders.push(path.to_str().unwrap().to_string());
} }
recurse_folders(&mut folders, &x.folders); recurse_folders(&mut folders, &x.folders);
s.insert(id.clone(), AccountSettings { s.insert(
folders: folders, id.clone(),
format: MailFormat::from_str(&x.format), AccountSettings {
sent_folder: x.sent_folder.clone(), folders: folders,
threaded: x.threaded, format: MailFormat::from_str(&x.format),
}); sent_folder: x.sent_folder.clone(),
threaded: x.threaded,
},
);
} }
Settings { accounts: s } Settings { accounts: s }
} }
} }

View File

@ -27,20 +27,25 @@ use nom;
pub type Result<T> = result::Result<T, MeliError>; pub type Result<T> = result::Result<T, MeliError>;
#[derive(Debug,Clone)] #[derive(Debug, Clone)]
pub struct MeliError { pub struct MeliError {
details: String details: String,
} }
impl MeliError { impl MeliError {
pub fn new<M>(msg: M) -> MeliError where M: Into<String> { pub fn new<M>(msg: M) -> MeliError
MeliError{details: msg.into()} where
M: Into<String>,
{
MeliError {
details: msg.into(),
}
} }
} }
impl fmt::Display for MeliError { impl fmt::Display for MeliError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,"{}",self.details) write!(f, "{}", self.details)
} }
} }

View File

@ -21,7 +21,7 @@
use mailbox::*; use mailbox::*;
use conf::AccountSettings; use conf::AccountSettings;
use std::ops::{IndexMut, Index}; use std::ops::{Index, IndexMut};
#[derive(Debug)] #[derive(Debug)]
pub struct Account { pub struct Account {
name: String, name: String,
@ -29,19 +29,21 @@ pub struct Account {
sent_folder: Option<usize>, sent_folder: Option<usize>,
settings: AccountSettings, settings: AccountSettings,
} }
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(|x| *x == settings.sent_folder); let sent_folder = settings
let mut folders = Vec::with_capacity(settings.folders.len()); .folders
for _ in 0..settings.folders.len() { .iter()
folders.push(None); .position(|x| *x == settings.sent_folder);
} let mut folders = Vec::with_capacity(settings.folders.len());
for _ in 0..settings.folders.len() {
folders.push(None);
}
Account { Account {
name: name, name: name,
folders: folders, folders: folders,
@ -60,8 +62,7 @@ impl Index<usize> for Account {
} }
} }
impl IndexMut<usize> for Account impl IndexMut<usize> for Account {
{
fn index_mut(&mut self, index: usize) -> &mut Option<Result<Mailbox>> { fn index_mut(&mut self, index: usize) -> &mut Option<Result<Mailbox>> {
if self.folders[index].is_none() { if self.folders[index].is_none() {
eprintln!("building folder {:?}", self.settings.folders[index]); eprintln!("building folder {:?}", self.settings.folders[index]);
@ -73,14 +74,18 @@ impl IndexMut<usize> for Account
self.folders[index] = Some(Mailbox::new(&path, &None)); self.folders[index] = Some(Mailbox::new(&path, &None));
eprintln!("Done!"); eprintln!("Done!");
} else { } else {
eprintln!("Now building folder {:?} with sent_folder", self.settings.folders[index]); eprintln!(
let (sent, cur) = "Now building folder {:?} with sent_folder",
{ self.settings.folders[index]
);
let (sent, cur) = {
let ptr = self.folders.as_mut_ptr(); let ptr = self.folders.as_mut_ptr();
unsafe { unsafe {
use std::slice::from_raw_parts_mut; use std::slice::from_raw_parts_mut;
(from_raw_parts_mut(ptr.offset(id as isize), id+1), (
from_raw_parts_mut(ptr.offset(index as isize), index+1)) from_raw_parts_mut(ptr.offset(id as isize), id + 1),
from_raw_parts_mut(ptr.offset(index as isize), index + 1),
)
} }
}; };
let sent_path = self.settings.folders[id].clone(); let sent_path = self.settings.folders[id].clone();
@ -93,13 +98,14 @@ impl IndexMut<usize> for Account
eprintln!("Done!"); eprintln!("Done!");
} }
} else { } else {
eprintln!("Now building folder {:?} without sent_folder", self.settings.folders[index]); eprintln!(
self.folders[index] = Some(Mailbox::new(&path, &None)); "Now building folder {:?} without sent_folder",
eprintln!("Done!"); self.settings.folders[index]
);
self.folders[index] = Some(Mailbox::new(&path, &None));
eprintln!("Done!");
}; };
} }
&mut self.folders[index] &mut self.folders[index]
} }
} }

View File

@ -21,7 +21,7 @@
use mailbox::email::Envelope; use mailbox::email::Envelope;
use error::{MeliError, Result}; use error::{MeliError, Result};
use mailbox::backends::{MailBackend, BackendOp, BackendOpGenerator}; use mailbox::backends::{BackendOp, BackendOpGenerator, MailBackend};
use mailbox::email::parser; use mailbox::email::parser;
extern crate crossbeam; extern crate crossbeam;
@ -33,7 +33,7 @@ pub struct MaildirType {
path: String, path: String,
} }
#[derive(Debug,Default)] #[derive(Debug, Default)]
pub struct MaildirOp { pub struct MaildirOp {
path: String, path: String,
slice: Option<Mmap>, slice: Option<Mmap>,
@ -59,11 +59,13 @@ impl MaildirOp {
impl BackendOp for MaildirOp { impl BackendOp for MaildirOp {
fn description(&self) -> String { fn description(&self) -> String {
format!("Path of file: {}", self.path) format!("Path of file: {}", self.path)
} }
fn as_bytes(&mut self) -> Result<&[u8]> { fn as_bytes(&mut self) -> Result<&[u8]> {
if self.slice.is_none() { if self.slice.is_none() {
self.slice = Some(Mmap::open_path(self.path.to_string(), Protection::Read).unwrap()); self.slice = Some(
Mmap::open_path(self.path.to_string(), Protection::Read).unwrap(),
);
} }
Ok(unsafe { self.slice.as_ref().unwrap().as_slice() }) Ok(unsafe { self.slice.as_ref().unwrap().as_slice() })
} }
@ -110,7 +112,7 @@ impl MailBackend for MaildirType {
impl MaildirType { impl MaildirType {
pub fn new(path: &str) -> Self { pub fn new(path: &str) -> Self {
MaildirType { MaildirType {
path: path.to_string() path: path.to_string(),
} }
} }
fn is_valid(path: &str) -> Result<()> { fn is_valid(path: &str) -> Result<()> {
@ -118,7 +120,9 @@ impl MaildirType {
for d in &["cur", "new", "tmp"] { for d in &["cur", "new", "tmp"] {
p.push(d); p.push(d);
if !p.is_dir() { if !p.is_dir() {
return Err(MeliError::new(format!("{} is not a valid maildir folder", path))); return Err(MeliError::new(
format!("{} is not a valid maildir folder", path),
));
} }
p.pop(); p.pop();
} }
@ -155,7 +159,6 @@ panic!("didn't parse"); },
r.push(m); r.push(m);
*/ */
} }
let mut threads = Vec::with_capacity(cores); let mut threads = Vec::with_capacity(cores);
if !files.is_empty() { if !files.is_empty() {
@ -163,16 +166,16 @@ panic!("didn't parse"); },
let chunk_size = if count / cores > 0 { let chunk_size = if count / cores > 0 {
count / cores count / cores
} else { } else {
count count
}; };
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<Envelope> = 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) = Envelope::from(Box::new(BackendOpGenerator::new(Box::new(move || { if let Some(e) = Envelope::from(Box::new(BackendOpGenerator::new(
Box::new(MaildirOp::new(e_copy.clone())) Box::new(move || Box::new(MaildirOp::new(e_copy.clone()))),
} )))) { ))) {
local_r.push(e); local_r.push(e);
} }
} }

View File

@ -75,9 +75,9 @@ 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.
* */ * */
pub struct BackendOpGenerator(Box<Fn() -> Box<BackendOp>>); pub struct BackendOpGenerator(Box<Fn() -> Box<BackendOp>>);
@ -97,4 +97,3 @@ impl fmt::Debug for BackendOpGenerator {
write!(f, "BackendOpGenerator: {}", op.description()) write!(f, "BackendOpGenerator: {}", op.description())
} }
} }

View File

@ -30,20 +30,23 @@ use std::ascii::AsciiExt;
* Multipart * Multipart
*/ */
#[derive(Clone,Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum MultipartType { pub enum MultipartType {
Mixed, Mixed,
Alternative, Alternative,
Digest, Digest,
Unsupported { tag: String }, Unsupported { tag: String },
} }
#[derive(Clone,Debug)] #[derive(Clone, Debug)]
pub enum AttachmentType { pub enum AttachmentType {
Data { tag: String }, Data { tag: String },
Text { content: String }, Text { content: String },
Multipart { of_type: MultipartType, subattachments: Vec<Attachment>, } Multipart {
of_type: MultipartType,
subattachments: Vec<Attachment>,
},
} }
#[derive(Clone,Debug)] #[derive(Clone, Debug)]
pub enum ContentType { pub enum ContentType {
Text, Text,
Multipart { boundary: String }, Multipart { boundary: String },
@ -53,19 +56,13 @@ pub enum ContentType {
impl Display for ContentType { impl Display for ContentType {
fn fmt(&self, f: &mut Formatter) -> Result { fn fmt(&self, f: &mut Formatter) -> Result {
match *self { match *self {
ContentType::Text => { ContentType::Text => write!(f, "text"),
write!(f, "text") ContentType::Multipart { .. } => write!(f, "multipart"),
}, ContentType::Unsupported { tag: ref t } => write!(f, "{}", t),
ContentType::Multipart { .. } => {
write!(f, "multipart")
},
ContentType::Unsupported { tag: ref t } => {
write!(f, "{}", t)
},
} }
} }
} }
#[derive(Clone,Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum ContentSubType { pub enum ContentSubType {
Plain, Plain,
Other { tag: String }, Other { tag: String },
@ -73,16 +70,12 @@ pub enum ContentSubType {
impl Display for ContentSubType { impl Display for ContentSubType {
fn fmt(&self, f: &mut Formatter) -> Result { fn fmt(&self, f: &mut Formatter) -> Result {
match *self { match *self {
ContentSubType::Plain => { ContentSubType::Plain => write!(f, "plain"),
write!(f, "plain") ContentSubType::Other { tag: ref t } => write!(f, "{}", t),
},
ContentSubType::Other { tag: ref t } => {
write!(f, "{}", t)
},
} }
} }
} }
#[derive(Clone,Debug)] #[derive(Clone, Debug)]
pub enum ContentTransferEncoding { pub enum ContentTransferEncoding {
_8Bit, _8Bit,
_7Bit, _7Bit,
@ -110,111 +103,111 @@ impl AttachmentBuilder {
} }
pub fn content_type(&mut self, value: &str) -> &Self { pub fn content_type(&mut self, value: &str) -> &Self {
match parser::content_type(value.as_bytes()).to_full_result() { match parser::content_type(value.as_bytes()).to_full_result() {
Ok((ct, cst, params)) => { Ok((ct, cst, params)) => if ct.eq_ignore_ascii_case("multipart") {
if ct.eq_ignore_ascii_case("multipart") { let mut boundary = None;
let mut boundary = None; for (n, v) in params {
for (n, v) in params { if n.eq_ignore_ascii_case("boundary") {
if n.eq_ignore_ascii_case("boundary") { boundary = Some(format!("--{}--", v).to_string());
boundary = Some(format!("--{}--", v).to_string()); break;
break;
}
} }
assert!(boundary.is_some());
self.content_type.0 = ContentType::Multipart { boundary: boundary.unwrap() };
self.content_type.1 = ContentSubType::Other { tag: cst.to_string() };
} else if ct.eq_ignore_ascii_case("text") {
self.content_type.0 = ContentType::Text;
if !cst.eq_ignore_ascii_case("plain") {
self.content_type.1 = ContentSubType::Other { tag: cst.to_ascii_lowercase() };
}
} else {
self.content_type.0 = ContentType::Unsupported { tag: ct.to_ascii_lowercase() };
self.content_type.1 = ContentSubType::Other { tag: cst.to_ascii_lowercase() };
} }
assert!(boundary.is_some());
self.content_type.0 = ContentType::Multipart {
boundary: boundary.unwrap(),
};
self.content_type.1 = ContentSubType::Other {
tag: cst.to_string(),
};
} else if ct.eq_ignore_ascii_case("text") {
self.content_type.0 = ContentType::Text;
if !cst.eq_ignore_ascii_case("plain") {
self.content_type.1 = ContentSubType::Other {
tag: cst.to_ascii_lowercase(),
};
}
} else {
self.content_type.0 = ContentType::Unsupported {
tag: ct.to_ascii_lowercase(),
};
self.content_type.1 = ContentSubType::Other {
tag: cst.to_ascii_lowercase(),
};
}, },
Err(v) => { Err(v) => {
eprintln!("parsing error in content_type: {:?} {:?}", value, v); eprintln!("parsing error in content_type: {:?} {:?}", value, v);
} }
} }
self self
} }
pub fn content_transfer_encoding(&mut self, value: &str) -> &Self { pub fn content_transfer_encoding(&mut self, value: &str) -> &Self {
self.content_transfer_encoding = self.content_transfer_encoding = if value.eq_ignore_ascii_case("base64") {
if value.eq_ignore_ascii_case("base64") { ContentTransferEncoding::Base64
ContentTransferEncoding::Base64 } else if value.eq_ignore_ascii_case("7bit") {
} else if value.eq_ignore_ascii_case("7bit") { ContentTransferEncoding::_7Bit
ContentTransferEncoding::_7Bit } else if value.eq_ignore_ascii_case("8bit") {
} else if value.eq_ignore_ascii_case("8bit") { ContentTransferEncoding::_8Bit
ContentTransferEncoding::_8Bit } else if value.eq_ignore_ascii_case("quoted-printable") {
} else if value.eq_ignore_ascii_case("quoted-printable") { ContentTransferEncoding::QuotedPrintable
ContentTransferEncoding::QuotedPrintable } else {
} else { ContentTransferEncoding::Other {
ContentTransferEncoding::Other { tag: value.to_ascii_lowercase() } tag: value.to_ascii_lowercase(),
}; }
};
self self
} }
fn decode(&self) -> String { fn decode(&self) -> String {
match self.content_transfer_encoding { match self.content_transfer_encoding {
ContentTransferEncoding::Base64 => { ContentTransferEncoding::Base64 => {
match ::base64::decode(&::std::str::from_utf8(&self.raw).unwrap().trim().lines().fold(String::with_capacity(self.raw.len()), |mut acc, x| { acc.push_str(x); acc })) { match ::base64::decode(&::std::str::from_utf8(&self.raw)
Ok( ref v ) => { .unwrap()
let s = String::from_utf8_lossy(v); .trim()
if s.find("\r\n").is_some() { .lines()
s.replace("\r\n","\n") .fold(String::with_capacity(self.raw.len()), |mut acc, x| {
} else { acc.push_str(x);
s.into_owned() acc
} })) {
}, Ok(ref v) => {
_ => { let s = String::from_utf8_lossy(v);
String::from_utf8_lossy(&self.raw).into_owned() if s.find("\r\n").is_some() {
s.replace("\r\n", "\n")
} else {
s.into_owned()
} }
} }
}, _ => String::from_utf8_lossy(&self.raw).into_owned(),
ContentTransferEncoding::QuotedPrintable => {
parser::quoted_printable_text(&self.raw).to_full_result().unwrap()
},
ContentTransferEncoding::_7Bit | ContentTransferEncoding::_8Bit | ContentTransferEncoding::Other { .. } => {
String::from_utf8_lossy(&self.raw).into_owned()
} }
} }
ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_text(&self.raw)
.to_full_result()
.unwrap(),
ContentTransferEncoding::_7Bit |
ContentTransferEncoding::_8Bit |
ContentTransferEncoding::Other { .. } => {
String::from_utf8_lossy(&self.raw).into_owned()
}
}
} }
pub fn build(self) -> Attachment { pub fn build(self) -> Attachment {
let attachment_type = let attachment_type = match self.content_type.0 {
match self.content_type.0 { ContentType::Text => AttachmentType::Text {
ContentType::Text => { content: self.decode(),
AttachmentType::Text { content: self.decode() }
}, },
ContentType::Multipart { boundary: ref b } => { ContentType::Multipart { boundary: ref b } => {
let multipart_type = let multipart_type = match self.content_type.1 {
match self.content_type.1 { ContentSubType::Other { ref tag } => match tag.as_ref() {
ContentSubType::Other { ref tag } => { "mixed" => MultipartType::Mixed,
match tag.as_ref() { "alternative" => MultipartType::Alternative,
"mixed" => { "digest" => MultipartType::Digest,
MultipartType::Mixed t => MultipartType::Unsupported { tag: t.to_string() },
}, },
"alternative" => { _ => panic!(),
MultipartType::Alternative };
},
"digest" => {
MultipartType::Digest
},
t => {
MultipartType::Unsupported { tag: t.to_string() }
},
}
},
_ => {
panic!()
}
};
AttachmentType::Multipart { AttachmentType::Multipart {
of_type: multipart_type, of_type: multipart_type,
subattachments: Attachment::subattachments(&self.raw, b), subattachments: Attachment::subattachments(&self.raw, b),
} }
}, }
ContentType::Unsupported { ref tag } => { ContentType::Unsupported { ref tag } => AttachmentType::Data { tag: tag.clone() },
AttachmentType::Data { tag: tag.clone() }
},
}; };
Attachment { Attachment {
content_type: self.content_type, content_type: self.content_type,
@ -222,13 +215,11 @@ self
raw: self.raw, raw: self.raw,
attachment_type: attachment_type, attachment_type: attachment_type,
} }
} }
} }
#[derive(Clone,Debug)] #[derive(Clone, Debug)]
pub struct Attachment { pub struct Attachment {
content_type: (ContentType, ContentSubType), content_type: (ContentType, ContentSubType),
content_transfer_encoding: ContentTransferEncoding, content_transfer_encoding: ContentTransferEncoding,
@ -243,27 +234,25 @@ impl Attachment {
match self.attachment_type { match self.attachment_type {
AttachmentType::Data { .. } => { AttachmentType::Data { .. } => {
text.push_str(&format!("Data attachment of type {}", self.get_tag())); text.push_str(&format!("Data attachment of type {}", self.get_tag()));
}, }
AttachmentType::Text { content: ref t } => { AttachmentType::Text { content: ref t } => {
text.push_str(t); text.push_str(t);
}, }
AttachmentType::Multipart { AttachmentType::Multipart {
of_type: ref multipart_type, of_type: ref multipart_type,
subattachments: ref sub_att_vec, subattachments: ref sub_att_vec,
} => { } => if *multipart_type == MultipartType::Alternative {
if *multipart_type == MultipartType::Alternative { for a in sub_att_vec {
for a in sub_att_vec { if a.content_type.1 == ContentSubType::Plain {
if a.content_type.1 == ContentSubType::Plain {
a.get_text_recursive(text);
break;
}
}
} else {
for a in sub_att_vec {
a.get_text_recursive(text); a.get_text_recursive(text);
text.push_str("\n\n"); break;
} }
} }
} else {
for a in sub_att_vec {
a.get_text_recursive(text);
text.push_str("\n\n");
}
}, },
} }
} }
@ -280,7 +269,8 @@ impl Attachment {
} }
pub fn subattachments(raw: &[u8], boundary: &str) -> Vec<Attachment> { pub fn subattachments(raw: &[u8], boundary: &str) -> Vec<Attachment> {
let boundary_length = boundary.len(); let boundary_length = boundary.len();
match parser::attachments(raw, &boundary[0..boundary_length - 2], boundary).to_full_result() { match parser::attachments(raw, &boundary[0..boundary_length - 2], boundary).to_full_result()
{
Ok(attachments) => { Ok(attachments) => {
let mut vec = Vec::with_capacity(attachments.len()); let mut vec = Vec::with_capacity(attachments.len());
for a in attachments { for a in attachments {
@ -292,26 +282,30 @@ impl Attachment {
eprintln!("{}\n", ::std::string::String::from_utf8_lossy(a)); eprintln!("{}\n", ::std::string::String::from_utf8_lossy(a));
eprintln!("-------------------------------\n"); eprintln!("-------------------------------\n");
continue continue;
} }
}; };
let mut builder = AttachmentBuilder::new(body); let mut builder = AttachmentBuilder::new(body);
for (name, value) in headers { for (name, value) in headers {
if name.eq_ignore_ascii_case("content-type") { if name.eq_ignore_ascii_case("content-type") {
builder.content_type(value); builder.content_type(value);
} else if name.eq_ignore_ascii_case("content-transfer-encoding") { } else if name.eq_ignore_ascii_case("content-transfer-encoding") {
builder.content_transfer_encoding(value); builder.content_transfer_encoding(value);
} }
} }
vec.push(builder.build()); vec.push(builder.build());
} }
vec vec
}, }
a => { a => {
eprintln!("error in 469 {:?}\n\traw: {:?}\n\tboundary: {:?}", a, ::std::str::from_utf8(raw).unwrap(), boundary); eprintln!(
"error in 469 {:?}\n\traw: {:?}\n\tboundary: {:?}",
a,
::std::str::from_utf8(raw).unwrap(),
boundary
);
Vec::new() Vec::new()
}, }
} }
} }
} }

View File

@ -25,6 +25,7 @@ use mailbox::backends::BackendOpGenerator;
use self::attachments::*; use self::attachments::*;
use std::string::String; use std::string::String;
use std::sync::Arc;
//use std; //use std;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt; use std::fmt;
@ -36,7 +37,7 @@ use chrono;
use chrono::TimeZone; use chrono::TimeZone;
/* Helper struct to return slices from a struct on demand */ /* Helper struct to return slices from a struct on demand */
#[derive(Clone,Debug)] #[derive(Clone, Debug)]
struct StrBuilder { struct StrBuilder {
offset: usize, offset: usize,
length: usize, length: usize,
@ -49,15 +50,18 @@ pub trait StrBuild {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct MessageID (String, StrBuilder); pub struct MessageID(String, StrBuilder);
impl StrBuild for MessageID { impl StrBuild for MessageID {
fn new(string: &str, slice: &str) -> Self { fn new(string: &str, slice: &str) -> Self {
let offset = string.find(slice).unwrap(); let offset = string.find(slice).unwrap();
MessageID (string.to_string(), StrBuilder { MessageID(
offset: offset, string.to_string(),
length: slice.len() + 1, StrBuilder {
}) offset: offset,
length: slice.len() + 1,
},
)
} }
fn get_raw(&self) -> &str { fn get_raw(&self) -> &str {
let offset = self.1.offset; let offset = self.1.offset;
@ -73,7 +77,16 @@ impl StrBuild for MessageID {
fn test_strbuilder() { fn test_strbuilder() {
let m_id = "<20170825132332.6734-1-el13635@mail.ntua.gr>"; let m_id = "<20170825132332.6734-1-el13635@mail.ntua.gr>";
let (_, slice) = parser::message_id(m_id.as_bytes()).unwrap(); let (_, slice) = parser::message_id(m_id.as_bytes()).unwrap();
assert_eq!(MessageID::new(m_id, slice), MessageID (m_id.to_string(), StrBuilder{offset: 1, length: 43})); assert_eq!(
MessageID::new(m_id, slice),
MessageID(
m_id.to_string(),
StrBuilder {
offset: 1,
length: 43,
}
)
);
} }
impl PartialEq for MessageID { impl PartialEq for MessageID {
@ -87,17 +100,15 @@ impl fmt::Debug for MessageID {
} }
} }
#[derive(Clone,Debug)] #[derive(Clone, Debug)]
struct References { struct References {
raw: String, raw: String,
refs: Vec<MessageID>, refs: Vec<MessageID>,
} }
use std::sync::Arc;
/* A very primitive mail object */ /* A very primitive mail object */
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Envelope pub struct Envelope {
{
date: String, date: String,
from: Option<String>, from: Option<String>,
to: Option<String>, to: Option<String>,
@ -115,13 +126,16 @@ pub struct Envelope
} }
impl Envelope impl Envelope {
{
pub fn get_date(&self) -> i64 { pub fn get_date(&self) -> i64 {
self.timestamp self.timestamp
} }
pub fn get_datetime(&self) -> chrono::DateTime<chrono::FixedOffset> { pub fn get_datetime(&self) -> chrono::DateTime<chrono::FixedOffset> {
self.datetime.unwrap_or_else(|| { chrono::FixedOffset::west(0).ymd(1970, 1, 1).and_hms(0, 0, 0)}) self.datetime.unwrap_or_else(|| {
chrono::FixedOffset::west(0)
.ymd(1970, 1, 1)
.and_hms(0, 0, 0)
})
} }
pub fn get_date_as_str(&self) -> &str { pub fn get_date_as_str(&self) -> &str {
&self.date &self.date
@ -147,7 +161,7 @@ impl Envelope
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);
for (name, value) in headers { for (name, value) in headers {
@ -165,19 +179,19 @@ impl Envelope
pub fn get_subject(&self) -> &str { pub fn get_subject(&self) -> &str {
match self.subject { match self.subject {
Some(ref s) => s, Some(ref s) => s,
_ => "" _ => "",
} }
} }
pub fn get_in_reply_to(&self) -> &str { pub fn get_in_reply_to(&self) -> &str {
match self.in_reply_to { match self.in_reply_to {
Some(ref s) => s.get_val(), Some(ref s) => s.get_val(),
_ => "" _ => "",
} }
} }
pub fn get_in_reply_to_raw(&self) -> &str { pub fn get_in_reply_to_raw(&self) -> &str {
match self.in_reply_to { match self.in_reply_to {
Some(ref s) => s.get_raw(), Some(ref s) => s.get_raw(),
_ => "" _ => "",
} }
} }
pub fn get_message_id(&self) -> &str { pub fn get_message_id(&self) -> &str {
@ -203,10 +217,12 @@ impl Envelope
} }
fn set_in_reply_to(&mut self, new_val: &str) -> () { fn set_in_reply_to(&mut self, new_val: &str) -> () {
let slice = match parser::message_id(new_val.as_bytes()).to_full_result() { let slice = match parser::message_id(new_val.as_bytes()).to_full_result() {
Ok(v) => { v }, Ok(v) => v,
Err(v) => { eprintln!("{} {:?}",new_val, v); Err(v) => {
eprintln!("{} {:?}", new_val, v);
self.in_reply_to = None; self.in_reply_to = None;
return; } return;
}
}; };
self.in_reply_to = Some(MessageID::new(new_val, slice)); self.in_reply_to = Some(MessageID::new(new_val, slice));
} }
@ -215,23 +231,27 @@ impl Envelope
} }
fn set_message_id(&mut self, new_val: &str) -> () { fn set_message_id(&mut self, new_val: &str) -> () {
let slice = match parser::message_id(new_val.as_bytes()).to_full_result() { let slice = match parser::message_id(new_val.as_bytes()).to_full_result() {
Ok(v) => { v }, Ok(v) => v,
Err(v) => { eprintln!("{} {:?}",new_val, v); Err(v) => {
eprintln!("{} {:?}", new_val, v);
self.message_id = None; self.message_id = None;
return; } return;
}
}; };
self.message_id = Some(MessageID::new(new_val, slice)); self.message_id = Some(MessageID::new(new_val, slice));
} }
fn push_references(&mut self, new_val: &str) -> () { fn push_references(&mut self, new_val: &str) -> () {
let slice = match parser::message_id(new_val.as_bytes()).to_full_result() { let slice = match parser::message_id(new_val.as_bytes()).to_full_result() {
Ok(v) => { v }, Ok(v) => v,
Err(v) => { eprintln!("{} {:?}",new_val, v); Err(v) => {
return; } eprintln!("{} {:?}", new_val, v);
return;
}
}; };
let new_ref = MessageID::new(new_val, slice); let new_ref = MessageID::new(new_val, slice);
match self.references { match self.references {
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(|x| *x == new_ref) { if let Some(index) = s.refs.iter().position(|x| *x == new_ref) {
s.refs.remove(index); s.refs.remove(index);
@ -243,29 +263,39 @@ impl Envelope
} }
} }
s.refs.push(new_ref); s.refs.push(new_ref);
}, }
None => { None => {
let mut v = Vec::new(); let mut v = Vec::new();
v.push(new_ref); v.push(new_ref);
self.references = Some(References { raw: "".to_string(), refs: v, }); self.references = Some(References {
raw: "".to_string(),
refs: v,
});
} }
} }
} }
fn set_references(&mut self, new_val: String) -> () { fn set_references(&mut self, new_val: String) -> () {
match self.references { match self.references {
Some(ref mut s) => { Some(ref mut s) => {
s.raw = new_val; s.raw = new_val;
}, }
None => { None => {
let v = Vec::new(); let v = Vec::new();
self.references = Some(References { raw: new_val, refs: v, }); self.references = Some(References {
raw: new_val,
refs: v,
});
} }
} }
} }
pub fn get_references(&self) -> Vec<&MessageID> { pub fn get_references(&self) -> Vec<&MessageID> {
match self.references { match self.references {
Some(ref s) => s.refs.iter().fold(Vec::with_capacity(s.refs.len()), |mut acc, x| { acc.push(x); acc }), Some(ref s) => s.refs
.iter()
.fold(Vec::with_capacity(s.refs.len()), |mut acc, x| {
acc.push(x);
acc
}),
None => Vec::new(), None => Vec::new(),
} }
} }
@ -276,7 +306,7 @@ impl Envelope
self.thread self.thread
} }
pub fn set_thread(&mut self, new_val: usize) -> () { pub fn set_thread(&mut self, new_val: usize) -> () {
self.thread = new_val; self.thread = new_val;
} }
pub fn set_datetime(&mut self, new_val: Option<chrono::DateTime<chrono::FixedOffset>>) -> () { pub fn set_datetime(&mut self, new_val: Option<chrono::DateTime<chrono::FixedOffset>>) -> () {
self.datetime = new_val; self.datetime = new_val;
@ -301,20 +331,17 @@ impl Envelope
thread: 0, thread: 0,
operation_token: Arc::new(token), operation_token: Arc::new(token),
} }
} }
pub fn from(operation_token: Box<BackendOpGenerator>) -> Option<Envelope> { 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) => v,
v
},
_ => { _ => {
let operation = operation_token.generate(); let operation = operation_token.generate();
eprintln!("error in parsing mail\n{}", operation.description()); eprintln!("error in parsing mail\n{}", operation.description());
return None; return None;
}, }
}; };
let mut mail = Envelope::new(operation_token); let mut mail = Envelope::new(operation_token);

View File

@ -22,12 +22,12 @@ use std;
use std::str::from_utf8; use std::str::from_utf8;
use base64; use base64;
use chrono; use chrono;
use nom::{le_u8, is_hex_digit}; use nom::{is_hex_digit, le_u8};
use nom::{IResult,Needed,ErrorKind}; use nom::{ErrorKind, IResult, Needed};
use nom::{Compare, CompareResult}; use nom::{Compare, CompareResult};
use encoding::{Encoding, DecoderTrap}; use encoding::{DecoderTrap, Encoding};
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'=' && 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]) {
@ -45,7 +45,7 @@ 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)))
} }
@ -70,18 +70,15 @@ fn header_value(input: &[u8]) -> IResult<&[u8], &str> {
for (i, x) in input.iter().enumerate() { for (i, x) in input.iter().enumerate() {
if *x == b'\n' { if *x == b'\n' {
if (i + 1) < input_len && if (i + 1) < input_len &&
((input[i+1] != b' ' && input[i+1] != b'\t') || input[i+1] == b'\n') { ((input[i + 1] != b' ' && input[i + 1] != b'\t') || input[i + 1] == b'\n')
return match from_utf8(&input[0..i]) { {
Ok(v) => { return match from_utf8(&input[0..i]) {
IResult::Done(&input[(i+1)..], v) Ok(v) => IResult::Done(&input[(i + 1)..], v),
}, Err(_) => IResult::Error(error_code!(ErrorKind::Custom(43))),
Err(_) => { };
IResult::Error(error_code!(ErrorKind::Custom(43))) } else if i + 1 > input_len {
}, return IResult::Incomplete(Needed::Size(1));
} }
} else if i + 1 > input_len {
return IResult::Incomplete(Needed::Size(1));
}
} }
} }
IResult::Error(error_code!(ErrorKind::Custom(43))) IResult::Error(error_code!(ErrorKind::Custom(43)))
@ -90,12 +87,13 @@ fn header_value(input: &[u8]) -> IResult<&[u8], &str> {
/* Parse the name part of the header -> &str */ /* Parse the name part of the header -> &str */
named!(name<&str>, named!(name<&str>, map_res!(is_not!(":\n"), from_utf8));
map_res!(is_not!(":\n"), from_utf8));
/* Parse a single header as a tuple -> (&str, Vec<&str>) */ /* Parse a single header as a tuple -> (&str, Vec<&str>) */
named!(header<(&str, &str)>, named!(
separated_pair!(complete!(name), ws!(tag!(":")), complete!(header_value))); header<(&str, &str)>,
separated_pair!(complete!(name), ws!(tag!(":")), complete!(header_value))
);
/* Parse all headers -> Vec<(&str, Vec<&str>)> */ /* Parse all headers -> Vec<(&str, Vec<&str>)> */
named!(pub headers<std::vec::Vec<(&str, &str)>>, named!(pub headers<std::vec::Vec<(&str, &str)>>,
many1!(complete!(header))); many1!(complete!(header)));
@ -126,103 +124,95 @@ named!(pub attachment<(std::vec::Vec<(&str, &str)>, &[u8])>,
/* TODO: make a map of encodings and decoding functions so that they can be reused and easily /* TODO: make a map of encodings and decoding functions so that they can be reused and easily
* extended */ * extended */
use encoding::all::{ISO_8859_1,ISO_8859_2, ISO_8859_7, WINDOWS_1253, GBK}; use encoding::all::{ISO_8859_1, ISO_8859_2, ISO_8859_7, WINDOWS_1253, GBK};
fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> { fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
if input.len() < 5 { if input.len() < 5 {
return IResult::Incomplete(Needed::Unknown); return IResult::Incomplete(Needed::Unknown);
} else if input[0] != b'=' || input[1] != b'?' { } else if input[0] != b'=' || input[1] != b'?' {
return IResult::Error(error_code!(ErrorKind::Custom(43))) return IResult::Error(error_code!(ErrorKind::Custom(43)));
} }
for tag in &["UTF-8", "iso-8859-7", "windows-1253", "iso-8859-1", "iso-8859-2", "gbk"] { for tag in &[
"UTF-8",
"iso-8859-7",
"windows-1253",
"iso-8859-1",
"iso-8859-2",
"gbk",
] {
if let CompareResult::Ok = (&input[2..]).compare_no_case(*tag) { if let CompareResult::Ok = (&input[2..]).compare_no_case(*tag) {
let tag_len = tag.len(); let tag_len = tag.len();
/* tag must end with ?_? where _ is either Q or B, eg: =?UTF-8?B? */ /* tag must end with ?_? where _ is either Q or B, eg: =?UTF-8?B? */
if input[2+tag_len] != b'?' || input[2+tag_len+2] != b'?' { if input[2 + tag_len] != b'?' || input[2 + tag_len + 2] != b'?' {
return IResult::Error(error_code!(ErrorKind::Custom(43))) return IResult::Error(error_code!(ErrorKind::Custom(43)));
} }
/* See if input ends with "?=" and get ending index */ /* See if input ends with "?=" and get ending index */
let mut encoded_idx = None; let mut encoded_idx = None;
for i in (5+tag_len)..input.len() { for i in (5 + tag_len)..input.len() {
if input[i] == b'?' && i < input.len() && input[i+1] == b'=' { if input[i] == b'?' && i < input.len() && input[i + 1] == b'=' {
encoded_idx = Some(i); encoded_idx = Some(i);
break; break;
} }
};
if encoded_idx.is_none() {
return IResult::Error(error_code!(ErrorKind::Custom(43)))
} }
let encoded = &input[5+tag_len..encoded_idx.unwrap()]; if encoded_idx.is_none() {
return IResult::Error(error_code!(ErrorKind::Custom(43)));
}
let encoded = &input[5 + tag_len..encoded_idx.unwrap()];
let s:Vec<u8> = match input[2+tag_len+1] { let s: Vec<u8> = match input[2 + tag_len + 1] {
b'b' | b'B' => { b'b' | b'B' => match base64::decode(encoded) {
match base64::decode(encoded) { Ok(v) => v,
Ok(v) => { Err(_) => encoded.to_vec(),
v
},
Err(_) => {
encoded.to_vec()
},
}
}, },
b'q' | b'Q' => { b'q' | b'Q' => match get_quoted_printed_bytes(encoded) {
match get_quoted_printed_bytes(encoded) { IResult::Done(b"", s) => s,
IResult::Done(b"", s) => { _ => return IResult::Error(error_code!(ErrorKind::Custom(43))),
s
},
_ => {
return IResult::Error(error_code!(ErrorKind::Custom(43)))
},
}
},
_ => {
return IResult::Error(error_code!(ErrorKind::Custom(43)))
}, },
_ => return IResult::Error(error_code!(ErrorKind::Custom(43))),
}; };
match *tag { match *tag {
"UTF-8" => { "UTF-8" => {
return IResult::Done(&input[encoded_idx.unwrap()+2..], s); return IResult::Done(&input[encoded_idx.unwrap() + 2..], s);
}, }
"iso-8859-7" => { "iso-8859-7" => {
return if let Ok(v) = ISO_8859_7.decode(&s, DecoderTrap::Strict) { return if let Ok(v) = ISO_8859_7.decode(&s, DecoderTrap::Strict) {
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes()) IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
} else { } else {
IResult::Error(error_code!(ErrorKind::Custom(43))) IResult::Error(error_code!(ErrorKind::Custom(43)))
} }
}, }
"windows-1253" => { "windows-1253" => {
return if let Ok(v) = WINDOWS_1253.decode(&s, DecoderTrap::Strict) { return if let Ok(v) = WINDOWS_1253.decode(&s, DecoderTrap::Strict) {
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes()) IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
} else { } else {
IResult::Error(error_code!(ErrorKind::Custom(43))) IResult::Error(error_code!(ErrorKind::Custom(43)))
} }
}, }
"iso-8859-1" => { "iso-8859-1" => {
return if let Ok(v) = ISO_8859_1.decode(&s, DecoderTrap::Strict) { return if let Ok(v) = ISO_8859_1.decode(&s, DecoderTrap::Strict) {
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes()) IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
} else { } else {
IResult::Error(error_code!(ErrorKind::Custom(43))) IResult::Error(error_code!(ErrorKind::Custom(43)))
} }
}, }
"iso-8859-2" => { "iso-8859-2" => {
return if let Ok(v) = ISO_8859_2.decode(&s, DecoderTrap::Strict) { return if let Ok(v) = ISO_8859_2.decode(&s, DecoderTrap::Strict) {
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes()) IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
} else { } else {
IResult::Error(error_code!(ErrorKind::Custom(43))) IResult::Error(error_code!(ErrorKind::Custom(43)))
} }
}, }
"gbk" => { "gbk" => {
return if let Ok(v) = GBK.decode(&s, DecoderTrap::Strict) { return if let Ok(v) = GBK.decode(&s, DecoderTrap::Strict) {
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes()) IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
} else { } else {
IResult::Error(error_code!(ErrorKind::Custom(43))) IResult::Error(error_code!(ErrorKind::Custom(43)))
} }
}, }
_ => { _ => {
panic!(); panic!();
}, }
} }
} else { } else {
continue; continue;
@ -232,10 +222,14 @@ fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
IResult::Error(error_code!(ErrorKind::Custom(43))) IResult::Error(error_code!(ErrorKind::Custom(43)))
} }
named!(qp_underscore_header<u8>, named!(qp_underscore_header<u8>, do_parse!(tag!("_") >> ({ b' ' })));
do_parse!(tag!("_") >> ( { b' ' } )));
named!(get_quoted_printed_bytes<Vec<u8>>, many0!(alt_complete!(quoted_printable_byte | qp_underscore_header | le_u8))); named!(
get_quoted_printed_bytes<Vec<u8>>,
many0!(alt_complete!(
quoted_printable_byte | qp_underscore_header | le_u8
))
);
named!(encoded_word_list<String>, ws!(do_parse!( named!(encoded_word_list<String>, ws!(do_parse!(
list: separated_nonempty_list!(complete!(is_a!(" \n\r\t")), encoded_word) >> list: separated_nonempty_list!(complete!(is_a!(" \n\r\t")), encoded_word) >>
@ -273,41 +267,79 @@ named!(pub subject<String>, ws!(do_parse!(
#[test] #[test]
fn test_subject() { fn test_subject() {
let subject_s = "list.free.de mailing list memberships reminder".as_bytes(); let subject_s = "list.free.de mailing list memberships reminder".as_bytes();
assert_eq!((&b""[..], "list.free.de mailing list memberships reminder".to_string()), subject(subject_s).unwrap()); assert_eq!(
(
&b""[..],
"list.free.de mailing list memberships reminder".to_string()
),
subject(subject_s).unwrap()
);
let subject_s = "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?= =?UTF-8?B?z4fOuM63?=".as_bytes(); let subject_s = "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?= =?UTF-8?B?z4fOuM63?=".as_bytes();
assert_eq!((&b""[..], "Νέο προσωπικό μήνυμα αφίχθη".to_string()), subject(subject_s).unwrap()); assert_eq!(
(
&b""[..],
"Νέο προσωπικό μήνυμα αφίχθη".to_string()
),
subject(subject_s).unwrap()
);
let subject_s = "=?utf-8?B?bW9vZGxlOiDOsc69zrHPg866z4zPgM63z4POtyDOv868zqzOtM6xz4Igz4M=?= =?utf-8?B?z4XOts63z4TOrs+DzrXPic69?=".as_bytes(); let subject_s = "=?utf-8?B?bW9vZGxlOiDOsc69zrHPg866z4zPgM63z4POtyDOv868zqzOtM6xz4Igz4M=?= =?utf-8?B?z4XOts63z4TOrs+DzrXPic69?=".as_bytes();
assert_eq!((&b""[..], "moodle: ανασκόπηση ομάδας συζητήσεων".to_string()), subject(subject_s).unwrap()); assert_eq!(
(
&b""[..],
"moodle: ανασκόπηση ομάδας συζητήσεων".to_string()
),
subject(subject_s).unwrap()
);
let subject_s = "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?=".as_bytes(); let subject_s =
assert_eq!("Νέο προσωπικό μήνυμα αφί".to_string(), from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()); "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?=".as_bytes();
assert_eq!(
"Νέο προσωπικό μήνυμα αφί".to_string(),
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()
);
let subject_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=".as_bytes(); let subject_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=".as_bytes();
assert_eq!("Πρόσθε".to_string(), from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()); assert_eq!(
"Πρόσθε".to_string(),
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()
);
let subject_s = "=?iso-8859-7?B?UmU6INDx/OLr5+zhIOzlIPTn7SDh9fHp4e3eIOLh8eTp4Q==?=".as_bytes(); let subject_s = "=?iso-8859-7?B?UmU6INDx/OLr5+zhIOzlIPTn7SDh9fHp4e3eIOLh8eTp4Q==?=".as_bytes();
assert_eq!("Re: Πρόβλημα με την αυριανή βαρδια".to_string(), from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()); assert_eq!(
"Re: Πρόβλημα με την αυριανή βαρδια".to_string(),
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()
);
let subject_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?= let subject_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=
=?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?= =?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?=
=?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?=".as_bytes(); =?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?="
assert_eq!((&b""[..], "Πρόσθετη εξεταστική".to_string()), subject(subject_s).unwrap()); .as_bytes();
assert_eq!(
(
&b""[..],
"Πρόσθετη εξεταστική".to_string()
),
subject(subject_s).unwrap()
);
} }
fn eat_comments(input: &str) -> String { fn eat_comments(input: &str) -> String {
let mut in_comment = false; let mut in_comment = false;
input.chars().fold(String::with_capacity(input.len()), |mut acc, x| { input
if x == '(' && !in_comment { .chars()
in_comment = true; .fold(String::with_capacity(input.len()), |mut acc, x| {
acc if x == '(' && !in_comment {
} else if x == ')' && in_comment { in_comment = true;
in_comment = false; acc
acc } else if x == ')' && in_comment {
} else if in_comment { in_comment = false;
acc acc
} else { } else if in_comment {
acc.push(x); acc acc
} } else {
}) acc.push(x);
acc
}
})
} }
#[test] #[test]
@ -317,13 +349,16 @@ fn test_eat_comments() {
let s = "Thu, 31 Aug 2017 13:43:37 +0000 (UTC)"; let s = "Thu, 31 Aug 2017 13:43:37 +0000 (UTC)";
assert_eq!(eat_comments(s), "Thu, 31 Aug 2017 13:43:37 +0000 "); assert_eq!(eat_comments(s), "Thu, 31 Aug 2017 13:43:37 +0000 ");
} }
/* /*
* Date should tokenize input and convert the tokens, * Date should tokenize input and convert the tokens,
* right now we expect input will have no extra spaces in between tokens * right now we expect input will have no extra spaces in between tokens
* *
* We should use a custom parser here*/ * We should use a custom parser here*/
pub fn date(input: &str) -> Option<chrono::DateTime<chrono::FixedOffset>> { pub fn date(input: &str) -> Option<chrono::DateTime<chrono::FixedOffset>> {
let parsed_result = subject(eat_comments(input).as_bytes()).to_full_result().unwrap().replace("-", "+"); let parsed_result = subject(eat_comments(input).as_bytes())
.to_full_result()
.unwrap()
.replace("-", "+");
chrono::DateTime::parse_from_rfc2822(parsed_result.trim()).ok() chrono::DateTime::parse_from_rfc2822(parsed_result.trim()).ok()
} }
@ -340,16 +375,16 @@ named!(pub message_id<&str>,
map_res!(complete!(delimited!(tag!("<"), take_until1!(">"), tag!(">"))), from_utf8) map_res!(complete!(delimited!(tag!("<"), take_until1!(">"), tag!(">"))), from_utf8)
); );
fn message_id_peek(input: &[u8]) -> IResult<&[u8],&str> { fn message_id_peek(input: &[u8]) -> IResult<&[u8], &str> {
let input_length = input.len(); let input_length = input.len();
if input.is_empty() { if input.is_empty() {
IResult::Incomplete(Needed::Size(1)) IResult::Incomplete(Needed::Size(1))
} 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, &x) in input.iter().take(input_length).enumerate().skip(1) { for (i, &x) in input.iter().take(input_length).enumerate().skip(1) {
if x == 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());
} }
} }
IResult::Incomplete(Needed::Unknown) IResult::Incomplete(Needed::Unknown)
@ -381,19 +416,24 @@ named_args!(pub attachments<'a>(boundary: &'a str, boundary_end: &'a str) < Vec<
fn test_attachments() { fn test_attachments() {
use std::io::Read; use std::io::Read;
let mut buffer: Vec<u8> = Vec::new(); let mut buffer: Vec<u8> = Vec::new();
let _ = std::fs::File::open("test/attachment_test").unwrap().read_to_end(&mut buffer); let _ = std::fs::File::open("test/attachment_test")
.unwrap()
.read_to_end(&mut buffer);
let boundary = "--b1_4382d284f0c601a737bb32aaeda53160--"; let boundary = "--b1_4382d284f0c601a737bb32aaeda53160--";
let boundary_len = boundary.len(); let boundary_len = boundary.len();
let (_, body) = match mail(&buffer).to_full_result() { let (_, body) = match mail(&buffer).to_full_result() {
Ok(v) => v, Ok(v) => v,
Err(_) => { panic!() } Err(_) => panic!(),
}; };
let attachments = attachments(body, &boundary[0..boundary_len-2], &boundary).to_full_result().unwrap(); let attachments = attachments(body, &boundary[0..boundary_len - 2], &boundary)
.to_full_result()
.unwrap();
assert_eq!(attachments.len(), 4); assert_eq!(attachments.len(), 4);
} }
named!(content_type_parameter< (&str, &str) >, named!(
do_parse!( content_type_parameter<(&str, &str)>,
do_parse!(
tag!(";") >> tag!(";") >>
name: terminated!(map_res!(ws!(take_until!("=")), from_utf8), tag!("=")) >> name: terminated!(map_res!(ws!(take_until!("=")), from_utf8), tag!("=")) >>
value: map_res!(ws!( value: map_res!(ws!(
@ -402,7 +442,8 @@ named!(content_type_parameter< (&str, &str) >,
( { ( {
(name, value) (name, value)
} ) } )
)); )
);
named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >, named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >,

View File

@ -29,14 +29,14 @@ use error::Result;
pub mod accounts; pub mod accounts;
pub use mailbox::accounts::Account; pub use mailbox::accounts::Account;
mod thread; mod thread;
use mailbox::thread::{Container, build_threads}; use mailbox::thread::{build_threads, Container};
use std::option::Option; use std::option::Option;
/*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<Envelope>, pub collection: Vec<Envelope>,
pub threaded_collection: Vec<usize>, pub threaded_collection: Vec<usize>,
@ -45,8 +45,7 @@ pub struct Mailbox{
} }
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<Envelope> = maildir::MaildirType::new(path).get()?; let mut collection: Vec<Envelope> = maildir::MaildirType::new(path).get()?;
collection.sort_by(|a, b| a.get_date().cmp(&b.get_date())); collection.sort_by(|a, b| a.get_date().cmp(&b.get_date()));
@ -70,12 +69,11 @@ impl Mailbox
thread.get_message().unwrap() thread.get_message().unwrap()
} }
pub fn get_mail_and_thread(&mut self, i: usize) -> (&mut Envelope, Container) { 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) -> &Container { pub fn get_thread(&self, i: usize) -> &Container {
&self.threads[i] &self.threads[i]
} }
} }

View File

@ -103,7 +103,7 @@ impl Container {
fn set_show_subject(&mut self, set: bool) -> () { fn set_show_subject(&mut self, set: bool) -> () {
self.show_subject = set; self.show_subject = set;
} }
pub fn get_show_subject(&self) -> bool { pub fn get_show_subject(&self) -> bool {
self.show_subject self.show_subject
} }
} }
@ -111,18 +111,17 @@ impl Container {
impl PartialEq for Container { impl PartialEq for Container {
fn eq(&self, other: &Container) -> bool { fn eq(&self, other: &Container) -> bool {
match (self.message, other.message) { match (self.message, other.message) {
(Some(s), Some(o)) => { (Some(s), Some(o)) => s == o,
s == o _ => self.id == other.id,
},
_ => {
self.id == other.id
}
} }
} }
} }
fn build_collection(threads: &mut Vec<Container>, id_table: &mut FnvHashMap<std::string::String, usize>, collection: &mut [Envelope]) -> () 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() { for (i, x) in collection.iter_mut().enumerate() {
let x_index; /* x's index in threads */ let x_index; /* x's index in threads */
let m_id = x.get_message_id_raw().to_string(); let m_id = x.get_message_id_raw().to_string();
@ -142,17 +141,16 @@ fn build_collection(threads: &mut Vec<Container>, id_table: &mut FnvHashMap<std:
} else { } else {
/* Create a new Container object holding this message */ /* Create a new Container object holding this message */
x_index = threads.len(); x_index = threads.len();
threads.push( threads.push(Container {
Container { message: Some(i),
message: Some(i), id: x_index,
id: x_index, parent: None,
parent: None, first_child: None,
first_child: None, next_sibling: None,
next_sibling: None, date: x.get_date(),
date: x.get_date(), indentation: 0,
indentation: 0, show_subject: true,
show_subject: true, });
});
x.set_thread(x_index); x.set_thread(x_index);
id_table.insert(m_id, x_index); id_table.insert(m_id, x_index);
} }
@ -173,44 +171,43 @@ fn build_collection(threads: &mut Vec<Container>, id_table: &mut FnvHashMap<std:
continue; continue;
} }
iasf += 1; iasf += 1;
let parent_id = let parent_id = if id_table.contains_key(r.get_raw()) {
if id_table.contains_key(r.get_raw()) { let p = id_table[r.get_raw()];
let p = id_table[r.get_raw()]; if !(threads[p].is_descendant(threads, &threads[curr_ref]) ||
if !(threads[p].is_descendant(threads, &threads[curr_ref]) || threads[curr_ref].is_descendant(threads, &threads[p]))
threads[curr_ref].is_descendant(threads, &threads[p])) { {
threads[curr_ref].parent = Some(p); threads[curr_ref].parent = Some(p);
if threads[p].first_child.is_none() { if threads[p].first_child.is_none() {
threads[p].first_child = Some(curr_ref); threads[p].first_child = Some(curr_ref);
} else { } else {
let mut child_iter = threads[p].first_child.unwrap(); let mut child_iter = threads[p].first_child.unwrap();
while threads[child_iter].next_sibling.is_some() { 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[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 { p
let idx = threads.len(); } else {
threads.push( let idx = threads.len();
Container { threads.push(Container {
message: None, message: None,
id: idx, id: idx,
parent: None, parent: None,
first_child: Some(curr_ref), first_child: Some(curr_ref),
next_sibling: None, next_sibling: None,
date: x.get_date(), date: x.get_date(),
indentation: 0, indentation: 0,
show_subject: true, show_subject: true,
}); });
if threads[curr_ref].parent.is_none() { if threads[curr_ref].parent.is_none() {
threads[curr_ref].parent = Some(idx); threads[curr_ref].parent = Some(idx);
} }
id_table.insert(r.get_raw().to_string(), idx); id_table.insert(r.get_raw().to_string(), idx);
idx idx
}; };
/* update thread date */ /* update thread date */
let mut parent_iter = parent_id; let mut parent_iter = parent_id;
'date: loop { 'date: loop {
@ -219,8 +216,12 @@ fn build_collection(threads: &mut Vec<Container>, id_table: &mut FnvHashMap<std:
p.date = x.get_date(); p.date = x.get_date();
} }
match p.parent { match p.parent {
Some(p) => { parent_iter = p; }, Some(p) => {
None => { break 'date; }, parent_iter = p;
}
None => {
break 'date;
}
} }
} }
curr_ref = parent_id; curr_ref = parent_id;
@ -229,167 +230,196 @@ fn build_collection(threads: &mut Vec<Container>, id_table: &mut FnvHashMap<std:
} }
pub fn build_threads(collection: &mut Vec<Envelope>, sent_folder: &Option<Result<Mailbox>>) -> (Vec<Container>, Vec<usize>) { pub fn build_threads(
/* To reconstruct thread information from the mails we need: */ 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 */ /* a vector to hold thread members */
let mut threads: Vec<Container> = Vec::with_capacity((collection.len() as f64 * 1.2) as usize); let mut threads: Vec<Container> = Vec::with_capacity((collection.len() as f64 * 1.2) as usize);
/* A hash table of Message IDs */ /* A hash table of Message IDs */
let mut id_table: FnvHashMap<std::string::String, usize> = FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default()); 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 /* Add each message to id_table and threads, and link them together according to the
* References / In-Reply-To headers */ * References / In-Reply-To headers */
build_collection(&mut threads, &mut id_table, collection); build_collection(&mut threads, &mut id_table, collection);
let mut idx = collection.len(); let mut idx = collection.len();
let mut tidx = threads.len(); let mut tidx = threads.len();
/* Link messages from Sent folder if they are relevant to this folder. /* Link messages from Sent folder if they are relevant to this folder.
* This means that * This means that
* - if a message from Sent is a reply to a message in this folder, we will * - 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 * add it to the threading (but not the collection; non-threading users shouldn't care
* about this) * about this)
* - if a message in this folder is a reply to a message we sent, we will add it to the * - if a message in this folder is a reply to a message we sent, we will add it to the
* threading * threading
*/ */
if let Some(ref sent_box) = *sent_folder { if let Some(ref sent_box) = *sent_folder {
if sent_box.is_ok() { if sent_box.is_ok() {
let sent_mailbox = sent_box.as_ref(); let sent_mailbox = sent_box.as_ref();
let sent_mailbox = sent_mailbox.unwrap();; let sent_mailbox = sent_mailbox.unwrap();
for x in &sent_mailbox.collection { for x in &sent_mailbox.collection {
if id_table.contains_key(x.get_message_id_raw()) || 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())) { (!x.get_in_reply_to_raw().is_empty() &&
let mut x: Envelope = (*x).clone(); id_table.contains_key(x.get_in_reply_to_raw()))
if id_table.contains_key(x.get_message_id_raw()) { {
let c = id_table[x.get_message_id_raw()]; let mut x: Envelope = (*x).clone();
if threads[c].message.is_some() { if id_table.contains_key(x.get_message_id_raw()) {
/* skip duplicate message-id, but this should be handled instead */ let c = id_table[x.get_message_id_raw()];
continue; 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;
} }
threads[c].message = Some(idx); None => {
assert!(threads[c].has_children()); break 'date;
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;
} }
}
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 */ /* Walk over the elements of id_table, and gather a list of the Container objects that have
let mut root_set = Vec::with_capacity(collection.len()); * no parents. These are the root messages of each thread */
'root_set: for v in id_table.values() { let mut root_set = Vec::with_capacity(collection.len());
if threads[*v].parent.is_none() { 'root_set: for v in id_table.values() {
if !threads[*v].has_message() && threads[*v].has_children() && !threads[threads[*v].first_child.unwrap()].has_sibling() { if threads[*v].parent.is_none() {
/* Do not promote the children if doing so would promote them to the root set if !threads[*v].has_message() && threads[*v].has_children() &&
* -- unless there is only one child, in which case, do. */ !threads[threads[*v].first_child.unwrap()].has_sibling()
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]; /* Do not promote the children if doing so would promote them to the root set
if threads[root_subject_idx].has_message() { * -- unless there is only one child, in which case, do. */
let root_subject = collection[threads[root_subject_idx].get_message().unwrap()].get_subject(); root_set.push(threads[*v].first_child.unwrap());
/* If the Container has no Message, but does have children, remove this container but continue 'root_set;
* 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() { root_set.push(*v);
threads[i].parent = None; }
} }
let indentation = root_set.sort_by(|a, b| threads[*b].date.cmp(&threads[*a].date));
if thread.has_message() {
threads[i].set_indentation(indentation); /* Group messages together by thread in a collection so we can print them together */
if !threaded.contains(&i) { let mut threaded_collection: Vec<usize> = Vec::with_capacity(collection.len());
threaded.push(i); fn build_threaded(
} threads: &mut Vec<Container>,
indentation + 1 indentation: usize,
} else if indentation > 0 { threaded: &mut Vec<usize>,
indentation i: usize,
} else { root_subject_idx: usize,
indentation + 1 collection: &[Envelope],
}; ) {
if thread.has_children() { let thread = threads[i];
let mut fc = thread.get_first_child().unwrap(); if threads[root_subject_idx].has_message() {
loop { let root_subject =
build_threaded(threads, indentation, threaded, fc, i, collection); collection[threads[root_subject_idx].get_message().unwrap()].get_subject();
let thread_ = threads[fc]; /* If the Container has no Message, but does have children, remove this container but
if !thread_.has_sibling() { * promote its children to this level (that is, splice them in to the current child
break; * list.) */
} if indentation > 0 && thread.has_message() {
fc = thread_.get_next_sibling().unwrap(); 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);
} }
} }
} }
for i in &root_set { if thread.has_parent() && !threads[thread.get_parent().unwrap()].has_message() {
build_threaded(&mut threads, 0, &mut threaded_collection, *i, *i, collection); 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) (threads, threaded_collection)
} }

View File

@ -46,10 +46,7 @@ impl Window for ErrorWindow {
ncurses::waddstr(self.win, &self.description); ncurses::waddstr(self.win, &self.description);
ncurses::wrefresh(self.win); ncurses::wrefresh(self.win);
} }
fn handle_input(&mut self, _: i32) -> () { fn handle_input(&mut self, _: i32) -> () {}
}
} }
impl ErrorWindow { impl ErrorWindow {
pub fn new(err: MeliError) -> Self { pub fn new(err: MeliError) -> Self {
@ -80,7 +77,6 @@ pub struct Index {
/* threading */ /* threading */
threaded: bool, threaded: bool,
cursor_idx: usize, cursor_idx: usize,
} }
@ -97,9 +93,13 @@ impl Window for Index {
//ncurses::wclear(self.pad); //ncurses::wclear(self.pad);
if self.threaded { if self.threaded {
let mut indentations : Vec<bool> = Vec::with_capacity(6); let mut indentations: Vec<bool> = Vec::with_capacity(6);
/* Draw threaded view. */ /* Draw threaded view. */
let mut iter = self.mailbox.threaded_collection.iter().enumerate().peekable(); let mut iter = self.mailbox
.threaded_collection
.iter()
.enumerate()
.peekable();
/* This is just a desugared for loop so that we can use .peek() */ /* This is just a desugared for loop so that we can use .peek() */
while let Some((idx, i)) = iter.next() { while let Some((idx, i)) = iter.next() {
let container = self.mailbox.get_thread(*i); let container = self.mailbox.get_thread(*i);
@ -107,10 +107,12 @@ impl Window for Index {
assert_eq!(container.has_message(), true); assert_eq!(container.has_message(), true);
match iter.peek() { match iter.peek() {
Some(&(_, x)) if self.mailbox.get_thread(*x).get_indentation() == indentation => { Some(&(_, x))
if self.mailbox.get_thread(*x).get_indentation() == indentation =>
{
indentations.pop(); indentations.pop();
indentations.push(true); indentations.push(true);
}, }
_ => { _ => {
indentations.pop(); indentations.pop();
indentations.push(false); indentations.push(false);
@ -119,23 +121,36 @@ impl Window for Index {
if container.has_sibling() { if container.has_sibling() {
indentations.pop(); indentations.pop();
indentations.push(true); indentations.push(true);
} }
let x = &self.mailbox.collection[container.get_message().unwrap()]; let x = &self.mailbox.collection[container.get_message().unwrap()];
Index::draw_entry(self.pad, x, idx, indentation, container.has_sibling(), container.has_parent(), idx == self.cursor_idx, container.get_show_subject(), Some(&indentations)); Index::draw_entry(
self.pad,
x,
idx,
indentation,
container.has_sibling(),
container.has_parent(),
idx == self.cursor_idx,
container.get_show_subject(),
Some(&indentations),
);
match iter.peek() { match iter.peek() {
Some(&(_, x)) if self.mailbox.get_thread(*x).get_indentation() > indentation => { Some(&(_, x))
if self.mailbox.get_thread(*x).get_indentation() > indentation =>
{
indentations.push(false); indentations.push(false);
}, }
Some(&(_, x)) if self.mailbox.get_thread(*x).get_indentation() < indentation => { Some(&(_, x))
if self.mailbox.get_thread(*x).get_indentation() < indentation =>
{
for _ in 0..(indentation - self.mailbox.get_thread(*x).get_indentation()) { for _ in 0..(indentation - self.mailbox.get_thread(*x).get_indentation()) {
indentations.pop(); indentations.pop();
} }
},
_ => {
} }
_ => {}
} }
} }
/* /*
for (idx, i) in self.mailbox.threaded_collection.iter().enumerate() { for (idx, i) in self.mailbox.threaded_collection.iter().enumerate() {
let container = self.mailbox.get_thread(*i); let container = self.mailbox.get_thread(*i);
@ -155,7 +170,17 @@ impl Window for Index {
*/ */
} else { } else {
for (idx, x) in self.mailbox.collection.as_mut_slice().iter().enumerate() { for (idx, x) in self.mailbox.collection.as_mut_slice().iter().enumerate() {
Index::draw_entry(self.pad, x, idx, 0, false, false, idx == self.cursor_idx, true, None); Index::draw_entry(
self.pad,
x,
idx,
0,
false,
false,
idx == self.cursor_idx,
true,
None,
);
} }
} }
ncurses::getmaxyx(self.win, &mut self.screen_height, &mut self.screen_width); ncurses::getmaxyx(self.win, &mut self.screen_height, &mut self.screen_width);
@ -176,7 +201,7 @@ impl Window for Index {
ncurses::doupdate(); ncurses::doupdate();
if self.mailbox.get_length() == 0 { if self.mailbox.get_length() == 0 {
return; return;
} }
/* Draw newly highlighted entry */ /* Draw newly highlighted entry */
ncurses::wmove(self.pad, self.cursor_idx as i32, 0); ncurses::wmove(self.pad, self.cursor_idx as i32, 0);
let pair = super::COLOR_PAIR_CURSOR; let pair = super::COLOR_PAIR_CURSOR;
@ -212,24 +237,20 @@ impl Window for Index {
ncurses::getbegyx(self.win, &mut y, &mut x); ncurses::getbegyx(self.win, &mut y, &mut x);
let prev_idx = self.cursor_idx; let prev_idx = self.cursor_idx;
match motion { match motion {
ncurses::KEY_UP => { ncurses::KEY_UP => if self.cursor_idx > 0 {
if self.cursor_idx > 0 { self.cursor_idx -= 1;
self.cursor_idx -= 1; } else {
} else { return;
return;
}
}, },
ncurses::KEY_DOWN => { ncurses::KEY_DOWN => if self.cursor_idx < self.mailbox.get_length() - 1 {
if self.cursor_idx < self.mailbox.get_length() - 1 { self.cursor_idx += 1;
self.cursor_idx += 1; } else {
} else { return;
return;
}
}, },
10 => { 10 => {
self.show_pager(); self.show_pager();
self.redraw(); self.redraw();
}, }
_ => { _ => {
return; return;
} }
@ -257,21 +278,23 @@ impl Window for Index {
let mut x = 32; let mut x = 32;
loop { loop {
match ncurses::mvwinch(self.pad, prev_idx as i32, x) & ncurses::A_CHARTEXT() { match ncurses::mvwinch(self.pad, prev_idx as i32, x) & ncurses::A_CHARTEXT() {
32 => { /* ASCII code for space */ 32 => {
/* ASCII code for space */
ncurses::wchgat(self.pad, x, 0, pair); ncurses::wchgat(self.pad, x, 0, pair);
}, }
62 => { /* ASCII code for '>' */ 62 => {
/* ASCII code for '>' */
ncurses::wchgat(self.pad, x, 0, super::COLOR_PAIR_THREAD_INDENT); ncurses::wchgat(self.pad, x, 0, super::COLOR_PAIR_THREAD_INDENT);
ncurses::wmove(self.pad, prev_idx as i32, x + 1); ncurses::wmove(self.pad, prev_idx as i32, x + 1);
break; break;
} }
_ => { _ => {
ncurses::wchgat(self.pad, x, 0, super::COLOR_PAIR_THREAD_INDENT); ncurses::wchgat(self.pad, x, 0, super::COLOR_PAIR_THREAD_INDENT);
}, }
} }
x += 1; x += 1;
} }
} }
ncurses::wchgat(self.pad, -1, 0, pair); ncurses::wchgat(self.pad, -1, 0, pair);
} }
@ -295,7 +318,8 @@ impl Window for Index {
* i-1 * i-1
*/ */
if pminrow != pminrow_prev && if pminrow != pminrow_prev &&
pminrow + self.screen_height > self.mailbox.get_length() as i32 { pminrow + self.screen_height > self.mailbox.get_length() as i32
{
/* touch dead entries in index (tell ncurses to redraw the empty lines next refresh) */ /* touch dead entries in index (tell ncurses to redraw the empty lines next refresh) */
let live_entries = self.mailbox.get_length() as i32 - pminrow; let live_entries = self.mailbox.get_length() as i32 - pminrow;
ncurses::wredrawln(self.win, live_entries, self.screen_height); ncurses::wredrawln(self.win, live_entries, self.screen_height);
@ -356,26 +380,37 @@ 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: &Envelope, i: usize, indent: usize, fn draw_entry(
has_sibling: bool, has_parent: bool, highlight: bool, win: ncurses::WINDOW,
show_subject: bool, indentations: Option<&Vec<bool>>) { mail: &Envelope,
i: usize,
indent: usize,
has_sibling: bool,
has_parent: bool,
highlight: bool,
show_subject: bool,
indentations: Option<&Vec<bool>>,
) {
/* TODO: use addchstr */ /* TODO: use addchstr */
let pair = let pair = if highlight {
if highlight { super::COLOR_PAIR_CURSOR
super::COLOR_PAIR_CURSOR } else if i % 2 == 0 {
} else if i % 2 == 0 { super::COLOR_PAIR_THREAD_EVEN
super::COLOR_PAIR_THREAD_EVEN } else {
} else { super::COLOR_PAIR_THREAD_ODD
super::COLOR_PAIR_THREAD_ODD };
};
let attr = ncurses::COLOR_PAIR(pair); let attr = ncurses::COLOR_PAIR(pair);
ncurses::wattron(win, attr); ncurses::wattron(win, attr);
ncurses::waddstr(win, &format!("{}\t", i)); ncurses::waddstr(win, &format!("{}\t", i));
ncurses::waddstr(win, &mail.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string()); ncurses::waddstr(
win,
&mail.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string(),
);
ncurses::waddch(win, '\t' as u64); ncurses::waddch(win, '\t' as u64);
for i in 0..indent { for i in 0..indent {
if indentations.is_some() && indentations.unwrap().len() > i && indentations.unwrap()[i] { if indentations.is_some() && indentations.unwrap().len() > i && indentations.unwrap()[i]
{
ncurses::wattron(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_THREAD_INDENT)); ncurses::wattron(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_THREAD_INDENT));
ncurses::waddstr(win, ""); ncurses::waddstr(win, "");
ncurses::wattroff(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_THREAD_INDENT)); ncurses::wattroff(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_THREAD_INDENT));
@ -401,7 +436,7 @@ impl Index {
} }
ncurses::wattron(win, attr); ncurses::wattron(win, attr);
if show_subject { if show_subject {
ncurses::waddstr(win, &format!("{:.85}",mail.get_subject())); ncurses::waddstr(win, &format!("{:.85}", mail.get_subject()));
/* /*
if indent == 0 { if indent == 0 {
if mail.get_subject().chars().count() < 85 { if mail.get_subject().chars().count() < 85 {
@ -426,8 +461,7 @@ impl Index {
if self.mailbox.get_length() == 0 { if self.mailbox.get_length() == 0 {
return; return;
} }
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 Envelope = 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]
@ -452,7 +486,7 @@ impl Index {
} }
} }
} }
impl Drop for Index { impl Drop for Index {
fn drop(&mut self) { fn drop(&mut self) {
ncurses::delwin(self.pad); ncurses::delwin(self.pad);
ncurses::wclear(self.win); ncurses::wclear(self.win);

View File

@ -55,7 +55,7 @@ impl TUI {
/* Set the window's background color. */ /* Set the window's background color. */
ncurses::bkgd( ncurses::bkgd(
' ' as ncurses::chtype | ncurses::COLOR_PAIR(COLOR_PAIR_DEFAULT) as ncurses::chtype, ' ' as ncurses::chtype | ncurses::COLOR_PAIR(COLOR_PAIR_DEFAULT) as ncurses::chtype,
); );
TUI {} TUI {}
} }
} }

View File

@ -35,8 +35,7 @@ pub struct Pager {
} }
impl Pager { impl Pager {
pub fn new(parent: ncurses::WINDOW, pub fn new(parent: ncurses::WINDOW, entry: &mut mailbox::Envelope) -> 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);
@ -85,16 +84,13 @@ impl Pager {
return; return;
} }
match motion { match motion {
ncurses::KEY_UP => { ncurses::KEY_UP => if self.curr_y > 0 {
if self.curr_y > 0 { self.curr_y -= 1;
self.curr_y -= 1; },
} ncurses::KEY_DOWN => if self.curr_y < self.rows && self.rows - self.curr_y > pager_size
} {
ncurses::KEY_DOWN => { self.curr_y += 1;
if self.curr_y < self.rows && self.rows - self.curr_y > pager_size { },
self.curr_y += 1;
}
}
ncurses::KEY_NPAGE => { ncurses::KEY_NPAGE => {
if self.curr_y + h < self.rows && self.rows - self.curr_y - h > pager_size { if self.curr_y + h < self.rows && self.rows - self.curr_y - h > pager_size {
self.curr_y += pager_size; self.curr_y += pager_size;
@ -106,13 +102,11 @@ impl Pager {
}; };
} }
} }
ncurses::KEY_PPAGE => { ncurses::KEY_PPAGE => if self.curr_y >= pager_size {
if self.curr_y >= pager_size { self.curr_y -= pager_size;
self.curr_y -= pager_size; } else {
} else { self.curr_y = 0
self.curr_y = 0 },
}
}
_ => {} _ => {}
} }
/* /*
@ -140,31 +134,19 @@ impl Pager {
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: ");
ncurses::waddstr( ncurses::waddstr(win, mail.get_date_as_str());
win,
mail.get_date_as_str()
);
ncurses::waddstr(win, "\n"); ncurses::waddstr(win, "\n");
i += 1; i += 1;
ncurses::waddstr(win, "From: "); ncurses::waddstr(win, "From: ");
ncurses::waddstr( ncurses::waddstr(win, mail.get_from());
win,
mail.get_from(),
);
ncurses::waddstr(win, "\n"); ncurses::waddstr(win, "\n");
i += 1; i += 1;
ncurses::waddstr(win, "To: "); ncurses::waddstr(win, "To: ");
ncurses::waddstr( ncurses::waddstr(win, mail.get_to());
win,
mail.get_to(),
);
ncurses::waddstr(win, "\n"); ncurses::waddstr(win, "\n");
i += 1; i += 1;
ncurses::waddstr(win, "Subject: "); ncurses::waddstr(win, "Subject: ");
ncurses::waddstr( ncurses::waddstr(win, mail.get_subject());
win,
mail.get_subject(),
);
ncurses::waddstr(win, "\n"); ncurses::waddstr(win, "\n");
i += 1; i += 1;
ncurses::waddstr(win, "Message-ID: "); ncurses::waddstr(win, "Message-ID: ");
@ -176,17 +158,11 @@ impl Pager {
ncurses::waddstr(win, "\n"); ncurses::waddstr(win, "\n");
i += 1; i += 1;
ncurses::waddstr(win, "References: "); ncurses::waddstr(win, "References: ");
ncurses::waddstr( ncurses::waddstr(win, &format!("{:?}", mail.get_references()));
win,
&format!("{:?}", mail.get_references()),
);
ncurses::waddstr(win, "\n"); ncurses::waddstr(win, "\n");
i += 1; i += 1;
ncurses::waddstr(win, "In-Reply-To: "); ncurses::waddstr(win, "In-Reply-To: ");
ncurses::waddstr( ncurses::waddstr(win, mail.get_in_reply_to_raw());
win,
mail.get_in_reply_to_raw(),
);
ncurses::waddstr(win, "\n"); ncurses::waddstr(win, "\n");
i += 1; i += 1;
ncurses::wattroff(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_HEADERS)); ncurses::wattroff(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_HEADERS));
@ -196,7 +172,8 @@ impl Pager {
fn print_entry_content( fn print_entry_content(
win: ncurses::WINDOW, win: ncurses::WINDOW,
mail: &mut mailbox::Envelope, 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;
/* height and width of self.win, the pager window */ /* height and width of self.win, the pager window */
@ -231,7 +208,8 @@ impl Pager {
} }
fn print_entry( fn print_entry(
win: ncurses::WINDOW, win: ncurses::WINDOW,
mail: &mut mailbox::Envelope) -> (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)
} }