Add address_list parser
parent
ea9bdd2074
commit
ffbd70e40b
|
@ -27,7 +27,6 @@ pub use self::attachments::*;
|
||||||
|
|
||||||
use std::string::String;
|
use std::string::String;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
//use std;
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::option::Option;
|
use std::option::Option;
|
||||||
|
@ -35,6 +34,69 @@ use std::option::Option;
|
||||||
use chrono;
|
use chrono;
|
||||||
use chrono::TimeZone;
|
use chrono::TimeZone;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, )]
|
||||||
|
pub struct GroupAddress {
|
||||||
|
raw: String,
|
||||||
|
display_name: StrBuilder,
|
||||||
|
mailbox_list: Vec<Address>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, )]
|
||||||
|
pub struct MailboxAddress {
|
||||||
|
raw: String,
|
||||||
|
display_name: StrBuilder,
|
||||||
|
address_spec: StrBuilder,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, )]
|
||||||
|
pub enum Address {
|
||||||
|
Mailbox(MailboxAddress),
|
||||||
|
Group(GroupAddress),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Address {}
|
||||||
|
impl PartialEq for Address {
|
||||||
|
fn eq(&self, other: &Address) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(Address::Mailbox(_), Address::Group(_)) |
|
||||||
|
(Address::Group(_), Address::Mailbox(_)) => {
|
||||||
|
false
|
||||||
|
},
|
||||||
|
(Address::Mailbox(s), Address::Mailbox(o)) => {
|
||||||
|
s.address_spec.display(&s.raw) == o.address_spec.display(&o.raw)
|
||||||
|
},
|
||||||
|
(Address::Group(s), Address::Group(o)) => {
|
||||||
|
s.display_name.display(&s.raw) == o.display_name.display(&o.raw) &&
|
||||||
|
s.mailbox_list.iter().zip(o.mailbox_list.iter()).fold(true, |b, (s, o)| b && (s == o))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Address {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Address::Mailbox(m) if m.display_name.length > 0 => {
|
||||||
|
write!(f, "{} <{}>", m.display_name.display(&m.raw), m.address_spec.display(&m.raw))
|
||||||
|
},
|
||||||
|
Address::Group(g) => {
|
||||||
|
let attachment_strings: Vec<String> = g.mailbox_list.iter().map(|a| format!("{}", a)).collect();
|
||||||
|
write!(f, "{}: {}", g.display_name.display(&g.raw), attachment_strings.join(", "))
|
||||||
|
},
|
||||||
|
Address::Mailbox(m) => {
|
||||||
|
write!(f, "{}", m.address_spec.display(&m.raw))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// 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)]
|
||||||
struct StrBuilder {
|
struct StrBuilder {
|
||||||
|
@ -52,6 +114,14 @@ pub trait StrBuild {
|
||||||
fn val(&self) -> &str;
|
fn val(&self) -> &str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StrBuilder {
|
||||||
|
fn display<'a>(&self, s: &'a str) -> &'a str {
|
||||||
|
let offset = self.offset;
|
||||||
|
let length = self.length;
|
||||||
|
&s[offset..offset+length]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `MessageID` is accessed through the `StrBuild` trait.
|
/// `MessageID` is accessed through the `StrBuild` trait.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct MessageID(String, StrBuilder);
|
pub struct MessageID(String, StrBuilder);
|
||||||
|
@ -63,14 +133,14 @@ impl StrBuild for MessageID {
|
||||||
string.to_string(),
|
string.to_string(),
|
||||||
StrBuilder {
|
StrBuilder {
|
||||||
offset: offset,
|
offset: offset,
|
||||||
length: slice.len() + 1,
|
length: slice.len()+ 1,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
fn raw(&self) -> &str {
|
fn raw(&self) -> &str {
|
||||||
let offset = self.1.offset;
|
let offset = self.1.offset;
|
||||||
let length = self.1.length;
|
let length = self.1.length;
|
||||||
&self.0[offset..length]
|
&self.0[offset..offset+length-1]
|
||||||
}
|
}
|
||||||
fn val(&self) -> &str {
|
fn val(&self) -> &str {
|
||||||
&self.0
|
&self.0
|
||||||
|
@ -133,8 +203,8 @@ bitflags! {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Envelope {
|
pub struct Envelope {
|
||||||
date: String,
|
date: String,
|
||||||
from: Option<String>,
|
from: Vec<Address>,
|
||||||
to: Option<String>,
|
to: Vec<Address>,
|
||||||
body: Option<Attachment>,
|
body: Option<Attachment>,
|
||||||
subject: Option<String>,
|
subject: Option<String>,
|
||||||
message_id: Option<MessageID>,
|
message_id: Option<MessageID>,
|
||||||
|
@ -155,8 +225,8 @@ impl Envelope {
|
||||||
pub fn new(token: Box<BackendOpGenerator>) -> Self {
|
pub fn new(token: Box<BackendOpGenerator>) -> Self {
|
||||||
Envelope {
|
Envelope {
|
||||||
date: "".to_string(),
|
date: "".to_string(),
|
||||||
from: None,
|
from: Vec::new(),
|
||||||
to: None,
|
to: Vec::new(),
|
||||||
body: None,
|
body: None,
|
||||||
subject: None,
|
subject: None,
|
||||||
message_id: None,
|
message_id: None,
|
||||||
|
@ -198,23 +268,23 @@ impl Envelope {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if name.eq_ignore_ascii_case("to") {
|
if name.eq_ignore_ascii_case("to") {
|
||||||
let parse_result = parser::address_list(value.as_bytes());
|
let parse_result = parser::rfc2822address_list(value.as_bytes());
|
||||||
let value = if parse_result.is_done() {
|
let value = if parse_result.is_done() {
|
||||||
parse_result.to_full_result().unwrap()
|
parse_result.to_full_result().unwrap()
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
Vec::new()
|
||||||
};
|
};
|
||||||
self.set_to(value);
|
self.set_to(value);
|
||||||
} else if name.eq_ignore_ascii_case("from") {
|
} else if name.eq_ignore_ascii_case("from") {
|
||||||
let parse_result = parser::address_list(value.as_bytes());
|
let parse_result = parser::rfc2822address_list(value.as_bytes());
|
||||||
let value = if parse_result.is_done() {
|
let value = if parse_result.is_done() {
|
||||||
parse_result.to_full_result().unwrap()
|
parse_result.to_full_result().unwrap()
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
Vec::new()
|
||||||
};
|
};
|
||||||
self.set_from(value);
|
self.set_from(value);
|
||||||
} else if name.eq_ignore_ascii_case("subject") {
|
} else if name.eq_ignore_ascii_case("subject") {
|
||||||
let parse_result = parser::subject(value.trim().as_bytes());
|
let parse_result = parser::phrase(value.trim().as_bytes());
|
||||||
let value = if parse_result.is_done() {
|
let value = if parse_result.is_done() {
|
||||||
parse_result.to_full_result().unwrap()
|
parse_result.to_full_result().unwrap()
|
||||||
} else {
|
} else {
|
||||||
|
@ -269,17 +339,20 @@ impl Envelope {
|
||||||
pub fn date_as_str(&self) -> &str {
|
pub fn date_as_str(&self) -> &str {
|
||||||
&self.date
|
&self.date
|
||||||
}
|
}
|
||||||
pub fn from(&self) -> &str {
|
pub fn from(&self) -> &Vec<Address> {
|
||||||
match self.from {
|
&self.from
|
||||||
Some(ref s) => s,
|
|
||||||
None => "",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pub fn to(&self) -> &str {
|
|
||||||
match self.to {
|
pub fn from_to_string(&self) -> String {
|
||||||
Some(ref s) => s,
|
let _strings: Vec<String> = self.from.iter().map(|a| format!("{}", a)).collect();
|
||||||
None => "",
|
_strings.join(", ")
|
||||||
}
|
}
|
||||||
|
pub fn to(&self) -> &Vec<Address> {
|
||||||
|
&self.to
|
||||||
|
}
|
||||||
|
pub fn to_to_string(&self) -> String {
|
||||||
|
let _strings: Vec<String> = self.to.iter().map(|a| format!("{}", a)).collect();
|
||||||
|
_strings.join(", ")
|
||||||
}
|
}
|
||||||
pub fn body(&self) -> Attachment {
|
pub fn body(&self) -> Attachment {
|
||||||
let mut operation = self.operation_token.generate();
|
let mut operation = self.operation_token.generate();
|
||||||
|
@ -338,11 +411,11 @@ impl Envelope {
|
||||||
fn set_date(&mut self, new_val: String) -> () {
|
fn set_date(&mut self, new_val: String) -> () {
|
||||||
self.date = new_val;
|
self.date = new_val;
|
||||||
}
|
}
|
||||||
fn set_from(&mut self, new_val: String) -> () {
|
fn set_from(&mut self, new_val: Vec<Address>) -> () {
|
||||||
self.from = Some(new_val);
|
self.from = new_val;
|
||||||
}
|
}
|
||||||
fn set_to(&mut self, new_val: String) -> () {
|
fn set_to(&mut self, new_val: Vec<Address>) -> () {
|
||||||
self.to = Some(new_val);
|
self.to = new_val;
|
||||||
}
|
}
|
||||||
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() {
|
||||||
|
|
|
@ -26,6 +26,16 @@ use nom::{is_hex_digit, le_u8};
|
||||||
use nom::{ErrorKind, IResult, Needed};
|
use nom::{ErrorKind, IResult, Needed};
|
||||||
use nom::{Compare, CompareResult};
|
use nom::{Compare, CompareResult};
|
||||||
use encoding::{DecoderTrap, Encoding};
|
use encoding::{DecoderTrap, Encoding};
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
macro_rules! is_whitespace {
|
||||||
|
($var:ident) => (
|
||||||
|
$var == b' ' && $var == b'\t' && $var == b'\n' && $var == b'\r'
|
||||||
|
);
|
||||||
|
($var:expr) => (
|
||||||
|
$var == b' ' && $var == b'\t' && $var == b'\n' && $var == b'\r'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -259,6 +269,205 @@ named!(ascii_token<String>, do_parse!(
|
||||||
String::from_utf8_lossy(word).into_owned()
|
String::from_utf8_lossy(word).into_owned()
|
||||||
} )));
|
} )));
|
||||||
|
|
||||||
|
fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
|
||||||
|
if input.is_empty() || input.len() < 3 {
|
||||||
|
IResult::Incomplete(Needed::Size(1))
|
||||||
|
} else if !is_whitespace!(input[0]) {
|
||||||
|
let mut display_name = StrBuilder {
|
||||||
|
offset: 0,
|
||||||
|
length: 0,
|
||||||
|
};
|
||||||
|
let mut flag = false;
|
||||||
|
for (i, b) in input[0..].iter().enumerate() {
|
||||||
|
if *b == b'<' {
|
||||||
|
|
||||||
|
display_name.length = if i != 0 { i-1 } else { 0 };
|
||||||
|
flag = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !flag {
|
||||||
|
return IResult::Error(error_code!(ErrorKind::Custom(43)));
|
||||||
|
}
|
||||||
|
let mut end = input.len();
|
||||||
|
let mut flag = false;
|
||||||
|
for (i, b) in input[display_name.length+2..].iter().enumerate() {
|
||||||
|
if *b == b'@' {
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
if *b == b'>' {
|
||||||
|
end = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if flag {
|
||||||
|
let mut address_spec = StrBuilder {
|
||||||
|
offset: display_name.length + 2,
|
||||||
|
length: end,
|
||||||
|
};
|
||||||
|
match phrase(&input[0..end+display_name.length+3]) {
|
||||||
|
IResult::Error(e) => IResult::Error(e),
|
||||||
|
IResult::Incomplete(i) => IResult::Incomplete(i),
|
||||||
|
IResult::Done(rest, raw) => {
|
||||||
|
display_name.length = raw.find('<').unwrap();
|
||||||
|
address_spec.offset = display_name.length + 1;
|
||||||
|
address_spec.length = raw.len() - display_name.length - 2;
|
||||||
|
IResult::Done(rest, Address::Mailbox(MailboxAddress {
|
||||||
|
raw: raw,
|
||||||
|
display_name: display_name,
|
||||||
|
address_spec: address_spec,
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
fn addr_spec(input: &[u8]) -> IResult<&[u8], Address> {
|
||||||
|
if input.is_empty() || input.len() < 3 {
|
||||||
|
IResult::Incomplete(Needed::Size(1))
|
||||||
|
} else if !is_whitespace!(input[0]) {
|
||||||
|
let mut end = input[1..].len();
|
||||||
|
let mut flag = false;
|
||||||
|
for (i, b) in input[1..].iter().enumerate() {
|
||||||
|
if *b == b'@' {
|
||||||
|
flag = true;
|
||||||
|
}
|
||||||
|
if is_whitespace!(*b) {
|
||||||
|
end = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if flag {
|
||||||
|
IResult::Done(&input[end..], Address::Mailbox(MailboxAddress {
|
||||||
|
raw: String::from_utf8_lossy(&input[0..end+1]).to_string(),
|
||||||
|
display_name: StrBuilder {
|
||||||
|
offset: 0,
|
||||||
|
length: 0,
|
||||||
|
},
|
||||||
|
address_spec: StrBuilder {
|
||||||
|
offset: 0,
|
||||||
|
length: input[0..end+1].len(),
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
IResult::Error(error_code!(ErrorKind::Custom(43)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
IResult::Error(error_code!(ErrorKind::Custom(42)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
named!(mailbox<Address>, ws!(alt_complete!(
|
||||||
|
display_addr |
|
||||||
|
addr_spec
|
||||||
|
)));
|
||||||
|
named!(mailbox_list<Vec<Address>>, many0!(mailbox));
|
||||||
|
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mailbox() {
|
||||||
|
{
|
||||||
|
let s = b"epilys@postretch";
|
||||||
|
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));
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let s = b"Manos <epilys@postretch>";
|
||||||
|
eprintln!("{:?}", display_addr(s).unwrap());
|
||||||
|
let r = display_addr(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!(":") >>
|
||||||
|
// mailbox_list: many0!(mailbox) >>
|
||||||
|
// end: is_a!(";") >>
|
||||||
|
// ({
|
||||||
|
//
|
||||||
|
// })
|
||||||
|
// )));
|
||||||
|
//
|
||||||
|
|
||||||
|
fn group(input: &[u8]) -> IResult<&[u8], Address> {
|
||||||
|
let mut flag = false;
|
||||||
|
let mut dlength = 0;
|
||||||
|
for (i, b) in input.iter().enumerate() {
|
||||||
|
if *b == b':' {
|
||||||
|
flag = true;
|
||||||
|
dlength = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !flag {
|
||||||
|
return IResult::Error(error_code!(ErrorKind::Custom(43)));
|
||||||
|
}
|
||||||
|
|
||||||
|
match mailbox_list(&input[dlength..]) {
|
||||||
|
IResult::Error(e) => {
|
||||||
|
return IResult::Error(e);
|
||||||
|
}
|
||||||
|
IResult::Done(rest, vec) => {
|
||||||
|
let size: usize = (rest.as_ptr() as usize) - ((&input[0..] as &[u8]).as_ptr() as usize);
|
||||||
|
return IResult::Done(rest, Address::Group(GroupAddress {
|
||||||
|
raw: String::from_utf8(input[0..size].to_vec()).unwrap(),
|
||||||
|
display_name: StrBuilder {
|
||||||
|
offset: 0,
|
||||||
|
length: dlength,
|
||||||
|
},
|
||||||
|
mailbox_list: vec,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
IResult::Incomplete(i) => {
|
||||||
|
return IResult::Incomplete(i);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
named!(address<Address>, ws!(
|
||||||
|
alt_complete!(mailbox | group)));
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_address() {
|
||||||
|
let s = b"Manos Pitsidianakis <el13635@mail.ntua.gr>,
|
||||||
|
qemu-devel <qemu-devel@nongnu.org>, qemu-block <qemu-block@nongnu.org>,
|
||||||
|
Alberto Garcia <berto@igalia.com>, Stefan Hajnoczi <stefanha@redhat.com>";
|
||||||
|
println!("{:?}", rfc2822address_list(s).unwrap());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
named!(pub rfc2822address_list<Vec<Address>>, ws!(
|
||||||
|
separated_list!(is_a!(","), address)
|
||||||
|
|
||||||
|
|
||||||
|
));
|
||||||
|
|
||||||
|
|
||||||
named!(pub address_list<String>, ws!(do_parse!(
|
named!(pub address_list<String>, ws!(do_parse!(
|
||||||
list: alt_complete!( encoded_word_list | ascii_token) >>
|
list: alt_complete!( encoded_word_list | ascii_token) >>
|
||||||
( {
|
( {
|
||||||
|
@ -278,7 +487,8 @@ named!(pub address_list<String>, ws!(do_parse!(
|
||||||
} )
|
} )
|
||||||
|
|
||||||
)));
|
)));
|
||||||
named!(pub subject<String>, ws!(do_parse!(
|
|
||||||
|
named!(pub phrase<String>, ws!(do_parse!(
|
||||||
list: many0!(alt_complete!( encoded_word_list | ascii_token)) >>
|
list: many0!(alt_complete!( encoded_word_list | ascii_token)) >>
|
||||||
( {
|
( {
|
||||||
let string_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc }) + list.len() - 1;
|
let string_len = list.iter().fold(0, |mut acc, x| { acc+=x.len(); acc }) + list.len() - 1;
|
||||||
|
@ -298,52 +508,52 @@ named!(pub subject<String>, ws!(do_parse!(
|
||||||
)));
|
)));
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_subject() {
|
fn test_phrase() {
|
||||||
let subject_s = "list.free.de mailing list memberships reminder".as_bytes();
|
let phrase_s = "list.free.de mailing list memberships reminder".as_bytes();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(
|
(
|
||||||
&b""[..],
|
&b""[..],
|
||||||
"list.free.de mailing list memberships reminder".to_string()
|
"list.free.de mailing list memberships reminder".to_string()
|
||||||
),
|
),
|
||||||
subject(subject_s).unwrap()
|
phrase(phrase_s).unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let subject_s = "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?= =?UTF-8?B?z4fOuM63?=".as_bytes();
|
let phrase_s = "=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?= =?UTF-8?B?z4fOuM63?=".as_bytes();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(
|
(
|
||||||
&b""[..],
|
&b""[..],
|
||||||
"Νέο προσωπικό μήνυμα αφίχθη".to_string()
|
"Νέο προσωπικό μήνυμα αφίχθη".to_string()
|
||||||
),
|
),
|
||||||
subject(subject_s).unwrap()
|
phrase(phrase_s).unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let subject_s = "=?utf-8?B?bW9vZGxlOiDOsc69zrHPg866z4zPgM63z4POtyDOv868zqzOtM6xz4Igz4M=?= =?utf-8?B?z4XOts63z4TOrs+DzrXPic69?=".as_bytes();
|
let phrase_s = "=?utf-8?B?bW9vZGxlOiDOsc69zrHPg866z4zPgM63z4POtyDOv868zqzOtM6xz4Igz4M=?= =?utf-8?B?z4XOts63z4TOrs+DzrXPic69?=".as_bytes();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
(
|
(
|
||||||
&b""[..],
|
&b""[..],
|
||||||
"moodle: ανασκόπηση ομάδας συζητήσεων".to_string()
|
"moodle: ανασκόπηση ομάδας συζητήσεων".to_string()
|
||||||
),
|
),
|
||||||
subject(subject_s).unwrap()
|
phrase(phrase_s).unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
let subject_s =
|
let phrase_s =
|
||||||
"=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?=".as_bytes();
|
"=?UTF-8?B?zp3Orc6/IM+Az4HOv8+Dz4nPgM65zrrPjCDOvM6uzr3Phc68zrEgzrHPhs6v?=".as_bytes();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"Νέο προσωπικό μήνυμα αφί".to_string(),
|
"Νέο προσωπικό μήνυμα αφί".to_string(),
|
||||||
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()
|
from_utf8(&encoded_word(phrase_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 phrase_s = "=?UTF-8?Q?=CE=A0=CF=81=CF=8C=CF=83=CE=B8=CE=B5?=".as_bytes();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"Πρόσθε".to_string(),
|
"Πρόσθε".to_string(),
|
||||||
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()
|
from_utf8(&encoded_word(phrase_s).to_full_result().unwrap()).unwrap()
|
||||||
);
|
);
|
||||||
let subject_s = "=?iso-8859-7?B?UmU6INDx/OLr5+zhIOzlIPTn7SDh9fHp4e3eIOLh8eTp4Q==?=".as_bytes();
|
let phrase_s = "=?iso-8859-7?B?UmU6INDx/OLr5+zhIOzlIPTn7SDh9fHp4e3eIOLh8eTp4Q==?=".as_bytes();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
"Re: Πρόβλημα με την αυριανή βαρδια".to_string(),
|
"Re: Πρόβλημα με την αυριανή βαρδια".to_string(),
|
||||||
from_utf8(&encoded_word(subject_s).to_full_result().unwrap()).unwrap()
|
from_utf8(&encoded_word(phrase_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 phrase_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?="
|
=?UTF-8?Q?=CE=B1=CF=83=CF=84=CE=B9=CE=BA=CE=AE?="
|
||||||
.as_bytes();
|
.as_bytes();
|
||||||
|
@ -352,7 +562,7 @@ fn test_subject() {
|
||||||
&b""[..],
|
&b""[..],
|
||||||
"Πρόσθετη εξεταστική".to_string()
|
"Πρόσθετη εξεταστική".to_string()
|
||||||
),
|
),
|
||||||
subject(subject_s).unwrap()
|
phrase(phrase_s).unwrap()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
fn eat_comments(input: &str) -> String {
|
fn eat_comments(input: &str) -> String {
|
||||||
|
@ -388,7 +598,7 @@ fn test_eat_comments() {
|
||||||
*
|
*
|
||||||
* 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())
|
let parsed_result = phrase(eat_comments(input).as_bytes())
|
||||||
.to_full_result()
|
.to_full_result()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.replace("-", "+");
|
.replace("-", "+");
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
use linkify::{LinkFinder, Link};
|
use linkify::{Link, LinkFinder};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
use mime_apps::query_default_app;
|
use mime_apps::query_default_app;
|
||||||
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
enum ViewMode {
|
enum ViewMode {
|
||||||
Normal,
|
Normal,
|
||||||
|
@ -35,8 +34,11 @@ pub struct MailView {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MailView {
|
impl MailView {
|
||||||
pub fn new(coordinates: (usize, usize, usize), pager: Option<Pager>, subview: Option<Box<MailView>>) -> Self {
|
pub fn new(
|
||||||
|
coordinates: (usize, usize, usize),
|
||||||
|
pager: Option<Pager>,
|
||||||
|
subview: Option<Box<MailView>>,
|
||||||
|
) -> Self {
|
||||||
MailView {
|
MailView {
|
||||||
coordinates: coordinates,
|
coordinates: coordinates,
|
||||||
pager: pager,
|
pager: pager,
|
||||||
|
@ -49,15 +51,20 @@ impl MailView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Component for MailView {
|
impl Component for MailView {
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
let upper_left = upper_left!(area);
|
let upper_left = upper_left!(area);
|
||||||
let bottom_right = bottom_right!(area);
|
let bottom_right = bottom_right!(area);
|
||||||
|
|
||||||
let (envelope_idx, y): (usize, usize) = {
|
let (envelope_idx, y): (usize, usize) = {
|
||||||
let threaded = context.accounts[self.coordinates.0].runtime_settings.threaded;
|
let threaded = context.accounts[self.coordinates.0]
|
||||||
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1].as_ref().unwrap().as_ref().unwrap();
|
.runtime_settings
|
||||||
|
.threaded;
|
||||||
|
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap();
|
||||||
let envelope_idx: usize = if threaded {
|
let envelope_idx: usize = if threaded {
|
||||||
mailbox.threaded_mail(self.coordinates.2)
|
mailbox.threaded_mail(self.coordinates.2)
|
||||||
} else {
|
} else {
|
||||||
|
@ -66,98 +73,125 @@ impl Component for MailView {
|
||||||
|
|
||||||
let envelope: &Envelope = &mailbox.collection[envelope_idx];
|
let envelope: &Envelope = &mailbox.collection[envelope_idx];
|
||||||
|
|
||||||
let (x,y) = write_string_to_grid(&format!("Date: {}", envelope.date_as_str()),
|
let (x, y) = write_string_to_grid(
|
||||||
grid,
|
&format!("Date: {}", envelope.date_as_str()),
|
||||||
Color::Byte(33),
|
grid,
|
||||||
Color::Default,
|
Color::Byte(33),
|
||||||
area,
|
Color::Default,
|
||||||
true);
|
area,
|
||||||
|
true,
|
||||||
|
);
|
||||||
for x in x..=get_x(bottom_right) {
|
for x in x..=get_x(bottom_right) {
|
||||||
grid[(x, y)].set_ch(' ');
|
grid[(x, y)].set_ch(' ');
|
||||||
grid[(x, y)].set_bg(Color::Default);
|
grid[(x, y)].set_bg(Color::Default);
|
||||||
grid[(x, y)].set_fg(Color::Default);
|
grid[(x, y)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let (x,y) = write_string_to_grid(&format!("From: {}", envelope.from()),
|
let (x, y) = write_string_to_grid(
|
||||||
grid,
|
&format!("From: {}", envelope.from_to_string()),
|
||||||
Color::Byte(33),
|
grid,
|
||||||
Color::Default,
|
Color::Byte(33),
|
||||||
(set_y(upper_left, y+1), bottom_right),
|
Color::Default,
|
||||||
true);
|
(set_y(upper_left, y + 1), bottom_right),
|
||||||
|
true,
|
||||||
|
);
|
||||||
for x in x..=get_x(bottom_right) {
|
for x in x..=get_x(bottom_right) {
|
||||||
grid[(x, y)].set_ch(' ');
|
grid[(x, y)].set_ch(' ');
|
||||||
grid[(x, y)].set_bg(Color::Default);
|
grid[(x, y)].set_bg(Color::Default);
|
||||||
grid[(x, y)].set_fg(Color::Default);
|
grid[(x, y)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let (x,y) = write_string_to_grid(&format!("To: {}", envelope.to()),
|
let (x, y) = write_string_to_grid(
|
||||||
grid,
|
&format!("To: {}", envelope.to_to_string()),
|
||||||
Color::Byte(33),
|
grid,
|
||||||
Color::Default,
|
Color::Byte(33),
|
||||||
(set_y(upper_left, y+1), bottom_right),
|
Color::Default,
|
||||||
true);
|
(set_y(upper_left, y + 1), bottom_right),
|
||||||
|
true,
|
||||||
|
);
|
||||||
for x in x..=get_x(bottom_right) {
|
for x in x..=get_x(bottom_right) {
|
||||||
grid[(x, y)].set_ch(' ');
|
grid[(x, y)].set_ch(' ');
|
||||||
grid[(x, y)].set_bg(Color::Default);
|
grid[(x, y)].set_bg(Color::Default);
|
||||||
grid[(x, y)].set_fg(Color::Default);
|
grid[(x, y)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let (x,y) = write_string_to_grid(&format!("Subject: {}", envelope.subject()),
|
let (x, y) = write_string_to_grid(
|
||||||
grid,
|
&format!("Subject: {}", envelope.subject()),
|
||||||
Color::Byte(33),
|
grid,
|
||||||
Color::Default,
|
Color::Byte(33),
|
||||||
(set_y(upper_left, y+1), bottom_right),
|
Color::Default,
|
||||||
true);
|
(set_y(upper_left, y + 1), bottom_right),
|
||||||
|
true,
|
||||||
|
);
|
||||||
for x in x..=get_x(bottom_right) {
|
for x in x..=get_x(bottom_right) {
|
||||||
grid[(x, y)].set_ch(' ');
|
grid[(x, y)].set_ch(' ');
|
||||||
grid[(x, y)].set_bg(Color::Default);
|
grid[(x, y)].set_bg(Color::Default);
|
||||||
grid[(x, y)].set_fg(Color::Default);
|
grid[(x, y)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
let (x, y) = write_string_to_grid(&format!("Message-ID: {}", envelope.message_id_raw()),
|
let (x, y) = write_string_to_grid(
|
||||||
grid,
|
&format!("Message-ID: {}", envelope.message_id_raw()),
|
||||||
Color::Byte(33),
|
grid,
|
||||||
Color::Default,
|
Color::Byte(33),
|
||||||
(set_y(upper_left, y+1), bottom_right),
|
Color::Default,
|
||||||
true);
|
(set_y(upper_left, y + 1), bottom_right),
|
||||||
|
true,
|
||||||
|
);
|
||||||
for x in x..=get_x(bottom_right) {
|
for x in x..=get_x(bottom_right) {
|
||||||
grid[(x, y)].set_ch(' ');
|
grid[(x, y)].set_ch(' ');
|
||||||
grid[(x, y)].set_bg(Color::Default);
|
grid[(x, y)].set_bg(Color::Default);
|
||||||
grid[(x, y)].set_fg(Color::Default);
|
grid[(x, y)].set_fg(Color::Default);
|
||||||
}
|
}
|
||||||
clear_area(grid,
|
clear_area(grid, (set_y(upper_left, y + 1), set_y(bottom_right, y + 2)));
|
||||||
(set_y(upper_left, y+1), set_y(bottom_right, y+2)));
|
context
|
||||||
context.dirty_areas.push_back((upper_left, set_y(bottom_right, y+1)));
|
.dirty_areas
|
||||||
|
.push_back((upper_left, set_y(bottom_right, y + 1)));
|
||||||
(envelope_idx, y + 1)
|
(envelope_idx, y + 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
if self.dirty {
|
if self.dirty {
|
||||||
let buf = {
|
let buf = {
|
||||||
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].as_ref().unwrap().as_ref().unwrap();
|
let mailbox = &mut context.accounts[mailbox_idx.0][mailbox_idx.1]
|
||||||
let envelope : &Envelope = &mailbox.collection[envelope_idx];
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap();
|
||||||
|
let envelope: &Envelope = &mailbox.collection[envelope_idx];
|
||||||
|
|
||||||
let finder = LinkFinder::new();
|
let finder = LinkFinder::new();
|
||||||
let mut text = match self.mode {
|
let mut text = match self.mode {
|
||||||
ViewMode::Url => {
|
ViewMode::Url => {
|
||||||
let mut t = envelope.body().text().to_string();
|
let mut t = envelope.body().text().to_string();
|
||||||
for (lidx, l) in finder.links(&envelope.body().text()).enumerate() {
|
for (lidx, l) in finder.links(&envelope.body().text()).enumerate() {
|
||||||
t.insert_str(l.start()+(lidx*3), &format!("[{}]", lidx));
|
t.insert_str(l.start() + (lidx * 3), &format!("[{}]", lidx));
|
||||||
}
|
}
|
||||||
if envelope.body().count_attachments() > 1 {
|
if envelope.body().count_attachments() > 1 {
|
||||||
t = envelope.body().attachments().iter().enumerate().fold(t, |mut s, (idx, a)| { s.push_str(&format!("[{}] {}\n\n", idx, a)); s });
|
t = envelope.body().attachments().iter().enumerate().fold(
|
||||||
|
t,
|
||||||
|
|mut s, (idx, a)| {
|
||||||
|
s.push_str(&format!("[{}] {}\n\n", idx, a));
|
||||||
|
s
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
t
|
t
|
||||||
},
|
}
|
||||||
ViewMode::Attachment(aidx) => {
|
ViewMode::Attachment(aidx) => {
|
||||||
let attachments = envelope.body().attachments();
|
let attachments = envelope.body().attachments();
|
||||||
let mut ret = format!("Viewing attachment. Press `r` to return \n");
|
let mut ret = format!("Viewing attachment. Press `r` to return \n");
|
||||||
ret.push_str(&attachments[aidx].text());
|
ret.push_str(&attachments[aidx].text());
|
||||||
ret
|
ret
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let mut t = envelope.body().text().to_string();
|
let mut t = envelope.body().text().to_string();
|
||||||
if envelope.body().count_attachments() > 1 {
|
if envelope.body().count_attachments() > 1 {
|
||||||
t = envelope.body().attachments().iter().enumerate().fold(t, |mut s, (idx, a)| { s.push_str(&format!("[{}] {}\n\n", idx, a)); s });
|
t = envelope.body().attachments().iter().enumerate().fold(
|
||||||
|
t,
|
||||||
|
|mut s, (idx, a)| {
|
||||||
|
s.push_str(&format!("[{}] {}\n\n", idx, a));
|
||||||
|
s
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
t
|
t
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
let mut buf = CellBuffer::from(&text);
|
let mut buf = CellBuffer::from(&text);
|
||||||
match self.mode {
|
match self.mode {
|
||||||
|
@ -172,10 +206,10 @@ impl Component for MailView {
|
||||||
buf[(l.start() + shift - 3, 0)].set_fg(Color::Byte(226));
|
buf[(l.start() + shift - 3, 0)].set_fg(Color::Byte(226));
|
||||||
}
|
}
|
||||||
// Each Cell represents one char so next line will be:
|
// Each Cell represents one char so next line will be:
|
||||||
shift += r.chars().count()+1;
|
shift += r.chars().count() + 1;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
_ => {},
|
_ => {}
|
||||||
}
|
}
|
||||||
buf
|
buf
|
||||||
};
|
};
|
||||||
|
@ -188,29 +222,41 @@ impl Component for MailView {
|
||||||
self.pager = Some(Pager::from_buf(buf, cursor_pos));
|
self.pager = Some(Pager::from_buf(buf, cursor_pos));
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
}
|
}
|
||||||
self.pager.as_mut().map(|p| p.draw(grid, (set_y(upper_left, y + 1),bottom_right), context));
|
self.pager
|
||||||
|
.as_mut()
|
||||||
|
.map(|p| p.draw(grid, (set_y(upper_left, y + 1), bottom_right), context));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||||
match event.event_type {
|
match event.event_type {
|
||||||
UIEventType::Input(Key::Esc) => {
|
UIEventType::Input(Key::Esc) => {
|
||||||
self.cmd_buf.clear();
|
self.cmd_buf.clear();
|
||||||
},
|
}
|
||||||
UIEventType::Input(Key::Char(c)) if c >= '0' && c <= '9' => { //TODO:this should be an Action
|
UIEventType::Input(Key::Char(c)) if c >= '0' && c <= '9' => {
|
||||||
|
//TODO:this should be an Action
|
||||||
self.cmd_buf.push(c);
|
self.cmd_buf.push(c);
|
||||||
},
|
}
|
||||||
UIEventType::Input(Key::Char('r')) if self.mode.is_attachment() => { //TODO:one quit shortcut?
|
UIEventType::Input(Key::Char('r')) if self.mode.is_attachment() => {
|
||||||
|
//TODO:one quit shortcut?
|
||||||
self.mode = ViewMode::Normal;
|
self.mode = ViewMode::Normal;
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
},
|
}
|
||||||
UIEventType::Input(Key::Char('a')) if self.cmd_buf.len() > 0 && self.mode == ViewMode::Normal => { //TODO:this should be an Action
|
UIEventType::Input(Key::Char('a'))
|
||||||
|
if self.cmd_buf.len() > 0 && self.mode == ViewMode::Normal =>
|
||||||
|
{
|
||||||
|
//TODO:this should be an Action
|
||||||
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 threaded = context.accounts[self.coordinates.0].runtime_settings.threaded;
|
let threaded = context.accounts[self.coordinates.0]
|
||||||
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1].as_ref().unwrap().as_ref().unwrap();
|
.runtime_settings
|
||||||
|
.threaded;
|
||||||
|
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap();
|
||||||
let envelope_idx: usize = if threaded {
|
let envelope_idx: usize = if threaded {
|
||||||
mailbox.threaded_mail(self.coordinates.2)
|
mailbox.threaded_mail(self.coordinates.2)
|
||||||
} else {
|
} else {
|
||||||
|
@ -223,11 +269,16 @@ impl Component for MailView {
|
||||||
ContentType::Text => {
|
ContentType::Text => {
|
||||||
self.mode = ViewMode::Attachment(lidx);
|
self.mode = ViewMode::Attachment(lidx);
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
},
|
}
|
||||||
ContentType::Multipart { .. } => {
|
ContentType::Multipart { .. } => {
|
||||||
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::StatusNotification(format!("Multipart attachments are not supported yet.")) });
|
context.replies.push_back(UIEvent {
|
||||||
|
id: 0,
|
||||||
|
event_type: UIEventType::StatusNotification(format!(
|
||||||
|
"Multipart attachments are not supported yet."
|
||||||
|
)),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
},
|
}
|
||||||
ContentType::Unsupported { .. } => {
|
ContentType::Unsupported { .. } => {
|
||||||
let attachment_type = u.mime_type();
|
let attachment_type = u.mime_type();
|
||||||
let binary = query_default_app(&attachment_type);
|
let binary = query_default_app(&attachment_type);
|
||||||
|
@ -240,28 +291,45 @@ impl Component for MailView {
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect(&format!("Failed to start {}", binary.display()));
|
.expect(&format!("Failed to start {}", binary.display()));
|
||||||
} else {
|
} else {
|
||||||
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::StatusNotification(format!("Couldn't find a default application for type {}", attachment_type)) });
|
context.replies.push_back(UIEvent {
|
||||||
|
id: 0,
|
||||||
|
event_type: UIEventType::StatusNotification(format!(
|
||||||
|
"Couldn't find a default application for type {}",
|
||||||
|
attachment_type
|
||||||
|
)),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::StatusNotification(format!("Attachment `{}` not found.", lidx)) });
|
context.replies.push_back(UIEvent {
|
||||||
|
id: 0,
|
||||||
|
event_type: UIEventType::StatusNotification(format!(
|
||||||
|
"Attachment `{}` not found.",
|
||||||
|
lidx
|
||||||
|
)),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
UIEventType::Input(Key::Char('g'))
|
||||||
},
|
if self.cmd_buf.len() > 0 && self.mode == ViewMode::Url =>
|
||||||
UIEventType::Input(Key::Char('g')) if self.cmd_buf.len() > 0 && self.mode == ViewMode::Url => { //TODO:this should be an Action
|
{
|
||||||
|
//TODO:this should be an Action
|
||||||
|
|
||||||
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 threaded = context.accounts[self.coordinates.0].runtime_settings.threaded;
|
let threaded = context.accounts[self.coordinates.0]
|
||||||
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1].as_ref().unwrap().as_ref().unwrap();
|
.runtime_settings
|
||||||
|
.threaded;
|
||||||
|
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.as_ref()
|
||||||
|
.unwrap();
|
||||||
let envelope_idx: usize = if threaded {
|
let envelope_idx: usize = if threaded {
|
||||||
mailbox.threaded_mail(self.coordinates.2)
|
mailbox.threaded_mail(self.coordinates.2)
|
||||||
} else {
|
} else {
|
||||||
|
@ -275,33 +343,37 @@ impl Component for MailView {
|
||||||
if let Some(u) = links.get(lidx) {
|
if let Some(u) = links.get(lidx) {
|
||||||
u.as_str().to_string()
|
u.as_str().to_string()
|
||||||
} else {
|
} else {
|
||||||
context.replies.push_back(UIEvent { id: 0, event_type: UIEventType::StatusNotification(format!("Link `{}` not found.", lidx)) });
|
context.replies.push_back(UIEvent {
|
||||||
|
id: 0,
|
||||||
|
event_type: UIEventType::StatusNotification(format!(
|
||||||
|
"Link `{}` not found.",
|
||||||
|
lidx
|
||||||
|
)),
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Command::new("xdg-open")
|
Command::new("xdg-open")
|
||||||
.arg(url)
|
.arg(url)
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("Failed to start xdg_open");
|
.expect("Failed to start xdg_open");
|
||||||
|
}
|
||||||
},
|
UIEventType::Input(Key::Char('u')) => {
|
||||||
UIEventType::Input(Key::Char('u')) => { //TODO:this should be an Action
|
//TODO:this should be an Action
|
||||||
match self.mode {
|
match self.mode {
|
||||||
ViewMode::Normal => { self.mode = ViewMode::Url },
|
ViewMode::Normal => self.mode = ViewMode::Url,
|
||||||
ViewMode::Url => { self.mode = ViewMode::Normal },
|
ViewMode::Url => self.mode = ViewMode::Normal,
|
||||||
_ => {},
|
_ => {}
|
||||||
}
|
}
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
},
|
}
|
||||||
_ => {},
|
_ => {}
|
||||||
}
|
}
|
||||||
if let Some(ref mut sub) = self.subview {
|
if let Some(ref mut sub) = self.subview {
|
||||||
sub.process_event(event, context);
|
sub.process_event(event, context);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if let Some(ref mut p) = self.pager {
|
if let Some(ref mut p) = self.pager {
|
||||||
p.process_event(event, context);
|
p.process_event(event, context);
|
||||||
|
@ -309,7 +381,8 @@ impl Component for MailView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
self.dirty || self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false) ||
|
self.dirty
|
||||||
self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
|| self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||||
|
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue