Move backend logic to backend and keep Envelope abstract

This commit is contained in:
Manos Pitsidianakis 2018-08-12 16:55:45 +03:00
parent e316640f68
commit 5889494e9e
No known key found for this signature in database
GPG Key ID: 73627C2F690DF710
11 changed files with 344 additions and 253 deletions

View File

@ -48,7 +48,7 @@ pub struct Account {
impl Account { impl Account {
pub fn new(name: String, settings: AccountSettings, map: &Backends) -> Self { pub fn new(name: String, settings: AccountSettings, map: &Backends) -> Self {
let backend = map.get(settings.format())(&settings); let mut backend = map.get(settings.format())(&settings);
let ref_folders: Vec<Folder> = backend.folders(); let ref_folders: Vec<Folder> = backend.folders();
let mut folders: Vec<Option<Result<Mailbox>>> = Vec::with_capacity(ref_folders.len()); let mut folders: Vec<Option<Result<Mailbox>>> = Vec::with_capacity(ref_folders.len());
let mut workers: Vec<Worker> = Vec::new(); let mut workers: Vec<Worker> = Vec::new();

View File

@ -23,7 +23,7 @@ use async::*;
use conf::AccountSettings; use conf::AccountSettings;
use error::{MeliError, Result}; use error::{MeliError, Result};
use mailbox::backends::{ use mailbox::backends::{
BackendFolder, BackendOp, BackendOpGenerator, Folder, MailBackend, RefreshEvent, BackendFolder, BackendOp, Folder, MailBackend, RefreshEvent,
RefreshEventConsumer, RefreshEventConsumer,
}; };
use mailbox::email::parser; use mailbox::email::parser;
@ -43,41 +43,52 @@ extern crate crossbeam;
use memmap::{Mmap, Protection}; use memmap::{Mmap, Protection};
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::fs; use std::fs;
use std::sync::{Mutex, Arc};
use std::hash::Hasher; use std::hash::Hasher;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
extern crate fnv;
use self::fnv::FnvHashMap;
/// `BackendOp` implementor for Maildir /// `BackendOp` implementor for Maildir
#[derive(Debug, Default)] #[derive(Debug)]
pub struct MaildirOp { pub struct MaildirOp {
path: String, hash_index: Arc<Mutex<FnvHashMap<u64, (usize, String)>>>,
hash: u64,
slice: Option<Mmap>, slice: Option<Mmap>,
} }
impl Clone for MaildirOp { impl Clone for MaildirOp {
fn clone(&self) -> Self { fn clone(&self) -> Self {
MaildirOp { MaildirOp {
path: self.path.clone(), hash_index: self.hash_index.clone(),
hash: self.hash.clone(),
slice: None, slice: None,
} }
} }
} }
impl MaildirOp { impl MaildirOp {
pub fn new(path: String) -> Self { pub fn new(hash: u64, hash_index: Arc<Mutex<FnvHashMap<u64, (usize, String)>>>) -> Self {
MaildirOp { MaildirOp {
path: path, hash_index,
hash,
slice: None, slice: None,
} }
} }
fn path(&self) -> String {
let hash_index = self.hash_index.clone();
let map = hash_index.lock().unwrap();
map.get(&self.hash).unwrap().1.clone()
}
} }
impl BackendOp for MaildirOp { impl<'a> BackendOp for MaildirOp {
fn description(&self) -> String { fn description(&self) -> String {
format!("Path of file: {}", self.path) format!("Path of file:")// self.0ipath)
} }
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)?); self.slice = Some(Mmap::open_path(self.path(), Protection::Read)?);
} }
/* Unwrap is safe since we use ? above. */ /* Unwrap is safe since we use ? above. */
Ok(unsafe { self.slice.as_ref().unwrap().as_slice() }) Ok(unsafe { self.slice.as_ref().unwrap().as_slice() })
@ -94,13 +105,12 @@ impl BackendOp for MaildirOp {
} }
fn fetch_flags(&self) -> Flag { fn fetch_flags(&self) -> Flag {
let mut flag = Flag::default(); let mut flag = Flag::default();
let path = PathBuf::from(&self.path); let path = self.path();
let filename = path.file_name().unwrap().to_str().unwrap(); if !path.contains(":2,") {
if !filename.contains(":2,") {
return flag; return flag;
} }
for f in filename.chars().rev() { for f in path.chars().rev() {
match f { match f {
',' => break, ',' => break,
'D' => flag |= Flag::DRAFT, 'D' => flag |= Flag::DRAFT,
@ -115,13 +125,18 @@ impl BackendOp for MaildirOp {
flag flag
} }
fn set_flag(&mut self, envelope: &mut Envelope, f: &Flag) -> Result<()> { fn set_flag(&mut self, envelope: &mut Envelope, f: &Flag) -> Result<()> {
let idx: usize = self.path.rfind(":2,").ok_or(MeliError::new(format!( let path = self.path();
"Invalid email filename: {:?}", let idx: usize = path.rfind(":2,").ok_or(MeliError::new(format!(
self "Invalid email filename: {:?}",
)))? + 3; self
let mut new_name: String = self.path[..idx].to_string(); )))? + 3;
let mut new_name: String = path[..idx].to_string();
let mut flags = self.fetch_flags(); let mut flags = self.fetch_flags();
if !(flags & *f).is_empty() {
return Ok(());
}
flags.toggle(*f); flags.toggle(*f);
if !(flags & Flag::DRAFT).is_empty() { if !(flags & Flag::DRAFT).is_empty() {
new_name.push('D'); new_name.push('D');
@ -142,10 +157,11 @@ impl BackendOp for MaildirOp {
new_name.push('T'); new_name.push('T');
} }
fs::rename(&self.path, &new_name)?; fs::rename(&path, &new_name)?;
envelope.set_operation_token(Box::new(BackendOpGenerator::new(Box::new(move || { let hash = envelope.hash();
Box::new(MaildirOp::new(new_name.clone())) let hash_index = self.hash_index.clone();
})))); let mut map = hash_index.lock().unwrap();
map.get_mut(&hash).unwrap().1 = new_name;
Ok(()) Ok(())
} }
} }
@ -154,6 +170,8 @@ impl BackendOp for MaildirOp {
#[derive(Debug)] #[derive(Debug)]
pub struct MaildirType { pub struct MaildirType {
folders: Vec<MaildirFolder>, folders: Vec<MaildirFolder>,
hash_index: Arc<Mutex<FnvHashMap<u64, (usize, String)>>>,
path: String, path: String,
} }
@ -161,7 +179,7 @@ impl MailBackend for MaildirType {
fn folders(&self) -> Vec<Folder> { fn folders(&self) -> Vec<Folder> {
self.folders.iter().map(|f| f.clone()).collect() self.folders.iter().map(|f| f.clone()).collect()
} }
fn get(&self, folder: &Folder) -> Async<Result<Vec<Envelope>>> { fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
self.multicore(4, folder) self.multicore(4, folder)
} }
fn watch(&self, sender: RefreshEventConsumer) -> Result<()> { fn watch(&self, sender: RefreshEventConsumer) -> Result<()> {
@ -188,25 +206,25 @@ impl MailBackend for MaildirType {
match rx.recv() { match rx.recv() {
Ok(event) => match event { Ok(event) => match event {
DebouncedEvent::Create(mut pathbuf) DebouncedEvent::Create(mut pathbuf)
| DebouncedEvent::Remove(mut pathbuf) => { | DebouncedEvent::Remove(mut pathbuf) => {
let path = if pathbuf.is_dir() { let path = if pathbuf.is_dir() {
if pathbuf.ends_with("cur") | pathbuf.ends_with("new") { if pathbuf.ends_with("cur") | pathbuf.ends_with("new") {
pathbuf.pop();
}
pathbuf.to_str().unwrap()
} else {
pathbuf.pop(); pathbuf.pop();
} pathbuf.parent().unwrap().to_str().unwrap()
pathbuf.to_str().unwrap() };
} else { eprintln!(" got event in {}", path);
pathbuf.pop();
pathbuf.parent().unwrap().to_str().unwrap()
};
eprintln!(" got event in {}", path);
let mut hasher = DefaultHasher::new(); let mut hasher = DefaultHasher::new();
hasher.write(path.as_bytes()); hasher.write(path.as_bytes());
sender.send(RefreshEvent { sender.send(RefreshEvent {
folder: format!("{}", path), folder: format!("{}", path),
hash: hasher.finish(), hash: hasher.finish(),
}); });
} }
_ => {} _ => {}
}, },
Err(e) => eprintln!("watch error: {:?}", e), Err(e) => eprintln!("watch error: {:?}", e),
@ -215,6 +233,9 @@ impl MailBackend for MaildirType {
})?; })?;
Ok(()) Ok(())
} }
fn operation(&self, hash: u64) -> Box<BackendOp> {
Box::new(MaildirOp::new(hash, self.hash_index.clone()))
}
} }
impl MaildirType { impl MaildirType {
@ -235,7 +256,7 @@ impl MaildirType {
path.to_str().unwrap().to_string(), path.to_str().unwrap().to_string(),
path.file_name().unwrap().to_str().unwrap().to_string(), path.file_name().unwrap().to_str().unwrap().to_string(),
path_children, path_children,
) { ) {
folders.push(f); folders.push(f);
children.push(folders.len() - 1); children.push(folders.len() - 1);
} }
@ -251,13 +272,14 @@ impl MaildirType {
path.to_str().unwrap().to_string(), path.to_str().unwrap().to_string(),
path.file_name().unwrap().to_str().unwrap().to_string(), path.file_name().unwrap().to_str().unwrap().to_string(),
Vec::with_capacity(0), Vec::with_capacity(0),
) { ) {
folders.push(f); folders.push(f);
} }
} }
folders[0].children = recurse_folders(&mut folders, &path); folders[0].children = recurse_folders(&mut folders, &path);
MaildirType { MaildirType {
folders, folders,
hash_index: Arc::new(Mutex::new(FnvHashMap::with_capacity_and_hasher(0, Default::default()))),
path: f.root_folder().to_string(), path: f.root_folder().to_string(),
} }
} }
@ -270,7 +292,7 @@ impl MaildirType {
unreachable!() unreachable!()
} }
pub fn multicore(&self, cores: usize, folder: &Folder) -> Async<Result<Vec<Envelope>>> { pub fn multicore(&mut self, cores: usize, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
let mut w = AsyncBuilder::new(); let mut w = AsyncBuilder::new();
let handle = { let handle = {
let tx = w.tx(); let tx = w.tx();
@ -278,6 +300,8 @@ impl MaildirType {
let folder: &MaildirFolder = &self.folders[self.owned_folder_idx(folder)]; let folder: &MaildirFolder = &self.folders[self.owned_folder_idx(folder)];
let path = folder.path().to_string(); let path = folder.path().to_string();
let name = format!("parsing {:?}", folder.name()); let name = format!("parsing {:?}", folder.name());
let map = self.hash_index.clone();
let map2 = self.hash_index.clone();
thread::Builder::new() thread::Builder::new()
.name(name) .name(name)
@ -305,25 +329,48 @@ impl MaildirType {
}; };
for chunk in files.chunks(chunk_size) { for chunk in files.chunks(chunk_size) {
let mut tx = tx.clone(); let mut tx = tx.clone();
let map = map.clone();
let s = scope.spawn(move || { let s = scope.spawn(move || {
let len = chunk.len(); let len = chunk.len();
let size = if len <= 100 { 100 } else { (len / 100) * 100 }; let size = if len <= 100 { 100 } else { (len / 100) * 100 };
let mut local_r: Vec< let mut local_r: Vec<Envelope> = Vec::with_capacity(chunk.len());
Envelope,
> = Vec::with_capacity(chunk.len());
for c in chunk.chunks(size) { for c in chunk.chunks(size) {
let map = map.clone();
let len = c.len(); let len = c.len();
for e in c { for file in c {
let e_copy = e.to_string(); let e_copy = file.to_string();
if let Some(mut e) = Envelope::from_token(Box::new( /*
BackendOpGenerator::new(Box::new(move || { * get hash
Box::new(MaildirOp::new(e_copy.clone())) * lock map
})), * see if its inside otherwise add it
)) { * check cache
if e.populate_headers().is_err() { * generate Envelope
continue; * add to local_r
} */
local_r.push(e); {
let mut hasher = DefaultHasher::new();
let hash = {
let slice = Mmap::open_path(&e_copy, Protection::Read).unwrap();
/* Unwrap is safe since we use ? above. */
hasher.write(
unsafe { slice.as_slice() });
hasher.finish()
};
{
let mut map = map.lock().unwrap();
if (*map).contains_key(&hash) {
continue;
}
(*map).insert(hash, (0, e_copy));
}
// TODO: Check cache
let op = Box::new(MaildirOp::new(hash, map.clone()));
if let Some(mut e) = Envelope::from_token(op, hash) {
local_r.push(e);
} else {
continue;
}
} }
} }
tx.send(AsyncStatus::ProgressReport(len)); tx.send(AsyncStatus::ProgressReport(len));
@ -338,13 +385,20 @@ impl MaildirType {
let mut result = t.join(); let mut result = t.join();
r.append(&mut result); r.append(&mut result);
} }
let mut map = map2.lock().unwrap();
for (idx, e) in r.iter().enumerate() {
let mut y = (*map)[&e.hash()].clone();
y.0 = idx;
(*map).insert(e.hash(),y);
}
tx.send(AsyncStatus::Finished); tx.send(AsyncStatus::Finished);
Ok(r) Ok(r)
}) })
.unwrap() .unwrap()
}; };
w.build(handle) w.build(handle)
} }
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
@ -379,9 +433,9 @@ impl MaildirFolder {
p.push(d); p.push(d);
if !p.is_dir() { if !p.is_dir() {
return Err(MeliError::new(format!( return Err(MeliError::new(format!(
"{} is not a valid maildir folder", "{} is not a valid maildir folder",
path path
))); )));
} }
p.pop(); p.pop();
} }

View File

@ -92,9 +92,10 @@ impl RefreshEventConsumer {
} }
} }
pub trait MailBackend: ::std::fmt::Debug { pub trait MailBackend: ::std::fmt::Debug {
fn get(&self, folder: &Folder) -> Async<Result<Vec<Envelope>>>; fn get(&mut self, folder: &Folder) -> Async<Result<Vec<Envelope>>>;
fn watch(&self, sender: RefreshEventConsumer) -> Result<()>; fn watch(&self, sender: RefreshEventConsumer) -> Result<()>;
fn folders(&self) -> Vec<Folder>; fn folders(&self) -> Vec<Folder>;
fn operation(&self, hash: u64) -> Box<BackendOp>;
//login function //login function
} }

