various fixes

- Clippy fixes
- Rewrite header value parser
- Rewrite string allocations in header encodings
- Thread mail parsing for maildir.rs
- Split crate to lib and bin
embed
Manos Pitsidianakis 2017-09-07 23:00:08 +03:00
parent 63670259f8
commit 04ff21a55f
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
15 changed files with 600 additions and 370 deletions

View File

@ -3,6 +3,14 @@ name = "meli"
version = "0.1.0" version = "0.1.0"
authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"] authors = ["Manos Pitsidianakis <el13635@mail.ntua.gr>"]
[lib]
name = "melib"
path = "src/lib.rs"
[[bin]]
name = "meli"
path = "src/bin.rs"
[dependencies] [dependencies]
chrono = "0.4" chrono = "0.4"
xdg = "2.1.0" xdg = "2.1.0"
@ -12,6 +20,7 @@ serde = "^1.0.8"
nom = "3.2.0" nom = "3.2.0"
memmap = "*" memmap = "*"
base64 = "*" base64 = "*"
crossbeam = "^0.3.0"
[dependencies.ncurses] [dependencies.ncurses]
features = ["wide"] features = ["wide"]
@ -19,5 +28,4 @@ optional = false
version = "5.86.0" version = "5.86.0"
[profile.release] [profile.release]
#lto = true lto = true
opt-level = 2

28
benches/maildir.rs 100644
View File

@ -0,0 +1,28 @@
#![feature(test)]
extern crate melib;
use melib::mailbox::backends::MailBackend;
use melib::mailbox::backends::maildir::*;
extern crate test;
use self::test::Bencher;
#[bench]
fn bench_threads_1(b: &mut Bencher) {
b.iter(|| MaildirType::new("").get_multicore(1));
}
#[bench]
fn bench_threads_2(b: &mut Bencher) {
b.iter(|| MaildirType::new("").get_multicore(2));
}
#[bench]
fn bench_threads_3(b: &mut Bencher) {
b.iter(|| MaildirType::new("").get_multicore(3));
}
#[bench]
fn bench_threads_4(b: &mut Bencher) {
b.iter(|| MaildirType::new("").get_multicore(4));
}
#[bench]
fn bench_threads_6(b: &mut Bencher) {
b.iter(|| MaildirType::new("").get_multicore(6));
}

View File

@ -1,8 +1,8 @@
/* /*
* meli - main.rs * meli - bin.rs
* *
* Copyright 2017 Manos Pitsidianakis * Copyright 2017 Manos Pitsidianakis
* *
* This file is part of meli. * This file is part of meli.
* *
* meli is free software: you can redistribute it and/or modify * meli is free software: you can redistribute it and/or modify
@ -18,41 +18,31 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
extern crate ncurses;
pub mod mailbox;
mod ui; mod ui;
mod conf;
mod error;
use ui::index::*; use ui::index::*;
extern crate melib;
use melib::*;
use mailbox::*; use mailbox::*;
use conf::*; use conf::*;
#[macro_use] extern crate ncurses;
extern crate serde_derive;
/* parser */
#[macro_use]
extern crate nom;
extern crate chrono;
extern crate base64;
extern crate memmap;
fn main() { fn main() {
let locale_conf = ncurses::LcCategory::all; let locale_conf = ncurses::LcCategory::all;
ncurses::setlocale(locale_conf, "en_US.UTF-8"); ncurses::setlocale(locale_conf, "en_US.UTF-8");
let set = Settings::new(); let set = Settings::new();
let ui = ui::TUI::initialize(); let ui = ui::TUI::initialize();
//let mailbox = Mailbox::new("/home/epilys/Downloads/rust/nutt/Inbox4");
let mut j = 0; let mut j = 0;
let folder_length = set.accounts.get("norn").unwrap().folders.len(); let folder_length = set.accounts["norn"].folders.len();
'main : loop { 'main : loop {
ncurses::touchwin(ncurses::stdscr()); ncurses::touchwin(ncurses::stdscr());
ncurses::mv(0,0); ncurses::mv(0,0);
let mailbox = Mailbox::new(&set.accounts.get("norn").unwrap().folders[j], let mailbox = Mailbox::new(&set.accounts["norn"].folders[j],
Some(&set.accounts.get("norn").unwrap().sent_folder)); Some(&set.accounts["norn"].sent_folder));
let mut index: Box<Window> = match mailbox { let mut index: Box<Window> = match mailbox {
Ok(v) => { Ok(v) => {
Box::new(Index::new(v)) Box::new(Index::new(v))
}, },
Err(v) => { Err(v) => {
@ -93,6 +83,11 @@ fn main() {
break 'inner; break 'inner;
} }
}, },
Some(ncurses::WchResult::KeyCode(ncurses::KEY_RESIZE)) => {
eprintln!("key_resize");
index.redraw();
continue;
},
_ => {} _ => {}
} }
} }

View File

@ -2,7 +2,7 @@
* meli - configuration module. * meli - configuration module.
* *
* Copyright 2017 Manos Pitsidianakis * Copyright 2017 Manos Pitsidianakis
* *
* This file is part of meli. * This file is part of meli.
* *
* meli is free software: you can redistribute it and/or modify * meli is free software: you can redistribute it and/or modify
@ -21,7 +21,6 @@
extern crate xdg; extern crate xdg;
extern crate config; extern crate config;
use std::collections::HashMap; use std::collections::HashMap;
use std::io; use std::io;
use std::fs; use std::fs;
@ -34,10 +33,13 @@ enum MailFormat {
impl MailFormat { impl MailFormat {
pub fn from_str(x: &str) -> MailFormat { pub fn from_str(x: &str) -> MailFormat {
match x { match x.to_lowercase().as_ref() {
"maildir" | "Maildir" | "maildir" => {
"MailDir" => { MailFormat::Maildir }, MailFormat::Maildir
_ => { panic!("Unrecognizable mail format");} },
_ => {
panic!("Unrecognizable mail format")
}
} }
} }
} }
@ -78,13 +80,13 @@ impl FileSettings {
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));
match s.is_ok() { //.unwrap_or(Settings { }); if s.is_ok() {
true => { s.unwrap().deserialize().unwrap() }, s.unwrap().deserialize().unwrap()
false => { } 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().read_line(&mut buf).expect("Failed to read line");
FileSettings { ..Default::default() } }, FileSettings { ..Default::default() }
} }
} }
} }
@ -92,13 +94,13 @@ impl FileSettings {
impl Settings { impl Settings {
pub fn new() -> Settings { pub fn new() -> Settings {
let fs = FileSettings::new(); let fs = FileSettings::new();
let mut s: HashMap<String, Account> = HashMap::new(); let mut s: HashMap<String, Account> = HashMap::new();
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().next() { 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") ||
@ -110,8 +112,8 @@ impl Settings {
recurse_folders(folders, path); recurse_folders(folders, path);
} }
} }
} }
} }
}; };
let path = PathBuf::from(&x.folders); let path = PathBuf::from(&x.folders);

View File

@ -1,3 +1,23 @@
/*
* meli - error 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::error::Error; use std::error::Error;
use std::fmt; use std::fmt;
use std::result; use std::result;

33
src/lib.rs 100644
View File

@ -0,0 +1,33 @@
/*
* meli - lib.rs
*
* 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/>.
*/
pub mod mailbox;
pub mod conf;
pub mod error;
#[macro_use]
extern crate serde_derive;
/* parser */
#[macro_use]
extern crate nom;
extern crate chrono;
extern crate base64;
extern crate memmap;

