parent
cddea885f2
commit
fb745be27f
|
@ -10,6 +10,9 @@ use self::test::Bencher;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn mail_parse(b: &mut Bencher) {
|
fn mail_parse(b: &mut Bencher) {
|
||||||
b.iter(|| Envelope::from(Box::new(BackendOpGenerator::new(Box::new(move || {
|
b.iter(|| {
|
||||||
Box::new(MaildirOp::new("test/attachment_test".to_string()))})))) );
|
Envelope::from(Box::new(BackendOpGenerator::new(Box::new(move || {
|
||||||
|
Box::new(MaildirOp::new("test/attachment_test".to_string()))
|
||||||
|
}))))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
46
src/bin.rs
46
src/bin.rs
|
@ -37,17 +37,13 @@ fn main() {
|
||||||
let mut j = 0;
|
let mut j = 0;
|
||||||
let folder_length = set.accounts["norn"].folders.len();
|
let folder_length = set.accounts["norn"].folders.len();
|
||||||
let mut account = Account::new("norn".to_string(), set.accounts["norn"].clone());
|
let mut account = Account::new("norn".to_string(), set.accounts["norn"].clone());
|
||||||
'main : loop {
|
'main: loop {
|
||||||
ncurses::touchwin(ncurses::stdscr());
|
ncurses::touchwin(ncurses::stdscr());
|
||||||
ncurses::mv(0,0);
|
ncurses::mv(0, 0);
|
||||||
let mailbox = &mut account[j];
|
let mailbox = &mut account[j];
|
||||||
let mut index: Box<Window> = match *mailbox.as_ref().unwrap() {
|
let mut index: Box<Window> = match *mailbox.as_ref().unwrap() {
|
||||||
Ok(ref v) => {
|
Ok(ref v) => Box::new(Index::new(v)),
|
||||||
Box::new(Index::new(v))
|
Err(ref v) => Box::new(ErrorWindow::new((*v).clone())),
|
||||||
},
|
|
||||||
Err(ref v) => {
|
|
||||||
Box::new(ErrorWindow::new((*v).clone()))
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
//eprintln!("{:?}", set);
|
//eprintln!("{:?}", set);
|
||||||
ncurses::refresh();
|
ncurses::refresh();
|
||||||
|
@ -55,39 +51,35 @@ fn main() {
|
||||||
index.draw();
|
index.draw();
|
||||||
|
|
||||||
let mut ch;
|
let mut ch;
|
||||||
'inner : loop {
|
'inner: loop {
|
||||||
ch = ncurses::get_wch();
|
ch = ncurses::get_wch();
|
||||||
match ch {
|
match ch {
|
||||||
Some(ncurses::WchResult::KeyCode(k @ ncurses::KEY_UP)) |
|
Some(ncurses::WchResult::KeyCode(k @ ncurses::KEY_UP)) |
|
||||||
Some(ncurses::WchResult::KeyCode(k @ ncurses::KEY_DOWN)) => {
|
Some(ncurses::WchResult::KeyCode(k @ ncurses::KEY_DOWN)) => {
|
||||||
index.handle_input(k);
|
index.handle_input(k);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Some(ncurses::WchResult::Char(k @ 10)) => {
|
Some(ncurses::WchResult::Char(k @ 10)) => {
|
||||||
index.handle_input(k as i32);
|
index.handle_input(k as i32);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
Some(ncurses::WchResult::KeyCode(ncurses::KEY_F1)) |
|
Some(ncurses::WchResult::KeyCode(ncurses::KEY_F1)) |
|
||||||
Some(ncurses::WchResult::Char(113)) => {
|
Some(ncurses::WchResult::Char(113)) => {
|
||||||
break 'main;
|
break 'main;
|
||||||
},
|
}
|
||||||
Some(ncurses::WchResult::Char(74)) => {
|
Some(ncurses::WchResult::Char(74)) => if j < folder_length - 1 {
|
||||||
if j < folder_length - 1 {
|
j += 1;
|
||||||
j += 1;
|
break 'inner;
|
||||||
break 'inner;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Some(ncurses::WchResult::Char(75)) => {
|
Some(ncurses::WchResult::Char(75)) => if j > 0 {
|
||||||
if j > 0 {
|
j -= 1;
|
||||||
j -= 1;
|
break 'inner;
|
||||||
break 'inner;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
Some(ncurses::WchResult::KeyCode(ncurses::KEY_RESIZE)) => {
|
Some(ncurses::WchResult::KeyCode(ncurses::KEY_RESIZE)) => {
|
||||||
eprintln!("key_resize");
|
eprintln!("key_resize");
|
||||||
index.redraw();
|
index.redraw();
|
||||||
continue;
|
continue;
|
||||||
},
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,27 +19,23 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
extern crate xdg;
|
|
||||||
extern crate config;
|
extern crate config;
|
||||||
|
extern crate xdg;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{PathBuf, Path};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum MailFormat {
|
pub enum MailFormat {
|
||||||
Maildir
|
Maildir,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MailFormat {
|
impl MailFormat {
|
||||||
pub fn from_str(x: &str) -> MailFormat {
|
pub fn from_str(x: &str) -> MailFormat {
|
||||||
match x.to_lowercase().as_ref() {
|
match x.to_lowercase().as_ref() {
|
||||||
"maildir" => {
|
"maildir" => MailFormat::Maildir,
|
||||||
MailFormat::Maildir
|
_ => panic!("Unrecognizable mail format"),
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
panic!("Unrecognizable mail format")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,7 +45,7 @@ struct FileAccount {
|
||||||
folders: String,
|
folders: String,
|
||||||
format: String,
|
format: String,
|
||||||
sent_folder: String,
|
sent_folder: String,
|
||||||
threaded : bool,
|
threaded: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Default)]
|
#[derive(Debug, Deserialize, Default)]
|
||||||
|
@ -57,16 +53,16 @@ struct FileSettings {
|
||||||
accounts: HashMap<String, FileAccount>,
|
accounts: HashMap<String, FileAccount>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AccountSettings {
|
pub struct AccountSettings {
|
||||||
pub folders: Vec<String>,
|
pub folders: Vec<String>,
|
||||||
format: MailFormat,
|
format: MailFormat,
|
||||||
pub sent_folder: String,
|
pub sent_folder: String,
|
||||||
threaded : bool,
|
threaded: bool,
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Settings {
|
pub struct Settings {
|
||||||
pub accounts: HashMap<String, AccountSettings>,
|
pub accounts: HashMap<String, AccountSettings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,8 +70,9 @@ use self::config::{Config, File, FileFormat};
|
||||||
impl FileSettings {
|
impl FileSettings {
|
||||||
pub fn new() -> FileSettings {
|
pub fn new() -> FileSettings {
|
||||||
let xdg_dirs = xdg::BaseDirectories::with_prefix("meli").unwrap();
|
let xdg_dirs = xdg::BaseDirectories::with_prefix("meli").unwrap();
|
||||||
let config_path = xdg_dirs.place_config_file("config")
|
let config_path = xdg_dirs
|
||||||
.expect("cannot create configuration directory");
|
.place_config_file("config")
|
||||||
|
.expect("cannot create configuration directory");
|
||||||
//let setts = Config::default().merge(File::new(config_path.to_str().unwrap_or_default(), config::FileFormat::Toml)).unwrap();
|
//let setts = Config::default().merge(File::new(config_path.to_str().unwrap_or_default(), config::FileFormat::Toml)).unwrap();
|
||||||
let mut s = Config::new();
|
let mut s = Config::new();
|
||||||
let s = s.merge(File::new(config_path.to_str().unwrap(), FileFormat::Toml));
|
let s = s.merge(File::new(config_path.to_str().unwrap(), FileFormat::Toml));
|
||||||
|
@ -83,10 +80,14 @@ impl FileSettings {
|
||||||
if s.is_ok() {
|
if s.is_ok() {
|
||||||
s.unwrap().deserialize().unwrap()
|
s.unwrap().deserialize().unwrap()
|
||||||
} else {
|
} else {
|
||||||
eprintln!("{:?}",s.err().unwrap());
|
eprintln!("{:?}", s.err().unwrap());
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
io::stdin().read_line(&mut buf).expect("Failed to read line");
|
io::stdin()
|
||||||
FileSettings { ..Default::default() }
|
.read_line(&mut buf)
|
||||||
|
.expect("Failed to read line");
|
||||||
|
FileSettings {
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,40 +100,39 @@ impl Settings {
|
||||||
for (id, x) in fs.accounts {
|
for (id, x) in fs.accounts {
|
||||||
let mut folders = Vec::new();
|
let mut folders = Vec::new();
|
||||||
fn recurse_folders<P: AsRef<Path>>(folders: &mut Vec<String>, p: P) {
|
fn recurse_folders<P: AsRef<Path>>(folders: &mut Vec<String>, p: P) {
|
||||||
for mut f in fs::read_dir(p).unwrap() {
|
for mut f in fs::read_dir(p).unwrap() {
|
||||||
for f in f.iter_mut() {
|
for f in f.iter_mut() {
|
||||||
{
|
{
|
||||||
let path = f.path();
|
let path = f.path();
|
||||||
if path.ends_with("cur") || path.ends_with("new") ||
|
if path.ends_with("cur") || path.ends_with("new") ||
|
||||||
path.ends_with("tmp") {
|
path.ends_with("tmp")
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
folders.push(path.to_str().unwrap().to_string());
|
folders.push(path.to_str().unwrap().to_string());
|
||||||
recurse_folders(folders, path);
|
recurse_folders(folders, path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
let path = PathBuf::from(&x.folders);
|
let path = PathBuf::from(&x.folders);
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
folders.push(path.to_str().unwrap().to_string());
|
folders.push(path.to_str().unwrap().to_string());
|
||||||
}
|
}
|
||||||
recurse_folders(&mut folders, &x.folders);
|
recurse_folders(&mut folders, &x.folders);
|
||||||
s.insert(id.clone(), AccountSettings {
|
s.insert(
|
||||||
folders: folders,
|
id.clone(),
|
||||||
format: MailFormat::from_str(&x.format),
|
AccountSettings {
|
||||||
sent_folder: x.sent_folder.clone(),
|
folders: folders,
|
||||||
threaded: x.threaded,
|
format: MailFormat::from_str(&x.format),
|
||||||
});
|
sent_folder: x.sent_folder.clone(),
|
||||||
|
threaded: x.threaded,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings { accounts: s }
|
Settings { accounts: s }
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
15
src/error.rs
15
src/error.rs
|
@ -27,20 +27,25 @@ use nom;
|
||||||
|
|
||||||
pub type Result<T> = result::Result<T, MeliError>;
|
pub type Result<T> = result::Result<T, MeliError>;
|
||||||
|
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MeliError {
|
pub struct MeliError {
|
||||||
details: String
|
details: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MeliError {
|
impl MeliError {
|
||||||
pub fn new<M>(msg: M) -> MeliError where M: Into<String> {
|
pub fn new<M>(msg: M) -> MeliError
|
||||||
MeliError{details: msg.into()}
|
where
|
||||||
|
M: Into<String>,
|
||||||
|
{
|
||||||
|
MeliError {
|
||||||
|
details: msg.into(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for MeliError {
|
impl fmt::Display for MeliError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f,"{}",self.details)
|
write!(f, "{}", self.details)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
use mailbox::*;
|
use mailbox::*;
|
||||||
use conf::AccountSettings;
|
use conf::AccountSettings;
|
||||||
use std::ops::{IndexMut, Index};
|
use std::ops::{Index, IndexMut};
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -29,19 +29,21 @@ pub struct Account {
|
||||||
|
|
||||||
sent_folder: Option<usize>,
|
sent_folder: Option<usize>,
|
||||||
|
|
||||||
|
|
||||||
settings: AccountSettings,
|
settings: AccountSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Account {
|
impl Account {
|
||||||
pub fn new(name: String, settings: AccountSettings) -> Self {
|
pub fn new(name: String, settings: AccountSettings) -> Self {
|
||||||
eprintln!("new acc" );
|
eprintln!("new acc");
|
||||||
let sent_folder = settings.folders.iter().position(|x| *x == settings.sent_folder);
|
let sent_folder = settings
|
||||||
let mut folders = Vec::with_capacity(settings.folders.len());
|
.folders
|
||||||
for _ in 0..settings.folders.len() {
|
.iter()
|
||||||
folders.push(None);
|
.position(|x| *x == settings.sent_folder);
|
||||||
}
|
let mut folders = Vec::with_capacity(settings.folders.len());
|
||||||
|
for _ in 0..settings.folders.len() {
|
||||||
|
folders.push(None);
|
||||||
|
}
|
||||||
Account {
|
Account {
|
||||||
name: name,
|
name: name,
|
||||||
folders: folders,
|
folders: folders,
|
||||||
|
@ -60,8 +62,7 @@ impl Index<usize> for Account {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IndexMut<usize> for Account
|
impl IndexMut<usize> for Account {
|
||||||
{
|
|
||||||
fn index_mut(&mut self, index: usize) -> &mut Option<Result<Mailbox>> {
|
fn index_mut(&mut self, index: usize) -> &mut Option<Result<Mailbox>> {
|
||||||
if self.folders[index].is_none() {
|
if self.folders[index].is_none() {
|
||||||
eprintln!("building folder {:?}", self.settings.folders[index]);
|
eprintln!("building folder {:?}", self.settings.folders[index]);
|
||||||
|
@ -73,14 +74,18 @@ impl IndexMut<usize> for Account
|
||||||
self.folders[index] = Some(Mailbox::new(&path, &None));
|
self.folders[index] = Some(Mailbox::new(&path, &None));
|
||||||
eprintln!("Done!");
|
eprintln!("Done!");
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Now building folder {:?} with sent_folder", self.settings.folders[index]);
|
eprintln!(
|
||||||
let (sent, cur) =
|
"Now building folder {:?} with sent_folder",
|
||||||
{
|
self.settings.folders[index]
|
||||||
|
);
|
||||||
|
let (sent, cur) = {
|
||||||
let ptr = self.folders.as_mut_ptr();
|
let ptr = self.folders.as_mut_ptr();
|
||||||
unsafe {
|
unsafe {
|
||||||
use std::slice::from_raw_parts_mut;
|
use std::slice::from_raw_parts_mut;
|
||||||
(from_raw_parts_mut(ptr.offset(id as isize), id+1),
|
(
|
||||||
from_raw_parts_mut(ptr.offset(index as isize), index+1))
|
from_raw_parts_mut(ptr.offset(id as isize), id + 1),
|
||||||
|
from_raw_parts_mut(ptr.offset(index as isize), index + 1),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let sent_path = self.settings.folders[id].clone();
|
let sent_path = self.settings.folders[id].clone();
|
||||||
|
@ -93,13 +98,14 @@ impl IndexMut<usize> for Account
|
||||||
eprintln!("Done!");
|
eprintln!("Done!");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Now building folder {:?} without sent_folder", self.settings.folders[index]);
|
eprintln!(
|
||||||
self.folders[index] = Some(Mailbox::new(&path, &None));
|
"Now building folder {:?} without sent_folder",
|
||||||
eprintln!("Done!");
|
self.settings.folders[index]
|
||||||
|
);
|
||||||
|
self.folders[index] = Some(Mailbox::new(&path, &None));
|
||||||
|
eprintln!("Done!");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
&mut self.folders[index]
|
&mut self.folders[index]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
use mailbox::email::Envelope;
|
use mailbox::email::Envelope;
|
||||||
use error::{MeliError, Result};
|
use error::{MeliError, Result};
|
||||||
use mailbox::backends::{MailBackend, BackendOp, BackendOpGenerator};
|
use mailbox::backends::{BackendOp, BackendOpGenerator, MailBackend};
|
||||||
use mailbox::email::parser;
|
use mailbox::email::parser;
|
||||||
|
|
||||||
extern crate crossbeam;
|
extern crate crossbeam;
|
||||||
|
@ -33,7 +33,7 @@ pub struct MaildirType {
|
||||||
path: String,
|
path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug,Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct MaildirOp {
|
pub struct MaildirOp {
|
||||||
path: String,
|
path: String,
|
||||||
slice: Option<Mmap>,
|
slice: Option<Mmap>,
|
||||||
|
@ -59,11 +59,13 @@ impl MaildirOp {
|
||||||
|
|
||||||
impl BackendOp for MaildirOp {
|
impl BackendOp for MaildirOp {
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> String {
|
||||||
format!("Path of file: {}", self.path)
|
format!("Path of file: {}", self.path)
|
||||||
}
|
}
|
||||||
fn as_bytes(&mut self) -> Result<&[u8]> {
|
fn as_bytes(&mut self) -> Result<&[u8]> {
|
||||||
if self.slice.is_none() {
|
if self.slice.is_none() {
|
||||||
self.slice = Some(Mmap::open_path(self.path.to_string(), Protection::Read).unwrap());
|
self.slice = Some(
|
||||||
|
Mmap::open_path(self.path.to_string(), Protection::Read).unwrap(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Ok(unsafe { self.slice.as_ref().unwrap().as_slice() })
|
Ok(unsafe { self.slice.as_ref().unwrap().as_slice() })
|
||||||
}
|
}
|
||||||
|
@ -110,7 +112,7 @@ impl MailBackend for MaildirType {
|
||||||
impl MaildirType {
|
impl MaildirType {
|
||||||
pub fn new(path: &str) -> Self {
|
pub fn new(path: &str) -> Self {
|
||||||
MaildirType {
|
MaildirType {
|
||||||
path: path.to_string()
|
path: path.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn is_valid(path: &str) -> Result<()> {
|
fn is_valid(path: &str) -> Result<()> {
|
||||||
|
@ -118,7 +120,9 @@ impl MaildirType {
|
||||||
for d in &["cur", "new", "tmp"] {
|
for d in &["cur", "new", "tmp"] {
|
||||||
p.push(d);
|
p.push(d);
|
||||||
if !p.is_dir() {
|
if !p.is_dir() {
|
||||||
return Err(MeliError::new(format!("{} is not a valid maildir folder", path)));
|
return Err(MeliError::new(
|
||||||
|
format!("{} is not a valid maildir folder", path),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
p.pop();
|
p.pop();
|
||||||
}
|
}
|
||||||
|
@ -155,7 +159,6 @@ panic!("didn't parse"); },
|
||||||
|
|
||||||
r.push(m);
|
r.push(m);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
let mut threads = Vec::with_capacity(cores);
|
let mut threads = Vec::with_capacity(cores);
|
||||||
if !files.is_empty() {
|
if !files.is_empty() {
|
||||||
|
@ -163,16 +166,16 @@ panic!("didn't parse"); },
|
||||||
let chunk_size = if count / cores > 0 {
|
let chunk_size = if count / cores > 0 {
|
||||||
count / cores
|
count / cores
|
||||||
} else {
|
} else {
|
||||||
count
|
count
|
||||||
};
|
};
|
||||||
for chunk in files.chunks(chunk_size) {
|
for chunk in files.chunks(chunk_size) {
|
||||||
let s = scope.spawn(move || {
|
let s = scope.spawn(move || {
|
||||||
let mut local_r:Vec<Envelope> = Vec::with_capacity(chunk.len());
|
let mut local_r: Vec<Envelope> = Vec::with_capacity(chunk.len());
|
||||||
for e in chunk {
|
for e in chunk {
|
||||||
let e_copy = e.to_string();
|
let e_copy = e.to_string();
|
||||||
if let Some(e) = Envelope::from(Box::new(BackendOpGenerator::new(Box::new(move || {
|
if let Some(e) = Envelope::from(Box::new(BackendOpGenerator::new(
|
||||||
Box::new(MaildirOp::new(e_copy.clone()))
|
Box::new(move || Box::new(MaildirOp::new(e_copy.clone()))),
|
||||||
} )))) {
|
))) {
|
||||||
local_r.push(e);
|
local_r.push(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,9 +75,9 @@ pub trait BackendOp: ::std::fmt::Debug + ::std::marker::Send {
|
||||||
fn fetch_body(&mut self) -> Result<&[u8]>;
|
fn fetch_body(&mut self) -> Result<&[u8]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `BackendOpGenerator` is a wrapper for a closure that returns a `BackendOp` object
|
/// `BackendOpGenerator` is a wrapper for a closure that returns a `BackendOp` object
|
||||||
/// See `BackendOp` for details.
|
/// See `BackendOp` for details.
|
||||||
/*
|
/*
|
||||||
* I know this sucks, but that's the best way I found that rustc deems safe.
|
* I know this sucks, but that's the best way I found that rustc deems safe.
|
||||||
* */
|
* */
|
||||||
pub struct BackendOpGenerator(Box<Fn() -> Box<BackendOp>>);
|
pub struct BackendOpGenerator(Box<Fn() -> Box<BackendOp>>);
|
||||||
|
@ -97,4 +97,3 @@ impl fmt::Debug for BackendOpGenerator {
|
||||||
write!(f, "BackendOpGenerator: {}", op.description())
|
write!(f, "BackendOpGenerator: {}", op.description())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,20 +30,23 @@ use std::ascii::AsciiExt;
|
||||||
* Multipart
|
* Multipart
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#[derive(Clone,Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum MultipartType {
|
pub enum MultipartType {
|
||||||
Mixed,
|
Mixed,
|
||||||
Alternative,
|
Alternative,
|
||||||
Digest,
|
Digest,
|
||||||
Unsupported { tag: String },
|
Unsupported { tag: String },
|
||||||
}
|
}
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum AttachmentType {
|
pub enum AttachmentType {
|
||||||
Data { tag: String },
|
Data { tag: String },
|
||||||
Text { content: String },
|
Text { content: String },
|
||||||
Multipart { of_type: MultipartType, subattachments: Vec<Attachment>, }
|
Multipart {
|
||||||
|
of_type: MultipartType,
|
||||||
|
subattachments: Vec<Attachment>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ContentType {
|
pub enum ContentType {
|
||||||
Text,
|
Text,
|
||||||
Multipart { boundary: String },
|
Multipart { boundary: String },
|
||||||
|
@ -53,19 +56,13 @@ pub enum ContentType {
|
||||||
impl Display for ContentType {
|
impl Display for ContentType {
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||||
match *self {
|
match *self {
|
||||||
ContentType::Text => {
|
ContentType::Text => write!(f, "text"),
|
||||||
write!(f, "text")
|
ContentType::Multipart { .. } => write!(f, "multipart"),
|
||||||
},
|
ContentType::Unsupported { tag: ref t } => write!(f, "{}", t),
|
||||||
ContentType::Multipart { .. } => {
|
|
||||||
write!(f, "multipart")
|
|
||||||
},
|
|
||||||
ContentType::Unsupported { tag: ref t } => {
|
|
||||||
write!(f, "{}", t)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone,Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum ContentSubType {
|
pub enum ContentSubType {
|
||||||
Plain,
|
Plain,
|
||||||
Other { tag: String },
|
Other { tag: String },
|
||||||
|
@ -73,16 +70,12 @@ pub enum ContentSubType {
|
||||||
impl Display for ContentSubType {
|
impl Display for ContentSubType {
|
||||||
fn fmt(&self, f: &mut Formatter) -> Result {
|
fn fmt(&self, f: &mut Formatter) -> Result {
|
||||||
match *self {
|
match *self {
|
||||||
ContentSubType::Plain => {
|
ContentSubType::Plain => write!(f, "plain"),
|
||||||
write!(f, "plain")
|
ContentSubType::Other { tag: ref t } => write!(f, "{}", t),
|
||||||
},
|
|
||||||
ContentSubType::Other { tag: ref t } => {
|
|
||||||
write!(f, "{}", t)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum ContentTransferEncoding {
|
pub enum ContentTransferEncoding {
|
||||||
_8Bit,
|
_8Bit,
|
||||||
_7Bit,
|
_7Bit,
|
||||||
|
@ -110,111 +103,111 @@ impl AttachmentBuilder {
|
||||||
}
|
}
|
||||||
pub fn content_type(&mut self, value: &str) -> &Self {
|
pub fn content_type(&mut self, value: &str) -> &Self {
|
||||||
match parser::content_type(value.as_bytes()).to_full_result() {
|
match parser::content_type(value.as_bytes()).to_full_result() {
|
||||||
Ok((ct, cst, params)) => {
|
Ok((ct, cst, params)) => if ct.eq_ignore_ascii_case("multipart") {
|
||||||
if ct.eq_ignore_ascii_case("multipart") {
|
let mut boundary = None;
|
||||||
let mut boundary = None;
|
for (n, v) in params {
|
||||||
for (n, v) in params {
|
if n.eq_ignore_ascii_case("boundary") {
|
||||||
if n.eq_ignore_ascii_case("boundary") {
|
boundary = Some(format!("--{}--", v).to_string());
|
||||||
boundary = Some(format!("--{}--", v).to_string());
|
break;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
assert!(boundary.is_some());
|
|
||||||
self.content_type.0 = ContentType::Multipart { boundary: boundary.unwrap() };
|
|
||||||
self.content_type.1 = ContentSubType::Other { tag: cst.to_string() };
|
|
||||||
} else if ct.eq_ignore_ascii_case("text") {
|
|
||||||
self.content_type.0 = ContentType::Text;
|
|
||||||
if !cst.eq_ignore_ascii_case("plain") {
|
|
||||||
self.content_type.1 = ContentSubType::Other { tag: cst.to_ascii_lowercase() };
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.content_type.0 = ContentType::Unsupported { tag: ct.to_ascii_lowercase() };
|
|
||||||
self.content_type.1 = ContentSubType::Other { tag: cst.to_ascii_lowercase() };
|
|
||||||
}
|
}
|
||||||
|
assert!(boundary.is_some());
|
||||||
|
self.content_type.0 = ContentType::Multipart {
|
||||||
|
boundary: boundary.unwrap(),
|
||||||
|
};
|
||||||
|
self.content_type.1 = ContentSubType::Other {
|
||||||
|
tag: cst.to_string(),
|
||||||
|
};
|
||||||
|
} else if ct.eq_ignore_ascii_case("text") {
|
||||||
|
self.content_type.0 = ContentType::Text;
|
||||||
|
if !cst.eq_ignore_ascii_case("plain") {
|
||||||
|
self.content_type.1 = ContentSubType::Other {
|
||||||
|
tag: cst.to_ascii_lowercase(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.content_type.0 = ContentType::Unsupported {
|
||||||
|
tag: ct.to_ascii_lowercase(),
|
||||||
|
};
|
||||||
|
self.content_type.1 = ContentSubType::Other {
|
||||||
|
tag: cst.to_ascii_lowercase(),
|
||||||
|
};
|
||||||
},
|
},
|
||||||
Err(v) => {
|
Err(v) => {
|
||||||
eprintln!("parsing error in content_type: {:?} {:?}", value, v);
|
eprintln!("parsing error in content_type: {:?} {:?}", value, v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
pub fn content_transfer_encoding(&mut self, value: &str) -> &Self {
|
pub fn content_transfer_encoding(&mut self, value: &str) -> &Self {
|
||||||
self.content_transfer_encoding =
|
self.content_transfer_encoding = if value.eq_ignore_ascii_case("base64") {
|
||||||
if value.eq_ignore_ascii_case("base64") {
|
ContentTransferEncoding::Base64
|
||||||
ContentTransferEncoding::Base64
|
} else if value.eq_ignore_ascii_case("7bit") {
|
||||||
} else if value.eq_ignore_ascii_case("7bit") {
|
ContentTransferEncoding::_7Bit
|
||||||
ContentTransferEncoding::_7Bit
|
} else if value.eq_ignore_ascii_case("8bit") {
|
||||||
} else if value.eq_ignore_ascii_case("8bit") {
|
ContentTransferEncoding::_8Bit
|
||||||
ContentTransferEncoding::_8Bit
|
} else if value.eq_ignore_ascii_case("quoted-printable") {
|
||||||
} else if value.eq_ignore_ascii_case("quoted-printable") {
|
ContentTransferEncoding::QuotedPrintable
|
||||||
ContentTransferEncoding::QuotedPrintable
|
} else {
|
||||||
} else {
|
ContentTransferEncoding::Other {
|
||||||
ContentTransferEncoding::Other { tag: value.to_ascii_lowercase() }
|
tag: value.to_ascii_lowercase(),
|
||||||
};
|
}
|
||||||
|
};
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
fn decode(&self) -> String {
|
fn decode(&self) -> String {
|
||||||
match self.content_transfer_encoding {
|
match self.content_transfer_encoding {
|
||||||
ContentTransferEncoding::Base64 => {
|
ContentTransferEncoding::Base64 => {
|
||||||
match ::base64::decode(&::std::str::from_utf8(&self.raw).unwrap().trim().lines().fold(String::with_capacity(self.raw.len()), |mut acc, x| { acc.push_str(x); acc })) {
|
match ::base64::decode(&::std::str::from_utf8(&self.raw)
|
||||||
Ok( ref v ) => {
|
.unwrap()
|
||||||
let s = String::from_utf8_lossy(v);
|
.trim()
|
||||||
if s.find("\r\n").is_some() {
|
.lines()
|
||||||
s.replace("\r\n","\n")
|
.fold(String::with_capacity(self.raw.len()), |mut acc, x| {
|
||||||
} else {
|
acc.push_str(x);
|
||||||
s.into_owned()
|
acc
|
||||||
}
|
})) {
|
||||||
},
|
Ok(ref v) => {
|
||||||
_ => {
|
let s = String::from_utf8_lossy(v);
|
||||||
String::from_utf8_lossy(&self.raw).into_owned()
|
if s.find("\r\n").is_some() {
|
||||||
|
s.replace("\r\n", "\n")
|
||||||
|
} else {
|
||||||
|
s.into_owned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
_ => String::from_utf8_lossy(&self.raw).into_owned(),
|
||||||
ContentTransferEncoding::QuotedPrintable => {
|
|
||||||
parser::quoted_printable_text(&self.raw).to_full_result().unwrap()
|
|
||||||
},
|
|
||||||
ContentTransferEncoding::_7Bit | ContentTransferEncoding::_8Bit | ContentTransferEncoding::Other { .. } => {
|
|
||||||
String::from_utf8_lossy(&self.raw).into_owned()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_text(&self.raw)
|
||||||
|
.to_full_result()
|
||||||
|
.unwrap(),
|
||||||
|
ContentTransferEncoding::_7Bit |
|
||||||
|
ContentTransferEncoding::_8Bit |
|
||||||
|
ContentTransferEncoding::Other { .. } => {
|
||||||
|
String::from_utf8_lossy(&self.raw).into_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pub fn build(self) -> Attachment {
|
pub fn build(self) -> Attachment {
|
||||||
let attachment_type =
|
let attachment_type = match self.content_type.0 {
|
||||||
match self.content_type.0 {
|
ContentType::Text => AttachmentType::Text {
|
||||||
ContentType::Text => {
|
content: self.decode(),
|
||||||
AttachmentType::Text { content: self.decode() }
|
|
||||||
},
|
},
|
||||||
ContentType::Multipart { boundary: ref b } => {
|
ContentType::Multipart { boundary: ref b } => {
|
||||||
let multipart_type =
|
let multipart_type = match self.content_type.1 {
|
||||||
match self.content_type.1 {
|
ContentSubType::Other { ref tag } => match tag.as_ref() {
|
||||||
ContentSubType::Other { ref tag } => {
|
"mixed" => MultipartType::Mixed,
|
||||||
match tag.as_ref() {
|
"alternative" => MultipartType::Alternative,
|
||||||
"mixed" => {
|
"digest" => MultipartType::Digest,
|
||||||
MultipartType::Mixed
|
t => MultipartType::Unsupported { tag: t.to_string() },
|
||||||
},
|
},
|
||||||
"alternative" => {
|
_ => panic!(),
|
||||||
MultipartType::Alternative
|
};
|
||||||
},
|
|
||||||
"digest" => {
|
|
||||||
MultipartType::Digest
|
|
||||||
},
|
|
||||||
t => {
|
|
||||||
MultipartType::Unsupported { tag: t.to_string() }
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
panic!()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
AttachmentType::Multipart {
|
AttachmentType::Multipart {
|
||||||
of_type: multipart_type,
|
of_type: multipart_type,
|
||||||
subattachments: Attachment::subattachments(&self.raw, b),
|
subattachments: Attachment::subattachments(&self.raw, b),
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
ContentType::Unsupported { ref tag } => {
|
ContentType::Unsupported { ref tag } => AttachmentType::Data { tag: tag.clone() },
|
||||||
AttachmentType::Data { tag: tag.clone() }
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
Attachment {
|
Attachment {
|
||||||
content_type: self.content_type,
|
content_type: self.content_type,
|
||||||
|
@ -222,13 +215,11 @@ self
|
||||||
raw: self.raw,
|
raw: self.raw,
|
||||||
attachment_type: attachment_type,
|
attachment_type: attachment_type,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Attachment {
|
pub struct Attachment {
|
||||||
content_type: (ContentType, ContentSubType),
|
content_type: (ContentType, ContentSubType),
|
||||||
content_transfer_encoding: ContentTransferEncoding,
|
content_transfer_encoding: ContentTransferEncoding,
|
||||||
|
@ -243,27 +234,25 @@ impl Attachment {
|
||||||
match self.attachment_type {
|
match self.attachment_type {
|
||||||
AttachmentType::Data { .. } => {
|
AttachmentType::Data { .. } => {
|
||||||
text.push_str(&format!("Data attachment of type {}", self.get_tag()));
|
text.push_str(&format!("Data attachment of type {}", self.get_tag()));
|
||||||
},
|
}
|
||||||
AttachmentType::Text { content: ref t } => {
|
AttachmentType::Text { content: ref t } => {
|
||||||
text.push_str(t);
|
text.push_str(t);
|
||||||
},
|
}
|
||||||
AttachmentType::Multipart {
|
AttachmentType::Multipart {
|
||||||
of_type: ref multipart_type,
|
of_type: ref multipart_type,
|
||||||
subattachments: ref sub_att_vec,
|
subattachments: ref sub_att_vec,
|
||||||
} => {
|
} => if *multipart_type == MultipartType::Alternative {
|
||||||
if *multipart_type == MultipartType::Alternative {
|
for a in sub_att_vec {
|
||||||
for a in sub_att_vec {
|
if a.content_type.1 == ContentSubType::Plain {
|
||||||
if a.content_type.1 == ContentSubType::Plain {
|
|
||||||
a.get_text_recursive(text);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for a in sub_att_vec {
|
|
||||||
a.get_text_recursive(text);
|
a.get_text_recursive(text);
|
||||||
text.push_str("\n\n");
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
for a in sub_att_vec {
|
||||||
|
a.get_text_recursive(text);
|
||||||
|
text.push_str("\n\n");
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -280,7 +269,8 @@ impl Attachment {
|
||||||
}
|
}
|
||||||
pub fn subattachments(raw: &[u8], boundary: &str) -> Vec<Attachment> {
|
pub fn subattachments(raw: &[u8], boundary: &str) -> Vec<Attachment> {
|
||||||
let boundary_length = boundary.len();
|
let boundary_length = boundary.len();
|
||||||
match parser::attachments(raw, &boundary[0..boundary_length - 2], boundary).to_full_result() {
|
match parser::attachments(raw, &boundary[0..boundary_length - 2], boundary).to_full_result()
|
||||||
|
{
|
||||||
Ok(attachments) => {
|
Ok(attachments) => {
|
||||||
let mut vec = Vec::with_capacity(attachments.len());
|
let mut vec = Vec::with_capacity(attachments.len());
|
||||||
for a in attachments {
|
for a in attachments {
|
||||||
|
@ -292,26 +282,30 @@ impl Attachment {
|
||||||
eprintln!("{}\n", ::std::string::String::from_utf8_lossy(a));
|
eprintln!("{}\n", ::std::string::String::from_utf8_lossy(a));
|
||||||
eprintln!("-------------------------------\n");
|
eprintln!("-------------------------------\n");
|
||||||
|
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut builder = AttachmentBuilder::new(body);
|
let mut builder = AttachmentBuilder::new(body);
|
||||||
for (name, value) in headers {
|
for (name, value) in headers {
|
||||||
if name.eq_ignore_ascii_case("content-type") {
|
if name.eq_ignore_ascii_case("content-type") {
|
||||||
builder.content_type(value);
|
builder.content_type(value);
|
||||||
} else if name.eq_ignore_ascii_case("content-transfer-encoding") {
|
} else if name.eq_ignore_ascii_case("content-transfer-encoding") {
|
||||||
builder.content_transfer_encoding(value);
|
builder.content_transfer_encoding(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vec.push(builder.build());
|
vec.push(builder.build());
|
||||||
}
|
}
|
||||||
vec
|
vec
|
||||||
},
|
}
|
||||||
a => {
|
a => {
|
||||||
eprintln!("error in 469 {:?}\n\traw: {:?}\n\tboundary: {:?}", a, ::std::str::from_utf8(raw).unwrap(), boundary);
|
eprintln!(
|
||||||
|
"error in 469 {:?}\n\traw: {:?}\n\tboundary: {:?}",
|
||||||
|
a,
|
||||||
|
::std::str::from_utf8(raw).unwrap(),
|
||||||
|
boundary
|
||||||
|
);
|
||||||
Vec::new()
|
Vec::new()
|
||||||
},
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ use mailbox::backends::BackendOpGenerator;
|
||||||
use self::attachments::*;
|
use self::attachments::*;
|
||||||
|
|
||||||
use std::string::String;
|
use std::string::String;
|
||||||
|
use std::sync::Arc;
|
||||||
//use std;
|
//use std;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
@ -36,7 +37,7 @@ use chrono;
|
||||||
use chrono::TimeZone;
|
use chrono::TimeZone;
|
||||||
|
|
||||||
/* Helper struct to return slices from a struct on demand */
|
/* Helper struct to return slices from a struct on demand */
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct StrBuilder {
|
struct StrBuilder {
|
||||||
offset: usize,
|
offset: usize,
|
||||||
length: usize,
|
length: usize,
|
||||||
|
@ -49,15 +50,18 @@ pub trait StrBuild {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MessageID (String, StrBuilder);
|
pub struct MessageID(String, StrBuilder);
|
||||||
|
|
||||||
impl StrBuild for MessageID {
|
impl StrBuild for MessageID {
|
||||||
fn new(string: &str, slice: &str) -> Self {
|
fn new(string: &str, slice: &str) -> Self {
|
||||||
let offset = string.find(slice).unwrap();
|
let offset = string.find(slice).unwrap();
|
||||||
MessageID (string.to_string(), StrBuilder {
|
MessageID(
|
||||||
offset: offset,
|
string.to_string(),
|
||||||
length: slice.len() + 1,
|
StrBuilder {
|
||||||
})
|
offset: offset,
|
||||||
|
length: slice.len() + 1,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
fn get_raw(&self) -> &str {
|
fn get_raw(&self) -> &str {
|
||||||
let offset = self.1.offset;
|
let offset = self.1.offset;
|
||||||
|
@ -73,7 +77,16 @@ impl StrBuild for MessageID {
|
||||||
fn test_strbuilder() {
|
fn test_strbuilder() {
|
||||||
let m_id = "<20170825132332.6734-1-el13635@mail.ntua.gr>";
|
let m_id = "<20170825132332.6734-1-el13635@mail.ntua.gr>";
|
||||||
let (_, slice) = parser::message_id(m_id.as_bytes()).unwrap();
|
let (_, slice) = parser::message_id(m_id.as_bytes()).unwrap();
|
||||||
assert_eq!(MessageID::new(m_id, slice), MessageID (m_id.to_string(), StrBuilder{offset: 1, length: 43}));
|
assert_eq!(
|
||||||
|
MessageID::new(m_id, slice),
|
||||||
|
MessageID(
|
||||||
|
m_id.to_string(),
|
||||||
|
StrBuilder {
|
||||||
|
offset: 1,
|
||||||
|
length: 43,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq for MessageID {
|
impl PartialEq for MessageID {
|
||||||
|
@ -87,17 +100,15 @@ impl fmt::Debug for MessageID {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct References {
|
struct References {
|
||||||
raw: String,
|
raw: String,
|
||||||
refs: Vec<MessageID>,
|
refs: Vec<MessageID>,
|
||||||
}
|
}
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
/* A very primitive mail object */
|
/* A very primitive mail object */
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Envelope
|
pub struct Envelope {
|
||||||
{
|
|
||||||
date: String,
|
date: String,
|
||||||
from: Option<String>,
|
from: Option<String>,
|
||||||
to: Option<String>,
|
to: Option<String>,
|
||||||
|
@ -115,13 +126,16 @@ pub struct Envelope
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Envelope
|
impl Envelope {
|
||||||
{
|
|
||||||
pub fn get_date(&self) -> i64 {
|
pub fn get_date(&self) -> i64 {
|
||||||
self.timestamp
|
self.timestamp
|
||||||
}
|
}
|
||||||
pub fn get_datetime(&self) -> chrono::DateTime<chrono::FixedOffset> {
|
pub fn get_datetime(&self) -> chrono::DateTime<chrono::FixedOffset> {
|
||||||
self.datetime.unwrap_or_else(|| { chrono::FixedOffset::west(0).ymd(1970, 1, 1).and_hms(0, 0, 0)})
|
self.datetime.unwrap_or_else(|| {
|
||||||
|
chrono::FixedOffset::west(0)
|
||||||
|
.ymd(1970, 1, 1)
|
||||||
|
.and_hms(0, 0, 0)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
pub fn get_date_as_str(&self) -> &str {
|
pub fn get_date_as_str(&self) -> &str {
|
||||||
&self.date
|
&self.date
|
||||||
|
@ -147,7 +161,7 @@ impl Envelope
|
||||||
let operation = self.operation_token.generate();
|
let operation = self.operation_token.generate();
|
||||||
eprintln!("error in parsing mail\n{}", operation.description());
|
eprintln!("error in parsing mail\n{}", operation.description());
|
||||||
panic!()
|
panic!()
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
let mut builder = AttachmentBuilder::new(body);
|
let mut builder = AttachmentBuilder::new(body);
|
||||||
for (name, value) in headers {
|
for (name, value) in headers {
|
||||||
|
@ -165,19 +179,19 @@ impl Envelope
|
||||||
pub fn get_subject(&self) -> &str {
|
pub fn get_subject(&self) -> &str {
|
||||||
match self.subject {
|
match self.subject {
|
||||||
Some(ref s) => s,
|
Some(ref s) => s,
|
||||||
_ => ""
|
_ => "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_in_reply_to(&self) -> &str {
|
pub fn get_in_reply_to(&self) -> &str {
|
||||||
match self.in_reply_to {
|
match self.in_reply_to {
|
||||||
Some(ref s) => s.get_val(),
|
Some(ref s) => s.get_val(),
|
||||||
_ => ""
|
_ => "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_in_reply_to_raw(&self) -> &str {
|
pub fn get_in_reply_to_raw(&self) -> &str {
|
||||||
match self.in_reply_to {
|
match self.in_reply_to {
|
||||||
Some(ref s) => s.get_raw(),
|
Some(ref s) => s.get_raw(),
|
||||||
_ => ""
|
_ => "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_message_id(&self) -> &str {
|
pub fn get_message_id(&self) -> &str {
|
||||||
|
@ -203,10 +217,12 @@ impl Envelope
|
||||||
}
|
}
|
||||||
fn set_in_reply_to(&mut self, new_val: &str) -> () {
|
fn set_in_reply_to(&mut self, new_val: &str) -> () {
|
||||||
let slice = match parser::message_id(new_val.as_bytes()).to_full_result() {
|
let slice = match parser::message_id(new_val.as_bytes()).to_full_result() {
|
||||||
Ok(v) => { v },
|
Ok(v) => v,
|
||||||
Err(v) => { eprintln!("{} {:?}",new_val, v);
|
Err(v) => {
|
||||||
|
eprintln!("{} {:?}", new_val, v);
|
||||||
self.in_reply_to = None;
|
self.in_reply_to = None;
|
||||||
return; }
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
self.in_reply_to = Some(MessageID::new(new_val, slice));
|
self.in_reply_to = Some(MessageID::new(new_val, slice));
|
||||||
}
|
}
|
||||||
|
@ -215,23 +231,27 @@ impl Envelope
|
||||||
}
|
}
|
||||||
fn set_message_id(&mut self, new_val: &str) -> () {
|
fn set_message_id(&mut self, new_val: &str) -> () {
|
||||||
let slice = match parser::message_id(new_val.as_bytes()).to_full_result() {
|
let slice = match parser::message_id(new_val.as_bytes()).to_full_result() {
|
||||||
Ok(v) => { v },
|
Ok(v) => v,
|
||||||
Err(v) => { eprintln!("{} {:?}",new_val, v);
|
Err(v) => {
|
||||||
|
eprintln!("{} {:?}", new_val, v);
|
||||||
self.message_id = None;
|
self.message_id = None;
|
||||||
return; }
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
self.message_id = Some(MessageID::new(new_val, slice));
|
self.message_id = Some(MessageID::new(new_val, slice));
|
||||||
}
|
}
|
||||||
fn push_references(&mut self, new_val: &str) -> () {
|
fn push_references(&mut self, new_val: &str) -> () {
|
||||||
let slice = match parser::message_id(new_val.as_bytes()).to_full_result() {
|
let slice = match parser::message_id(new_val.as_bytes()).to_full_result() {
|
||||||
Ok(v) => { v },
|
Ok(v) => v,
|
||||||
Err(v) => { eprintln!("{} {:?}",new_val, v);
|
Err(v) => {
|
||||||
return; }
|
eprintln!("{} {:?}", new_val, v);
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
let new_ref = MessageID::new(new_val, slice);
|
let new_ref = MessageID::new(new_val, slice);
|
||||||
match self.references {
|
match self.references {
|
||||||
Some(ref mut s) => {
|
Some(ref mut s) => {
|
||||||
if s.refs.contains(&new_ref) {
|
if s.refs.contains(&new_ref) {
|
||||||
if s.refs[s.refs.len() - 1] != new_ref {
|
if s.refs[s.refs.len() - 1] != new_ref {
|
||||||
if let Some(index) = s.refs.iter().position(|x| *x == new_ref) {
|
if let Some(index) = s.refs.iter().position(|x| *x == new_ref) {
|
||||||
s.refs.remove(index);
|
s.refs.remove(index);
|
||||||
|
@ -243,29 +263,39 @@ impl Envelope
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.refs.push(new_ref);
|
s.refs.push(new_ref);
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
let mut v = Vec::new();
|
let mut v = Vec::new();
|
||||||
v.push(new_ref);
|
v.push(new_ref);
|
||||||
self.references = Some(References { raw: "".to_string(), refs: v, });
|
self.references = Some(References {
|
||||||
|
raw: "".to_string(),
|
||||||
|
refs: v,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
fn set_references(&mut self, new_val: String) -> () {
|
fn set_references(&mut self, new_val: String) -> () {
|
||||||
match self.references {
|
match self.references {
|
||||||
Some(ref mut s) => {
|
Some(ref mut s) => {
|
||||||
s.raw = new_val;
|
s.raw = new_val;
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
let v = Vec::new();
|
let v = Vec::new();
|
||||||
self.references = Some(References { raw: new_val, refs: v, });
|
self.references = Some(References {
|
||||||
|
raw: new_val,
|
||||||
|
refs: v,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get_references(&self) -> Vec<&MessageID> {
|
pub fn get_references(&self) -> Vec<&MessageID> {
|
||||||
match self.references {
|
match self.references {
|
||||||
Some(ref s) => s.refs.iter().fold(Vec::with_capacity(s.refs.len()), |mut acc, x| { acc.push(x); acc }),
|
Some(ref s) => s.refs
|
||||||
|
.iter()
|
||||||
|
.fold(Vec::with_capacity(s.refs.len()), |mut acc, x| {
|
||||||
|
acc.push(x);
|
||||||
|
acc
|
||||||
|
}),
|
||||||
None => Vec::new(),
|
None => Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -276,7 +306,7 @@ impl Envelope
|
||||||
self.thread
|
self.thread
|
||||||
}
|
}
|
||||||
pub fn set_thread(&mut self, new_val: usize) -> () {
|
pub fn set_thread(&mut self, new_val: usize) -> () {
|
||||||
self.thread = new_val;
|
self.thread = new_val;
|
||||||
}
|
}
|
||||||
pub fn set_datetime(&mut self, new_val: Option<chrono::DateTime<chrono::FixedOffset>>) -> () {
|
pub fn set_datetime(&mut self, new_val: Option<chrono::DateTime<chrono::FixedOffset>>) -> () {
|
||||||
self.datetime = new_val;
|
self.datetime = new_val;
|
||||||
|
@ -301,20 +331,17 @@ impl Envelope
|
||||||
thread: 0,
|
thread: 0,
|
||||||
|
|
||||||
operation_token: Arc::new(token),
|
operation_token: Arc::new(token),
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn from(operation_token: Box<BackendOpGenerator>) -> Option<Envelope> {
|
pub fn from(operation_token: Box<BackendOpGenerator>) -> Option<Envelope> {
|
||||||
let mut operation = operation_token.generate();
|
let mut operation = operation_token.generate();
|
||||||
let headers = match parser::headers(operation.fetch_headers().unwrap()).to_full_result() {
|
let headers = match parser::headers(operation.fetch_headers().unwrap()).to_full_result() {
|
||||||
Ok(v) => {
|
Ok(v) => v,
|
||||||
v
|
|
||||||
},
|
|
||||||
_ => {
|
_ => {
|
||||||
let operation = operation_token.generate();
|
let operation = operation_token.generate();
|
||||||
eprintln!("error in parsing mail\n{}", operation.description());
|
eprintln!("error in parsing mail\n{}", operation.description());
|
||||||
return None;
|
return None;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut mail = Envelope::new(operation_token);
|
let mut mail = Envelope::new(operation_token);
|
||||||
|
|
|
@ -22,12 +22,12 @@ use std;
|
||||||
use std::str::from_utf8;
|
use std::str::from_utf8;
|
||||||
use base64;
|
use base64;
|
||||||
use chrono;
|
use chrono;
|
||||||
use nom::{le_u8, is_hex_digit};
|
use nom::{is_hex_digit, le_u8};
|
||||||
use nom::{IResult,Needed,ErrorKind};
|
use nom::{ErrorKind, IResult, Needed};
|
||||||
use nom::{Compare, CompareResult};
|
use nom::{Compare, CompareResult};
|
||||||
use encoding::{Encoding, DecoderTrap};
|
use encoding::{DecoderTrap, Encoding};
|
||||||
|
|
||||||
fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8],u8> {
|
fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8], u8> {
|
||||||
if input.is_empty() || input.len() < 3 {
|
if input.is_empty() || input.len() < 3 {
|
||||||
IResult::Incomplete(Needed::Size(1))
|
IResult::Incomplete(Needed::Size(1))
|
||||||
} else if input[0] == b'=' && is_hex_digit(input[1]) && is_hex_digit(input[2]) {
|
} else if input[0] == b'=' && is_hex_digit(input[1]) && is_hex_digit(input[2]) {
|
||||||
|
@ -45,7 +45,7 @@ fn quoted_printable_byte(input: &[u8]) -> IResult<&[u8],u8> {
|
||||||
} else {
|
} else {
|
||||||
input[2] - 87
|
input[2] - 87
|
||||||
};
|
};
|
||||||
IResult::Done(&input[3..], a*16+b)
|
IResult::Done(&input[3..], a * 16 + b)
|
||||||
} else {
|
} else {
|
||||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||||
}
|
}
|
||||||
|
@ -70,18 +70,15 @@ fn header_value(input: &[u8]) -> IResult<&[u8], &str> {
|
||||||
for (i, x) in input.iter().enumerate() {
|
for (i, x) in input.iter().enumerate() {
|
||||||
if *x == b'\n' {
|
if *x == b'\n' {
|
||||||
if (i + 1) < input_len &&
|
if (i + 1) < input_len &&
|
||||||
((input[i+1] != b' ' && input[i+1] != b'\t') || input[i+1] == b'\n') {
|
((input[i + 1] != b' ' && input[i + 1] != b'\t') || input[i + 1] == b'\n')
|
||||||
return match from_utf8(&input[0..i]) {
|
{
|
||||||
Ok(v) => {
|
return match from_utf8(&input[0..i]) {
|
||||||
IResult::Done(&input[(i+1)..], v)
|
Ok(v) => IResult::Done(&input[(i + 1)..], v),
|
||||||
},
|
Err(_) => IResult::Error(error_code!(ErrorKind::Custom(43))),
|
||||||
Err(_) => {
|
};
|
||||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
} else if i + 1 > input_len {
|
||||||
},
|
return IResult::Incomplete(Needed::Size(1));
|
||||||
}
|
}
|
||||||
} else if i + 1 > input_len {
|
|
||||||
return IResult::Incomplete(Needed::Size(1));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||||
|
@ -90,12 +87,13 @@ fn header_value(input: &[u8]) -> IResult<&[u8], &str> {
|
||||||
|
|
||||||
|
|
||||||
/* Parse the name part of the header -> &str */
|
/* Parse the name part of the header -> &str */
|
||||||
named!(name<&str>,
|
named!(name<&str>, map_res!(is_not!(":\n"), from_utf8));
|
||||||
map_res!(is_not!(":\n"), from_utf8));
|
|
||||||
|
|
||||||
/* Parse a single header as a tuple -> (&str, Vec<&str>) */
|
/* Parse a single header as a tuple -> (&str, Vec<&str>) */
|
||||||
named!(header<(&str, &str)>,
|
named!(
|
||||||
separated_pair!(complete!(name), ws!(tag!(":")), complete!(header_value)));
|
header<(&str, &str)>,
|
||||||
|
separated_pair!(complete!(name), ws!(tag!(":")), complete!(header_value))
|
||||||
|
);
|
||||||
/* Parse all headers -> Vec<(&str, Vec<&str>)> */
|
/* Parse all headers -> Vec<(&str, Vec<&str>)> */
|
||||||
named!(pub headers<std::vec::Vec<(&str, &str)>>,
|
named!(pub headers<std::vec::Vec<(&str, &str)>>,
|
||||||
many1!(complete!(header)));
|
many1!(complete!(header)));
|
||||||
|
@ -126,103 +124,95 @@ named!(pub attachment<(std::vec::Vec<(&str, &str)>, &[u8])>,
|
||||||
|
|
||||||
/* TODO: make a map of encodings and decoding functions so that they can be reused and easily
|
/* TODO: make a map of encodings and decoding functions so that they can be reused and easily
|
||||||
* extended */
|
* extended */
|
||||||
use encoding::all::{ISO_8859_1,ISO_8859_2, ISO_8859_7, WINDOWS_1253, GBK};
|
use encoding::all::{ISO_8859_1, ISO_8859_2, ISO_8859_7, WINDOWS_1253, GBK};
|
||||||
|
|
||||||
fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
||||||
if input.len() < 5 {
|
if input.len() < 5 {
|
||||||
return IResult::Incomplete(Needed::Unknown);
|
return IResult::Incomplete(Needed::Unknown);
|
||||||
} else if input[0] != b'=' || input[1] != b'?' {
|
} else if input[0] != b'=' || input[1] != b'?' {
|
||||||
return IResult::Error(error_code!(ErrorKind::Custom(43)))
|
return IResult::Error(error_code!(ErrorKind::Custom(43)));
|
||||||
}
|
}
|
||||||
for tag in &["UTF-8", "iso-8859-7", "windows-1253", "iso-8859-1", "iso-8859-2", "gbk"] {
|
for tag in &[
|
||||||
|
"UTF-8",
|
||||||
|
"iso-8859-7",
|
||||||
|
"windows-1253",
|
||||||
|
"iso-8859-1",
|
||||||
|
"iso-8859-2",
|
||||||
|
"gbk",
|
||||||
|
] {
|
||||||
if let CompareResult::Ok = (&input[2..]).compare_no_case(*tag) {
|
if let CompareResult::Ok = (&input[2..]).compare_no_case(*tag) {
|
||||||
let tag_len = tag.len();
|
let tag_len = tag.len();
|
||||||
/* tag must end with ?_? where _ is either Q or B, eg: =?UTF-8?B? */
|
/* tag must end with ?_? where _ is either Q or B, eg: =?UTF-8?B? */
|
||||||
if input[2+tag_len] != b'?' || input[2+tag_len+2] != b'?' {
|
if input[2 + tag_len] != b'?' || input[2 + tag_len + 2] != b'?' {
|
||||||
return IResult::Error(error_code!(ErrorKind::Custom(43)))
|
return IResult::Error(error_code!(ErrorKind::Custom(43)));
|
||||||
}
|
}
|
||||||
/* See if input ends with "?=" and get ending index */
|
/* See if input ends with "?=" and get ending index */
|
||||||
let mut encoded_idx = None;
|
let mut encoded_idx = None;
|
||||||
for i in (5+tag_len)..input.len() {
|
for i in (5 + tag_len)..input.len() {
|
||||||
if input[i] == b'?' && i < input.len() && input[i+1] == b'=' {
|
if input[i] == b'?' && i < input.len() && input[i + 1] == b'=' {
|
||||||
encoded_idx = Some(i);
|
encoded_idx = Some(i);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
if encoded_idx.is_none() {
|
|
||||||
return IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
||||||
}
|
}
|
||||||
let encoded = &input[5+tag_len..encoded_idx.unwrap()];
|
if encoded_idx.is_none() {
|
||||||
|
return IResult::Error(error_code!(ErrorKind::Custom(43)));
|
||||||
|
}
|
||||||
|
let encoded = &input[5 + tag_len..encoded_idx.unwrap()];
|
||||||
|
|
||||||
let s:Vec<u8> = match input[2+tag_len+1] {
|
let s: Vec<u8> = match input[2 + tag_len + 1] {
|
||||||
b'b' | b'B' => {
|
b'b' | b'B' => match base64::decode(encoded) {
|
||||||
match base64::decode(encoded) {
|
Ok(v) => v,
|
||||||
Ok(v) => {
|
Err(_) => encoded.to_vec(),
|
||||||
v
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
encoded.to_vec()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
b'q' | b'Q' => {
|
b'q' | b'Q' => match get_quoted_printed_bytes(encoded) {
|
||||||
match get_quoted_printed_bytes(encoded) {
|
IResult::Done(b"", s) => s,
|
||||||
IResult::Done(b"", s) => {
|
_ => return IResult::Error(error_code!(ErrorKind::Custom(43))),
|
||||||
s
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return IResult::Error(error_code!(ErrorKind::Custom(43)))
|
|
||||||
},
|
},
|
||||||
|
_ => return IResult::Error(error_code!(ErrorKind::Custom(43))),
|
||||||
};
|
};
|
||||||
|
|
||||||
match *tag {
|
match *tag {
|
||||||
"UTF-8" => {
|
"UTF-8" => {
|
||||||
return IResult::Done(&input[encoded_idx.unwrap()+2..], s);
|
return IResult::Done(&input[encoded_idx.unwrap() + 2..], s);
|
||||||
},
|
}
|
||||||
"iso-8859-7" => {
|
"iso-8859-7" => {
|
||||||
return if let Ok(v) = ISO_8859_7.decode(&s, DecoderTrap::Strict) {
|
return if let Ok(v) = ISO_8859_7.decode(&s, DecoderTrap::Strict) {
|
||||||
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes())
|
IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
|
||||||
} else {
|
} else {
|
||||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"windows-1253" => {
|
"windows-1253" => {
|
||||||
return if let Ok(v) = WINDOWS_1253.decode(&s, DecoderTrap::Strict) {
|
return if let Ok(v) = WINDOWS_1253.decode(&s, DecoderTrap::Strict) {
|
||||||
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes())
|
IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
|
||||||
} else {
|
} else {
|
||||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"iso-8859-1" => {
|
"iso-8859-1" => {
|
||||||
return if let Ok(v) = ISO_8859_1.decode(&s, DecoderTrap::Strict) {
|
return if let Ok(v) = ISO_8859_1.decode(&s, DecoderTrap::Strict) {
|
||||||
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes())
|
IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
|
||||||
} else {
|
} else {
|
||||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"iso-8859-2" => {
|
"iso-8859-2" => {
|
||||||
return if let Ok(v) = ISO_8859_2.decode(&s, DecoderTrap::Strict) {
|
return if let Ok(v) = ISO_8859_2.decode(&s, DecoderTrap::Strict) {
|
||||||
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes())
|
IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
|
||||||
} else {
|
} else {
|
||||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"gbk" => {
|
"gbk" => {
|
||||||
return if let Ok(v) = GBK.decode(&s, DecoderTrap::Strict) {
|
return if let Ok(v) = GBK.decode(&s, DecoderTrap::Strict) {
|
||||||
IResult::Done(&input[encoded_idx.unwrap()+2..], v.into_bytes())
|
IResult::Done(&input[encoded_idx.unwrap() + 2..], v.into_bytes())
|
||||||
} else {
|
} else {
|
||||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
panic!();
|
panic!();
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
|
@ -232,10 +222,14 @@ fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
||||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||||
}
|
}
|
||||||
|
|
||||||
named!(qp_underscore_header<u8>,
|
named!(qp_underscore_header<u8>, do_parse!(tag!("_") >> ({ b' ' })));
|
||||||
do_parse!(tag!("_") >> ( { b' ' } )));
|
|
||||||
|
|
||||||
named!(get_quoted_printed_bytes<Vec<u8>>, many0!(alt_complete!(quoted_printable_byte | qp_underscore_header | le_u8)));
|
named!(
|
||||||
|
get_quoted_printed_bytes<Vec<u8>>,
|
||||||
|
many0!(alt_complete!(
|
||||||
|
quoted_printable_byte | qp_underscore_header | le_u8
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
named!(encoded_word_list<String>, ws!(do_parse!(
|
named!(encoded_word_list<String>, ws!(do_parse!(
|
||||||
list: separated_nonempty_list!(complete!(is_a!(" \n\r\t")), encoded_word) >>
|
list: separated_nonempty_list!(complete!(is_a!(" \n\r\t")), encoded_word) >>
|
||||||
|
@ -273,41 +267,79 @@ named!(pub subject<String>, ws!(do_parse!(
|
||||||
#[test]
|
#[test]
|
||||||
fn test_subject() {
|
fn test_subject() {
|
||||||
let subject_s = "list.free.de mailing list memberships reminder".as_bytes();
|
let subject_s = "list.free.de mailing list memberships reminder".as_bytes();
|
||||||
assert_eq!((&b""[..], "list.free.de mailing list memberships reminder".to_string()), subject(subject_s).unwrap());
|
assert_eq!(
|
||||||
|
(
|
||||||
|
&b""[..],
|
||||||
|
"list.free.de mailing list memberships reminder".to_string()
|
||||||
|
),
|
||||||
|
subject(subject_s).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
let subject_s = "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?= =?UTF-8?B?z4fOuM63?=".as_bytes();
|
let subject_s = "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?= =?UTF-8?B?z4fOuM63?=".as_bytes();
|
||||||
assert_eq!((&b""[..], "Νέο προσωπικό μήνυμα αφίχθη".to_string()), subject(subject_s).unwrap());
|
assert_eq!(
|
||||||
|
(
|
||||||
|
&b""[..],
|
||||||
|
"Νέο προσωπικό μήνυμα αφίχθη".to_string()
|
||||||
|
),
|
||||||
|
subject(subject_s).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
let subject_s = "=?utf-8?B?bW9vZGxlOiDOsc69zrHPg866z4zPgM63z4POtyDOv868zqzOtM6xz4Igz4M=?= =?utf-8?B?z4XOts63z4TOrs+DzrXPic69?=".as_bytes();
|
let subject_s = "=?utf-8?B?bW9vZGxlOiDOsc69zrHPg866z4zPgM63z4POtyDOv868zqzOtM6xz4Igz4M=?= =?utf-8?B?z4XOts63z4TOrs+DzrXPic69?=".as_bytes();
|
||||||
assert_eq!((&b""[..], "moodle: ανασκόπηση ομάδας συζητήσεων".to_string()), subject(subject_s).unwrap());
|
assert_eq!(
|
||||||
|
(
|
||||||
|
&b""[..],
|
||||||
|
"moodle: ανασκόπηση ομάδας συζητήσεων".to_string()
|
||||||
|
),
|
||||||
|
subject(subject_s).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
let subject_s = "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?=".as_bytes();
|
let subject_s =
|
||||||
assert_eq!("Νέο προσωπικό μήνυμα αφί".to_string(), from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap());
|
"=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?=".as_bytes();
|
||||||
|
assert_eq!(
|
||||||
|
"Νέο προσωπικό μήνυμα αφί".to_string(),
|
||||||
|
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()
|
||||||
|
);
|
||||||
let subject_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=".as_bytes();
|
let subject_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=".as_bytes();
|
||||||
assert_eq!("Πρόσθε".to_string(), from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap());
|
assert_eq!(
|
||||||
|
"Πρόσθε".to_string(),
|
||||||
|
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()
|
||||||
|
);
|
||||||
let subject_s = "=?iso-8859-7?B?UmU6INDx/OLr5+zhIOzlIPTn7SDh9fHp4e3eIOLh8eTp4Q==?=".as_bytes();
|
let subject_s = "=?iso-8859-7?B?UmU6INDx/OLr5+zhIOzlIPTn7SDh9fHp4e3eIOLh8eTp4Q==?=".as_bytes();
|
||||||
assert_eq!("Re: Πρόβλημα με την αυριανή βαρδια".to_string(), from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap());
|
assert_eq!(
|
||||||
|
"Re: Πρόβλημα με την αυριανή βαρδια".to_string(),
|
||||||
|
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
let subject_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=
|
let subject_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=
|
||||||
=?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?=
|
=?UTF-8?Q?=CF=84=CE=B7_=CE=B5=CE=BE=CE=B5=CF=84?=
|
||||||
=?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?=".as_bytes();
|
=?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?="
|
||||||
assert_eq!((&b""[..], "Πρόσθετη εξεταστική".to_string()), subject(subject_s).unwrap());
|
.as_bytes();
|
||||||
|
assert_eq!(
|
||||||
|
(
|
||||||
|
&b""[..],
|
||||||
|
"Πρόσθετη εξεταστική".to_string()
|
||||||
|
),
|
||||||
|
subject(subject_s).unwrap()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
fn eat_comments(input: &str) -> String {
|
fn eat_comments(input: &str) -> String {
|
||||||
let mut in_comment = false;
|
let mut in_comment = false;
|
||||||
input.chars().fold(String::with_capacity(input.len()), |mut acc, x| {
|
input
|
||||||
if x == '(' && !in_comment {
|
.chars()
|
||||||
in_comment = true;
|
.fold(String::with_capacity(input.len()), |mut acc, x| {
|
||||||
acc
|
if x == '(' && !in_comment {
|
||||||
} else if x == ')' && in_comment {
|
in_comment = true;
|
||||||
in_comment = false;
|
acc
|
||||||
acc
|
} else if x == ')' && in_comment {
|
||||||
} else if in_comment {
|
in_comment = false;
|
||||||
acc
|
acc
|
||||||
} else {
|
} else if in_comment {
|
||||||
acc.push(x); acc
|
acc
|
||||||
}
|
} else {
|
||||||
})
|
acc.push(x);
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -317,13 +349,16 @@ fn test_eat_comments() {
|
||||||
let s = "Thu, 31 Aug 2017 13:43:37 +0000 (UTC)";
|
let s = "Thu, 31 Aug 2017 13:43:37 +0000 (UTC)";
|
||||||
assert_eq!(eat_comments(s), "Thu, 31 Aug 2017 13:43:37 +0000 ");
|
assert_eq!(eat_comments(s), "Thu, 31 Aug 2017 13:43:37 +0000 ");
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
* Date should tokenize input and convert the tokens,
|
* Date should tokenize input and convert the tokens,
|
||||||
* right now we expect input will have no extra spaces in between tokens
|
* right now we expect input will have no extra spaces in between tokens
|
||||||
*
|
*
|
||||||
* We should use a custom parser here*/
|
* We should use a custom parser here*/
|
||||||
pub fn date(input: &str) -> Option<chrono::DateTime<chrono::FixedOffset>> {
|
pub fn date(input: &str) -> Option<chrono::DateTime<chrono::FixedOffset>> {
|
||||||
let parsed_result = subject(eat_comments(input).as_bytes()).to_full_result().unwrap().replace("-", "+");
|
let parsed_result = subject(eat_comments(input).as_bytes())
|
||||||
|
.to_full_result()
|
||||||
|
.unwrap()
|
||||||
|
.replace("-", "+");
|
||||||
chrono::DateTime::parse_from_rfc2822(parsed_result.trim()).ok()
|
chrono::DateTime::parse_from_rfc2822(parsed_result.trim()).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -340,16 +375,16 @@ named!(pub message_id<&str>,
|
||||||
map_res!(complete!(delimited!(tag!("<"), take_until1!(">"), tag!(">"))), from_utf8)
|
map_res!(complete!(delimited!(tag!("<"), take_until1!(">"), tag!(">"))), from_utf8)
|
||||||
);
|
);
|
||||||
|
|
||||||
fn message_id_peek(input: &[u8]) -> IResult<&[u8],&str> {
|
fn message_id_peek(input: &[u8]) -> IResult<&[u8], &str> {
|
||||||
let input_length = input.len();
|
let input_length = input.len();
|
||||||
if input.is_empty() {
|
if input.is_empty() {
|
||||||
IResult::Incomplete(Needed::Size(1))
|
IResult::Incomplete(Needed::Size(1))
|
||||||
} else if input_length == 2 || input[0] != b'<' {
|
} else if input_length == 2 || input[0] != b'<' {
|
||||||
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||||
} else {
|
} else {
|
||||||
for (i, &x) in input.iter().take(input_length).enumerate().skip(1) {
|
for (i, &x) in input.iter().take(input_length).enumerate().skip(1) {
|
||||||
if x == b'>' {
|
if x == b'>' {
|
||||||
return IResult::Done(&input[i+1..], from_utf8(&input[0..i+1]).unwrap());
|
return IResult::Done(&input[i + 1..], from_utf8(&input[0..i + 1]).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
IResult::Incomplete(Needed::Unknown)
|
IResult::Incomplete(Needed::Unknown)
|
||||||
|
@ -381,19 +416,24 @@ named_args!(pub attachments<'a>(boundary: &'a str, boundary_end: &'a str) < Vec<
|
||||||
fn test_attachments() {
|
fn test_attachments() {
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
let mut buffer: Vec<u8> = Vec::new();
|
let mut buffer: Vec<u8> = Vec::new();
|
||||||
let _ = std::fs::File::open("test/attachment_test").unwrap().read_to_end(&mut buffer);
|
let _ = std::fs::File::open("test/attachment_test")
|
||||||
|
.unwrap()
|
||||||
|
.read_to_end(&mut buffer);
|
||||||
let boundary = "--b1_4382d284f0c601a737bb32aaeda53160--";
|
let boundary = "--b1_4382d284f0c601a737bb32aaeda53160--";
|
||||||
let boundary_len = boundary.len();
|
let boundary_len = boundary.len();
|
||||||
let (_, body) = match mail(&buffer).to_full_result() {
|
let (_, body) = match mail(&buffer).to_full_result() {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(_) => { panic!() }
|
Err(_) => panic!(),
|
||||||
};
|
};
|
||||||
let attachments = attachments(body, &boundary[0..boundary_len-2], &boundary).to_full_result().unwrap();
|
let attachments = attachments(body, &boundary[0..boundary_len - 2], &boundary)
|
||||||
|
.to_full_result()
|
||||||
|
.unwrap();
|
||||||
assert_eq!(attachments.len(), 4);
|
assert_eq!(attachments.len(), 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
named!(content_type_parameter< (&str, &str) >,
|
named!(
|
||||||
do_parse!(
|
content_type_parameter<(&str, &str)>,
|
||||||
|
do_parse!(
|
||||||
tag!(";") >>
|
tag!(";") >>
|
||||||
name: terminated!(map_res!(ws!(take_until!("=")), from_utf8), tag!("=")) >>
|
name: terminated!(map_res!(ws!(take_until!("=")), from_utf8), tag!("=")) >>
|
||||||
value: map_res!(ws!(
|
value: map_res!(ws!(
|
||||||
|
@ -402,7 +442,8 @@ named!(content_type_parameter< (&str, &str) >,
|
||||||
( {
|
( {
|
||||||
(name, value)
|
(name, value)
|
||||||
} )
|
} )
|
||||||
));
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >,
|
named!(pub content_type< (&str, &str, Vec<(&str, &str)>) >,
|
||||||
|
|
|
@ -29,14 +29,14 @@ use error::Result;
|
||||||
pub mod accounts;
|
pub mod accounts;
|
||||||
pub use mailbox::accounts::Account;
|
pub use mailbox::accounts::Account;
|
||||||
mod thread;
|
mod thread;
|
||||||
use mailbox::thread::{Container, build_threads};
|
use mailbox::thread::{build_threads, Container};
|
||||||
|
|
||||||
use std::option::Option;
|
use std::option::Option;
|
||||||
|
|
||||||
|
|
||||||
/*a Mailbox represents a folder of mail. Currently only Maildir is supported.*/
|
/*a Mailbox represents a folder of mail. Currently only Maildir is supported.*/
|
||||||
#[derive(Debug,Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Mailbox{
|
pub struct Mailbox {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub collection: Vec<Envelope>,
|
pub collection: Vec<Envelope>,
|
||||||
pub threaded_collection: Vec<usize>,
|
pub threaded_collection: Vec<usize>,
|
||||||
|
@ -45,8 +45,7 @@ pub struct Mailbox{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Mailbox
|
impl Mailbox {
|
||||||
{
|
|
||||||
pub fn new(path: &str, sent_folder: &Option<Result<Mailbox>>) -> Result<Mailbox> {
|
pub fn new(path: &str, sent_folder: &Option<Result<Mailbox>>) -> Result<Mailbox> {
|
||||||
let mut collection: Vec<Envelope> = maildir::MaildirType::new(path).get()?;
|
let mut collection: Vec<Envelope> = maildir::MaildirType::new(path).get()?;
|
||||||
collection.sort_by(|a, b| a.get_date().cmp(&b.get_date()));
|
collection.sort_by(|a, b| a.get_date().cmp(&b.get_date()));
|
||||||
|
@ -70,12 +69,11 @@ impl Mailbox
|
||||||
thread.get_message().unwrap()
|
thread.get_message().unwrap()
|
||||||
}
|
}
|
||||||
pub fn get_mail_and_thread(&mut self, i: usize) -> (&mut Envelope, Container) {
|
pub fn get_mail_and_thread(&mut self, i: usize) -> (&mut Envelope, Container) {
|
||||||
let x = &mut self.collection.as_mut_slice()[i];
|
let x = &mut self.collection.as_mut_slice()[i];
|
||||||
let thread = self.threads[x.get_thread()];
|
let thread = self.threads[x.get_thread()];
|
||||||
(x, thread)
|
(x, thread)
|
||||||
}
|
}
|
||||||
pub fn get_thread(&self, i: usize) -> &Container {
|
pub fn get_thread(&self, i: usize) -> &Container {
|
||||||
&self.threads[i]
|
&self.threads[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,7 @@ impl Container {
|
||||||
fn set_show_subject(&mut self, set: bool) -> () {
|
fn set_show_subject(&mut self, set: bool) -> () {
|
||||||
self.show_subject = set;
|
self.show_subject = set;
|
||||||
}
|
}
|
||||||
pub fn get_show_subject(&self) -> bool {
|
pub fn get_show_subject(&self) -> bool {
|
||||||
self.show_subject
|
self.show_subject
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,18 +111,17 @@ impl Container {
|
||||||
impl PartialEq for Container {
|
impl PartialEq for Container {
|
||||||
fn eq(&self, other: &Container) -> bool {
|
fn eq(&self, other: &Container) -> bool {
|
||||||
match (self.message, other.message) {
|
match (self.message, other.message) {
|
||||||
(Some(s), Some(o)) => {
|
(Some(s), Some(o)) => s == o,
|
||||||
s == o
|
_ => self.id == other.id,
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
self.id == other.id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_collection(threads: &mut Vec<Container>, id_table: &mut FnvHashMap<std::string::String, usize>, collection: &mut [Envelope]) -> ()
|
fn build_collection(
|
||||||
{
|
threads: &mut Vec<Container>,
|
||||||
|
id_table: &mut FnvHashMap<std::string::String, usize>,
|
||||||
|
collection: &mut [Envelope],
|
||||||
|
) -> () {
|
||||||
for (i, x) in collection.iter_mut().enumerate() {
|
for (i, x) in collection.iter_mut().enumerate() {
|
||||||
let x_index; /* x's index in threads */
|
let x_index; /* x's index in threads */
|
||||||
let m_id = x.get_message_id_raw().to_string();
|
let m_id = x.get_message_id_raw().to_string();
|
||||||
|
@ -142,17 +141,16 @@ fn build_collection(threads: &mut Vec<Container>, id_table: &mut FnvHashMap<std:
|
||||||
} else {
|
} else {
|
||||||
/* Create a new Container object holding this message */
|
/* Create a new Container object holding this message */
|
||||||
x_index = threads.len();
|
x_index = threads.len();
|
||||||
threads.push(
|
threads.push(Container {
|
||||||
Container {
|
message: Some(i),
|
||||||
message: Some(i),
|
id: x_index,
|
||||||
id: x_index,
|
parent: None,
|
||||||
parent: None,
|
first_child: None,
|
||||||
first_child: None,
|
next_sibling: None,
|
||||||
next_sibling: None,
|
date: x.get_date(),
|
||||||
date: x.get_date(),
|
indentation: 0,
|
||||||
indentation: 0,
|
show_subject: true,
|
||||||
show_subject: true,
|
});
|
||||||
});
|
|
||||||
x.set_thread(x_index);
|
x.set_thread(x_index);
|
||||||
id_table.insert(m_id, x_index);
|
id_table.insert(m_id, x_index);
|
||||||
}
|
}
|
||||||
|
@ -173,44 +171,43 @@ fn build_collection(threads: &mut Vec<Container>, id_table: &mut FnvHashMap<std:
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
iasf += 1;
|
iasf += 1;
|
||||||
let parent_id =
|
let parent_id = if id_table.contains_key(r.get_raw()) {
|
||||||
if id_table.contains_key(r.get_raw()) {
|
let p = id_table[r.get_raw()];
|
||||||
let p = id_table[r.get_raw()];
|
if !(threads[p].is_descendant(threads, &threads[curr_ref]) ||
|
||||||
if !(threads[p].is_descendant(threads, &threads[curr_ref]) ||
|
threads[curr_ref].is_descendant(threads, &threads[p]))
|
||||||
threads[curr_ref].is_descendant(threads, &threads[p])) {
|
{
|
||||||
threads[curr_ref].parent = Some(p);
|
threads[curr_ref].parent = Some(p);
|
||||||
if threads[p].first_child.is_none() {
|
if threads[p].first_child.is_none() {
|
||||||
threads[p].first_child = Some(curr_ref);
|
threads[p].first_child = Some(curr_ref);
|
||||||
} else {
|
} else {
|
||||||
let mut child_iter = threads[p].first_child.unwrap();
|
let mut child_iter = threads[p].first_child.unwrap();
|
||||||
while threads[child_iter].next_sibling.is_some() {
|
while threads[child_iter].next_sibling.is_some() {
|
||||||
threads[child_iter].parent = Some(p);
|
|
||||||
child_iter = threads[child_iter].next_sibling.unwrap();
|
|
||||||
}
|
|
||||||
threads[child_iter].next_sibling = Some(curr_ref);
|
|
||||||
threads[child_iter].parent = Some(p);
|
threads[child_iter].parent = Some(p);
|
||||||
|
child_iter = threads[child_iter].next_sibling.unwrap();
|
||||||
}
|
}
|
||||||
|
threads[child_iter].next_sibling = Some(curr_ref);
|
||||||
|
threads[child_iter].parent = Some(p);
|
||||||
}
|
}
|
||||||
p
|
}
|
||||||
} else {
|
p
|
||||||
let idx = threads.len();
|
} else {
|
||||||
threads.push(
|
let idx = threads.len();
|
||||||
Container {
|
threads.push(Container {
|
||||||
message: None,
|
message: None,
|
||||||
id: idx,
|
id: idx,
|
||||||
parent: None,
|
parent: None,
|
||||||
first_child: Some(curr_ref),
|
first_child: Some(curr_ref),
|
||||||
next_sibling: None,
|
next_sibling: None,
|
||||||
date: x.get_date(),
|
date: x.get_date(),
|
||||||
indentation: 0,
|
indentation: 0,
|
||||||
show_subject: true,
|
show_subject: true,
|
||||||
});
|
});
|
||||||
if threads[curr_ref].parent.is_none() {
|
if threads[curr_ref].parent.is_none() {
|
||||||
threads[curr_ref].parent = Some(idx);
|
threads[curr_ref].parent = Some(idx);
|
||||||
}
|
}
|
||||||
id_table.insert(r.get_raw().to_string(), idx);
|
id_table.insert(r.get_raw().to_string(), idx);
|
||||||
idx
|
idx
|
||||||
};
|
};
|
||||||
/* update thread date */
|
/* update thread date */
|
||||||
let mut parent_iter = parent_id;
|
let mut parent_iter = parent_id;
|
||||||
'date: loop {
|
'date: loop {
|
||||||
|
@ -219,8 +216,12 @@ fn build_collection(threads: &mut Vec<Container>, id_table: &mut FnvHashMap<std:
|
||||||
p.date = x.get_date();
|
p.date = x.get_date();
|
||||||
}
|
}
|
||||||
match p.parent {
|
match p.parent {
|
||||||
Some(p) => { parent_iter = p; },
|
Some(p) => {
|
||||||
None => { break 'date; },
|
parent_iter = p;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
break 'date;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
curr_ref = parent_id;
|
curr_ref = parent_id;
|
||||||
|
@ -229,167 +230,196 @@ fn build_collection(threads: &mut Vec<Container>, id_table: &mut FnvHashMap<std:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn build_threads(collection: &mut Vec<Envelope>, sent_folder: &Option<Result<Mailbox>>) -> (Vec<Container>, Vec<usize>) {
|
pub fn build_threads(
|
||||||
/* To reconstruct thread information from the mails we need: */
|
collection: &mut Vec<Envelope>,
|
||||||
|
sent_folder: &Option<Result<Mailbox>>,
|
||||||
|
) -> (Vec<Container>, Vec<usize>) {
|
||||||
|
/* To reconstruct thread information from the mails we need: */
|
||||||
|
|
||||||
/* a vector to hold thread members */
|
/* a vector to hold thread members */
|
||||||
let mut threads: Vec<Container> = Vec::with_capacity((collection.len() as f64 * 1.2) as usize);
|
let mut threads: Vec<Container> = Vec::with_capacity((collection.len() as f64 * 1.2) as usize);
|
||||||
/* A hash table of Message IDs */
|
/* A hash table of Message IDs */
|
||||||
let mut id_table: FnvHashMap<std::string::String, usize> = FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default());
|
let mut id_table: FnvHashMap<std::string::String, usize> =
|
||||||
|
FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default());
|
||||||
|
|
||||||
/* Add each message to id_table and threads, and link them together according to the
|
/* Add each message to id_table and threads, and link them together according to the
|
||||||
* References / In-Reply-To headers */
|
* References / In-Reply-To headers */
|
||||||
build_collection(&mut threads, &mut id_table, collection);
|
build_collection(&mut threads, &mut id_table, collection);
|
||||||
let mut idx = collection.len();
|
let mut idx = collection.len();
|
||||||
let mut tidx = threads.len();
|
let mut tidx = threads.len();
|
||||||
/* Link messages from Sent folder if they are relevant to this folder.
|
/* Link messages from Sent folder if they are relevant to this folder.
|
||||||
* This means that
|
* This means that
|
||||||
* - if a message from Sent is a reply to a message in this folder, we will
|
* - if a message from Sent is a reply to a message in this folder, we will
|
||||||
* add it to the threading (but not the collection; non-threading users shouldn't care
|
* add it to the threading (but not the collection; non-threading users shouldn't care
|
||||||
* about this)
|
* about this)
|
||||||
* - if a message in this folder is a reply to a message we sent, we will add it to the
|
* - if a message in this folder is a reply to a message we sent, we will add it to the
|
||||||
* threading
|
* threading
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if let Some(ref sent_box) = *sent_folder {
|
if let Some(ref sent_box) = *sent_folder {
|
||||||
if sent_box.is_ok() {
|
if sent_box.is_ok() {
|
||||||
let sent_mailbox = sent_box.as_ref();
|
let sent_mailbox = sent_box.as_ref();
|
||||||
let sent_mailbox = sent_mailbox.unwrap();;
|
let sent_mailbox = sent_mailbox.unwrap();
|
||||||
|
|
||||||
for x in &sent_mailbox.collection {
|
for x in &sent_mailbox.collection {
|
||||||
if id_table.contains_key(x.get_message_id_raw()) ||
|
if id_table.contains_key(x.get_message_id_raw()) ||
|
||||||
(!x.get_in_reply_to_raw().is_empty() && id_table.contains_key(x.get_in_reply_to_raw())) {
|
(!x.get_in_reply_to_raw().is_empty() &&
|
||||||
let mut x: Envelope = (*x).clone();
|
id_table.contains_key(x.get_in_reply_to_raw()))
|
||||||
if id_table.contains_key(x.get_message_id_raw()) {
|
{
|
||||||
let c = id_table[x.get_message_id_raw()];
|
let mut x: Envelope = (*x).clone();
|
||||||
if threads[c].message.is_some() {
|
if id_table.contains_key(x.get_message_id_raw()) {
|
||||||
/* skip duplicate message-id, but this should be handled instead */
|
let c = id_table[x.get_message_id_raw()];
|
||||||
continue;
|
if threads[c].message.is_some() {
|
||||||
|
/* skip duplicate message-id, but this should be handled instead */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
threads[c].message = Some(idx);
|
||||||
|
assert!(threads[c].has_children());
|
||||||
|
threads[c].date = x.get_date();
|
||||||
|
x.set_thread(c);
|
||||||
|
} else if !x.get_in_reply_to_raw().is_empty() &&
|
||||||
|
id_table.contains_key(x.get_in_reply_to_raw())
|
||||||
|
{
|
||||||
|
let p = id_table[x.get_in_reply_to_raw()];
|
||||||
|
let c = if id_table.contains_key(x.get_message_id_raw()) {
|
||||||
|
id_table[x.get_message_id_raw()]
|
||||||
|
} else {
|
||||||
|
threads.push(Container {
|
||||||
|
message: Some(idx),
|
||||||
|
id: tidx,
|
||||||
|
parent: Some(p),
|
||||||
|
first_child: None,
|
||||||
|
next_sibling: None,
|
||||||
|
date: x.get_date(),
|
||||||
|
indentation: 0,
|
||||||
|
show_subject: true,
|
||||||
|
});
|
||||||
|
id_table.insert(x.get_message_id_raw().to_string(), tidx);
|
||||||
|
x.set_thread(tidx);
|
||||||
|
tidx += 1;
|
||||||
|
tidx - 1
|
||||||
|
};
|
||||||
|
threads[c].parent = Some(p);
|
||||||
|
if threads[p].is_descendant(&threads, &threads[c]) ||
|
||||||
|
threads[c].is_descendant(&threads, &threads[p])
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if threads[p].first_child.is_none() {
|
||||||
|
threads[p].first_child = Some(c);
|
||||||
|
} else {
|
||||||
|
let mut fc = threads[p].first_child.unwrap();
|
||||||
|
while threads[fc].next_sibling.is_some() {
|
||||||
|
threads[fc].parent = Some(p);
|
||||||
|
fc = threads[fc].next_sibling.unwrap();
|
||||||
|
}
|
||||||
|
threads[fc].next_sibling = Some(c);
|
||||||
|
threads[fc].parent = Some(p);
|
||||||
|
}
|
||||||
|
/* update thread date */
|
||||||
|
let mut parent_iter = p;
|
||||||
|
'date: loop {
|
||||||
|
let p = &mut threads[parent_iter];
|
||||||
|
if p.date < x.get_date() {
|
||||||
|
p.date = x.get_date();
|
||||||
|
}
|
||||||
|
match p.parent {
|
||||||
|
Some(p) => {
|
||||||
|
parent_iter = p;
|
||||||
}
|
}
|
||||||
threads[c].message = Some(idx);
|
None => {
|
||||||
assert!(threads[c].has_children());
|
break 'date;
|
||||||
threads[c].date = x.get_date();
|
|
||||||
x.set_thread(c);
|
|
||||||
} else if !x.get_in_reply_to_raw().is_empty() && id_table.contains_key(x.get_in_reply_to_raw()) {
|
|
||||||
let p = id_table[x.get_in_reply_to_raw()];
|
|
||||||
let c = if id_table.contains_key(x.get_message_id_raw()) {
|
|
||||||
id_table[x.get_message_id_raw()]
|
|
||||||
} else {
|
|
||||||
threads.push(
|
|
||||||
Container {
|
|
||||||
message: Some(idx),
|
|
||||||
id: tidx,
|
|
||||||
parent: Some(p),
|
|
||||||
first_child: None,
|
|
||||||
next_sibling: None,
|
|
||||||
date: x.get_date(),
|
|
||||||
indentation: 0,
|
|
||||||
show_subject: true,
|
|
||||||
});
|
|
||||||
id_table.insert(x.get_message_id_raw().to_string(), tidx);
|
|
||||||
x.set_thread(tidx);
|
|
||||||
tidx += 1;
|
|
||||||
tidx - 1
|
|
||||||
};
|
|
||||||
threads[c].parent = Some(p);
|
|
||||||
if threads[p].is_descendant(&threads, &threads[c]) ||
|
|
||||||
threads[c].is_descendant(&threads, &threads[p]) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if threads[p].first_child.is_none() {
|
|
||||||
threads[p].first_child = Some(c);
|
|
||||||
} else {
|
|
||||||
let mut fc = threads[p].first_child.unwrap();
|
|
||||||
while threads[fc].next_sibling.is_some() {
|
|
||||||
threads[fc].parent = Some(p);
|
|
||||||
fc = threads[fc].next_sibling.unwrap();
|
|
||||||
}
|
|
||||||
threads[fc].next_sibling = Some(c);
|
|
||||||
threads[fc].parent = Some(p);
|
|
||||||
}
|
|
||||||
/* update thread date */
|
|
||||||
let mut parent_iter = p;
|
|
||||||
'date: loop {
|
|
||||||
let p = &mut threads[parent_iter];
|
|
||||||
if p.date < x.get_date() {
|
|
||||||
p.date = x.get_date();
|
|
||||||
}
|
|
||||||
match p.parent {
|
|
||||||
Some(p) => { parent_iter = p; },
|
|
||||||
None => { break 'date; },
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
collection.push(x);
|
|
||||||
idx += 1;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
collection.push(x);
|
||||||
|
idx += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Walk over the elements of id_table, and gather a list of the Container objects that have
|
}
|
||||||
* no parents. These are the root messages of each thread */
|
/* Walk over the elements of id_table, and gather a list of the Container objects that have
|
||||||
let mut root_set = Vec::with_capacity(collection.len());
|
* no parents. These are the root messages of each thread */
|
||||||
'root_set: for v in id_table.values() {
|
let mut root_set = Vec::with_capacity(collection.len());
|
||||||
if threads[*v].parent.is_none() {
|
'root_set: for v in id_table.values() {
|
||||||
if !threads[*v].has_message() && threads[*v].has_children() && !threads[threads[*v].first_child.unwrap()].has_sibling() {
|
if threads[*v].parent.is_none() {
|
||||||
/* Do not promote the children if doing so would promote them to the root set
|
if !threads[*v].has_message() && threads[*v].has_children() &&
|
||||||
* -- unless there is only one child, in which case, do. */
|
!threads[threads[*v].first_child.unwrap()].has_sibling()
|
||||||
root_set.push(threads[*v].first_child.unwrap());
|
|
||||||
continue 'root_set;
|
|
||||||
}
|
|
||||||
root_set.push(*v);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
root_set.sort_by(|a, b| threads[*b].date.cmp(&threads[*a].date));
|
|
||||||
|
|
||||||
/* Group messages together by thread in a collection so we can print them together */
|
|
||||||
let mut threaded_collection: Vec<usize> = Vec::with_capacity(collection.len());
|
|
||||||
fn build_threaded(threads: &mut Vec<Container>, indentation: usize, threaded: &mut Vec<usize>, i: usize, root_subject_idx: usize, collection: &[Envelope])
|
|
||||||
{
|
{
|
||||||
let thread = threads[i];
|
/* Do not promote the children if doing so would promote them to the root set
|
||||||
if threads[root_subject_idx].has_message() {
|
* -- unless there is only one child, in which case, do. */
|
||||||
let root_subject = collection[threads[root_subject_idx].get_message().unwrap()].get_subject();
|
root_set.push(threads[*v].first_child.unwrap());
|
||||||
/* If the Container has no Message, but does have children, remove this container but
|
continue 'root_set;
|
||||||
* promote its children to this level (that is, splice them in to the current child
|
|
||||||
* list.) */
|
|
||||||
if indentation > 0 && thread.has_message() {
|
|
||||||
let subject = collection[thread.get_message().unwrap()].get_subject();
|
|
||||||
if subject == root_subject || subject.starts_with("Re: ") && subject.ends_with(root_subject) {
|
|
||||||
threads[i].set_show_subject(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if thread.has_parent() && !threads[thread.get_parent().unwrap()].has_message() {
|
root_set.push(*v);
|
||||||
threads[i].parent = None;
|
}
|
||||||
}
|
}
|
||||||
let indentation =
|
root_set.sort_by(|a, b| threads[*b].date.cmp(&threads[*a].date));
|
||||||
if thread.has_message() {
|
|
||||||
threads[i].set_indentation(indentation);
|
/* Group messages together by thread in a collection so we can print them together */
|
||||||
if !threaded.contains(&i) {
|
let mut threaded_collection: Vec<usize> = Vec::with_capacity(collection.len());
|
||||||
threaded.push(i);
|
fn build_threaded(
|
||||||
}
|
threads: &mut Vec<Container>,
|
||||||
indentation + 1
|
indentation: usize,
|
||||||
} else if indentation > 0 {
|
threaded: &mut Vec<usize>,
|
||||||
indentation
|
i: usize,
|
||||||
} else {
|
root_subject_idx: usize,
|
||||||
indentation + 1
|
collection: &[Envelope],
|
||||||
};
|
) {
|
||||||
if thread.has_children() {
|
let thread = threads[i];
|
||||||
let mut fc = thread.get_first_child().unwrap();
|
if threads[root_subject_idx].has_message() {
|
||||||
loop {
|
let root_subject =
|
||||||
build_threaded(threads, indentation, threaded, fc, i, collection);
|
collection[threads[root_subject_idx].get_message().unwrap()].get_subject();
|
||||||
let thread_ = threads[fc];
|
/* If the Container has no Message, but does have children, remove this container but
|
||||||
if !thread_.has_sibling() {
|
* promote its children to this level (that is, splice them in to the current child
|
||||||
break;
|
* list.) */
|
||||||
}
|
if indentation > 0 && thread.has_message() {
|
||||||
fc = thread_.get_next_sibling().unwrap();
|
let subject = collection[thread.get_message().unwrap()].get_subject();
|
||||||
|
if subject == root_subject ||
|
||||||
|
subject.starts_with("Re: ") && subject.ends_with(root_subject)
|
||||||
|
{
|
||||||
|
threads[i].set_show_subject(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i in &root_set {
|
if thread.has_parent() && !threads[thread.get_parent().unwrap()].has_message() {
|
||||||
build_threaded(&mut threads, 0, &mut threaded_collection, *i, *i, collection);
|
threads[i].parent = None;
|
||||||
}
|
}
|
||||||
|
let indentation = if thread.has_message() {
|
||||||
|
threads[i].set_indentation(indentation);
|
||||||
|
if !threaded.contains(&i) {
|
||||||
|
threaded.push(i);
|
||||||
|
}
|
||||||
|
indentation + 1
|
||||||
|
} else if indentation > 0 {
|
||||||
|
indentation
|
||||||
|
} else {
|
||||||
|
indentation + 1
|
||||||
|
};
|
||||||
|
if thread.has_children() {
|
||||||
|
let mut fc = thread.get_first_child().unwrap();
|
||||||
|
loop {
|
||||||
|
build_threaded(threads, indentation, threaded, fc, i, collection);
|
||||||
|
let thread_ = threads[fc];
|
||||||
|
if !thread_.has_sibling() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
fc = thread_.get_next_sibling().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i in &root_set {
|
||||||
|
build_threaded(
|
||||||
|
&mut threads,
|
||||||
|
0,
|
||||||
|
&mut threaded_collection,
|
||||||
|
*i,
|
||||||
|
*i,
|
||||||
|
collection,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
(threads, threaded_collection)
|
(threads, threaded_collection)
|
||||||
}
|
}
|
||||||
|
|
144
src/ui/index.rs
144
src/ui/index.rs
|
@ -46,10 +46,7 @@ impl Window for ErrorWindow {
|
||||||
ncurses::waddstr(self.win, &self.description);
|
ncurses::waddstr(self.win, &self.description);
|
||||||
ncurses::wrefresh(self.win);
|
ncurses::wrefresh(self.win);
|
||||||
}
|
}
|
||||||
fn handle_input(&mut self, _: i32) -> () {
|
fn handle_input(&mut self, _: i32) -> () {}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl ErrorWindow {
|
impl ErrorWindow {
|
||||||
pub fn new(err: MeliError) -> Self {
|
pub fn new(err: MeliError) -> Self {
|
||||||
|
@ -80,7 +77,6 @@ pub struct Index {
|
||||||
/* threading */
|
/* threading */
|
||||||
threaded: bool,
|
threaded: bool,
|
||||||
|
|
||||||
|
|
||||||
cursor_idx: usize,
|
cursor_idx: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,9 +93,13 @@ impl Window for Index {
|
||||||
//ncurses::wclear(self.pad);
|
//ncurses::wclear(self.pad);
|
||||||
|
|
||||||
if self.threaded {
|
if self.threaded {
|
||||||
let mut indentations : Vec<bool> = Vec::with_capacity(6);
|
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
||||||
/* Draw threaded view. */
|
/* Draw threaded view. */
|
||||||
let mut iter = self.mailbox.threaded_collection.iter().enumerate().peekable();
|
let mut iter = self.mailbox
|
||||||
|
.threaded_collection
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.peekable();
|
||||||
/* This is just a desugared for loop so that we can use .peek() */
|
/* This is just a desugared for loop so that we can use .peek() */
|
||||||
while let Some((idx, i)) = iter.next() {
|
while let Some((idx, i)) = iter.next() {
|
||||||
let container = self.mailbox.get_thread(*i);
|
let container = self.mailbox.get_thread(*i);
|
||||||
|
@ -107,10 +107,12 @@ impl Window for Index {
|
||||||
|
|
||||||
assert_eq!(container.has_message(), true);
|
assert_eq!(container.has_message(), true);
|
||||||
match iter.peek() {
|
match iter.peek() {
|
||||||
Some(&(_, x)) if self.mailbox.get_thread(*x).get_indentation() == indentation => {
|
Some(&(_, x))
|
||||||
|
if self.mailbox.get_thread(*x).get_indentation() == indentation =>
|
||||||
|
{
|
||||||
indentations.pop();
|
indentations.pop();
|
||||||
indentations.push(true);
|
indentations.push(true);
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
indentations.pop();
|
indentations.pop();
|
||||||
indentations.push(false);
|
indentations.push(false);
|
||||||
|
@ -119,23 +121,36 @@ impl Window for Index {
|
||||||
if container.has_sibling() {
|
if container.has_sibling() {
|
||||||
indentations.pop();
|
indentations.pop();
|
||||||
indentations.push(true);
|
indentations.push(true);
|
||||||
}
|
}
|
||||||
let x = &self.mailbox.collection[container.get_message().unwrap()];
|
let x = &self.mailbox.collection[container.get_message().unwrap()];
|
||||||
Index::draw_entry(self.pad, x, idx, indentation, container.has_sibling(), container.has_parent(), idx == self.cursor_idx, container.get_show_subject(), Some(&indentations));
|
Index::draw_entry(
|
||||||
|
self.pad,
|
||||||
|
x,
|
||||||
|
idx,
|
||||||
|
indentation,
|
||||||
|
container.has_sibling(),
|
||||||
|
container.has_parent(),
|
||||||
|
idx == self.cursor_idx,
|
||||||
|
container.get_show_subject(),
|
||||||
|
Some(&indentations),
|
||||||
|
);
|
||||||
match iter.peek() {
|
match iter.peek() {
|
||||||
Some(&(_, x)) if self.mailbox.get_thread(*x).get_indentation() > indentation => {
|
Some(&(_, x))
|
||||||
|
if self.mailbox.get_thread(*x).get_indentation() > indentation =>
|
||||||
|
{
|
||||||
indentations.push(false);
|
indentations.push(false);
|
||||||
},
|
}
|
||||||
Some(&(_, x)) if self.mailbox.get_thread(*x).get_indentation() < indentation => {
|
Some(&(_, x))
|
||||||
|
if self.mailbox.get_thread(*x).get_indentation() < indentation =>
|
||||||
|
{
|
||||||
for _ in 0..(indentation - self.mailbox.get_thread(*x).get_indentation()) {
|
for _ in 0..(indentation - self.mailbox.get_thread(*x).get_indentation()) {
|
||||||
indentations.pop();
|
indentations.pop();
|
||||||
}
|
}
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
}
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
for (idx, i) in self.mailbox.threaded_collection.iter().enumerate() {
|
for (idx, i) in self.mailbox.threaded_collection.iter().enumerate() {
|
||||||
let container = self.mailbox.get_thread(*i);
|
let container = self.mailbox.get_thread(*i);
|
||||||
|
|
||||||
|
@ -155,7 +170,17 @@ impl Window for Index {
|
||||||
*/
|
*/
|
||||||
} else {
|
} else {
|
||||||
for (idx, x) in self.mailbox.collection.as_mut_slice().iter().enumerate() {
|
for (idx, x) in self.mailbox.collection.as_mut_slice().iter().enumerate() {
|
||||||
Index::draw_entry(self.pad, x, idx, 0, false, false, idx == self.cursor_idx, true, None);
|
Index::draw_entry(
|
||||||
|
self.pad,
|
||||||
|
x,
|
||||||
|
idx,
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
idx == self.cursor_idx,
|
||||||
|
true,
|
||||||
|
None,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ncurses::getmaxyx(self.win, &mut self.screen_height, &mut self.screen_width);
|
ncurses::getmaxyx(self.win, &mut self.screen_height, &mut self.screen_width);
|
||||||
|
@ -176,7 +201,7 @@ impl Window for Index {
|
||||||
ncurses::doupdate();
|
ncurses::doupdate();
|
||||||
if self.mailbox.get_length() == 0 {
|
if self.mailbox.get_length() == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
/* Draw newly highlighted entry */
|
/* Draw newly highlighted entry */
|
||||||
ncurses::wmove(self.pad, self.cursor_idx as i32, 0);
|
ncurses::wmove(self.pad, self.cursor_idx as i32, 0);
|
||||||
let pair = super::COLOR_PAIR_CURSOR;
|
let pair = super::COLOR_PAIR_CURSOR;
|
||||||
|
@ -212,24 +237,20 @@ impl Window for Index {
|
||||||
ncurses::getbegyx(self.win, &mut y, &mut x);
|
ncurses::getbegyx(self.win, &mut y, &mut x);
|
||||||
let prev_idx = self.cursor_idx;
|
let prev_idx = self.cursor_idx;
|
||||||
match motion {
|
match motion {
|
||||||
ncurses::KEY_UP => {
|
ncurses::KEY_UP => if self.cursor_idx > 0 {
|
||||||
if self.cursor_idx > 0 {
|
self.cursor_idx -= 1;
|
||||||
self.cursor_idx -= 1;
|
} else {
|
||||||
} else {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
ncurses::KEY_DOWN => {
|
ncurses::KEY_DOWN => if self.cursor_idx < self.mailbox.get_length() - 1 {
|
||||||
if self.cursor_idx < self.mailbox.get_length() - 1 {
|
self.cursor_idx += 1;
|
||||||
self.cursor_idx += 1;
|
} else {
|
||||||
} else {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
10 => {
|
10 => {
|
||||||
self.show_pager();
|
self.show_pager();
|
||||||
self.redraw();
|
self.redraw();
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -257,21 +278,23 @@ impl Window for Index {
|
||||||
let mut x = 32;
|
let mut x = 32;
|
||||||
loop {
|
loop {
|
||||||
match ncurses::mvwinch(self.pad, prev_idx as i32, x) & ncurses::A_CHARTEXT() {
|
match ncurses::mvwinch(self.pad, prev_idx as i32, x) & ncurses::A_CHARTEXT() {
|
||||||
32 => { /* ASCII code for space */
|
32 => {
|
||||||
|
/* ASCII code for space */
|
||||||
ncurses::wchgat(self.pad, x, 0, pair);
|
ncurses::wchgat(self.pad, x, 0, pair);
|
||||||
},
|
}
|
||||||
62 => { /* ASCII code for '>' */
|
62 => {
|
||||||
|
/* ASCII code for '>' */
|
||||||
ncurses::wchgat(self.pad, x, 0, super::COLOR_PAIR_THREAD_INDENT);
|
ncurses::wchgat(self.pad, x, 0, super::COLOR_PAIR_THREAD_INDENT);
|
||||||
ncurses::wmove(self.pad, prev_idx as i32, x + 1);
|
ncurses::wmove(self.pad, prev_idx as i32, x + 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
ncurses::wchgat(self.pad, x, 0, super::COLOR_PAIR_THREAD_INDENT);
|
ncurses::wchgat(self.pad, x, 0, super::COLOR_PAIR_THREAD_INDENT);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
x += 1;
|
x += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ncurses::wchgat(self.pad, -1, 0, pair);
|
ncurses::wchgat(self.pad, -1, 0, pair);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +318,8 @@ impl Window for Index {
|
||||||
* └ i-1 ┘
|
* └ i-1 ┘
|
||||||
*/
|
*/
|
||||||
if pminrow != pminrow_prev &&
|
if pminrow != pminrow_prev &&
|
||||||
pminrow + self.screen_height > self.mailbox.get_length() as i32 {
|
pminrow + self.screen_height > self.mailbox.get_length() as i32
|
||||||
|
{
|
||||||
/* touch dead entries in index (tell ncurses to redraw the empty lines next refresh) */
|
/* touch dead entries in index (tell ncurses to redraw the empty lines next refresh) */
|
||||||
let live_entries = self.mailbox.get_length() as i32 - pminrow;
|
let live_entries = self.mailbox.get_length() as i32 - pminrow;
|
||||||
ncurses::wredrawln(self.win, live_entries, self.screen_height);
|
ncurses::wredrawln(self.win, live_entries, self.screen_height);
|
||||||
|
@ -356,26 +380,37 @@ impl Index {
|
||||||
|
|
||||||
/* draw_entry() doesn't take &mut self because borrow checker complains if it's called from
|
/* draw_entry() doesn't take &mut self because borrow checker complains if it's called from
|
||||||
* another method. */
|
* another method. */
|
||||||
fn draw_entry(win: ncurses::WINDOW, mail: &Envelope, i: usize, indent: usize,
|
fn draw_entry(
|
||||||
has_sibling: bool, has_parent: bool, highlight: bool,
|
win: ncurses::WINDOW,
|
||||||
show_subject: bool, indentations: Option<&Vec<bool>>) {
|
mail: &Envelope,
|
||||||
|
i: usize,
|
||||||
|
indent: usize,
|
||||||
|
has_sibling: bool,
|
||||||
|
has_parent: bool,
|
||||||
|
highlight: bool,
|
||||||
|
show_subject: bool,
|
||||||
|
indentations: Option<&Vec<bool>>,
|
||||||
|
) {
|
||||||
/* TODO: use addchstr */
|
/* TODO: use addchstr */
|
||||||
let pair =
|
let pair = if highlight {
|
||||||
if highlight {
|
super::COLOR_PAIR_CURSOR
|
||||||
super::COLOR_PAIR_CURSOR
|
} else if i % 2 == 0 {
|
||||||
} else if i % 2 == 0 {
|
super::COLOR_PAIR_THREAD_EVEN
|
||||||
super::COLOR_PAIR_THREAD_EVEN
|
} else {
|
||||||
} else {
|
super::COLOR_PAIR_THREAD_ODD
|
||||||
super::COLOR_PAIR_THREAD_ODD
|
};
|
||||||
};
|
|
||||||
let attr = ncurses::COLOR_PAIR(pair);
|
let attr = ncurses::COLOR_PAIR(pair);
|
||||||
ncurses::wattron(win, attr);
|
ncurses::wattron(win, attr);
|
||||||
|
|
||||||
ncurses::waddstr(win, &format!("{}\t", i));
|
ncurses::waddstr(win, &format!("{}\t", i));
|
||||||
ncurses::waddstr(win, &mail.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string());
|
ncurses::waddstr(
|
||||||
|
win,
|
||||||
|
&mail.get_datetime().format("%Y-%m-%d %H:%M:%S").to_string(),
|
||||||
|
);
|
||||||
ncurses::waddch(win, '\t' as u64);
|
ncurses::waddch(win, '\t' as u64);
|
||||||
for i in 0..indent {
|
for i in 0..indent {
|
||||||
if indentations.is_some() && indentations.unwrap().len() > i && indentations.unwrap()[i] {
|
if indentations.is_some() && indentations.unwrap().len() > i && indentations.unwrap()[i]
|
||||||
|
{
|
||||||
ncurses::wattron(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_THREAD_INDENT));
|
ncurses::wattron(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_THREAD_INDENT));
|
||||||
ncurses::waddstr(win, "│");
|
ncurses::waddstr(win, "│");
|
||||||
ncurses::wattroff(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_THREAD_INDENT));
|
ncurses::wattroff(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_THREAD_INDENT));
|
||||||
|
@ -401,7 +436,7 @@ impl Index {
|
||||||
}
|
}
|
||||||
ncurses::wattron(win, attr);
|
ncurses::wattron(win, attr);
|
||||||
if show_subject {
|
if show_subject {
|
||||||
ncurses::waddstr(win, &format!("{:.85}",mail.get_subject()));
|
ncurses::waddstr(win, &format!("{:.85}", mail.get_subject()));
|
||||||
/*
|
/*
|
||||||
if indent == 0 {
|
if indent == 0 {
|
||||||
if mail.get_subject().chars().count() < 85 {
|
if mail.get_subject().chars().count() < 85 {
|
||||||
|
@ -426,8 +461,7 @@ impl Index {
|
||||||
if self.mailbox.get_length() == 0 {
|
if self.mailbox.get_length() == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ncurses::getmaxyx(self.win,
|
ncurses::getmaxyx(self.win, &mut self.screen_height, &mut self.screen_width);
|
||||||
&mut self.screen_height, &mut self.screen_width);
|
|
||||||
let x: &mut Envelope = if self.threaded {
|
let x: &mut Envelope = if self.threaded {
|
||||||
let i = self.mailbox.get_threaded_mail(self.cursor_idx);
|
let i = self.mailbox.get_threaded_mail(self.cursor_idx);
|
||||||
&mut self.mailbox.collection[i]
|
&mut self.mailbox.collection[i]
|
||||||
|
@ -452,7 +486,7 @@ impl Index {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl Drop for Index {
|
impl Drop for Index {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
ncurses::delwin(self.pad);
|
ncurses::delwin(self.pad);
|
||||||
ncurses::wclear(self.win);
|
ncurses::wclear(self.win);
|
||||||
|
|
|
@ -55,7 +55,7 @@ impl TUI {
|
||||||
/* Set the window's background color. */
|
/* Set the window's background color. */
|
||||||
ncurses::bkgd(
|
ncurses::bkgd(
|
||||||
' ' as ncurses::chtype | ncurses::COLOR_PAIR(COLOR_PAIR_DEFAULT) as ncurses::chtype,
|
' ' as ncurses::chtype | ncurses::COLOR_PAIR(COLOR_PAIR_DEFAULT) as ncurses::chtype,
|
||||||
);
|
);
|
||||||
TUI {}
|
TUI {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,8 +35,7 @@ pub struct Pager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pager {
|
impl Pager {
|
||||||
pub fn new(parent: ncurses::WINDOW,
|
pub fn new(parent: ncurses::WINDOW, entry: &mut mailbox::Envelope) -> Pager {
|
||||||
entry: &mut mailbox::Envelope) -> Pager {
|
|
||||||
let mut screen_height = 0;
|
let mut screen_height = 0;
|
||||||
let mut screen_width = 0;
|
let mut screen_width = 0;
|
||||||
ncurses::getmaxyx(parent, &mut screen_height, &mut screen_width);
|
ncurses::getmaxyx(parent, &mut screen_height, &mut screen_width);
|
||||||
|
@ -85,16 +84,13 @@ impl Pager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
match motion {
|
match motion {
|
||||||
ncurses::KEY_UP => {
|
ncurses::KEY_UP => if self.curr_y > 0 {
|
||||||
if self.curr_y > 0 {
|
self.curr_y -= 1;
|
||||||
self.curr_y -= 1;
|
},
|
||||||
}
|
ncurses::KEY_DOWN => if self.curr_y < self.rows && self.rows - self.curr_y > pager_size
|
||||||
}
|
{
|
||||||
ncurses::KEY_DOWN => {
|
self.curr_y += 1;
|
||||||
if self.curr_y < self.rows && self.rows - self.curr_y > pager_size {
|
},
|
||||||
self.curr_y += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ncurses::KEY_NPAGE => {
|
ncurses::KEY_NPAGE => {
|
||||||
if self.curr_y + h < self.rows && self.rows - self.curr_y - h > pager_size {
|
if self.curr_y + h < self.rows && self.rows - self.curr_y - h > pager_size {
|
||||||
self.curr_y += pager_size;
|
self.curr_y += pager_size;
|
||||||
|
@ -106,13 +102,11 @@ impl Pager {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ncurses::KEY_PPAGE => {
|
ncurses::KEY_PPAGE => if self.curr_y >= pager_size {
|
||||||
if self.curr_y >= pager_size {
|
self.curr_y -= pager_size;
|
||||||
self.curr_y -= pager_size;
|
} else {
|
||||||
} else {
|
self.curr_y = 0
|
||||||
self.curr_y = 0
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
@ -140,31 +134,19 @@ impl Pager {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
ncurses::wattron(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_HEADERS));
|
ncurses::wattron(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_HEADERS));
|
||||||
ncurses::waddstr(win, "Date: ");
|
ncurses::waddstr(win, "Date: ");
|
||||||
ncurses::waddstr(
|
ncurses::waddstr(win, mail.get_date_as_str());
|
||||||
win,
|
|
||||||
mail.get_date_as_str()
|
|
||||||
);
|
|
||||||
ncurses::waddstr(win, "\n");
|
ncurses::waddstr(win, "\n");
|
||||||
i += 1;
|
i += 1;
|
||||||
ncurses::waddstr(win, "From: ");
|
ncurses::waddstr(win, "From: ");
|
||||||
ncurses::waddstr(
|
ncurses::waddstr(win, mail.get_from());
|
||||||
win,
|
|
||||||
mail.get_from(),
|
|
||||||
);
|
|
||||||
ncurses::waddstr(win, "\n");
|
ncurses::waddstr(win, "\n");
|
||||||
i += 1;
|
i += 1;
|
||||||
ncurses::waddstr(win, "To: ");
|
ncurses::waddstr(win, "To: ");
|
||||||
ncurses::waddstr(
|
ncurses::waddstr(win, mail.get_to());
|
||||||
win,
|
|
||||||
mail.get_to(),
|
|
||||||
);
|
|
||||||
ncurses::waddstr(win, "\n");
|
ncurses::waddstr(win, "\n");
|
||||||
i += 1;
|
i += 1;
|
||||||
ncurses::waddstr(win, "Subject: ");
|
ncurses::waddstr(win, "Subject: ");
|
||||||
ncurses::waddstr(
|
ncurses::waddstr(win, mail.get_subject());
|
||||||
win,
|
|
||||||
mail.get_subject(),
|
|
||||||
);
|
|
||||||
ncurses::waddstr(win, "\n");
|
ncurses::waddstr(win, "\n");
|
||||||
i += 1;
|
i += 1;
|
||||||
ncurses::waddstr(win, "Message-ID: ");
|
ncurses::waddstr(win, "Message-ID: ");
|
||||||
|
@ -176,17 +158,11 @@ impl Pager {
|
||||||
ncurses::waddstr(win, "\n");
|
ncurses::waddstr(win, "\n");
|
||||||
i += 1;
|
i += 1;
|
||||||
ncurses::waddstr(win, "References: ");
|
ncurses::waddstr(win, "References: ");
|
||||||
ncurses::waddstr(
|
ncurses::waddstr(win, &format!("{:?}", mail.get_references()));
|
||||||
win,
|
|
||||||
&format!("{:?}", mail.get_references()),
|
|
||||||
);
|
|
||||||
ncurses::waddstr(win, "\n");
|
ncurses::waddstr(win, "\n");
|
||||||
i += 1;
|
i += 1;
|
||||||
ncurses::waddstr(win, "In-Reply-To: ");
|
ncurses::waddstr(win, "In-Reply-To: ");
|
||||||
ncurses::waddstr(
|
ncurses::waddstr(win, mail.get_in_reply_to_raw());
|
||||||
win,
|
|
||||||
mail.get_in_reply_to_raw(),
|
|
||||||
);
|
|
||||||
ncurses::waddstr(win, "\n");
|
ncurses::waddstr(win, "\n");
|
||||||
i += 1;
|
i += 1;
|
||||||
ncurses::wattroff(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_HEADERS));
|
ncurses::wattroff(win, ncurses::COLOR_PAIR(super::COLOR_PAIR_HEADERS));
|
||||||
|
@ -196,7 +172,8 @@ impl Pager {
|
||||||
fn print_entry_content(
|
fn print_entry_content(
|
||||||
win: ncurses::WINDOW,
|
win: ncurses::WINDOW,
|
||||||
mail: &mut mailbox::Envelope,
|
mail: &mut mailbox::Envelope,
|
||||||
height: i32) -> (ncurses::WINDOW, i32, i32) {
|
height: i32,
|
||||||
|
) -> (ncurses::WINDOW, i32, i32) {
|
||||||
let mut h = 0;
|
let mut h = 0;
|
||||||
let mut w = 0;
|
let mut w = 0;
|
||||||
/* height and width of self.win, the pager window */
|
/* height and width of self.win, the pager window */
|
||||||
|
@ -231,7 +208,8 @@ impl Pager {
|
||||||
}
|
}
|
||||||
fn print_entry(
|
fn print_entry(
|
||||||
win: ncurses::WINDOW,
|
win: ncurses::WINDOW,
|
||||||
mail: &mut mailbox::Envelope) -> (ncurses::WINDOW, i32, i32) {
|
mail: &mut mailbox::Envelope,
|
||||||
|
) -> (ncurses::WINDOW, i32, i32) {
|
||||||
let header_height = Pager::print_entry_headers(win, mail);
|
let header_height = Pager::print_entry_headers(win, mail);
|
||||||
Pager::print_entry_content(win, mail, header_height + 2)
|
Pager::print_entry_content(win, mail, header_height + 2)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue