diff --git a/melib/src/conf/mod.rs b/melib/src/conf/mod.rs index 7092961ec..2ddc0f788 100644 --- a/melib/src/conf/mod.rs +++ b/melib/src/conf/mod.rs @@ -51,6 +51,9 @@ impl Folder { children: children, } } + pub fn hash(&self) -> u64 { + self.hash + } pub fn path(&self) -> &str { &self.path } diff --git a/melib/src/error.rs b/melib/src/error.rs index c0c9b581f..02a219850 100644 --- a/melib/src/error.rs +++ b/melib/src/error.rs @@ -27,6 +27,7 @@ use std::error::Error; use std::fmt; use std::io; use std::result; +use std::borrow::Cow; use nom; @@ -74,6 +75,13 @@ impl From for MeliError { } } +impl<'a> From> for MeliError { + #[inline] + fn from(kind: Cow<'_, str>) -> MeliError { + MeliError::new(format!("{:?}", kind)) + } +} + //use std::option; //impl From for MeliError { // #[inline] diff --git a/melib/src/mailbox/backends/maildir.rs b/melib/src/mailbox/backends/maildir.rs index 5a54bf0bc..7a88053d9 100644 --- a/melib/src/mailbox/backends/maildir.rs +++ b/melib/src/mailbox/backends/maildir.rs @@ -172,6 +172,7 @@ impl MailBackend for MaildirType { if MaildirType::is_valid(&f).is_err() { continue; } + eprintln!("watching {}", f.path()); let mut p = PathBuf::from(&f.path()); p.push("cur"); watcher.watch(&p, RecursiveMode::NonRecursive).unwrap(); @@ -182,8 +183,17 @@ impl MailBackend for MaildirType { loop { match rx.recv() { Ok(event) => match event { - DebouncedEvent::Create(pathbuf) => { - let path = pathbuf.parent().unwrap().to_str().unwrap(); + DebouncedEvent::Create(mut pathbuf) | DebouncedEvent::Remove(mut pathbuf) => { + let path = if pathbuf.is_dir() { + if pathbuf.ends_with("cur") | pathbuf.ends_with("new") { + pathbuf.pop(); + } + pathbuf.to_str().unwrap() + } else { + pathbuf.pop(); + pathbuf.parent().unwrap().to_str().unwrap() + }; + eprintln!(" got event in {}", path); let mut hasher = DefaultHasher::new(); hasher.write(path.as_bytes()); diff --git a/melib/src/mailbox/email/attachment_types.rs b/melib/src/mailbox/email/attachment_types.rs new file mode 100644 index 000000000..8ce38fc21 --- /dev/null +++ b/melib/src/mailbox/email/attachment_types.rs @@ -0,0 +1,107 @@ +use std::fmt::{Display, Formatter, Result as FmtResult}; + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Charset { + Ascii, + UTF8, + UTF16, + ISO8859_1, + ISO8859_2, + ISO8859_7, + Windows1252, + Windows1253, + GBK, + GB2312, +} + +impl Default for Charset { + fn default() -> Self { + Charset::UTF8 + } +} + +impl<'a> From<&'a[u8]> for Charset { + fn from(b: &'a [u8]) -> Self { + // TODO: Case insensitivity + match b { + b"us-ascii" | b"ascii" | b"US-ASCII" => Charset::Ascii, + b"utf-8" | b"UTF-8" => Charset::UTF8, + b"utf-16" | b"UTF-16" => Charset::UTF16, + b"iso-8859-1" | b"ISO-8859-1" => Charset::ISO8859_1, + b"iso-8859-2" | b"ISO-8859-2" => Charset::ISO8859_2, + b"iso-8859-7" | b"ISO-8859-7" => Charset::ISO8859_7, + b"windows-1252" | b"Windows-1252" => Charset::Windows1252, + b"windows-1253" | b"Windows-1253" => Charset::Windows1253, + b"GBK" | b"gbk" => Charset::GBK, + b"gb2312" | b"GB2312" => Charset::GB2312, + _ => Charset::Ascii, + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum MultipartType { + Mixed, + Alternative, + Digest, + Unsupported { tag: Vec }, +} + +impl Display for MultipartType { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match self { + MultipartType::Mixed => write!(f, "multipart/mixed"), + MultipartType::Alternative => write!(f, "multipart/alternative"), + MultipartType::Digest => write!(f, "multipart/digest"), + MultipartType::Unsupported { tag: ref t } => { + write!(f, "multipart/{}", String::from_utf8_lossy(t)) + } + } + } +} + +#[derive(Clone, Debug)] +pub enum ContentType { + Text { charset: Charset }, + Multipart { boundary: Vec }, + Unsupported { tag: Vec }, +} + +impl Default for ContentType { + fn default() -> Self { + ContentType::Text{ charset: Charset::UTF8 } + } + +} + +impl Display for ContentType { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match *self { + ContentType::Text { .. } => write!(f, "text"), + ContentType::Multipart { .. } => write!(f, "multipart"), + ContentType::Unsupported { tag: ref t } => write!(f, "{}", String::from_utf8_lossy(t)), + } + } +} +#[derive(Clone, Debug, PartialEq)] +pub enum ContentSubType { + Plain, + Other { tag: Vec }, +} +impl Display for ContentSubType { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match *self { + ContentSubType::Plain => write!(f, "plain"), + ContentSubType::Other { tag: ref t } => write!(f, "{}", String::from_utf8_lossy(t)), + } + } +} +#[derive(Clone, Debug)] +pub enum ContentTransferEncoding { + _8Bit, + _7Bit, + Base64, + QuotedPrintable, + Other { tag: Vec }, +} + diff --git a/melib/src/mailbox/email/attachments.rs b/melib/src/mailbox/email/attachments.rs index e3ef22d6d..f0a2c7303 100644 --- a/melib/src/mailbox/email/attachments.rs +++ b/melib/src/mailbox/email/attachments.rs @@ -18,41 +18,12 @@ * You should have received a copy of the GNU General Public License * along with meli. If not, see . */ -use mailbox::email::parser; -use mailbox::email::parser::BytesExt; - use std::fmt::{Display, Formatter, Result as FmtResult}; use std::str; - use data_encoding::BASE64_MIME; +use mailbox::email::parser; -/* - * - * Data - * Text { content: Vec } - * Multipart - */ - -#[derive(Clone, Debug, PartialEq)] -pub enum MultipartType { - Mixed, - Alternative, - Digest, - Unsupported { tag: Vec }, -} - -impl Display for MultipartType { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - match self { - MultipartType::Mixed => write!(f, "multipart/mixed"), - MultipartType::Alternative => write!(f, "multipart/alternative"), - MultipartType::Digest => write!(f, "multipart/digest"), - MultipartType::Unsupported { tag: ref t } => { - write!(f, "multipart/{}", String::from_utf8_lossy(t)) - } - } - } -} +pub use mailbox::email::attachment_types::*; #[derive(Clone, Debug)] pub enum AttachmentType { @@ -68,6 +39,30 @@ pub enum AttachmentType { }, } +/* + * + * Data + * Text { content: Vec } + * Multipart + */ +// TODO: Add example. +// +pub struct AttachmentBuilder { + content_type: (ContentType, ContentSubType), + content_transfer_encoding: ContentTransferEncoding, + + raw: Vec, +} + +#[derive(Clone, Debug)] +pub struct Attachment { + content_type: (ContentType, ContentSubType), + content_transfer_encoding: ContentTransferEncoding, + + raw: Vec, + + attachment_type: AttachmentType, +} impl Display for AttachmentType { fn fmt(&self, f: &mut Formatter) -> FmtResult { match self { @@ -77,57 +72,11 @@ impl Display for AttachmentType { } } } -#[derive(Clone, Debug)] -pub enum ContentType { - Text, - Multipart { boundary: Vec }, - Unsupported { tag: Vec }, -} - -impl Display for ContentType { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - match *self { - ContentType::Text => write!(f, "text"), - ContentType::Multipart { .. } => write!(f, "multipart"), - ContentType::Unsupported { tag: ref t } => write!(f, "{}", String::from_utf8_lossy(t)), - } - } -} -#[derive(Clone, Debug, PartialEq)] -pub enum ContentSubType { - Plain, - Other { tag: Vec }, -} -impl Display for ContentSubType { - fn fmt(&self, f: &mut Formatter) -> FmtResult { - match *self { - ContentSubType::Plain => write!(f, "plain"), - ContentSubType::Other { tag: ref t } => write!(f, "{}", String::from_utf8_lossy(t)), - } - } -} -#[derive(Clone, Debug)] -pub enum ContentTransferEncoding { - _8Bit, - _7Bit, - Base64, - QuotedPrintable, - Other { tag: Vec }, -} - -/// TODO: Add example. -/// -pub struct AttachmentBuilder { - content_type: (ContentType, ContentSubType), - content_transfer_encoding: ContentTransferEncoding, - - raw: Vec, -} impl AttachmentBuilder { pub fn new(content: &[u8]) -> Self { AttachmentBuilder { - content_type: (ContentType::Text, ContentSubType::Plain), + content_type: (Default::default() , ContentSubType::Plain), content_transfer_encoding: ContentTransferEncoding::_7Bit, raw: content.to_vec(), } @@ -152,7 +101,13 @@ impl AttachmentBuilder { }; self.content_type.1 = ContentSubType::Other { tag: cst.into() }; } else if ct.eq_ignore_ascii_case(b"text") { - self.content_type.0 = ContentType::Text; + self.content_type.0 = Default::default(); + for (n, v) in params { + if n.eq_ignore_ascii_case(b"charset") { + self.content_type.0 = ContentType::Text { charset: Charset::from(v) }; + break; + } + } if !cst.eq_ignore_ascii_case(b"plain") { self.content_type.1 = ContentSubType::Other { tag: cst.to_ascii_lowercase(), @@ -189,42 +144,29 @@ impl AttachmentBuilder { self } fn decode(&self) -> Vec { - // TODO: Use charset for decoding + let charset = match self.content_type.0 { + ContentType::Text{ charset: c } => c, + _ => Default::default(), + }; + let decoded_result = parser::decode_charset(&self.raw, charset); + let b: &[u8] = decoded_result.as_ref().map(|v| v.as_bytes()).unwrap_or_else(|_| &self.raw); + match self.content_transfer_encoding { - ContentTransferEncoding::Base64 => match BASE64_MIME.decode( - str::from_utf8(&self.raw) - .unwrap() - .trim() - .lines() - .fold(String::with_capacity(self.raw.len()), |mut acc, x| { - acc.push_str(x); - acc - }) - .as_bytes(), - ) { - Ok(ref s) => { - let s: Vec = s.clone(); - { - let slice = &s[..]; - if slice.find(b"\r\n").is_some() { - s.replace(b"\r\n", b"\n"); - } - } - s - } - _ => self.raw.clone(), + ContentTransferEncoding::Base64 => match BASE64_MIME.decode(b) { + Ok(v) => v, + _ => b.to_vec(), }, - ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_text(&self.raw) + ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_text(b) .to_full_result() .unwrap(), - ContentTransferEncoding::_7Bit - | ContentTransferEncoding::_8Bit - | ContentTransferEncoding::Other { .. } => self.raw.clone(), + ContentTransferEncoding::_7Bit + | ContentTransferEncoding::_8Bit + | ContentTransferEncoding::Other { .. } => b.to_vec(), } } pub fn build(self) -> Attachment { let attachment_type = match self.content_type.0 { - ContentType::Text => AttachmentType::Text { + ContentType::Text { .. } => AttachmentType::Text { content: self.decode(), }, ContentType::Multipart { boundary: ref b } => { @@ -295,15 +237,6 @@ impl AttachmentBuilder { } } -#[derive(Clone, Debug)] -pub struct Attachment { - content_type: (ContentType, ContentSubType), - content_transfer_encoding: ContentTransferEncoding, - - raw: Vec, - - attachment_type: AttachmentType, -} impl Display for Attachment { fn fmt(&self, f: &mut Formatter) -> FmtResult { @@ -409,17 +342,23 @@ pub fn interpret_format_flowed(_t: &str) -> String { } pub fn decode(a: &Attachment) -> Vec { - // TODO: Use charset for decoding + let charset = match a.content_type.0 { + ContentType::Text{ charset: c } => c, + _ => Default::default(), + }; + let decoded_result = parser::decode_charset(a.bytes(), charset); + let b: &[u8] = decoded_result.as_ref().map(|v| v.as_bytes()).unwrap_or_else(|_| a.bytes()); + match a.content_transfer_encoding { - ContentTransferEncoding::Base64 => match BASE64_MIME.decode(a.bytes()) { + ContentTransferEncoding::Base64 => match BASE64_MIME.decode(b) { Ok(v) => v, - _ => a.bytes().to_vec(), + _ => b.to_vec(), }, - ContentTransferEncoding::QuotedPrintable => parser::quoted_printed_bytes(&a.bytes()) + ContentTransferEncoding::QuotedPrintable => parser::quoted_printed_bytes(b) .to_full_result() .unwrap(), ContentTransferEncoding::_7Bit | ContentTransferEncoding::_8Bit - | ContentTransferEncoding::Other { .. } => a.bytes().to_vec(), + | ContentTransferEncoding::Other { .. } => b.to_vec(), } } diff --git a/melib/src/mailbox/email/mod.rs b/melib/src/mailbox/email/mod.rs index c81e8959b..8f3039a70 100644 --- a/melib/src/mailbox/email/mod.rs +++ b/melib/src/mailbox/email/mod.rs @@ -22,13 +22,14 @@ /*! * Email parsing, handling, sending etc. */ +mod attachment_types; pub mod attachments; -pub mod parser; - pub use self::attachments::*; +pub mod parser; +use parser::BytesExt; + use error::{MeliError, Result}; use mailbox::backends::BackendOpGenerator; -use parser::BytesExt; use std::borrow::Cow; use std::cmp::Ordering; diff --git a/melib/src/mailbox/email/parser.rs b/melib/src/mailbox/email/parser.rs index 487ffebc7..2309b80fe 100644 --- a/melib/src/mailbox/email/parser.rs +++ b/melib/src/mailbox/email/parser.rs @@ -166,7 +166,7 @@ named!(pub attachment<(std::vec::Vec<(&[u8], &[u8])>, &[u8])>, /* TODO: make a map of encodings and decoding functions so that they can be reused and easily * 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, WINDOWS_1252, GBK}; fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec> { if input.len() < 5 { @@ -264,8 +264,41 @@ fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec> { IResult::Error(error_code!(ErrorKind::Custom(43))) } +pub fn decode_charset(s: &[u8], charset: Charset) -> Result { + match charset { + Charset::UTF8 | Charset::Ascii => { + Ok(String::from_utf8(s.to_vec()).unwrap()) + } + Charset::ISO8859_7 => { + Ok(ISO_8859_7.decode(s, DecoderTrap::Strict)?) + } + Charset::ISO8859_1 => { + Ok(ISO_8859_1.decode(s, DecoderTrap::Strict)?) + } + Charset::ISO8859_2 => { + Ok(ISO_8859_2.decode(s, DecoderTrap::Strict)?) + } + Charset::GBK => { + Ok(GBK.decode(s, DecoderTrap::Strict)?) + } + Charset::Windows1252 => { + Ok(WINDOWS_1252.decode(s, DecoderTrap::Strict)?) + }, + Charset::Windows1253 => { + Ok(WINDOWS_1253.decode(s, DecoderTrap::Strict)?) + }, + Charset::GB2312 => { + unimplemented!() + }, + Charset::UTF16 => { + unimplemented!() + }, + } +} + named!(qp_underscore_header, do_parse!(tag!("_") >> ({ b' ' }))); +/// For atoms in Header values. named!( pub quoted_printed_bytes>, many0!(alt_complete!( diff --git a/src/bin.rs b/src/bin.rs index ea1b58fc3..628ff06ea 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -184,6 +184,7 @@ fn main() { }, ThreadEvent::RefreshMailbox { hash : h } => { eprintln!("got refresh mailbox hash {:x}", h); + state.hash_to_folder(h); //state.rcv_event(UIEvent { id: 0, event_type: UIEventType::Notification(n.clone())}); state.redraw(); /* Don't handle this yet. */ diff --git a/ui/src/components/mail/view.rs b/ui/src/components/mail/view.rs index 8a8c22122..516e20021 100644 --- a/ui/src/components/mail/view.rs +++ b/ui/src/components/mail/view.rs @@ -292,7 +292,7 @@ impl Component for MailView { let envelope: &Envelope = &mailbox.collection[envelope_idx]; if let Some(u) = envelope.body().attachments().get(lidx) { match u.content_type().0 { - ContentType::Text => { + ContentType::Text { .. } => { self.mode = ViewMode::Attachment(lidx); self.dirty = true; } diff --git a/ui/src/state.rs b/ui/src/state.rs index edc5135dd..c41c9549b 100644 --- a/ui/src/state.rs +++ b/ui/src/state.rs @@ -41,6 +41,7 @@ use termion::{clear, cursor, style}; /// A context container for loaded settings, accounts, UI changes, etc. pub struct Context { pub accounts: Vec, + mailbox_hashes: FnvHashMap, pub settings: Settings, pub runtime_settings: Settings, @@ -151,6 +152,8 @@ impl State { context: Context { accounts, + mailbox_hashes: FnvHashMap::with_capacity_and_hasher(1, Default::default()), + _backends: backends, settings: settings.clone(), runtime_settings: settings, @@ -172,14 +175,24 @@ impl State { cursor::Goto(1, 1) ).unwrap(); s.flush(); - for account in &mut s.context.accounts { + for (x, account) in s.context.accounts.iter_mut().enumerate() { + for (y, folder) in account.settings.folders.iter().enumerate() { + s.context.mailbox_hashes.insert(folder.hash(), (x, y)); + } let sender = s.sender.clone(); account.watch(RefreshEventConsumer::new(Box::new(move |r| { sender.send(ThreadEvent::from(r)); }))); + } + for (k, v) in &s.context.mailbox_hashes { + eprintln!("{:x} -> {:?}", k, v); + } s } + pub fn hash_to_folder(&self, hash: u64) { + eprintln!("got refresh {:?}", self.context.mailbox_hashes[&hash]); + } /// If an owned thread returns a `ThreadEvent::ThreadJoin` event to `State` then it must remove /// the thread from its list and `join` it.