View File

@ -2,7 +2,7 @@
* meli - mailbox module. * meli - mailbox module.
* *
* Copyright 2017 Manos Pitsidianakis * Copyright 2017 Manos Pitsidianakis
* *
* This file is part of meli. * This file is part of meli.
* *
* meli is free software: you can redistribute it and/or modify * meli is free software: you can redistribute it and/or modify
@ -18,20 +18,24 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
//use std::io::prelude::*;
//use std::fs::File;
use std::path::PathBuf;
use mailbox::email::Mail; use mailbox::email::Mail;
use error::{MeliError, Result}; use error::{MeliError, Result};
use mailbox::backends::MailBackend; use mailbox::backends::MailBackend;
extern crate crossbeam;
use std::path::PathBuf;
pub struct MaildirType { pub struct MaildirType {
path: String, path: String,
} }
impl MailBackend for MaildirType { impl MailBackend for MaildirType {
fn get(&self) -> Result<Vec<Mail>> { fn get(&self) -> Result<Vec<Mail>> {
self.get_multicore(4)
/*
MaildirType::is_valid(&self.path)?; MaildirType::is_valid(&self.path)?;
let mut path = PathBuf::from(&self.path); let mut path = PathBuf::from(&self.path);
path.push("cur"); path.push("cur");
@ -44,10 +48,48 @@ impl MailBackend for MaildirType {
let path = x.path(); let path = x.path();
Ok(path.to_str().unwrap().to_string()) Ok(path.to_str().unwrap().to_string())
})?; })?;
match Mail::from(e) { match Mail::from(&e) {
Some(e) => {r.push(e);}, Some(e) => {r.push(e);},
None => {} None => {}
} }
}
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"] {
p.push(d);
if !p.is_dir() {
return Err(MeliError::new(format!("{} is not a valid maildir folder", path)));
}
p.pop();
}
Ok(())
}
pub fn get_multicore(&self, cores: usize) -> 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 files: Vec<String> = Vec::with_capacity(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())
})?;
files.push(e);
/* /*
f.read_to_end(&mut buffer)?; f.read_to_end(&mut buffer)?;
@ -66,25 +108,32 @@ panic!("didn't parse"); },
*/ */
} }
let mut threads = Vec::with_capacity(cores);
if !files.is_empty() {
crossbeam::scope(|scope| {
let chunk_size = if count / cores > 0 {
count / cores
} else {
count
};
for chunk in files.chunks(chunk_size) {
let s = scope.spawn(move || {
let mut local_r:Vec<Mail> = Vec::with_capacity(chunk.len());
for e in chunk {
if let Some(e) = Mail::from(e) {
local_r.push(e);
}
}
local_r
});
threads.push(s);
}
});
}
for t in threads {
let mut result = t.join();
r.append(&mut result);
}
Ok(r) 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(())
}
}

View File

@ -1,3 +1,23 @@
/*
* meli - backends 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/>.
*/
pub mod maildir; pub mod maildir;
use mailbox::email::Mail; use mailbox::email::Mail;

View File