View File

@ -2,7 +2,7 @@ use mailbox::email::parser::BytesExt;
use std::fmt::{Display, Formatter, Result as FmtResult}; use std::fmt::{Display, Formatter, Result as FmtResult};
use std::str; use std::str;
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq, Serialize)]
pub enum Charset { pub enum Charset {
Ascii, Ascii,
UTF8, UTF8,
@ -52,7 +52,7 @@ impl<'a> From<&'a [u8]> for Charset {
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq, Serialize)]
pub enum MultipartType { pub enum MultipartType {
Mixed, Mixed,
Alternative, Alternative,
@ -73,7 +73,7 @@ impl Display for MultipartType {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize)]
pub enum ContentType { pub enum ContentType {
Text { charset: Charset }, Text { charset: Charset },
Multipart { boundary: Vec<u8> }, Multipart { boundary: Vec<u8> },
@ -108,7 +108,7 @@ impl ContentType {
} }
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq, Serialize)]
pub enum ContentSubType { pub enum ContentSubType {
Plain, Plain,
Html, Html,
@ -134,7 +134,7 @@ impl Display for ContentSubType {
} }
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize)]
pub enum ContentTransferEncoding { pub enum ContentTransferEncoding {
_8Bit, _8Bit,
_7Bit, _7Bit,

View File

@ -25,7 +25,7 @@ use std::str;
pub use mailbox::email::attachment_types::*; pub use mailbox::email::attachment_types::*;
#[derive(Clone)] #[derive(Clone, Serialize)]
pub enum AttachmentType { pub enum AttachmentType {
Data { Data {
tag: Vec<u8>, tag: Vec<u8>,
@ -71,7 +71,7 @@ pub struct AttachmentBuilder {
raw: Vec<u8>, raw: Vec<u8>,
} }
#[derive(Clone)] #[derive(Clone, Serialize)]
pub struct Attachment { pub struct Attachment {
content_type: (ContentType, ContentSubType), content_type: (ContentType, ContentSubType),
content_transfer_encoding: ContentTransferEncoding, content_transfer_encoding: ContentTransferEncoding,

View File

@ -29,7 +29,7 @@ pub mod parser;
use parser::BytesExt; use parser::BytesExt;
use error::{MeliError, Result}; use error::{MeliError, Result};
use mailbox::backends::BackendOpGenerator; use mailbox::backends::BackendOp;
use std::borrow::Cow; use std::borrow::Cow;
use std::cmp::Ordering; use std::cmp::Ordering;
@ -38,26 +38,25 @@ use std::fmt;
use std::hash::Hasher; use std::hash::Hasher;
use std::option::Option; use std::option::Option;
use std::string::String; use std::string::String;
use std::sync::Arc;
use chrono; use chrono;
use chrono::TimeZone; use chrono::TimeZone;
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize)]
pub struct GroupAddress { pub struct GroupAddress {
raw: Vec<u8>, raw: Vec<u8>,
display_name: StrBuilder, display_name: StrBuilder,
mailbox_list: Vec<Address>, mailbox_list: Vec<Address>,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize)]
pub struct MailboxAddress { pub struct MailboxAddress {
raw: Vec<u8>, raw: Vec<u8>,
display_name: StrBuilder, display_name: StrBuilder,
address_spec: StrBuilder, address_spec: StrBuilder,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize)]
pub enum Address { pub enum Address {
Mailbox(MailboxAddress), Mailbox(MailboxAddress),
Group(GroupAddress), Group(GroupAddress),
@ -109,7 +108,7 @@ impl fmt::Display for Address {
} }
/// Helper struct to return slices from a struct field on demand. /// Helper struct to return slices from a struct field on demand.
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize)]
struct StrBuilder { struct StrBuilder {
offset: usize, offset: usize,
length: usize, length: usize,
@ -134,7 +133,7 @@ impl StrBuilder {
} }
/// `MessageID` is accessed through the `StrBuild` trait. /// `MessageID` is accessed through the `StrBuild` trait.
#[derive(Clone)] #[derive(Clone, Serialize)]
pub struct MessageID(Vec<u8>, StrBuilder); pub struct MessageID(Vec<u8>, StrBuilder);
impl StrBuild for MessageID { impl StrBuild for MessageID {
@ -185,14 +184,14 @@ impl fmt::Debug for MessageID {
} }
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug, Serialize)]
struct References { struct References {
raw: Vec<u8>, raw: Vec<u8>,
refs: Vec<MessageID>, refs: Vec<MessageID>,
} }
bitflags! { bitflags! {
#[derive(Default)] #[derive(Default, Serialize)]
pub struct Flag: u8 { pub struct Flag: u8 {
const PASSED = 0b00000001; const PASSED = 0b00000001;
const REPLIED = 0b00000010; const REPLIED = 0b00000010;
@ -237,7 +236,7 @@ impl EnvelopeBuilder {
/// Access to the underlying email object in the account's backend (for example the file or the /// Access to the underlying email object in the account's backend (for example the file or the
/// entry in an IMAP server) is given through `operation_token`. For more information see /// entry in an IMAP server) is given through `operation_token`. For more information see
/// `BackendOp`. /// `BackendOp`.
#[derive(Debug, Clone)] #[derive(Debug, Clone, Serialize)]
pub struct Envelope { pub struct Envelope {
date: String, date: String,
from: Vec<Address>, from: Vec<Address>,
@ -248,19 +247,18 @@ pub struct Envelope {
in_reply_to: Option<MessageID>, in_reply_to: Option<MessageID>,
references: Option<References>, references: Option<References>,
datetime: Option<chrono::DateTime<chrono::FixedOffset>>,
timestamp: u64, timestamp: u64,
thread: usize, thread: usize,
operation_token: Arc<Box<BackendOpGenerator>>, hash: u64,
flags: Flag, flags: Flag,
} }
impl Envelope { impl Envelope {
pub fn new(token: Box<BackendOpGenerator>) -> Self { pub fn new(hash: u64) -> Self {
Envelope { Envelope {
date: "".to_string(), date: String::new(),
from: Vec::new(), from: Vec::new(),
to: Vec::new(), to: Vec::new(),
body: None, body: None,
@ -269,103 +267,105 @@ impl Envelope {
in_reply_to: None, in_reply_to: None,
references: None, references: None,
datetime: None,
timestamp: 0, timestamp: 0,
thread: 0, thread: 0,
operation_token: Arc::new(token), hash,
flags: Flag::default(), flags: Flag::default(),
} }
} }
pub fn from_token(operation_token: Box<BackendOpGenerator>) -> Option<Envelope> { pub fn from_token(operation: Box<BackendOp>, hash: u64) -> Option<Envelope> {
let operation = operation_token.generate(); let mut e = Envelope::new(hash);
let mut e = Envelope::new(operation_token);
e.flags = operation.fetch_flags(); e.flags = operation.fetch_flags();
Some(e) let res = e.populate_headers(operation).ok();
if res.is_some() {
Some(e)
} else {
None
}
} }
pub fn set_operation_token(&mut self, operation_token: Box<BackendOpGenerator>) { pub fn hash(&self) -> u64 {
self.operation_token = Arc::new(operation_token); self.hash
} }
pub fn populate_headers(&mut self, mut operation: Box<BackendOp>) -> Result<()> {
{
let headers = match parser::headers(operation.fetch_headers()?).to_full_result() {
Ok(v) => v,
Err(e) => {
eprintln!("error in parsing mail\n");
return Err(MeliError::from(e));
}
};
pub fn populate_headers(&mut self) -> Result<()> { let mut in_reply_to = None;
let mut operation = self.operation_token.generate(); let mut datetime = None;
let headers = match parser::headers(operation.fetch_headers()?).to_full_result() {
Ok(v) => v,
Err(e) => {
let operation = self.operation_token.generate();
eprintln!("error in parsing mail\n{}", operation.description());
return Err(MeliError::from(e));
}
};
let mut in_reply_to = None; for (name, value) in headers {
let mut datetime = None; if value.len() == 1 && value.is_empty() {
continue;
for (name, value) in headers { }
if value.len() == 1 && value.is_empty() { if name.eq_ignore_ascii_case(b"to") {
continue; let parse_result = parser::rfc2822address_list(value);
} let value = if parse_result.is_done() {
if name.eq_ignore_ascii_case(b"to") { parse_result.to_full_result().unwrap()
let parse_result = parser::rfc2822address_list(value); } else {
let value = if parse_result.is_done() { Vec::new()
parse_result.to_full_result().unwrap() };
} else { self.set_to(value);
Vec::new() } else if name.eq_ignore_ascii_case(b"from") {
}; let parse_result = parser::rfc2822address_list(value);
self.set_to(value); let value = if parse_result.is_done() {
} else if name.eq_ignore_ascii_case(b"from") { parse_result.to_full_result().unwrap()
let parse_result = parser::rfc2822address_list(value); } else {
let value = if parse_result.is_done() { Vec::new()
parse_result.to_full_result().unwrap() };
} else { self.set_from(value);
Vec::new() } else if name.eq_ignore_ascii_case(b"subject") {
}; let parse_result = parser::phrase(value.trim());
self.set_from(value); let value = if parse_result.is_done() {
} else if name.eq_ignore_ascii_case(b"subject") { parse_result.to_full_result().unwrap()
let parse_result = parser::phrase(value.trim()); } else {
let value = if parse_result.is_done() { "".into()
parse_result.to_full_result().unwrap() };
} else { self.set_subject(value);
"".into() } else if name.eq_ignore_ascii_case(b"message-id") {
}; self.set_message_id(value);
self.set_subject(value); } else if name.eq_ignore_ascii_case(b"references") {
} else if name.eq_ignore_ascii_case(b"message-id") { {
self.set_message_id(value); let parse_result = parser::references(value);
} else if name.eq_ignore_ascii_case(b"references") { if parse_result.is_done() {
{ for v in parse_result.to_full_result().unwrap() {
let parse_result = parser::references(value); self.push_references(v);
if parse_result.is_done() { }
for v in parse_result.to_full_result().unwrap() {
self.push_references(v);
} }
} }
self.set_references(value);
} else if name.eq_ignore_ascii_case(b"in-reply-to") {
self.set_in_reply_to(value);
in_reply_to = Some(value);
} else if name.eq_ignore_ascii_case(b"date") {
self.set_date(value);
datetime = Some(value);
} }
self.set_references(value);
} else if name.eq_ignore_ascii_case(b"in-reply-to") {
self.set_in_reply_to(value);
in_reply_to = Some(value);
} else if name.eq_ignore_ascii_case(b"date") {
self.set_date(value);
datetime = Some(value);
} }
} /*
/* * https://tools.ietf.org/html/rfc5322#section-3.6.4
* https://tools.ietf.org/html/rfc5322#section-3.6.4 *
* * if self.message_id.is_none() ...
* if self.message_id.is_none() ... */
*/ if let Some(ref mut x) = in_reply_to {
if let Some(ref mut x) = in_reply_to { self.push_references(x);
self.push_references(x);
}
if let Some(ref mut d) = datetime {
if let Some(d) = parser::date(d) {
self.set_datetime(d);
} }
} if let Some(ref mut d) = datetime {
if let Some(d) = parser::date(d) {
self.set_datetime(d);
}
}
}
if self.message_id.is_none() { if self.message_id.is_none() {
let mut h = DefaultHasher::new(); let mut h = DefaultHasher::new();
h.write(&self.bytes()); h.write(&self.bytes(operation));
self.set_message_id(format!("<{:x}>", h.finish()).as_bytes()); self.set_message_id(format!("<{:x}>", h.finish()).as_bytes());
} }
Ok(()) Ok(())
@ -375,11 +375,12 @@ impl Envelope {
} }
pub fn datetime(&self) -> chrono::DateTime<chrono::FixedOffset> { pub fn datetime(&self) -> chrono::DateTime<chrono::FixedOffset> {
self.datetime.unwrap_or_else(|| { if let Some(d) = parser::date(&self.date.as_bytes()) {
return d;
}
chrono::FixedOffset::west(0) chrono::FixedOffset::west(0)
.ymd(1970, 1, 1) .ymd(1970, 1, 1)
.and_hms(0, 0, 0) .and_hms(0, 0, 0)
})
} }
pub fn date_as_str(&self) -> &str { pub fn date_as_str(&self) -> &str {
&self.date &self.date
@ -399,21 +400,18 @@ impl Envelope {
let _strings: Vec<String> = self.to.iter().map(|a| format!("{}", a)).collect(); let _strings: Vec<String> = self.to.iter().map(|a| format!("{}", a)).collect();
_strings.join(", ") _strings.join(", ")
} }
pub fn bytes(&self) -> Vec<u8> { pub fn bytes(&self, mut operation: Box<BackendOp>) -> Vec<u8> {
let mut operation = self.operation_token.generate();
operation operation
.as_bytes() .as_bytes()
.map(|v| v.into()) .map(|v| v.into())
.unwrap_or_else(|_| Vec::new()) .unwrap_or_else(|_| Vec::new())
} }
pub fn body(&self) -> Attachment { pub fn body(&self, mut operation: Box<BackendOp>) -> Attachment {
let mut operation = self.operation_token.generate();
let file = operation.as_bytes(); let file = operation.as_bytes();
let (headers, body) = match parser::mail(file.unwrap()).to_full_result() { let (headers, body) = match parser::mail(file.unwrap()).to_full_result() {
Ok(v) => v, Ok(v) => v,
Err(_) => { Err(_) => {
let operation = self.operation_token.generate(); eprintln!("error in parsing mail\n");
eprintln!("error in parsing mail\n{}", operation.description());
let error_msg = b"Mail cannot be shown because of errors."; let error_msg = b"Mail cannot be shown because of errors.";
let mut builder = AttachmentBuilder::new(error_msg); let mut builder = AttachmentBuilder::new(error_msg);
return builder.build(); return builder.build();
@ -564,11 +562,9 @@ impl Envelope {
self.thread = new_val; self.thread = new_val;
} }
pub fn set_datetime(&mut self, new_val: chrono::DateTime<chrono::FixedOffset>) -> () { pub fn set_datetime(&mut self, new_val: chrono::DateTime<chrono::FixedOffset>) -> () {
self.datetime = Some(new_val);
self.timestamp = new_val.timestamp() as u64; self.timestamp = new_val.timestamp() as u64;
} }
pub fn set_flag(&mut self, f: Flag) -> Result<()> { pub fn set_flag(&mut self, f: Flag, mut operation: Box<BackendOp>) -> Result<()> {
let mut operation = self.operation_token.generate();
operation.set_flag(self, &f)?; operation.set_flag(self, &f)?;
self.flags |= f; self.flags |= f;
Ok(()) Ok(())
@ -576,11 +572,11 @@ impl Envelope {
pub fn flags(&self) -> Flag { pub fn flags(&self) -> Flag {
self.flags self.flags
} }
pub fn set_seen(&mut self) -> Result<()> { pub fn set_seen(&mut self, operation: Box<BackendOp>) -> Result<()> {
self.set_flag(Flag::SEEN) self.set_flag(Flag::SEEN, operation)
} }
pub fn is_seen(&self) -> bool { pub fn is_seen(&self) -> bool {
!(self.flags & Flag::SEEN).is_empty() self.flags.contains(Flag::SEEN)
} }
} }

View File

@ -128,9 +128,6 @@ named!(
named!(pub headers<std::vec::Vec<(&[u8], &[u8])>>, named!(pub headers<std::vec::Vec<(&[u8], &[u8])>>,
many1!(complete!(header))); many1!(complete!(header)));
//named!(pub headers_raw<&[u8]>,
//take_until1!("\n\n"));
pub fn headers_raw(input: &[u8]) -> IResult<&[u8], &[u8]> { pub fn headers_raw(input: &[u8]) -> IResult<&[u8], &[u8]> {
if input.is_empty() { if input.is_empty() {
return IResult::Incomplete(Needed::Unknown); return IResult::Incomplete(Needed::Unknown);
@ -417,22 +414,19 @@ named!(
named!(mailbox_list<Vec<Address>>, many0!(mailbox)); named!(mailbox_list<Vec<Address>>, many0!(mailbox));
#[test] #[test]
fn test_mailbox() { fn test_addresses() {
{ {
let s = b"epilys@postretch"; let s = b"user@domain";
let r = mailbox(s).unwrap().1; let r = mailbox(s).unwrap().1;
match r { match r {
Address::Mailbox(ref m) => { Address::Mailbox(ref m) => {
println!( assert!(m.display_name == b"" && m.address_spec == b"user@domain");
"----\n`{}`, `{}`\n----", },
m.display_name.display(&m.raw), _ => assert!(false),
m.address_spec.display(&m.raw)
);
}
_ => {}
} }
} }
let s = b"Manos <epilys@postretch>"; {
let s = b"Name <user@domain>";
eprintln!("{:?}", display_addr(s).unwrap()); eprintln!("{:?}", display_addr(s).unwrap());
let r = display_addr(s).unwrap().1; let r = display_addr(s).unwrap().1;
match r { match r {
@ -446,17 +440,25 @@ fn test_mailbox() {
_ => {} _ => {}
} }
} }
{
let s = b"user@domain";
let r = mailbox(s).unwrap().1;
match r {
Address::Mailbox(ref m) => {
println!(
"----\n`{}`, `{}`\n----",
m.display_name.display(&m.raw),
m.address_spec.display(&m.raw)
);
}
_ => {}
}
}
}
//named!(group_t<GroupAddress>, ws!( do_parse!( /*
// display_name: take_until1!(":") >> * group of recipients eg. undisclosed-recipients;
// mailbox_list: many0!(mailbox) >> */
// end: is_a!(";") >>
// ({
//
// })
// )));
//
fn group(input: &[u8]) -> IResult<&[u8], Address> { fn group(input: &[u8]) -> IResult<&[u8], Address> {
let mut flag = false; let mut flag = false;
let mut dlength = 0; let mut dlength = 0;
@ -499,9 +501,9 @@ named!(address<Address>, ws!(alt_complete!(mailbox | group)));
#[test] #[test]
fn test_address() { fn test_address() {
let s = b"Manos Pitsidianakis <el13635@mail.ntua.gr>, let s = b"Obit Oppidum <user@domain>,
qemu-devel <qemu-devel@nongnu.org>, qemu-block <qemu-block@nongnu.org>, list <list@domain.tld>, list2 <list2@domain.tld>,
Alberto Garcia <berto@igalia.com>, Stefan Hajnoczi <stefanha@redhat.com>"; Bobit Boppidum <user@otherdomain.com>, Cobit Coppidum <user2@otherdomain.com>";
println!("{:?}", rfc2822address_list(s).unwrap()); println!("{:?}", rfc2822address_list(s).unwrap());
} }
@ -552,11 +554,11 @@ named!(pub phrase<Vec<u8>>, ws!(do_parse!(
#[test] #[test]
fn test_phrase() { fn test_phrase() {
let phrase_s = "list.free.de mailing list memberships reminder".as_bytes(); let phrase_s = "mailing list memberships reminder".as_bytes();
assert_eq!( assert_eq!(
( (
&b""[..], &b""[..],
"list.free.de mailing list memberships reminder".to_string() "mailing list memberships reminder".to_string()
), ),
phrase(phrase_s).unwrap() phrase(phrase_s).unwrap()
); );
@ -739,16 +741,3 @@ named!(pub content_type< (&[u8], &[u8], Vec<(&[u8], &[u8])>) >,
(_type, _subtype, parameters) (_type, _subtype, parameters)
} ) } )
)); ));
//named!(pub quoted_printable_text<Vec<u8>>,
// do_parse!(
// bytes: many0!(alt_complete!(
// preceded!(tag!("=\n"), quoted_printable_byte) |
// preceded!(tag!("=\n"), le_u8) |
// quoted_printable_byte |
// le_u8)) >>
// ( {
// bytes
// } )
// )
//);

View File

@ -182,7 +182,7 @@ fn main() {
}, },
ThreadEvent::RefreshMailbox { hash : h } => { ThreadEvent::RefreshMailbox { hash : h } => {
state.hash_to_folder(h); state.hash_to_folder(h);
//state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Notification(n.clone())}); state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Notification(String::from("Update in mailbox"))});
state.redraw(); state.redraw();
}, },
ThreadEvent::UIEvent(UIEventType::ChangeMode(f)) => { ThreadEvent::UIEvent(UIEventType::ChangeMode(f)) => {

View File

@ -20,6 +20,7 @@
*/ */
use super::*; use super::*;
use melib::mailbox::backends::BackendOp;
const MAX_COLS: usize = 500; const MAX_COLS: usize = 500;
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the thread's content in a /// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the thread's content in a
@ -89,7 +90,7 @@ impl CompactMailListing {
break; break;
} }
} }
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1] let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.as_ref() .as_ref()
.unwrap(); .unwrap();
@ -188,6 +189,7 @@ impl CompactMailListing {
container, container,
&indentations, &indentations,
len, len,
context.accounts[self.cursor_pos.0].backend.operation(envelope.hash()),
), ),
&mut content, &mut content,
fg_color, fg_color,
@ -347,6 +349,7 @@ impl CompactMailListing {
container: &Container, container: &Container,
indentations: &[bool], indentations: &[bool],
idx_width: usize, idx_width: usize,
op: Box<BackendOp>,
) -> String { ) -> String {
let has_sibling = container.has_sibling(); let has_sibling = container.has_sibling();
let has_parent = container.has_parent(); let has_parent = container.has_parent();
@ -383,7 +386,7 @@ impl CompactMailListing {
if show_subject { if show_subject {
s.push_str(&format!("{:.85}", envelope.subject())); s.push_str(&format!("{:.85}", envelope.subject()));
} }
let attach_count = envelope.body().count_attachments(); let attach_count = envelope.body(op).count_attachments();
if attach_count > 1 { if attach_count > 1 {
s.push_str(&format!(" {}", attach_count - 1)); s.push_str(&format!(" {}", attach_count - 1));
} }
@ -439,17 +442,34 @@ impl Component for CompactMailListing {
let threaded = context.accounts[self.cursor_pos.0] let threaded = context.accounts[self.cursor_pos.0]
.runtime_settings .runtime_settings
.threaded; .threaded;
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1] let account = &mut context.accounts[self.cursor_pos.0];
.as_mut() let (hash, is_seen) = {
.unwrap(); let mailbox = &mut account[self.cursor_pos.1]
let envelope: &mut Envelope = if threaded { .as_mut()
let i = mailbox.threaded_mail(idx); .unwrap();
&mut mailbox.collection[i] let envelope: &mut Envelope = if threaded {
} else { let i = mailbox.threaded_mail(idx);
&mut mailbox.collection[idx] &mut mailbox.collection[i]
} else {
&mut mailbox.collection[idx]
};
(envelope.hash(), envelope.is_seen())
}; };
if !envelope.is_seen() { if is_seen {
envelope.set_seen().unwrap(); let op = {
let backend = &account.backend;
backend.operation(hash)
};
let mailbox = &mut account[self.cursor_pos.1]
.as_mut()
.unwrap();
let envelope: &mut Envelope = if threaded {
let i = mailbox.threaded_mail(idx);
&mut mailbox.collection[i]
} else {
&mut mailbox.collection[idx]
};
envelope.set_seen(op).unwrap();
true true
} else { } else {
false false

View File

@ -21,6 +21,7 @@
use super::*; use super::*;
use melib::mailbox::backends::BackendOp;
mod compact; mod compact;
pub use self::compact::*; pub use self::compact::*;
@ -107,7 +108,7 @@ impl MailListing {
break; break;
} }
} }
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1] let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
.as_ref() .as_ref()
.unwrap(); .unwrap();
@ -139,10 +140,18 @@ impl MailListing {
let threads: &Vec<Container> = &mailbox.threads; let threads: &Vec<Container> = &mailbox.threads;
local_collection.sort_by(|a, b| match self.sort { local_collection.sort_by(|a, b| match self.sort {
(SortField::Date, SortOrder::Desc) => { (SortField::Date, SortOrder::Desc) => {
mailbox.thread(*b).date().cmp(&mailbox.thread(*a).date()) let a = mailbox.thread(*a);
let b = mailbox.thread(*b);
let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
mb.date().cmp(&ma.date())
} }
(SortField::Date, SortOrder::Asc) => { (SortField::Date, SortOrder::Asc) => {
mailbox.thread(*a).date().cmp(&mailbox.thread(*b).date()) let a = mailbox.thread(*a);
let b = mailbox.thread(*b);
let ma = &mailbox.collection[*a.message().as_ref().unwrap()];
let mb = &mailbox.collection[*b.message().as_ref().unwrap()];
ma.date().cmp(&mb.date())
} }
(SortField::Subject, SortOrder::Desc) => { (SortField::Subject, SortOrder::Desc) => {
let a = mailbox.thread(*a); let a = mailbox.thread(*a);
@ -211,6 +220,7 @@ impl MailListing {
container, container,
&indentations, &indentations,
len, len,
context.accounts[self.cursor_pos.0].backend.operation(envelope.hash())
), ),
&mut content, &mut content,
fg_color, fg_color,
@ -411,6 +421,7 @@ impl MailListing {
container: &Container, container: &Container,
indentations: &[bool], indentations: &[bool],
idx_width: usize, idx_width: usize,
op: Box<BackendOp>,
) -> String { ) -> String {
let has_sibling = container.has_sibling(); let has_sibling = container.has_sibling();
let has_parent = container.has_parent(); let has_parent = container.has_parent();
@ -447,7 +458,7 @@ impl MailListing {
if show_subject { if show_subject {
s.push_str(&format!("{:.85}", envelope.subject())); s.push_str(&format!("{:.85}", envelope.subject()));
} }
let attach_count = envelope.body().count_attachments(); let attach_count = envelope.body(op).count_attachments();
if attach_count > 1 { if attach_count > 1 {
s.push_str(&format!(" {}", attach_count - 1)); s.push_str(&format!(" {}", attach_count - 1));
} }
@ -503,17 +514,34 @@ impl Component for MailListing {
let threaded = context.accounts[self.cursor_pos.0] let threaded = context.accounts[self.cursor_pos.0]
.runtime_settings .runtime_settings
.threaded; .threaded;
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1] let account = &mut context.accounts[self.cursor_pos.0];
.as_mut() let (hash, is_seen) = {
.unwrap(); let mailbox = &mut account[self.cursor_pos.1]
let envelope: &mut Envelope = if threaded { .as_mut()
let i = mailbox.threaded_mail(idx); .unwrap();
&mut mailbox.collection[i] let envelope: &mut Envelope = if threaded {
} else { let i = mailbox.threaded_mail(idx);
&mut mailbox.collection[idx] &mut mailbox.collection[i]
} else {
&mut mailbox.collection[idx]
};
(envelope.hash(), envelope.is_seen())
}; };
if !envelope.is_seen() { if !is_seen {
envelope.set_seen().unwrap(); let op = {
let backend = &account.backend;
backend.operation(hash)
};
let mailbox = &mut account[self.cursor_pos.1]
.as_mut()
.unwrap();
let envelope: &mut Envelope = if threaded {
let i = mailbox.threaded_mail(idx);
&mut mailbox.collection[i]
} else {
&mut mailbox.collection[idx]
};
envelope.set_seen(op).unwrap();
true true
} else { } else {
false false

View File

@ -303,11 +303,12 @@ impl Component for MailView {
if self.dirty { if self.dirty {
let mailbox_idx = self.coordinates; // coordinates are mailbox idxs let mailbox_idx = self.coordinates; // coordinates are mailbox idxs
let mailbox = &mut context.accounts[mailbox_idx.0][mailbox_idx.1] let mailbox = &context.accounts[mailbox_idx.0][mailbox_idx.1]
.as_ref() .as_ref()
.unwrap(); .unwrap();
let envelope: &Envelope = &mailbox.collection[envelope_idx]; let envelope: &Envelope = &mailbox.collection[envelope_idx];
let body = envelope.body(); let op = context.accounts[mailbox_idx.0].backend.operation(envelope.hash());
let body = envelope.body(op);
match self.mode { match self.mode {
ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => { ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => {
self.subview = Some(Box::new(HtmlView::new(decode( self.subview = Some(Box::new(HtmlView::new(decode(
@ -372,9 +373,9 @@ impl Component for MailView {
self.cmd_buf.clear(); self.cmd_buf.clear();
{ {
let accounts = &mut context.accounts; let accounts = &context.accounts;
let threaded = accounts[self.coordinates.0].runtime_settings.threaded; let threaded = accounts[self.coordinates.0].runtime_settings.threaded;
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1] let mailbox = &accounts[self.coordinates.0][self.coordinates.1]
.as_ref() .as_ref()
.unwrap(); .unwrap();
let envelope_idx: usize = if threaded { let envelope_idx: usize = if threaded {
@ -384,7 +385,8 @@ impl Component for MailView {
}; };
let envelope: &Envelope = &mailbox.collection[envelope_idx]; let envelope: &Envelope = &mailbox.collection[envelope_idx];
if let Some(u) = envelope.body().attachments().get(lidx) { let op = context.accounts[self.coordinates.0].backend.operation(envelope.hash());
if let Some(u) = envelope.body(op).attachments().get(lidx) {
match u.content_type().0 { match u.content_type().0 {
ContentType::Text { .. } => { ContentType::Text { .. } => {
self.mode = ViewMode::Attachment(lidx); self.mode = ViewMode::Attachment(lidx);
@ -443,9 +445,9 @@ impl Component for MailView {
let lidx = self.cmd_buf.parse::<usize>().unwrap(); let lidx = self.cmd_buf.parse::<usize>().unwrap();
self.cmd_buf.clear(); self.cmd_buf.clear();
let url = { let url = {
let accounts = &mut context.accounts; let accounts = &context.accounts;
let threaded = accounts[self.coordinates.0].runtime_settings.threaded; let threaded = accounts[self.coordinates.0].runtime_settings.threaded;
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1] let mailbox = &accounts[self.coordinates.0][self.coordinates.1]
.as_ref() .as_ref()
.unwrap(); .unwrap();
let envelope_idx: usize = if threaded { let envelope_idx: usize = if threaded {
@ -456,7 +458,8 @@ impl Component for MailView {
let envelope: &Envelope = &mailbox.collection[envelope_idx]; let envelope: &Envelope = &mailbox.collection[envelope_idx];
let finder = LinkFinder::new(); let finder = LinkFinder::new();
let mut t = envelope.body().text().to_string(); let op = context.accounts[self.coordinates.0].backend.operation(envelope.hash());
let mut t = envelope.body(op).text().to_string();
let links: Vec<Link> = finder.links(&t).collect(); let links: Vec<Link> = finder.links(&t).collect();
if let Some(u) = links.get(lidx) { if let Some(u) = links.get(lidx) {
u.as_str().to_string() u.as_str().to_string()