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

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
*
*
* This file is part of meli.
*
* 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
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
extern crate ncurses;
pub mod mailbox;
mod ui;
mod conf;
mod error;
use ui::index::*;
extern crate melib;
use melib::*;
use mailbox::*;
use conf::*;
#[macro_use]
extern crate serde_derive;
/* parser */
#[macro_use]
extern crate nom;
extern crate chrono;
extern crate base64;
extern crate memmap;
extern crate ncurses;
fn main() {
let locale_conf = ncurses::LcCategory::all;
ncurses::setlocale(locale_conf, "en_US.UTF-8");
let set = Settings::new();
let ui = ui::TUI::initialize();
//let mailbox = Mailbox::new("/home/epilys/Downloads/rust/nutt/Inbox4");
let mut j = 0;
let folder_length = set.accounts.get("norn").unwrap().folders.len();
let folder_length = set.accounts["norn"].folders.len();
'main : loop {
ncurses::touchwin(ncurses::stdscr());
ncurses::mv(0,0);
let mailbox = Mailbox::new(&set.accounts.get("norn").unwrap().folders[j],
Some(&set.accounts.get("norn").unwrap().sent_folder));
let mailbox = Mailbox::new(&set.accounts["norn"].folders[j],
Some(&set.accounts["norn"].sent_folder));
let mut index: Box<Window> = match mailbox {
Ok(v) => {
Ok(v) => {
Box::new(Index::new(v))
},
Err(v) => {
@ -93,6 +83,11 @@ fn main() {
break 'inner;
}
},
Some(ncurses::WchResult::KeyCode(ncurses::KEY_RESIZE)) => {
eprintln!("key_resize");
index.redraw();
continue;
},
_ => {}
}
}

View File

@ -2,7 +2,7 @@
* meli - configuration module.
*
* Copyright 2017 Manos Pitsidianakis
*
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
@ -21,7 +21,6 @@
extern crate xdg;
extern crate config;
use std::collections::HashMap;
use std::io;
use std::fs;
@ -34,10 +33,13 @@ enum MailFormat {
impl MailFormat {
pub fn from_str(x: &str) -> MailFormat {
match x {
"maildir" | "Maildir" |
"MailDir" => { MailFormat::Maildir },
_ => { panic!("Unrecognizable mail format");}
match x.to_lowercase().as_ref() {
"maildir" => {
MailFormat::Maildir
},
_ => {
panic!("Unrecognizable mail format")
}
}
}
}
@ -78,13 +80,13 @@ impl FileSettings {
let mut s = Config::new();
let s = s.merge(File::new(config_path.to_str().unwrap(), FileFormat::Toml));
match s.is_ok() { //.unwrap_or(Settings { });
true => { s.unwrap().deserialize().unwrap() },
false => {
eprintln!("{:?}",s.err().unwrap());
let mut buf = String::new();
io::stdin().read_line(&mut buf).expect("Failed to read line");
FileSettings { ..Default::default() } },
if s.is_ok() {
s.unwrap().deserialize().unwrap()
} else {
eprintln!("{:?}",s.err().unwrap());
let mut buf = String::new();
io::stdin().read_line(&mut buf).expect("Failed to read line");
FileSettings { ..Default::default() }
}
}
}
@ -92,13 +94,13 @@ impl FileSettings {
impl Settings {
pub fn new() -> Settings {
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 {
let mut folders = Vec::new();
fn recurse_folders<P: AsRef<Path>>(folders: &mut Vec<String>, p: P) {
for mut f in fs::read_dir(p).unwrap() {
for f in f.iter_mut().next() {
for f in f.iter_mut() {
{
let path = f.path();
if path.ends_with("cur") || path.ends_with("new") ||
@ -110,8 +112,8 @@ impl Settings {
recurse_folders(folders, path);
}
}
}
}
}
};
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::fmt;
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.
*
* Copyright 2017 Manos Pitsidianakis
*
*
* This file is part of meli.
*
* 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
* 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 error::{MeliError, Result};
use mailbox::backends::MailBackend;
extern crate crossbeam;
use std::path::PathBuf;
pub struct MaildirType {
path: String,
}
impl MailBackend for MaildirType {
fn get(&self) -> Result<Vec<Mail>> {
self.get_multicore(4)
/*
MaildirType::is_valid(&self.path)?;
let mut path = PathBuf::from(&self.path);
path.push("cur");
@ -44,10 +48,48 @@ impl MailBackend for MaildirType {
let path = x.path();
Ok(path.to_str().unwrap().to_string())
})?;
match Mail::from(e) {
match Mail::from(&e) {
Some(e) => {r.push(e);},
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)?;
@ -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)
}
}
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;
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 {
Data { tag: String },
Text { content: String },
Multipart { of_type: MultipartType, subattachments: Vec<Box<Attachment>>, }
Multipart { of_type: MultipartType, subattachments: Vec<Attachment>, }
}
#[derive(Clone,Debug)]
pub enum ContentType {
@ -27,13 +49,13 @@ pub enum ContentType {
Unsupported { tag: String },
}
impl ::std::fmt::Display for ContentType {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
impl Display for ContentType {
fn fmt(&self, f: &mut Formatter) -> Result {
match *self {
ContentType::Text => {
write!(f, "text")
},
ContentType::Multipart { boundary: _ } => {
ContentType::Multipart { .. } => {
write!(f, "multipart")
},
ContentType::Unsupported { tag: ref t } => {
@ -47,8 +69,8 @@ pub enum ContentSubType {
Plain,
Other { tag: String },
}
impl ::std::fmt::Display for ContentSubType {
fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
impl Display for ContentSubType {
fn fmt(&self, f: &mut Formatter) -> Result {
match *self {
ContentSubType::Plain => {
write!(f, "plain")
@ -72,7 +94,7 @@ pub struct AttachmentBuilder {
content_type: (ContentType, ContentSubType),
content_transfer_encoding: ContentTransferEncoding,
raw: Box<Vec<u8>>,
raw: Vec<u8>,
}
impl AttachmentBuilder {
@ -80,18 +102,18 @@ impl AttachmentBuilder {
AttachmentBuilder {
content_type: (ContentType::Text, ContentSubType::Plain),
content_transfer_encoding: ContentTransferEncoding::_7Bit,
raw: Box::new(content.to_vec()),
raw: content.to_vec(),
}
}
pub fn content_type(&mut self, value: &str) -> &Self {
match parser::content_type(value.as_bytes()).to_full_result() {
Ok((ct, cst, params)) => {
Ok((ct, cst, params)) => {
match ct.to_lowercase().as_ref() {
"multipart" => {
let mut boundary = None;
for (n, v) in params {
if n.to_lowercase() == "boundary" {
boundary = Some(format!("--{}", v).to_string());
boundary = Some(format!("--{}--", v).to_string());
break;
}
}
@ -114,9 +136,10 @@ impl AttachmentBuilder {
self.content_type.1 = ContentSubType::Other { tag: cst.to_string() };
},
}
},
_ => {},
},
Err(v) => {
eprintln!("parsing error in content_type: {:?} {:?}", value, v);
}
}
self
}
@ -156,10 +179,7 @@ impl AttachmentBuilder {
ContentTransferEncoding::QuotedPrintable => {
parser::quoted_printable_text(&self.raw).to_full_result().unwrap()
},
ContentTransferEncoding::_7Bit | ContentTransferEncoding::_8Bit => {
String::from_utf8_lossy(&self.raw).into_owned()
},
ContentTransferEncoding::Other { tag: _ } => {
ContentTransferEncoding::_7Bit | ContentTransferEncoding::_8Bit | ContentTransferEncoding::Other { .. } => {
String::from_utf8_lossy(&self.raw).into_owned()
}
}
@ -190,12 +210,12 @@ impl AttachmentBuilder {
}
},
_ => {
panic!();
panic!()
}
};
AttachmentType::Multipart {
of_type: multipart_type,
subattachments: Attachment::subattachments(&self.raw, &b),
subattachments: Attachment::subattachments(&self.raw, b),
}
},
ContentType::Unsupported { ref tag } => {
@ -219,7 +239,7 @@ pub struct Attachment {
content_type: (ContentType, ContentSubType),
content_transfer_encoding: ContentTransferEncoding,
raw: Box<Vec<u8>>,
raw: Vec<u8>,
attachment_type: AttachmentType,
}
@ -227,7 +247,7 @@ pub struct Attachment {
impl Attachment {
fn get_text_recursive(&self, text: &mut String) {
match self.attachment_type {
AttachmentType::Data { tag: _ } => {
AttachmentType::Data { .. } => {
text.push_str(&format!("Data attachment of type {}", self.get_tag()));
},
AttachmentType::Text { content: ref t } => {
@ -264,8 +284,9 @@ impl Attachment {
pub fn get_tag(&self) -> String {
format!("{}/{}", self.content_type.0, self.content_type.1).to_string()
}
pub fn subattachments(raw: &[u8], boundary: &str) -> Vec<Box<Attachment>> {
match parser::attachments(raw, boundary, &format!("{}--", boundary)).to_full_result() {
pub fn subattachments(raw: &[u8], boundary: &str) -> Vec<Attachment> {
let boundary_length = boundary.len();
match parser::attachments(raw, &boundary[0..boundary_length - 2], boundary).to_full_result() {
Ok(attachments) => {
let mut vec = Vec::with_capacity(attachments.len());
for a in attachments {
@ -277,24 +298,23 @@ impl Attachment {
eprintln!("{}\n", ::std::string::String::from_utf8_lossy(a));
eprintln!("-------------------------------\n");
continue;}
continue
}
};
let mut builder = AttachmentBuilder::new(body);
for (name, value) in headers {
match name.to_lowercase().as_ref(){
"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" => {
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
},

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 memmap::{Mmap, Protection};
use std;
use std::cmp::Ordering;
use std::fmt;
use std::option::Option;
use std::io::prelude::*;
mod parser;
mod attachments;
use self::attachments::*;
use chrono;
use chrono::TimeZone;
@ -73,7 +93,7 @@ struct References {
}
/* A very primitive mail object */
#[derive(Clone,Debug)]
#[derive(Clone,Debug,Default)]
pub struct Mail {
date: String,
from: Option<String>,
@ -96,7 +116,7 @@ impl Mail {
}
}
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 {
&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 {
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(),
}
}
@ -243,21 +263,21 @@ impl Mail {
thread: 0,
}
}
pub fn from(path: std::string::String) -> Option<Self> {
let f = Mmap::open_path(path.clone(), Protection::Read).unwrap();
pub fn from(path: &str) -> Option<Self> {
let f = Mmap::open_path(path.to_string(), Protection::Read).unwrap();
let file = unsafe { f.as_slice() };
let (headers, body) = match parser::mail(file).to_full_result() {
Ok(v) => v,
Err(_) => {
eprintln!("error in parsing");
let path = std::path::PathBuf::from(&path);
Err(_) => {
eprintln!("error in parsing mail");
let path = std::path::PathBuf::from(path);
let mut buffer = Vec::new();
let _ = std::fs::File::open(path).unwrap().read_to_end(&mut buffer);
eprintln!("\n-------------------------------");
eprintln!("{}\n", std::string::String::from_utf8_lossy(&buffer));
eprintln!("{}\n", std::string::String::from_utf8_lossy(&buffer));
eprintln!("-------------------------------\n");
return None; }
};
let mut mail = Mail::new();
@ -266,96 +286,73 @@ impl Mail {
let mut builder = AttachmentBuilder::new(body);
for (name, value) in headers {
if value.len() == 1 && value[0].is_empty() {
if value.len() == 1 && value.is_empty() {
continue;
}
match name {
"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 value = match parse_result.is_done() {
true => {
parse_result.to_full_result().unwrap()
},
false => {
"".to_string()
},
let value = if parse_result.is_done() {
parse_result.to_full_result().unwrap()
} else {
"".to_string()
};
mail.set_to(value);
},
"From" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
let parse_result = parser::subject(value.as_bytes());
let value = match parse_result.is_done() {
true => {
parse_result.to_full_result().unwrap()
},
false => {
"".to_string()
},
let value = if parse_result.is_done() {
parse_result.to_full_result().unwrap()
} else {
"".to_string()
};
mail.set_from(value);
},
"Subject" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(" "); acc.push_str(x); acc });
let parse_result = parser::subject(value.trim().as_bytes());
let value = match parse_result.is_done() {
true => {
parse_result.to_full_result().unwrap()
},
false => {
"".to_string()
},
let value = if parse_result.is_done() {
parse_result.to_full_result().unwrap()
} else {
"".to_string()
};
mail.set_subject(value);
},
"Message-ID" | "Message-Id" | "Message-id" | "message-id" => {
mail.set_message_id(&value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc }));
mail.set_message_id(value);
},
"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());
match parse_result.is_done() {
true => {
for v in parse_result.to_full_result().unwrap() {
mail.push_references(v);
}
},
_ => {}
let parse_result = parser::references(value.as_bytes());
if parse_result.is_done() {
for v in parse_result.to_full_result().unwrap() {
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" => {
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); },
"Date" => {
let value = value.iter().fold(String::new(), |mut acc, x| { acc.push_str(x); acc });
mail.set_date(value.clone());
datetime = Some(value);
mail.set_date(value.to_string());
datetime = Some(value.to_string());
},
"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" => {
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 {
Some(ref mut x) => {
mail.push_references(x);
},
None => {},
if let Some(ref mut x) = in_reply_to {
mail.push_references(x);
};
mail.set_body(builder.build());
if datetime.is_some() {
mail.set_datetime(parser::date(&datetime.unwrap()));
if let Some(ref mut d) = datetime {
mail.set_datetime(parser::date(d));
}
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::str::from_utf8;
use base64;
use chrono;
use nom::le_u8;
use nom::{le_u8, is_hex_digit};
/* Wow this sucks! */
named!(quoted_printable_byte<u8>, do_parse!(
p: map_res!(preceded!(tag!("="), verify!(complete!(take!(2)), |s: &[u8]| { ::nom::is_hex_digit(s[0]) && ::nom::is_hex_digit(s[1]) })), std::str::from_utf8) >>
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()
} )));
@ -22,51 +42,55 @@ named!(quoted_printable_byte<u8>, do_parse!(
* Tue, 5 Jan 2016 21:30:44 +0100 (CET)
*/
/*
* if a header value is a Vec<&str>, this is the tail of that Vector
*/
named!(valuelist<&str>,
map_res!(delimited!(alt_complete!(tag!("\t") | tag!(" ")), take_until!("\n"), tag!("\n")), std::str::from_utf8)
);
use nom::{IResult,Needed,ErrorKind};
/* Parse the value part of the header -> Vec<&str> */
named!(value<Vec<&str>>,
do_parse!(
head: map_res!(terminated!(take_until!("\n"), tag!("\n")), std::str::from_utf8) >>
tail: many0!(valuelist) >>
( {
let tail_len = tail.len();
let tail: Vec<&str> = tail.iter().map(|v| { v.trim()}).collect();
let mut result = Vec::with_capacity(1 + tail.len());
result.push(head.trim());
if tail_len == 1 && tail[0] == "" {
result
} else {
tail.iter().fold(result, |mut acc, x| { acc.push(x); acc})
fn header_value(input: &[u8]) -> IResult<&[u8], &str> {
if input.is_empty() || input[0] == b'\n' {
IResult::Incomplete(Needed::Size(1))
} else {
let input_len = input.len();
for (i, x) in input.iter().enumerate() {
if *x == b'\n' {
if (i + 1) < input_len &&
((input[i+1] != b' ' && input[i+1] != b'\t') || input[i+1] == b'\n') {
return match from_utf8(&input[0..i]) {
Ok(v) => {
IResult::Done(&input[(i+1)..], v)
},
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 */
named!(name<&str>,
terminated!(verify!(map_res!(take_until1!(":"), std::str::from_utf8), | v: &str | { !v.contains("\n")} ), tag!(":")));
named!(name<&str>,
verify!(map_res!(take_until1!(":"), from_utf8), | v: &str | !v.contains('\n') ));
/* Parse a single header as a tuple -> (&str, Vec<&str>) */
named!(header<(&str, std::vec::Vec<&str>)>,
pair!(complete!(name), complete!(value)));
named!(header<(&str, &str)>,
separated_pair!(complete!(name), ws!(tag!(":")), complete!(header_value)));
/* 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)));
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 }))));
named!(pub attachment<(std::vec::Vec<(&str, std::vec::Vec<&str>)>, &[u8])>,
named!(pub attachment<(std::vec::Vec<(&str, &str)>, &[u8])>,
do_parse!(
opt!(is_a!(" \n\t\r")) >>
pair: pair!(many0!(complete!(header)), take_while!(call!(|_| { true }))) >>
( { 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
*/
@ -75,55 +99,69 @@ named!(pub attachment<(std::vec::Vec<(&str, std::vec::Vec<&str>)>, &[u8])>,
/* Encoded words
*"=?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!("?="))) >>
( {
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!("?="))));
//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) >>
( {
named!(get_bytes<Vec<u8>>, dbg!(many0!(alt!(quoted_printable_byte | le_u8))));
let bytes = get_bytes(raw).to_full_result().unwrap();
String::from_utf8_lossy(&bytes).into_owned()
named!(get_bytes<Vec<u8>>, many0!(alt_complete!(quoted_printable_byte | qp_underscore_header | le_u8)));
get_bytes(raw).to_full_result().unwrap()
} )));
named!(utf8_token<String>, alt_complete!(
named!(utf8_token<Vec<u8>>, alt_complete!(
utf8_token_base64 |
call!(utf8_token_quoted_p)));
named!(utf8_token_list<String>, ws!(do_parse!(
list: separated_nonempty_list!(complete!(tag!(" ")), utf8_token) >>
( {
( {
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!(
word: alt!(terminated!(take_until1!("=?"), peek!(tag_no_case!("=?UTF-8?"))) | take_while!(call!(|_| { true }))) >>
( {
String::from_utf8_lossy(word).into_owned()
} )));
/* Lots of copying here. TODO: fix it */
named!(pub subject<String>, ws!(do_parse!(
list: many0!(alt_complete!( utf8_token_list | ascii_token)) >>
( {
let list_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc });
let s = list.iter().fold(String::with_capacity(list_len), |mut acc, x| { acc.push_str(x); acc.push_str(" "); acc});
s.trim().to_string()
( {
let string_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc }) + list.len() - 1;
let list_len = list.len();
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)";
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>> {
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]
fn test_date() {
let s = "Thu, 31 Aug 2017 13:43:37 +0000 (UTC)";
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());
}
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_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!(
named_args!(pub attachments<'a>(boundary: &'a str, boundary_end: &'a str) < Vec<&'this_is_probably_unique_i_hope_please [u8]> >,
alt_complete!(do_parse!(
take_until!(boundary) >>
vecs: many0!(complete!(do_parse!(
tag!(boundary) >>
@ -196,33 +240,41 @@ named_args!(pub attachments<'a>(boundary: &'a str, boundary_end: &'a str) < Vec<
take_until!(boundary_end) >>
tag!(boundary_end) >>
( { Vec::<&[u8]>::new() } ))
)));
));
#[test]
fn test_attachments() {
use std::io::Read;
let mut buffer: Vec<u8> = Vec::new();
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() {
Ok(v) => v,
Err(_) => { panic!() }
};
//eprintln!("{:?}",std::str::from_utf8(body));
let attachments = attachments(body, boundary).to_full_result().unwrap();
let attachments = attachments(body, &boundary[0..boundary_len-2], &boundary).to_full_result().unwrap();
assert_eq!(attachments.len(), 4);
}
named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >,
named!(content_type_parameter< (&str, &str) >,
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!("/") >>
_subtype: map_res!(is_not!(";"), std::str::from_utf8) >>
parameters: many0!(preceded!(tag!(";"), pair!(
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)))) >>
_subtype: map_res!(is_not!(";"), from_utf8) >>
parameters: many0!(complete!(content_type_parameter)) >>
( {
(_type, _subtype, parameters)
} )

View File

@ -19,32 +19,25 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
//use std::cmp::Ordering;
//use std::fmt;
pub mod email;
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::collections::HashMap;
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;
/*a Mailbox represents a folder of mail. Currently only Maildir is supported.*/
#[derive(Debug)]
pub struct Mailbox{
pub path: String,
pub collection: Box<Vec<Mail>>,
pub collection: Vec<Mail>,
pub threaded_collection: Vec<usize>,
threads: Vec<Thread>,
length: usize,
@ -102,27 +95,22 @@ impl Thread {
pub fn get_indentation(&self) -> usize {
self.indentation
}
fn is_descendant(&self, threads: &Vec<Thread>, other: &Thread) -> bool {
fn is_descendant(&self, threads: &[Thread], other: &Thread) -> bool {
if self == other {
return true;
}
match self.first_child {
Some(v) => {
if threads[v].is_descendant(threads, other) {
return true;
}
},
None => {}
if let Some(v) = self.first_child {
if threads[v].is_descendant(threads, other) {
return true;
}
};
match self.next_sibling {
Some(v) => {
if threads[v].is_descendant(threads, other) {
return true;
}
},
None => {}
if let Some(v) = self.next_sibling {
if threads[v].is_descendant(threads, other) {
return true;
}
};
return false;
false
}
fn set_show_subject(&mut self, set: bool) -> () {
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() {
let x_index; /* x's index in threads */
let m_id = x.get_message_id_raw().to_string();
if id_table.contains_key(&m_id) {
let t = *(id_table.get(&m_id).unwrap());
let t = id_table[&m_id];
/* the already existing Thread should be empty, since we're
* seeing this message for the first time */
if threads[t].message.is_some() {
@ -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.
*/
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 =
if id_table.contains_key(r.get_raw()) {
let p = *(id_table.get(r.get_raw()).unwrap());
if !(threads[p].is_descendant(&threads, &threads[curr_ref]) ||
threads[curr_ref].is_descendant(&threads, &threads[p])) {
let p = id_table[r.get_raw()];
if !(threads[p].is_descendant(threads, &threads[curr_ref]) ||
threads[curr_ref].is_descendant(threads, &threads[p])) {
threads[curr_ref].parent = Some(p);
if threads[p].first_child.is_none() {
threads[p].first_child = Some(curr_ref);
@ -229,7 +217,7 @@ fn build_collection(threads: &mut Vec<Thread>, id_table: &mut HashMap<std::strin
/* update thread date */
let mut parent_iter = parent_id;
'date: loop {
let mut p = &mut threads[parent_iter];
let p = &mut threads[parent_iter];
if 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 {
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: */
/* a vector to hold thread members */
@ -278,7 +266,7 @@ impl Mailbox {
idx += 1;
}
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() {
/* skip duplicate message-id, but this should be handled instead */
continue;
@ -289,7 +277,7 @@ impl Mailbox {
x.set_thread(c);
}
if !x.get_in_reply_to_raw().is_empty() && id_table.contains_key(x.get_in_reply_to_raw()) {
let p = *(id_table.get(x.get_in_reply_to_raw()).unwrap());
let p = id_table[x.get_in_reply_to_raw()];
let c = if !id_table.contains_key(x.get_message_id_raw()) {
threads.push(
Thread {
@ -307,7 +295,7 @@ impl Mailbox {
tidx += 1;
tidx - 1
} else {
*(id_table.get(x.get_message_id_raw()).unwrap())
id_table[x.get_message_id_raw()]
};
threads[c].parent = Some(p);
if threads[p].is_descendant(&threads, &threads[c]) ||
@ -328,7 +316,7 @@ impl Mailbox {
/* update thread date */
let mut parent_iter = p;
'date: loop {
let mut p = &mut threads[parent_iter];
let p = &mut threads[parent_iter];
if 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
* no parents. These are the root messages of each thread */
let mut root_set = Vec::with_capacity(collection.len());
'root_set: for (_,v) in id_table.iter() {
'root_set: for v in id_table.values() {
if threads[*v].parent.is_none() {
if !threads[*v].has_message() && threads[*v].has_children() && !threads[threads[*v].first_child.unwrap()].has_sibling() {
/* Do not promote the children if doing so would promote them to the root set
@ -352,13 +340,13 @@ impl Mailbox {
continue 'root_set;
}
root_set.push(*v);
}
}
}
root_set.sort_by(|a, b| threads[*b].date.cmp(&threads[*a].date));
/* Group messages together by thread in a collection so we can print them together */
let mut threaded_collection: Vec<usize> = Vec::with_capacity(collection.len());
fn build_threaded(threads: &mut Vec<Thread>, indentation: usize, threaded: &mut Vec<usize>, i: usize, root_subject_idx: usize, collection: &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];
if threads[root_subject_idx].has_message() {
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() {
threads[i].parent = None;
}
let indentation =
let indentation =
if thread.has_message() {
threads[i].set_indentation(indentation);
if !threaded.contains(&i) {
@ -421,8 +409,7 @@ impl Mailbox {
thread.get_message().unwrap()
}
pub fn get_mail_and_thread(&mut self, i: usize) -> (&mut Mail, Thread) {
let ref mut x = self.collection.as_mut_slice()[i];
let x = &mut self.collection.as_mut_slice()[i];
let thread = self.threads[x.get_thread()];
(x, thread)
}

View File

@ -2,7 +2,7 @@
* meli - ui module.
*
* Copyright 2017 Manos Pitsidianakis
*
*
* This file is part of meli.
*
* 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
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
extern crate ncurses;
extern crate chrono;
use mailbox::email::Mail;
use mailbox::*;
use error::MeliError;
use std::error::Error;
//use self::chrono::NaiveDateTime;
extern crate ncurses;
use std::error::Error;
pub trait Window {
fn draw(&mut self) -> ();
fn redraw(&mut self) -> ();
fn handle_input(&mut self, input: i32) -> ();
}
@ -43,6 +42,10 @@ impl Window for ErrorWindow {
ncurses::waddstr(self.win, &self.description);
ncurses::wrefresh(self.win);
}
fn redraw(&mut self) -> () {
ncurses::waddstr(self.win, &self.description);
ncurses::wrefresh(self.win);
}
fn handle_input(&mut self, _: i32) -> () {
@ -98,43 +101,38 @@ impl Window for Index {
/* Draw threaded view. */
let mut iter = self.mailbox.threaded_collection.iter().enumerate().peekable();
/* This is just a desugared for loop so that we can use .peek() */
loop {
match iter.next() {
Some((idx, i)) => {
let container = self.mailbox.get_thread(*i);
let indentation = container.get_indentation();
while let Some((idx, i)) = iter.next() {
let container = self.mailbox.get_thread(*i);
let indentation = container.get_indentation();
assert_eq!(container.has_message(), true);
match iter.peek() {
Some(&(_, x)) if self.mailbox.get_thread(*x).get_indentation() == indentation => {
indentations.pop();
indentations.push(true);
},
_ => {
indentations.pop();
indentations.push(false);
}
}
if container.has_sibling() {
assert_eq!(container.has_message(), true);
match iter.peek() {
Some(&(_, x)) if self.mailbox.get_thread(*x).get_indentation() == indentation => {
indentations.pop();
indentations.push(true);
},
_ => {
indentations.pop();
indentations.push(false);
}
}
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.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,
);
}
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) {
if self.mailbox.get_length() == 0 {
return;
@ -203,7 +221,7 @@ impl Window for Index {
},
10 => {
self.show_pager();
self.draw();
self.redraw();
},
_ => {
return;
@ -212,18 +230,17 @@ impl Window for Index {
/* Draw newly highlighted entry */
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 */
ncurses::wmove(self.pad, prev_idx as i32, 0);
{
let pair = match self.threaded {
true if prev_idx % 2 == 0 => super::COLOR_PAIR_THREAD_EVEN,
true => super::COLOR_PAIR_THREAD_ODD,
false => super::COLOR_PAIR_DEFAULT,
let pair = if self.threaded && prev_idx % 2 == 0 {
super::COLOR_PAIR_THREAD_EVEN
} else if self.threaded {
super::COLOR_PAIR_THREAD_ODD
} else {
super::COLOR_PAIR_DEFAULT
};
ncurses::wchgat(self.pad, 32, 0, pair);
ncurses::wmove(self.pad, prev_idx as i32, 32);
@ -299,7 +316,7 @@ impl Index {
ncurses::getmaxyx(win, &mut screen_height, &mut screen_width);
//eprintln!("length is {}\n", 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(
pad,
' ' as ncurses::chtype |
@ -403,14 +420,12 @@ impl Index {
}
ncurses::getmaxyx(self.win,
&mut self.screen_height, &mut self.screen_width);
let x: &mut Mail;
if self.threaded {
let x: &mut Mail = if self.threaded {
let i = self.mailbox.get_threaded_mail(self.cursor_idx);
x = &mut self.mailbox.collection[i];
&mut self.mailbox.collection[i]
} 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);
pager.scroll(ncurses::KEY_DOWN);
pager.scroll(ncurses::KEY_UP);

View File

@ -2,7 +2,7 @@
* meli - ui module.
*
* Copyright 2017 Manos Pitsidianakis
*
*
* This file is part of meli.
*
* 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
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
extern crate ncurses;
pub mod index;
pub mod pager;
extern crate ncurses;
/* Color pairs; foreground && background. */
pub static COLOR_PAIR_DEFAULT: i16 = 1;
pub static COLOR_PAIR_CURSOR: i16 = 2;
@ -32,29 +35,29 @@ pub static COLOR_PAIR_THREAD_EVEN: i16 = 6;
pub struct TUI;
impl TUI {
pub fn initialize() -> Self {
/* start ncurses */
ncurses::initscr();
ncurses::keypad(ncurses::stdscr(), true);
ncurses::noecho();
ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
/* Start colors. */
pub fn initialize() -> Self {
/* start ncurses */
ncurses::initscr();
ncurses::keypad(ncurses::stdscr(), true);
ncurses::noecho();
ncurses::curs_set(ncurses::CURSOR_VISIBILITY::CURSOR_INVISIBLE);
/* Start colors. */
ncurses::start_color();
ncurses::start_color();
ncurses::init_pair(COLOR_PAIR_DEFAULT, 15, 0);
ncurses::init_pair(COLOR_PAIR_CURSOR, 251, 235);
ncurses::init_pair(COLOR_PAIR_HEADERS, 33, 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_EVEN, 15, 233);
ncurses::init_pair(COLOR_PAIR_DEFAULT, 15, 0);
ncurses::init_pair(COLOR_PAIR_CURSOR, 251, 235);
ncurses::init_pair(COLOR_PAIR_HEADERS, 33, 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_EVEN, 15, 233);
/* Set the window's background color. */
ncurses::bkgd(
' ' as ncurses::chtype | ncurses::COLOR_PAIR(COLOR_PAIR_DEFAULT) as ncurses::chtype,
);
TUI {}
}
/* Set the window's background color. */
ncurses::bkgd(
' ' as ncurses::chtype | ncurses::COLOR_PAIR(COLOR_PAIR_DEFAULT) as ncurses::chtype,
);
TUI {}
}
}
impl Drop for TUI {
fn drop(&mut self) {

View File

@ -2,7 +2,7 @@
* meli - ui module.
*
* Copyright 2017 Manos Pitsidianakis
*
*
* This file is part of meli.
*
* 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
* 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
* viewing */
@ -148,29 +149,29 @@ impl Pager {
ncurses::waddstr(win, "From: ");
ncurses::waddstr(
win,
&mail.get_from(),
mail.get_from(),
);
ncurses::waddstr(win, "\n");
i += 1;
ncurses::waddstr(win, "To: ");
ncurses::waddstr(
win,
&mail.get_to(),
mail.get_to(),
);
ncurses::waddstr(win, "\n");
i += 1;
ncurses::waddstr(win, "Subject: ");
ncurses::waddstr(
win,
&mail.get_subject(),
mail.get_subject(),
);
ncurses::waddstr(win, "\n");
i += 1;
ncurses::waddstr(win, "Message-ID: ");
ncurses::waddstr(
win,
&mail.get_message_id_raw(),
//&mail.get_message_id(),
mail.get_message_id_raw(),
//mail.get_message_id(),
);
ncurses::waddstr(win, "\n");
i += 1;
@ -184,7 +185,7 @@ impl Pager {
ncurses::waddstr(win, "In-Reply-To: ");
ncurses::waddstr(
win,
&mail.get_in_reply_to_raw(),
mail.get_in_reply_to_raw(),
);
ncurses::waddstr(win, "\n");
i += 1;
@ -204,7 +205,7 @@ impl Pager {
let mut y = 0;
/* y,x coordinates of upper left corner of win */
ncurses::getparyx(win, &mut y, &mut x);
let text = mail.get_body().get_text();
let lines: Vec<&str> = text.trim().split('\n').collect();
let lines_length = lines.len();
@ -226,7 +227,7 @@ impl Pager {
* β”” ┗━━━━━━━━━w β”˜ β”˜
*/
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(
win: ncurses::WINDOW,