@ -1,4 +1,26 @@
use super::parser; /*
* meli - attachments 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 mailbox::email::parser;
use std::fmt::{Display, Formatter, Result};
/* /*
* *
@ -18,7 +40,7 @@ pub enum MultipartType {
pub enum AttachmentType { pub enum AttachmentType {
Data { tag: String }, Data { tag: String },
Text { content: String }, Text { content: String },
Multipart { of_type: MultipartType, subattachments: Vec<Box<Attachment>>, } Multipart { of_type: MultipartType, subattachments: Vec<Attachment>, }
} }
#[derive(Clone,Debug)] #[derive(Clone,Debug)]
pub enum ContentType { pub enum ContentType {
@ -27,13 +49,13 @@ pub enum ContentType {
Unsupported { tag: String }, Unsupported { tag: String },
} }
impl ::std::fmt::Display for ContentType { impl Display for ContentType {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { fn fmt(&self, f: &mut Formatter) -> Result {
match *self { match *self {
ContentType::Text => { ContentType::Text => {
write!(f, "text") write!(f, "text")
}, },
ContentType::Multipart { boundary: _ } => { ContentType::Multipart { .. } => {
write!(f, "multipart") write!(f, "multipart")
}, },
ContentType::Unsupported { tag: ref t } => { ContentType::Unsupported { tag: ref t } => {
@ -47,8 +69,8 @@ pub enum ContentSubType {
Plain, Plain,
Other { tag: String }, Other { tag: String },
} }
impl ::std::fmt::Display for ContentSubType { impl Display for ContentSubType {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { fn fmt(&self, f: &mut Formatter) -> Result {
match *self { match *self {
ContentSubType::Plain => { ContentSubType::Plain => {
write!(f, "plain") write!(f, "plain")
@ -72,7 +94,7 @@ pub struct AttachmentBuilder {
content_type: (ContentType, ContentSubType), content_type: (ContentType, ContentSubType),
content_transfer_encoding: ContentTransferEncoding, content_transfer_encoding: ContentTransferEncoding,
raw: Box<Vec<u8>>, raw: Vec<u8>,
} }
impl AttachmentBuilder { impl AttachmentBuilder {
@ -80,18 +102,18 @@ impl AttachmentBuilder {
AttachmentBuilder { AttachmentBuilder {
content_type: (ContentType::Text, ContentSubType::Plain), content_type: (ContentType::Text, ContentSubType::Plain),
content_transfer_encoding: ContentTransferEncoding::_7Bit, content_transfer_encoding: ContentTransferEncoding::_7Bit,
raw: Box::new(content.to_vec()), raw: content.to_vec(),
} }
} }
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)) => {
match ct.to_lowercase().as_ref() { match ct.to_lowercase().as_ref() {
"multipart" => { "multipart" => {
let mut boundary = None; let mut boundary = None;
for (n, v) in params { for (n, v) in params {
if n.to_lowercase() == "boundary" { if n.to_lowercase() == "boundary" {
boundary = Some(format!("--{}", v).to_string()); boundary = Some(format!("--{}--", v).to_string());
break; break;
} }
} }
@ -114,9 +136,10 @@ impl AttachmentBuilder {
self.content_type.1 = ContentSubType::Other { tag: cst.to_string() }; self.content_type.1 = ContentSubType::Other { tag: cst.to_string() };
}, },
} }
},
}, Err(v) => {
_ => {}, eprintln!("parsing error in content_type: {:?} {:?}", value, v);
}
} }
self self
} }
@ -156,10 +179,7 @@ impl AttachmentBuilder {
ContentTransferEncoding::QuotedPrintable => { ContentTransferEncoding::QuotedPrintable => {
parser::quoted_printable_text(&self.raw).to_full_result().unwrap() parser::quoted_printable_text(&self.raw).to_full_result().unwrap()
}, },
ContentTransferEncoding::_7Bit | ContentTransferEncoding::_8Bit => { ContentTransferEncoding::_7Bit | ContentTransferEncoding::_8Bit | ContentTransferEncoding::Other { .. } => {
String::from_utf8_lossy(&self.raw).into_owned()
},
ContentTransferEncoding::Other { tag: _ } => {
String::from_utf8_lossy(&self.raw).into_owned() String::from_utf8_lossy(&self.raw).into_owned()
} }
} }
@ -190,12 +210,12 @@ impl AttachmentBuilder {
} }
}, },
_ => { _ => {
panic!(); 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 } => {
@ -219,7 +239,7 @@ pub struct Attachment {
content_type: (ContentType, ContentSubType), content_type: (ContentType, ContentSubType),
content_transfer_encoding: ContentTransferEncoding, content_transfer_encoding: ContentTransferEncoding,
raw: Box<Vec<u8>>, raw: Vec<u8>,
attachment_type: AttachmentType, attachment_type: AttachmentType,
} }
@ -227,7 +247,7 @@ pub struct Attachment {
impl Attachment { impl Attachment {
fn get_text_recursive(&self, text: &mut String) { fn get_text_recursive(&self, text: &mut String) {
match self.attachment_type { match self.attachment_type {
AttachmentType::Data { tag: _ } => { 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 } => {
@ -264,8 +284,9 @@ impl Attachment {
pub fn get_tag(&self) -> String { pub fn get_tag(&self) -> String {
format!("{}/{}", self.content_type.0, self.content_type.1).to_string() format!("{}/{}", self.content_type.0, self.content_type.1).to_string()
} }
pub fn subattachments(raw: &[u8], boundary: &str) -> Vec<Box<Attachment>> { pub fn subattachments(raw: &[u8], boundary: &str) -> Vec<Attachment> {
match parser::attachments(raw, boundary, &format!("{}--", boundary)).to_full_result() { let boundary_length = boundary.len();
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 {
@ -277,24 +298,23 @@ 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 {
match name.to_lowercase().as_ref(){ match name.to_lowercase().as_ref(){
"content-type" => { "content-type" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc }); builder.content_type(value);
builder.content_type(&value);
}, },
"content-transfer-encoding" => { "content-transfer-encoding" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc }); builder.content_transfer_encoding(value);
builder.content_transfer_encoding(&value);
}, },
_ => { _ => {
}, },
} }
} }
vec.push(Box::new(builder.build())); vec.push(builder.build());
} }
vec vec
}, },

View File

@ -1,15 +1,35 @@
/*
* meli - email 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/>.
*/
mod parser;
mod attachments;
use self::attachments::*;
use std::string::String; use std::string::String;
use memmap::{Mmap, Protection}; use memmap::{Mmap, Protection};
use std; use std;
use std::cmp::Ordering; use std::cmp::Ordering;
use std::fmt; use std::fmt;
use std::option::Option; use std::option::Option;
use std::io::prelude::*; use std::io::prelude::*;
mod parser;
mod attachments;
use self::attachments::*;
use chrono; use chrono;
use chrono::TimeZone; use chrono::TimeZone;
@ -73,7 +93,7 @@ struct References {
} }
/* A very primitive mail object */ /* A very primitive mail object */
#[derive(Clone,Debug)] #[derive(Clone,Debug,Default)]
pub struct Mail { pub struct Mail {
date: String, date: String,
from: Option<String>, from: Option<String>,
@ -96,7 +116,7 @@ impl Mail {
} }
} }
pub fn get_datetime(&self) -> chrono::DateTime<chrono::FixedOffset> { 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)) 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
@ -209,9 +229,9 @@ impl Mail {
} }
} }
} }
pub fn get_references<'a>(&'a self) -> Vec<&'a 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(),
} }
} }
@ -243,21 +263,21 @@ impl Mail {
thread: 0, thread: 0,
} }
} }
pub fn from(path: std::string::String) -> Option<Self> { pub fn from(path: &str) -> Option<Self> {
let f = Mmap::open_path(path.clone(), Protection::Read).unwrap(); let f = Mmap::open_path(path.to_string(), Protection::Read).unwrap();
let file = unsafe { f.as_slice() }; let file = unsafe { f.as_slice() };
let (headers, body) = match parser::mail(file).to_full_result() { let (headers, body) = match parser::mail(file).to_full_result() {
Ok(v) => v, Ok(v) => v,
Err(_) => { Err(_) => {
eprintln!("error in parsing"); eprintln!("error in parsing mail");
let path = std::path::PathBuf::from(&path); let path = std::path::PathBuf::from(path);
let mut buffer = Vec::new(); let mut buffer = Vec::new();
let _ = std::fs::File::open(path).unwrap().read_to_end(&mut buffer); let _ = std::fs::File::open(path).unwrap().read_to_end(&mut buffer);
eprintln!("\n-------------------------------"); eprintln!("\n-------------------------------");
eprintln!("{}\n", std::string::String::from_utf8_lossy(&buffer)); eprintln!("{}\n", std::string::String::from_utf8_lossy(&buffer));
eprintln!("-------------------------------\n"); eprintln!("-------------------------------\n");
return None; } return None; }
}; };
let mut mail = Mail::new(); let mut mail = Mail::new();
@ -266,96 +286,73 @@ impl Mail {
let mut builder = AttachmentBuilder::new(body); let mut builder = AttachmentBuilder::new(body);
for (name, value) in headers { for (name, value) in headers {
if value.len() == 1 && value[0].is_empty() { if value.len() == 1 && value.is_empty() {
continue; continue;
} }
match name { match name {
"To" => { "To" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
let parse_result = parser::subject(value.as_bytes()); let parse_result = parser::subject(value.as_bytes());
let value = match parse_result.is_done() { let value = if parse_result.is_done() {
true => { parse_result.to_full_result().unwrap()
parse_result.to_full_result().unwrap() } else {
}, "".to_string()
false => {
"".to_string()
},
}; };
mail.set_to(value); mail.set_to(value);
}, },
"From" => { "From" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
let parse_result = parser::subject(value.as_bytes()); let parse_result = parser::subject(value.as_bytes());
let value = match parse_result.is_done() { let value = if parse_result.is_done() {
true => { parse_result.to_full_result().unwrap()
parse_result.to_full_result().unwrap() } else {
}, "".to_string()
false => {
"".to_string()
},
}; };
mail.set_from(value); mail.set_from(value);
}, },
"Subject" => { "Subject" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(" "); acc.push_str(x); acc });
let parse_result = parser::subject(value.trim().as_bytes()); let parse_result = parser::subject(value.trim().as_bytes());
let value = match parse_result.is_done() { let value = if parse_result.is_done() {
true => { parse_result.to_full_result().unwrap()
parse_result.to_full_result().unwrap() } else {
}, "".to_string()
false => {
"".to_string()
},
}; };
mail.set_subject(value); mail.set_subject(value);
}, },
"Message-ID" | "Message-Id" | "Message-id" | "message-id" => { "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 })); mail.set_message_id(value);
}, },
"References" => { "References" => {
let folded_value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
{ {
let parse_result = parser::references(&folded_value.as_bytes()); let parse_result = parser::references(value.as_bytes());
match parse_result.is_done() { if parse_result.is_done() {
true => { for v in parse_result.to_full_result().unwrap() {
for v in parse_result.to_full_result().unwrap() { mail.push_references(v);
mail.push_references(v); }
}
},
_ => {}
} }
} }
mail.set_references(folded_value); mail.set_references(value.to_string());
}, },
"In-Reply-To" | "In-reply-to" | "In-Reply-to" | "in-reply-to" => { "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);
mail.set_in_reply_to(&value);
in_reply_to = Some(value); }, in_reply_to = Some(value); },
"Date" => { "Date" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc }); mail.set_date(value.to_string());
mail.set_date(value.clone()); datetime = Some(value.to_string());
datetime = Some(value);
}, },
"Content-Transfer-Encoding" => { "Content-Transfer-Encoding" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc }); builder.content_transfer_encoding(value);
builder.content_transfer_encoding(&value);
}, },
"Content-Type" => { "Content-Type" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc }); builder.content_type(value);
builder.content_type(&value);
}, },
_ => {}, _ => {},
} }
}; };
match in_reply_to { if let Some(ref mut x) = in_reply_to {
Some(ref mut x) => { mail.push_references(x);
mail.push_references(x);
},
None => {},
}; };
mail.set_body(builder.build()); mail.set_body(builder.build());
if datetime.is_some() { if let Some(ref mut d) = datetime {
mail.set_datetime(parser::date(&datetime.unwrap())); mail.set_datetime(parser::date(d));
} }
Some(mail) Some(mail)

