diff --git a/Cargo.toml b/Cargo.toml index 0461da594..d9dfc578e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,14 @@ name = "meli" version = "0.1.0" authors = ["Manos Pitsidianakis "] +[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 diff --git a/benches/maildir.rs b/benches/maildir.rs new file mode 100644 index 000000000..a5377323e --- /dev/null +++ b/benches/maildir.rs @@ -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)); +} diff --git a/src/main.rs b/src/bin.rs similarity index 82% rename from src/main.rs rename to src/bin.rs index 2ac7ccffd..34e16e02a 100644 --- a/src/main.rs +++ b/src/bin.rs @@ -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 . */ -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 = 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; + }, _ => {} } } diff --git a/src/conf/mod.rs b/src/conf/mod.rs index e3f52362b..93bfc3723 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -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 = HashMap::new(); - + let mut s: HashMap = HashMap::new(); + for (id, x) in fs.accounts { let mut folders = Vec::new(); fn recurse_folders>(folders: &mut Vec, 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); diff --git a/src/error.rs b/src/error.rs index 482a3f776..c61a438c9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -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 . + */ use std::error::Error; use std::fmt; use std::result; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..dbf636b5b --- /dev/null +++ b/src/lib.rs @@ -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 . + */ +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; diff --git a/src/mailbox/backends/maildir.rs b/src/mailbox/backends/maildir.rs index ef135e501..f9810d6eb 100644 --- a/src/mailbox/backends/maildir.rs +++ b/src/mailbox/backends/maildir.rs @@ -2,7 +2,7 @@ * meli - mailbox module. * * Copyright 2017 Manos Pitsidianakis - * + * * This file is part of meli. * * meli is free software: you can redistribute it and/or modify @@ -18,20 +18,24 @@ * You should have received a copy of the GNU General Public License * along with meli. If not, see . */ -//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> { + 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> { + 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 = 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 = 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(()) - } -} diff --git a/src/mailbox/backends/mod.rs b/src/mailbox/backends/mod.rs index 2905a2d55..82e17a9ab 100644 --- a/src/mailbox/backends/mod.rs +++ b/src/mailbox/backends/mod.rs @@ -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 . + */ pub mod maildir; use mailbox::email::Mail; diff --git a/src/mailbox/email/attachments.rs b/src/mailbox/email/attachments.rs index 1cc02238f..fb225db1c 100644 --- a/src/mailbox/email/attachments.rs +++ b/src/mailbox/email/attachments.rs @@ -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 . + */ +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>, } + Multipart { of_type: MultipartType, subattachments: Vec, } } #[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>, + raw: Vec, } 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>, + raw: Vec, 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> { - match parser::attachments(raw, boundary, &format!("{}--", boundary)).to_full_result() { + pub fn subattachments(raw: &[u8], boundary: &str) -> Vec { + 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 }, diff --git a/src/mailbox/email/mod.rs b/src/mailbox/email/mod.rs index 7a00fb56c..64fed939b 100644 --- a/src/mailbox/email/mod.rs +++ b/src/mailbox/email/mod.rs @@ -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 . + */ + +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, @@ -96,7 +116,7 @@ impl Mail { } } pub fn get_datetime(&self) -> chrono::DateTime { - 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 { - let f = Mmap::open_path(path.clone(), Protection::Read).unwrap(); + pub fn from(path: &str) -> Option { + 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) diff --git a/src/mailbox/email/parser.rs b/src/mailbox/email/parser.rs index da91b13e2..a838cc441 100644 --- a/src/mailbox/email/parser.rs +++ b/src/mailbox/email/parser.rs @@ -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 . + */ 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, 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, 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>, - 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)>>, +named!(headers>, 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, do_parse!( +named!(utf8_token_base64>, 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, escaped_transform!(call!(alpha), '=', quoted_printable_byte)); -named!(utf8_token_quoted_p, do_parse!( +named!(qp_underscore_header, + do_parse!(tag!("_") >> ( { b' ' } ))); + +named!(utf8_token_quoted_p>, do_parse!( raw: call!(utf8_token_quoted_p_raw) >> ( { - named!(get_bytes>, 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>, many0!(alt_complete!(quoted_printable_byte | qp_underscore_header | le_u8))); + get_bytes(raw).to_full_result().unwrap() } ))); -named!(utf8_token, alt_complete!( +named!(utf8_token>, alt_complete!( utf8_token_base64 | call!(utf8_token_quoted_p))); named!(utf8_token_list, 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, 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, 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::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>, 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 = 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) } ) diff --git a/src/mailbox/mod.rs b/src/mailbox/mod.rs index 556978602..9cbca4f94 100644 --- a/src/mailbox/mod.rs +++ b/src/mailbox/mod.rs @@ -19,32 +19,25 @@ * along with meli. If not, see . */ -//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>, + pub collection: Vec, pub threaded_collection: Vec, threads: Vec, length: usize, @@ -102,27 +95,22 @@ impl Thread { pub fn get_indentation(&self) -> usize { self.indentation } - fn is_descendant(&self, threads: &Vec, 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, id_table: &mut HashMap, collection: &mut Box>) -> () { +fn build_collection(threads: &mut Vec, id_table: &mut HashMap, 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, id_table: &mut HashMapB, 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, id_table: &mut HashMap, id_table: &mut HashMap) -> Result { - let mut collection: Box> = Box::new(maildir::MaildirType::new(path).get()?); + let mut collection: Vec = 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 = Vec::with_capacity(collection.len()); - fn build_threaded(threads: &mut Vec, indentation: usize, threaded: &mut Vec, i: usize, root_subject_idx: usize, collection: &Vec) { + fn build_threaded(threads: &mut Vec, indentation: usize, threaded: &mut Vec, 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) } diff --git a/src/ui/index.rs b/src/ui/index.rs index a82a13bb6..750890985 100644 --- a/src/ui/index.rs +++ b/src/ui/index.rs @@ -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 . */ -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); diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 9f5b5c104..6e123a140 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -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 . */ -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) { diff --git a/src/ui/pager.rs b/src/ui/pager.rs index 7511b602c..ee9a1f9ea 100644 --- a/src/ui/pager.rs +++ b/src/ui/pager.rs @@ -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 . */ -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,