View File

@ -1,12 +1,32 @@
//use memmap::{Mmap, Protection}; /*
* meli - parser 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; use std;
use std::str::from_utf8;
use base64; use base64;
use chrono; use chrono;
use nom::le_u8; use nom::{le_u8, is_hex_digit};
/* Wow this sucks! */ /* Wow this sucks! */
named!(quoted_printable_byte<u8>, do_parse!( 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) >> p: map_res!(preceded!(tag!("="), verify!(complete!(take!(2)), |s: &[u8]| is_hex_digit(s[0]) && is_hex_digit(s[1]) )), from_utf8) >>
( { ( {
u8::from_str_radix(p, 16).unwrap() u8::from_str_radix(p, 16).unwrap()
} ))); } )));
@ -22,51 +42,55 @@ named!(quoted_printable_byte<u8>, do_parse!(
* Tue, 5 Jan 2016 21:30:44 +0100 (CET) * Tue, 5 Jan 2016 21:30:44 +0100 (CET)
*/ */
/* use nom::{IResult,Needed,ErrorKind};
* 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> */ fn header_value(input: &[u8]) -> IResult<&[u8], &str> {
named!(value<Vec<&str>>, if input.is_empty() || input[0] == b'\n' {
do_parse!( IResult::Incomplete(Needed::Size(1))
head: map_res!(terminated!(take_until!("\n"), tag!("\n")), std::str::from_utf8) >> } else {
tail: many0!(valuelist) >> let input_len = input.len();
( { for (i, x) in input.iter().enumerate() {
let tail_len = tail.len(); if *x == b'\n' {
let tail: Vec<&str> = tail.iter().map(|v| { v.trim()}).collect(); if (i + 1) < input_len &&
let mut result = Vec::with_capacity(1 + tail.len()); ((input[i+1] != b' ' && input[i+1] != b'\t') || input[i+1] == b'\n') {
result.push(head.trim()); return match from_utf8(&input[0..i]) {
if tail_len == 1 && tail[0] == "" { Ok(v) => {
result IResult::Done(&input[(i+1)..], v)
} else { },
tail.iter().fold(result, |mut acc, x| { acc.push(x); acc}) Err(_) => {
IResult::Error(error_code!(ErrorKind::Custom(43)))
},
}
} else if i + 1 > input_len {
return IResult::Incomplete(Needed::Size(1));
}
} }
} ) }
)); IResult::Error(error_code!(ErrorKind::Custom(43)))
}
}
/* Parse the name part of the header -> &str */ /* Parse the name part of the header -> &str */
named!(name<&str>, named!(name<&str>,
terminated!(verify!(map_res!(take_until1!(":"), std::str::from_utf8), | v: &str | { !v.contains("\n")} ), tag!(":"))); verify!(map_res!(take_until1!(":"), from_utf8), | v: &str | !v.contains('\n') ));
/* Parse a single header as a tuple -> (&str, Vec<&str>) */ /* Parse a single header as a tuple -> (&str, Vec<&str>) */
named!(header<(&str, std::vec::Vec<&str>)>, named!(header<(&str, &str)>,
pair!(complete!(name), complete!(value))); separated_pair!(complete!(name), ws!(tag!(":")), complete!(header_value)));
/* Parse all headers -> Vec<(&str, Vec<&str>)> */ /* Parse all headers -> Vec<(&str, Vec<&str>)> */
named!(headers<std::vec::Vec<(&str, std::vec::Vec<&str>)>>, named!(headers<std::vec::Vec<(&str, &str)>>,
many1!(complete!(header))); many1!(complete!(header)));
named!(pub mail<(std::vec::Vec<(&str, std::vec::Vec<&str>)>, &[u8])>, named!(pub mail<(std::vec::Vec<(&str, &str)>, &[u8])>,
separated_pair!(headers, tag!("\n"), take_while!(call!(|_| { true })))); separated_pair!(headers, tag!("\n"), take_while!(call!(|_| { true }))));
named!(pub attachment<(std::vec::Vec<(&str, std::vec::Vec<&str>)>, &[u8])>, named!(pub attachment<(std::vec::Vec<(&str, &str)>, &[u8])>,
do_parse!( do_parse!(
opt!(is_a!(" \n\t\r")) >> opt!(is_a!(" \n\t\r")) >>
pair: pair!(many0!(complete!(header)), take_while!(call!(|_| { true }))) >> pair: pair!(many0!(complete!(header)), take_while!(call!(|_| { true }))) >>
( { pair } ))); ( { pair } )));
/* try chrono parse_from_str with several formats /* try chrono parse_from_str with several formats
* https://docs.rs/chrono/0.4.0/chrono/struct.DateTime.html#method.parse_from_str * https://docs.rs/chrono/0.4.0/chrono/struct.DateTime.html#method.parse_from_str
*/ */
@ -75,55 +99,69 @@ named!(pub attachment<(std::vec::Vec<(&str, std::vec::Vec<&str>)>, &[u8])>,
/* Encoded words /* Encoded words
*"=?charset?encoding?encoded text?=". *"=?charset?encoding?encoded text?=".
*/ */
named!(utf8_token_base64<String>, do_parse!( named!(utf8_token_base64<Vec<u8>>, do_parse!(
encoded: complete!(delimited!(tag_no_case!("=?UTF-8?B?"), take_until1!("?="), tag!("?="))) >> encoded: complete!(delimited!(tag_no_case!("=?UTF-8?B?"), take_until1!("?="), tag!("?="))) >>
( { ( {
match base64::decode(encoded) { match base64::decode(encoded) {
Ok(ref v) => { String::from_utf8_lossy(v).into_owned() Ok(v) => {
v
},
Err(_) => {
encoded.to_vec()
}, },
Err(_) => { String::from_utf8_lossy(encoded).into_owned() }
} }
} ) } )
)); ));
named!(utf8_token_quoted_p_raw<&[u8], &[u8]>, named!(utf8_token_quoted_p_raw<&[u8], &[u8]>,
complete!(delimited!(tag_no_case!("=?UTF-8?q?"), take_until1!("?="), tag!("?=")))); complete!(delimited!(tag_no_case!("=?UTF-8?q?"), take_until1!("?="), tag!("?="))));
//named!(utf8_token_quoted_p<String>, escaped_transform!(call!(alpha), '=', quoted_printable_byte)); //named!(utf8_token_quoted_p<String>, escaped_transform!(call!(alpha), '=', quoted_printable_byte));
named!(utf8_token_quoted_p<String>, do_parse!( named!(qp_underscore_header<u8>,
do_parse!(tag!("_") >> ( { b' ' } )));
named!(utf8_token_quoted_p<Vec<u8>>, do_parse!(
raw: call!(utf8_token_quoted_p_raw) >> raw: call!(utf8_token_quoted_p_raw) >>
( { ( {
named!(get_bytes<Vec<u8>>, dbg!(many0!(alt!(quoted_printable_byte | le_u8)))); named!(get_bytes<Vec<u8>>, many0!(alt_complete!(quoted_printable_byte | qp_underscore_header | le_u8)));
let bytes = get_bytes(raw).to_full_result().unwrap(); get_bytes(raw).to_full_result().unwrap()
String::from_utf8_lossy(&bytes).into_owned()
} ))); } )));
named!(utf8_token<String>, alt_complete!( named!(utf8_token<Vec<u8>>, alt_complete!(
utf8_token_base64 | utf8_token_base64 |
call!(utf8_token_quoted_p))); call!(utf8_token_quoted_p)));
named!(utf8_token_list<String>, ws!(do_parse!( named!(utf8_token_list<String>, ws!(do_parse!(
list: separated_nonempty_list!(complete!(tag!(" ")), utf8_token) >> list: separated_nonempty_list!(complete!(tag!(" ")), utf8_token) >>
( { ( {
let list_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc }); let list_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc });
list.iter().fold(String::with_capacity(list_len), |mut acc, x| { acc.push_str(x); acc}) let bytes = list.iter().fold(Vec::with_capacity(list_len), |mut acc, x| { acc.append(&mut x.clone()); acc});
String::from_utf8_lossy(&bytes).into_owned()
} ) } )
))); )));
named!(ascii_token<String>, do_parse!( named!(ascii_token<String>, do_parse!(
word: alt!(terminated!(take_until1!("=?"), peek!(tag_no_case!("=?UTF-8?"))) | take_while!(call!(|_| { true }))) >> word: alt!(terminated!(take_until1!("=?"), peek!(tag_no_case!("=?UTF-8?"))) | take_while!(call!(|_| { true }))) >>
( { ( {
String::from_utf8_lossy(word).into_owned() String::from_utf8_lossy(word).into_owned()
} ))); } )));
/* Lots of copying here. TODO: fix it */ /* Lots of copying here. TODO: fix it */
named!(pub subject<String>, ws!(do_parse!( named!(pub subject<String>, ws!(do_parse!(
list: many0!(alt_complete!( utf8_token_list | ascii_token)) >> list: many0!(alt_complete!( utf8_token_list | ascii_token)) >>
( { ( {
let list_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc }); let string_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc }) + list.len() - 1;
let s = list.iter().fold(String::with_capacity(list_len), |mut acc, x| { acc.push_str(x); acc.push_str(" "); acc}); let list_len = list.len();
s.trim().to_string() let mut i = 0;
list.iter().fold(String::with_capacity(string_len),
|acc, x| {
let mut acc = acc + &x.replace("\n", "");
if i != list_len - 1 {
acc.push_str(" ");
i+=1;
}
acc
})
} ) } )
))); )));
@ -159,27 +197,33 @@ 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, right now we expect input will have no extra /*
* spaces in between tokens */ * Date should tokenize input and convert the tokens,
* right now we expect input will have no extra spaces in between tokens
*
* 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>> {
chrono::DateTime::parse_from_rfc2822(eat_comments(input).trim()).ok() let parsed_result = subject(eat_comments(input).as_bytes()).to_full_result().unwrap().replace("-", "+");
chrono::DateTime::parse_from_rfc2822(parsed_result.trim()).ok()
} }
#[test] #[test]
fn test_date() { fn test_date() {
let s = "Thu, 31 Aug 2017 13:43:37 +0000 (UTC)"; let s = "Thu, 31 Aug 2017 13:43:37 +0000 (UTC)";
let _s = "Thu, 31 Aug 2017 13:43:37 +0000"; let _s = "Thu, 31 Aug 2017 13:43:37 +0000";
let __s = "=?utf-8?q?Thu=2C_31_Aug_2017_13=3A43=3A37_-0000?=";
assert_eq!(date(s).unwrap(), date(_s).unwrap()); assert_eq!(date(s).unwrap(), date(_s).unwrap());
assert_eq!(date(_s).unwrap(), date(__s).unwrap());
} }
named!(pub message_id<&str>, named!(pub message_id<&str>,
map_res!(complete!(delimited!(tag!("<"), take_until1!(">"), tag!(">"))), std::str::from_utf8) map_res!(complete!(delimited!(tag!("<"), take_until1!(">"), tag!(">"))), from_utf8)
); );
named!(pub references<Vec<&str>>, many0!(preceded!(is_not!("<"), message_id))); named!(pub references<Vec<&str>>, many0!(preceded!(is_not!("<"), message_id)));
named_args!(pub attachments<'a>(boundary: &'a str, boundary_end: &'a str) < Vec<&'this_is_probably_unique_i_hope_please [u8]> >, named_args!(pub attachments<'a>(boundary: &'a str, boundary_end: &'a str) < Vec<&'this_is_probably_unique_i_hope_please [u8]> >,
dbg!(alt_complete!(do_parse!( alt_complete!(do_parse!(
take_until!(boundary) >> take_until!(boundary) >>
vecs: many0!(complete!(do_parse!( vecs: many0!(complete!(do_parse!(
tag!(boundary) >> tag!(boundary) >>
@ -196,33 +240,41 @@ named_args!(pub attachments<'a>(boundary: &'a str, boundary_end: &'a str) < Vec<
take_until!(boundary_end) >> take_until!(boundary_end) >>
tag!(boundary_end) >> tag!(boundary_end) >>
( { Vec::<&[u8]>::new() } )) ( { Vec::<&[u8]>::new() } ))
))); ));
#[test] #[test]
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 (_, 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!() }
}; };
//eprintln!("{:?}",std::str::from_utf8(body)); let attachments = attachments(body, &boundary[0..boundary_len-2], &boundary).to_full_result().unwrap();
let attachments = attachments(body, boundary).to_full_result().unwrap();
assert_eq!(attachments.len(), 4); assert_eq!(attachments.len(), 4);
} }
named!(content_type_parameter< (&str, &str) >,
named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >,
do_parse!( do_parse!(
_type: map_res!(take_until!("/"), std::str::from_utf8) >> tag!(";") >>
name: terminated!(map_res!(ws!(take_until!("=")), from_utf8), tag!("=")) >>
value: map_res!(ws!(
alt_complete!(delimited!(tag!("\""), take_until!("\""), tag!("\"")) | is_not!(";"))),
from_utf8) >>
( {
(name, value)
} )
));
named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >,
do_parse!(
_type: map_res!(take_until!("/"), from_utf8) >>
tag!("/") >> tag!("/") >>
_subtype: map_res!(is_not!(";"), std::str::from_utf8) >> _subtype: map_res!(is_not!(";"), from_utf8) >>
parameters: many0!(preceded!(tag!(";"), pair!( parameters: many0!(complete!(content_type_parameter)) >>
terminated!(map_res!(ws!(take_until!("=")), std::str::from_utf8), tag!("=")),
map_res!(ws!(alt_complete!(
delimited!(tag!("\""), take_until!("\""), tag!("\"")) | is_not!(";")
)), std::str::from_utf8)))) >>
( { ( {
(_type, _subtype, parameters) (_type, _subtype, parameters)
} ) } )

View File

@ -19,32 +19,25 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
//use std::cmp::Ordering; pub mod email;
//use std::fmt; pub use self::email::*;
/* Mail backends. Currently only maildir is supported */
pub mod backends;
use mailbox::backends::MailBackend;
use mailbox::backends::maildir;
use error::Result;
use std::option::Option; use std::option::Option;
use std::collections::HashMap; use std::collections::HashMap;
use std; use std;
pub mod email;
pub use self::email::*;
/* Mail backends. Currently only maildir is supported */
mod backends;
use mailbox::backends::MailBackend;
use mailbox::backends::maildir;
use error::Result;
type UnixTimestamp = i64; type UnixTimestamp = i64;
/*a Mailbox represents a folder of mail. Currently only Maildir is supported.*/ /*a Mailbox represents a folder of mail. Currently only Maildir is supported.*/
#[derive(Debug)] #[derive(Debug)]
pub struct Mailbox{ pub struct Mailbox{
pub path: String, pub path: String,
pub collection: Box<Vec<Mail>>, pub collection: Vec<Mail>,
pub threaded_collection: Vec<usize>, pub threaded_collection: Vec<usize>,
threads: Vec<Thread>, threads: Vec<Thread>,
length: usize, length: usize,
@ -102,27 +95,22 @@ impl Thread {
pub fn get_indentation(&self) -> usize { pub fn get_indentation(&self) -> usize {
self.indentation self.indentation
} }
fn is_descendant(&self, threads: &Vec<Thread>, other: &Thread) -> bool { fn is_descendant(&self, threads: &[Thread], other: &Thread) -> bool {
if self == other { if self == other {
return true; return true;
} }
match self.first_child {
Some(v) => { if let Some(v) = self.first_child {
if threads[v].is_descendant(threads, other) { if threads[v].is_descendant(threads, other) {
return true; return true;
} }
},
None => {}
}; };
match self.next_sibling { if let Some(v) = self.next_sibling {
Some(v) => { if threads[v].is_descendant(threads, other) {
if threads[v].is_descendant(threads, other) { return true;
return true; }
}
},
None => {}
}; };
return false; false
} }
fn set_show_subject(&mut self, set: bool) -> () { fn set_show_subject(&mut self, set: bool) -> () {
self.show_subject = set; self.show_subject = set;
@ -145,12 +133,12 @@ impl PartialEq for Thread {
} }
} }
fn build_collection(threads: &mut Vec<Thread>, id_table: &mut HashMap<std::string::String, usize>, collection: &mut Box<Vec<Mail>>) -> () { fn build_collection(threads: &mut Vec<Thread>, id_table: &mut HashMap<std::string::String, usize>, collection: &mut [Mail]) -> () {
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();
if id_table.contains_key(&m_id) { if id_table.contains_key(&m_id) {
let t = *(id_table.get(&m_id).unwrap()); let t = id_table[&m_id];
/* the already existing Thread should be empty, since we're /* the already existing Thread should be empty, since we're
* seeing this message for the first time */ * seeing this message for the first time */
if threads[t].message.is_some() { if threads[t].message.is_some() {
@ -190,12 +178,12 @@ fn build_collection(threads: &mut Vec<Thread>, id_table: &mut HashMap<std::strin
* 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. * Do not add a link if adding that link would introduce a loop: that is, before asserting A->B, search down the children of B to see if A is reachable, and also search down the children of A to see if B is reachable. If either is already reachable as a child of the other, don't add the link.
*/ */
let mut curr_ref = x_index; let mut curr_ref = x_index;
'ref_loop: for &r in x.get_references().iter().rev() { for &r in x.get_references().iter().rev() {
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.get(r.get_raw()).unwrap()); 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);
@ -229,7 +217,7 @@ fn build_collection(threads: &mut Vec<Thread>, id_table: &mut HashMap<std::strin
/* update thread date */ /* update thread date */
let mut parent_iter = parent_id; let mut parent_iter = parent_id;
'date: loop { 'date: loop {
let mut p = &mut threads[parent_iter]; let p = &mut threads[parent_iter];
if p.date < x.get_date() { if p.date < x.get_date() {
p.date = x.get_date(); p.date = x.get_date();
} }
@ -248,7 +236,7 @@ fn build_collection(threads: &mut Vec<Thread>, id_table: &mut HashMap<std::strin
impl Mailbox { impl Mailbox {
pub fn new(path: &str, sent_folder: Option<&str>) -> Result<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()?); let mut collection: Vec<Mail> = maildir::MaildirType::new(path).get()?;
/* To reconstruct thread information from the mails we need: */ /* To reconstruct thread information from the mails we need: */
/* a vector to hold thread members */ /* a vector to hold thread members */
@ -278,7 +266,7 @@ impl Mailbox {
idx += 1; idx += 1;
} }
if id_table.contains_key(x.get_message_id_raw()) { if id_table.contains_key(x.get_message_id_raw()) {
let c = *(id_table.get(x.get_message_id_raw()).unwrap()); let c = id_table[x.get_message_id_raw()];
if threads[c].message.is_some() { if threads[c].message.is_some() {
/* skip duplicate message-id, but this should be handled instead */ /* skip duplicate message-id, but this should be handled instead */
continue; continue;
@ -289,7 +277,7 @@ impl Mailbox {
x.set_thread(c); x.set_thread(c);
} }
if !x.get_in_reply_to_raw().is_empty() && id_table.contains_key(x.get_in_reply_to_raw()) { 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 p = id_table[x.get_in_reply_to_raw()];
let c = if !id_table.contains_key(x.get_message_id_raw()) { let c = if !id_table.contains_key(x.get_message_id_raw()) {
threads.push( threads.push(
Thread { Thread {
@ -307,7 +295,7 @@ impl Mailbox {
tidx += 1; tidx += 1;
tidx - 1 tidx - 1
} else { } else {
*(id_table.get(x.get_message_id_raw()).unwrap()) id_table[x.get_message_id_raw()]
}; };
threads[c].parent = Some(p); threads[c].parent = Some(p);
if threads[p].is_descendant(&threads, &threads[c]) || if threads[p].is_descendant(&threads, &threads[c]) ||
@ -328,7 +316,7 @@ impl Mailbox {
/* update thread date */ /* update thread date */
let mut parent_iter = p; let mut parent_iter = p;
'date: loop { 'date: loop {
let mut p = &mut threads[parent_iter]; let p = &mut threads[parent_iter];
if p.date < x.get_date() { if p.date < x.get_date() {
p.date = x.get_date(); p.date = x.get_date();
} }
@ -343,7 +331,7 @@ impl Mailbox {
/* Walk over the elements of id_table, and gather a list of the Thread objects that have /* Walk over the elements of id_table, and gather a list of the Thread objects that have
* no parents. These are the root messages of each thread */ * no parents. These are the root messages of each thread */
let mut root_set = Vec::with_capacity(collection.len()); let mut root_set = Vec::with_capacity(collection.len());
'root_set: for (_,v) in id_table.iter() { 'root_set: for v in id_table.values() {
if threads[*v].parent.is_none() { if threads[*v].parent.is_none() {
if !threads[*v].has_message() && threads[*v].has_children() && !threads[threads[*v].first_child.unwrap()].has_sibling() { 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 /* Do not promote the children if doing so would promote them to the root set
@ -352,13 +340,13 @@ impl Mailbox {
continue 'root_set; continue 'root_set;
} }
root_set.push(*v); root_set.push(*v);
} }
} }
root_set.sort_by(|a, b| threads[*b].date.cmp(&threads[*a].date)); 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 */ /* 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()); 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>) { fn build_threaded(threads: &mut Vec<Thread>, indentation: usize, threaded: &mut Vec<usize>, i: usize, root_subject_idx: usize, collection: &[Mail]) {
let thread = threads[i]; let thread = threads[i];
if threads[root_subject_idx].has_message() { if threads[root_subject_idx].has_message() {
let root_subject = collection[threads[root_subject_idx].get_message().unwrap()].get_subject(); let root_subject = collection[threads[root_subject_idx].get_message().unwrap()].get_subject();
@ -375,7 +363,7 @@ impl Mailbox {
if thread.has_parent() && !threads[thread.get_parent().unwrap()].has_message() { if thread.has_parent() && !threads[thread.get_parent().unwrap()].has_message() {
threads[i].parent = None; threads[i].parent = None;
} }
let indentation = let indentation =
if thread.has_message() { if thread.has_message() {
threads[i].set_indentation(indentation); threads[i].set_indentation(indentation);
if !threaded.contains(&i) { if !threaded.contains(&i) {
@ -421,8 +409,7 @@ impl Mailbox {
thread.get_message().unwrap() thread.get_message().unwrap()
} }
pub fn get_mail_and_thread(&mut self, i: usize) -> (&mut Mail, Thread) { pub fn get_mail_and_thread(&mut self, i: usize) -> (&mut Mail, Thread) {
let x = &mut self.collection.as_mut_slice()[i];
let ref mut x = self.collection.as_mut_slice()[i];
let thread = self.threads[x.get_thread()]; let thread = self.threads[x.get_thread()];
(x, thread) (x, thread)
} }

View File

@ -2,7 +2,7 @@
* meli - ui module. * meli - ui module.
* *
* Copyright 2017 Manos Pitsidianakis * Copyright 2017 Manos Pitsidianakis
* *
* This file is part of meli. * This file is part of meli.
* *
* meli is free software: you can redistribute it and/or modify * meli is free software: you can redistribute it and/or modify
@ -18,17 +18,16 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
extern crate ncurses;
extern crate chrono;
use mailbox::email::Mail; use mailbox::email::Mail;
use mailbox::*; use mailbox::*;
use error::MeliError; use error::MeliError;
use std::error::Error;
//use self::chrono::NaiveDateTime; extern crate ncurses;
use std::error::Error;
pub trait Window { pub trait Window {
fn draw(&mut self) -> (); fn draw(&mut self) -> ();
fn redraw(&mut self) -> ();
fn handle_input(&mut self, input: i32) -> (); fn handle_input(&mut self, input: i32) -> ();
} }
@ -43,6 +42,10 @@ 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 redraw(&mut self) -> () {
ncurses::waddstr(self.win, &self.description);
ncurses::wrefresh(self.win);
}
fn handle_input(&mut self, _: i32) -> () { fn handle_input(&mut self, _: i32) -> () {
@ -98,43 +101,38 @@ impl Window for Index {
/* 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() */
loop { while let Some((idx, i)) = iter.next() {
match iter.next() { let container = self.mailbox.get_thread(*i);
Some((idx, i)) => { let indentation = container.get_indentation();
let container = self.mailbox.get_thread(*i);
let indentation = container.get_indentation();
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);
} }
} }
if container.has_sibling() { if container.has_sibling() {
indentations.pop();
indentations.push(true);
}
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));
match iter.peek() {
Some(&(_, x)) if self.mailbox.get_thread(*x).get_indentation() > indentation => {
indentations.push(false);
},
Some(&(_, x)) if self.mailbox.get_thread(*x).get_indentation() < indentation => {
for _ in 0..(indentation - self.mailbox.get_thread(*x).get_indentation()) {
indentations.pop(); indentations.pop();
indentations.push(true);
}
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));
match iter.peek() {
Some(&(_, x)) if self.mailbox.get_thread(*x).get_indentation() > indentation => {
indentations.push(false);
},
Some(&(_, x)) if self.mailbox.get_thread(*x).get_indentation() < indentation => {
for _ in 0..(indentation - self.mailbox.get_thread(*x).get_indentation()) {
indentations.pop();
}
},
_ => {
}
} }
}, },
None => break, _ => {
}
} }
} }
/* /*
@ -173,7 +171,27 @@ impl Window for Index {
self.screen_width - 1, self.screen_width - 1,
); );
} }
fn redraw(&mut self) -> () {
if self.mailbox.get_length() == 0 {
return;
}
ncurses::getmaxyx(self.win, &mut self.screen_height, &mut self.screen_width);
let mut x = 0;
let mut y = 0;
ncurses::getbegyx(self.win, &mut y, &mut x);
let pminrow =
(self.cursor_idx as i32).wrapping_div(self.screen_height) * self.screen_height;
ncurses::touchline(self.pad, 1, 1); ncurses::prefresh(
self.pad,
pminrow,
0,
y,
x,
self.screen_height - 1,
self.screen_width - 1,
);
ncurses::wrefresh(self.win);
}
fn handle_input(&mut self, motion: i32) { fn handle_input(&mut self, motion: i32) {
if self.mailbox.get_length() == 0 { if self.mailbox.get_length() == 0 {
return; return;
@ -203,7 +221,7 @@ impl Window for Index {
}, },
10 => { 10 => {
self.show_pager(); self.show_pager();
self.draw(); self.redraw();
}, },
_ => { _ => {
return; return;
@ -212,18 +230,17 @@ impl Window for Index {
/* 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);
/* Borrow x from self.mailbox in separate scopes or else borrow checker complains */ let pair = super::COLOR_PAIR_CURSOR;
{ ncurses::wchgat(self.pad, -1, 0, pair);
let pair = super::COLOR_PAIR_CURSOR;
ncurses::wchgat(self.pad, -1, 0, pair);
}
/* Draw previous highlighted entry normally */ /* Draw previous highlighted entry normally */
ncurses::wmove(self.pad, prev_idx as i32, 0); ncurses::wmove(self.pad, prev_idx as i32, 0);
{ {
let pair = match self.threaded { let pair = if self.threaded && prev_idx % 2 == 0 {
true if prev_idx % 2 == 0 => super::COLOR_PAIR_THREAD_EVEN, super::COLOR_PAIR_THREAD_EVEN
true => super::COLOR_PAIR_THREAD_ODD, } else if self.threaded {
false => super::COLOR_PAIR_DEFAULT, super::COLOR_PAIR_THREAD_ODD
} else {
super::COLOR_PAIR_DEFAULT
}; };
ncurses::wchgat(self.pad, 32, 0, pair); ncurses::wchgat(self.pad, 32, 0, pair);
ncurses::wmove(self.pad, prev_idx as i32, 32); ncurses::wmove(self.pad, prev_idx as i32, 32);
@ -299,7 +316,7 @@ impl Index {
ncurses::getmaxyx(win, &mut screen_height, &mut screen_width); ncurses::getmaxyx(win, &mut screen_height, &mut screen_width);
//eprintln!("length is {}\n", length); //eprintln!("length is {}\n", length);
let mailbox_length = mailbox.get_length(); let mailbox_length = mailbox.get_length();
let pad = ncurses::newpad(mailbox_length as i32, screen_width); let pad = ncurses::newpad(mailbox_length as i32, 1500);
ncurses::wbkgd( ncurses::wbkgd(
pad, pad,
' ' as ncurses::chtype | ' ' as ncurses::chtype |
@ -403,14 +420,12 @@ impl Index {
} }
ncurses::getmaxyx(self.win, ncurses::getmaxyx(self.win,
&mut self.screen_height, &mut self.screen_width); &mut self.screen_height, &mut self.screen_width);
let x: &mut Mail; let x: &mut Mail = if self.threaded {
if self.threaded {
let i = self.mailbox.get_threaded_mail(self.cursor_idx); let i = self.mailbox.get_threaded_mail(self.cursor_idx);
x = &mut self.mailbox.collection[i]; &mut self.mailbox.collection[i]
} else { } else {
x = &mut self.mailbox.collection[self.cursor_idx]; &mut self.mailbox.collection[self.cursor_idx]
} };
let mut pager = super::pager::Pager::new(self.win, x); let mut pager = super::pager::Pager::new(self.win, x);
pager.scroll(ncurses::KEY_DOWN); pager.scroll(ncurses::KEY_DOWN);
pager.scroll(ncurses::KEY_UP); pager.scroll(ncurses::KEY_UP);

View File

@ -2,7 +2,7 @@
* meli - ui module. * meli - ui module.
* *
* Copyright 2017 Manos Pitsidianakis * Copyright 2017 Manos Pitsidianakis
* *
* This file is part of meli. * This file is part of meli.
* *
* meli is free software: you can redistribute it and/or modify * meli is free software: you can redistribute it and/or modify
@ -18,9 +18,12 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
extern crate ncurses;
pub mod index; pub mod index;
pub mod pager; pub mod pager;
extern crate ncurses;
/* Color pairs; foreground && background. */ /* Color pairs; foreground && background. */
pub static COLOR_PAIR_DEFAULT: i16 = 1; pub static COLOR_PAIR_DEFAULT: i16 = 1;
pub static COLOR_PAIR_CURSOR: i16 = 2; pub static COLOR_PAIR_CURSOR: i16 = 2;
@ -32,29 +35,29 @@ pub static COLOR_PAIR_THREAD_EVEN: i16 = 6;
pub struct TUI; pub struct TUI;
impl TUI { impl TUI {
pub fn initialize() -> Self { pub fn initialize() -> Self {
/* start ncurses */ /* start ncurses */
ncurses::initscr(); ncurses::initscr();
ncurses::keypad(ncurses::stdscr(), true); ncurses::keypad(ncurses::stdscr(), true);
ncurses::noecho(); ncurses::noecho();
ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE); ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
/* Start colors. */ /* Start colors. */
ncurses::start_color(); ncurses::start_color();
ncurses::init_pair(COLOR_PAIR_DEFAULT, 15, 0); ncurses::init_pair(COLOR_PAIR_DEFAULT, 15, 0);
ncurses::init_pair(COLOR_PAIR_CURSOR, 251, 235); ncurses::init_pair(COLOR_PAIR_CURSOR, 251, 235);
ncurses::init_pair(COLOR_PAIR_HEADERS, 33, 0); ncurses::init_pair(COLOR_PAIR_HEADERS, 33, 0);
ncurses::init_pair(COLOR_PAIR_THREAD_INDENT, 5, 0); ncurses::init_pair(COLOR_PAIR_THREAD_INDENT, 5, 0);
ncurses::init_pair(COLOR_PAIR_THREAD_ODD, 15, 0); ncurses::init_pair(COLOR_PAIR_THREAD_ODD, 15, 0);
ncurses::init_pair(COLOR_PAIR_THREAD_EVEN, 15, 233); ncurses::init_pair(COLOR_PAIR_THREAD_EVEN, 15, 233);
/* 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 {}
} }
} }
impl Drop for TUI { impl Drop for TUI {
fn drop(&mut self) { fn drop(&mut self) {

View File

@ -2,7 +2,7 @@
* meli - ui module. * meli - ui module.
* *
* Copyright 2017 Manos Pitsidianakis * Copyright 2017 Manos Pitsidianakis
* *
* This file is part of meli. * This file is part of meli.
* *
* meli is free software: you can redistribute it and/or modify * meli is free software: you can redistribute it and/or modify
@ -18,9 +18,10 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
extern crate ncurses;
use super::super::mailbox; use mailbox;
extern crate ncurses;
/* Pager represents the part of the UI that shows the mail headers and body for /* Pager represents the part of the UI that shows the mail headers and body for
* viewing */ * viewing */
@ -148,29 +149,29 @@ impl Pager {
ncurses::waddstr(win, "From: "); ncurses::waddstr(win, "From: ");
ncurses::waddstr( ncurses::waddstr(
win, win,
&mail.get_from(), 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, win,
&mail.get_to(), 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, win,
&mail.get_subject(), 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: ");
ncurses::waddstr( ncurses::waddstr(
win, win,
&mail.get_message_id_raw(), mail.get_message_id_raw(),
//&mail.get_message_id(), //mail.get_message_id(),
); );
ncurses::waddstr(win, "\n"); ncurses::waddstr(win, "\n");
i += 1; i += 1;
@ -184,7 +185,7 @@ impl Pager {
ncurses::waddstr(win, "In-Reply-To: "); ncurses::waddstr(win, "In-Reply-To: ");
ncurses::waddstr( ncurses::waddstr(
win, win,
&mail.get_in_reply_to_raw(), mail.get_in_reply_to_raw(),
); );
ncurses::waddstr(win, "\n"); ncurses::waddstr(win, "\n");
i += 1; i += 1;
@ -204,7 +205,7 @@ impl Pager {
let mut y = 0; let mut y = 0;
/* y,x coordinates of upper left corner of win */ /* y,x coordinates of upper left corner of win */
ncurses::getparyx(win, &mut y, &mut x); ncurses::getparyx(win, &mut y, &mut x);
let text = mail.get_body().get_text(); let text = mail.get_body().get_text();
let lines: Vec<&str> = text.trim().split('\n').collect(); let lines: Vec<&str> = text.trim().split('\n').collect();
let lines_length = lines.len(); let lines_length = lines.len();
@ -226,7 +227,7 @@ impl Pager {
* β”” ┗━━━━━━━━━w β”˜ β”˜ * β”” ┗━━━━━━━━━w β”˜ β”˜
*/ */
ncurses::pnoutrefresh(pad, 0, 0, y + height, x, y + height - 1, w - 1); ncurses::pnoutrefresh(pad, 0, 0, y + height, x, y + height - 1, w - 1);
return (pad, lines_length as i32, height); (pad, lines_length as i32, height)
} }
fn print_entry( fn print_entry(
win: ncurses::WINDOW, win: ncurses::WINDOW,