Browse Source

Make backend folders completely agnostic (remove maildir logic from

conf)
embed
Manos Pitsidianakis 4 years ago
parent
commit
b98a04f35b
Signed by: epilys GPG Key ID: 73627C2F690DF710
  1. 10
      benches/maildir.rs
  2. 111
      melib/src/conf/mod.rs
  3. 2
      melib/src/error.rs
  4. 27
      melib/src/mailbox/accounts.rs
  5. 9
      melib/src/mailbox/backends/imap.rs
  6. 183
      melib/src/mailbox/backends/maildir.rs
  7. 7
      melib/src/mailbox/backends/mbox.rs
  8. 59
      melib/src/mailbox/backends/mod.rs
  9. 8
      melib/src/mailbox/email/attachment_types.rs
  10. 64
      melib/src/mailbox/email/attachments.rs
  11. 27
      melib/src/mailbox/email/mod.rs
  12. 47
      melib/src/mailbox/email/parser.rs
  13. 21
      melib/src/mailbox/mod.rs
  14. 11
      src/bin.rs
  15. 9
      src/python/mod.rs
  16. 45
      ui/src/components/mail/compose.rs
  17. 679
      ui/src/components/mail/listing/compact.rs
  18. 16
      ui/src/components/mail/listing/mod.rs
  19. 28
      ui/src/components/mail/mod.rs
  20. 8
      ui/src/components/mail/view/html.rs
  21. 97
      ui/src/components/mail/view/mod.rs
  22. 101
      ui/src/components/mail/view/thread.rs
  23. 15
      ui/src/components/mod.rs
  24. 8
      ui/src/components/notifications.rs
  25. 154
      ui/src/components/utilities.rs
  26. 1
      ui/src/compose/mod.rs
  27. 15
      ui/src/state.rs
  28. 9
      ui/src/types/helpers.rs

10
benches/maildir.rs

@ -9,35 +9,35 @@ use self::test::Bencher;
#[bench]
fn bench_threads_1(b: &mut Bencher) {
b.iter(|| {
let folder = Folder::new(String::from(""), vec![]);
let folder = Folder::new(String::from(""), String::from(""), vec![]);
MaildirType::new("").multicore(1, &folder)
});
}
#[bench]
fn bench_threads_2(b: &mut Bencher) {
b.iter(|| {
let folder = Folder::new(String::from(""), vec![]);
let folder = Folder::new(String::from(""), String::from(""), vec![]);
MaildirType::new("").multicore(2, &folder)
});
}
#[bench]
fn bench_threads_3(b: &mut Bencher) {
b.iter(|| {
let folder = Folder::new(String::from(""), vec![]);
let folder = Folder::new(String::from(""), String::from(""), vec![]);
MaildirType::new("").multicore(3, &folder)
});
}
#[bench]
fn bench_threads_4(b: &mut Bencher) {
b.iter(|| {
let folder = Folder::new(String::from(""), vec![]);
let folder = Folder::new(String::from(""), String::from(""), vec![]);
MaildirType::new("").multicore(4, &folder)
});
}
#[bench]
fn bench_threads_6(b: &mut Bencher) {
b.iter(|| {
let folder = Folder::new(String::from(""), vec![]);
let folder = Folder::new(String::from(""), String::from(""), vec![]);
MaildirType::new("").multicore(6, &folder)
});
}

111
melib/src/conf/mod.rs

@ -26,53 +26,22 @@ pub mod pager;
use pager::PagerSettings;
use std::collections::hash_map::DefaultHasher;
use std::collections::HashMap;
use std::fs;
use std::hash::Hasher;
use std::path::{Path, PathBuf};
#[derive(Debug, Default, Clone)]
pub struct Folder {
hash: u64,
name: String,
path: String,
children: Vec<usize>,
}
impl Folder {
pub fn new(path: String, file_name: String, children: Vec<usize>) -> Self {
let mut h = DefaultHasher::new();
h.write(&path.as_bytes());
Folder {
hash: h.finish(),
name: file_name,
path: path,
children: children,
}
}
pub fn hash(&self) -> u64 {
self.hash
}
pub fn path(&self) -> &str {
&self.path
}
pub fn name(&self) -> &str {
&self.name
}
pub fn children(&self) -> &Vec<usize> {
&self.children
}
}
#[derive(Debug, Deserialize)]
struct FileAccount {
folders: String,
pub struct FileAccount {
root_folder: String,
format: String,
sent_folder: String,
threaded: bool,
}
impl FileAccount {
pub fn folder(&self) -> &str {
&self.root_folder
}
}
#[derive(Debug, Deserialize)]
struct FileSettings {
accounts: HashMap<String, FileAccount>,
@ -82,7 +51,7 @@ struct FileSettings {
#[derive(Debug, Clone)]
pub struct AccountSettings {
name: String,
pub folders: Vec<Folder>,
root_folder: String,
format: String,
pub sent_folder: String,
pub threaded: bool,
@ -95,6 +64,9 @@ impl AccountSettings {
pub fn name(&self) -> &str {
&self.name
}
pub fn root_folder(&self) -> &str {
&self.root_folder
}
}
#[derive(Debug, Clone, Default)]
@ -126,53 +98,20 @@ impl Settings {
let mut s: HashMap<String, AccountSettings> = HashMap::new();
for (id, x) in fs.accounts {
let mut folders = Vec::new();
fn recurse_folders<P: AsRef<Path>>(folders: &mut Vec<Folder>, p: P) -> Vec<usize> {
let mut children = Vec::new();
for mut f in fs::read_dir(p).unwrap() {
for f in f.iter_mut() {
{
let path = f.path();
if path.ends_with("cur")
|| path.ends_with("new")
|| path.ends_with("tmp")
{
continue;
}
if path.is_dir() {
let path_children = recurse_folders(folders, &path);
folders.push(Folder::new(
path.to_str().unwrap().to_string(),
path.file_name().unwrap().to_str().unwrap().to_string(),
path_children,
));
children.push(folders.len() - 1);
}
}
}
}
children
let format = x.format.to_lowercase();
let sent_folder = x.sent_folder;
let threaded = x.threaded;
let root_folder = x.root_folder;
let acc = AccountSettings {
name: id.clone(),
root_folder,
format,
sent_folder,
threaded,
};
let path = PathBuf::from(&x.folders);
let path_children = recurse_folders(&mut folders, &path);
if path.is_dir() {
folders.push(Folder::new(
path.to_str().unwrap().to_string(),
path.file_name().unwrap().to_str().unwrap().to_string(),
path_children,
));
}
//folders.sort_by(|a, b| b.name.cmp(&a.name));
s.insert(
id.clone(),
AccountSettings {
name: id.clone(),
folders: folders,
format: x.format.to_lowercase(),
sent_folder: x.sent_folder.clone(),
threaded: x.threaded,
},
);
s.insert(id, acc);
}
Settings {

2
melib/src/error.rs

@ -23,11 +23,11 @@
* An error object for `melib`
*/
use std::borrow::Cow;
use std::error::Error;
use std::fmt;
use std::io;
use std::result;
use std::borrow::Cow;
use nom;

27
melib/src/mailbox/accounts.rs

@ -24,7 +24,7 @@
*/
use async::*;
use conf::{AccountSettings, Folder};
use conf::AccountSettings;
use mailbox::backends::{Backends, RefreshEventConsumer};
use mailbox::*;
use std::ops::{Index, IndexMut};
@ -47,15 +47,15 @@ pub struct Account {
}
impl Account {
pub fn new(name: String, settings: AccountSettings, backends: &Backends) -> Self {
let sent_folder = settings
.folders
pub fn new(name: String, settings: AccountSettings, map: &Backends) -> Self {
let backend = map.get(settings.format())(&settings);
let ref_folders: Vec<Folder> = backend.folders();
let mut folders: Vec<Option<Result<Mailbox>>> = Vec::with_capacity(ref_folders.len());
let mut workers: Vec<Worker> = Vec::new();
let sent_folder = ref_folders
.iter()
.position(|x| *x.path() == settings.sent_folder);
let mut folders = Vec::with_capacity(settings.folders.len());
let mut workers = Vec::new();
let backend = backends.get(settings.format());
for f in &settings.folders {
.position(|x: &Folder| x.name() == settings.sent_folder);
for f in ref_folders {
folders.push(None);
let mut handle = backend.get(&f);
workers.push(Some(handle));
@ -73,14 +73,14 @@ impl Account {
}
}
pub fn watch(&self, r: RefreshEventConsumer) -> () {
self.backend.watch(r, &self.settings.folders[..]);
self.backend.watch(r).unwrap();
}
/* This doesn't represent the number of correctly parsed mailboxes though */
pub fn len(&self) -> usize {
self.folders.len()
}
pub fn list_folders(&self) -> Vec<Folder> {
self.settings.folders.clone()
self.backend.folders()
}
pub fn name(&self) -> &str {
&self.name
@ -89,7 +89,8 @@ impl Account {
&mut self.workers
}
fn load_mailbox(&mut self, index: usize, envelopes: Result<Vec<Envelope>>) -> () {
let folder = &self.settings.folders[index];
let folders = self.backend.folders();
let folder = &folders[index];
if self.sent_folder.is_some() {
let id = self.sent_folder.unwrap();
if id == index {
@ -105,7 +106,7 @@ impl Account {
)
}
};
let sent_path = &self.settings.folders[id];
let sent_path = &folders[id];
if sent[0].is_none() {
sent[0] = Some(Mailbox::new(sent_path, &None, envelopes.clone()));
}

9
melib/src/mailbox/backends/imap.rs

@ -19,10 +19,10 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
/*
use async::*;
use conf::Folder;
use error::Result;
use mailbox::backends::{MailBackend, RefreshEventConsumer};
use mailbox::backends::{MailBackend, RefreshEventConsumer, Folder};
use mailbox::email::Envelope;
/// `BackendOp` implementor for Imap
@ -35,7 +35,6 @@ impl ImapOp {
}
}
/*
impl BackendOp for ImapOp {
fn description(&self) -> String {
@ -53,7 +52,7 @@ impl BackendOp for ImapOp {
fn fetch_flags(&self) -> Flag {
unimplemented!();
}
}*/
}
/// Imap backend
#[derive(Debug)]
@ -72,4 +71,4 @@ impl ImapType {
pub fn new(_path: &str) -> Self {
ImapType {}
}
}
}*/

183
melib/src/mailbox/backends/maildir.rs

@ -20,10 +20,11 @@
*/
use async::*;
use conf::Folder;
use conf::AccountSettings;
use error::{MeliError, Result};
use mailbox::backends::{
BackendOp, BackendOpGenerator, MailBackend, RefreshEvent, RefreshEventConsumer,
BackendFolder, BackendOp, BackendOpGenerator, Folder, MailBackend, RefreshEvent,
RefreshEventConsumer,
};
use mailbox::email::parser;
use mailbox::email::{Envelope, Flag};
@ -43,7 +44,7 @@ use memmap::{Mmap, Protection};
use std::collections::hash_map::DefaultHasher;
use std::fs;
use std::hash::Hasher;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
/// `BackendOp` implementor for Maildir
#[derive(Debug, Default)]
@ -152,38 +153,42 @@ impl BackendOp for MaildirOp {
/// Maildir backend https://cr.yp.to/proto/maildir.html
#[derive(Debug)]
pub struct MaildirType {
folders: Vec<MaildirFolder>,
path: String,
idx: (usize, usize),
}
impl MailBackend for MaildirType {
fn folders(&self) -> Vec<Folder> {
self.folders.iter().map(|f| f.clone()).collect()
}
fn get(&self, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
self.multicore(4, folder)
}
fn watch(&self, sender: RefreshEventConsumer, folders: &[Folder]) -> () {
let folders = folders.to_vec();
fn watch(&self, sender: RefreshEventConsumer) -> Result<()> {
let (tx, rx) = channel();
let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
for f in &self.folders {
if f.is_valid().is_err() {
continue;
}
eprintln!("watching {:?}", f);
let mut p = PathBuf::from(&f.path);
p.push("cur");
watcher.watch(&p, RecursiveMode::NonRecursive).unwrap();
p.pop();
p.push("new");
watcher.watch(&p, RecursiveMode::NonRecursive).unwrap();
}
thread::Builder::new()
.name("folder watch".to_string())
.spawn(move || {
let (tx, rx) = channel();
let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
for f in folders {
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();
p.pop();
p.push("new");
watcher.watch(&p, RecursiveMode::NonRecursive).unwrap();
}
// Move `watcher` in the closure's scope so that it doesn't get dropped.
let _watcher = watcher;
loop {
match rx.recv() {
Ok(event) => match event {
DebouncedEvent::Create(mut pathbuf) | DebouncedEvent::Remove(mut pathbuf) => {
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();
@ -207,45 +212,76 @@ impl MailBackend for MaildirType {
Err(e) => eprintln!("watch error: {:?}", e),
}
}
})
.unwrap();
})?;
Ok(())
}
}
impl MaildirType {
pub fn new(path: &str, idx: (usize, usize)) -> Self {
pub fn new(f: &AccountSettings) -> Self {
let mut folders: Vec<MaildirFolder> = Vec::new();
fn recurse_folders<P: AsRef<Path>>(folders: &mut Vec<MaildirFolder>, p: P) -> Vec<usize> {
let mut children = Vec::new();
for mut f in fs::read_dir(p).unwrap() {
for f in f.iter_mut() {
{
let path = f.path();
if path.ends_with("cur") || path.ends_with("new") || path.ends_with("tmp") {
continue;
}
if path.is_dir() {
let path_children = recurse_folders(folders, &path);
if let Ok(f) = MaildirFolder::new(
path.to_str().unwrap().to_string(),
path.file_name().unwrap().to_str().unwrap().to_string(),
path_children,
) {
folders.push(f);
children.push(folders.len() - 1);
}
}
}
}
}
children
};
let path = PathBuf::from(f.root_folder());
let path_children = recurse_folders(&mut folders, &path);
if path.is_dir() {
if let Ok(f) = MaildirFolder::new(
path.to_str().unwrap().to_string(),
path.file_name().unwrap().to_str().unwrap().to_string(),
path_children,
) {
folders.push(f);
}
}
MaildirType {
path: path.to_string(),
idx: idx,
folders,
path: f.root_folder().to_string(),
}
}
fn is_valid(f: &Folder) -> Result<()> {
let path = f.path();
let mut p = PathBuf::from(path);
for d in &["cur", "new", "tmp"] {
p.push(d);
if !p.is_dir() {
return Err(MeliError::new(format!(
"{} is not a valid maildir folder",
path
)));
fn owned_folder_idx(&self, folder: &Folder) -> usize {
for (idx, f) in self.folders.iter().enumerate() {
if f.hash() == folder.hash() {
return idx;
}
p.pop();
}
Ok(())
unreachable!()
}
pub fn multicore(&self, cores: usize, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
let mut w = AsyncBuilder::new();
let handle = {
let tx = w.tx();
// TODO: Avoid clone
let folder = folder.clone();
let folder: &MaildirFolder = &self.folders[self.owned_folder_idx(folder)];
let path = folder.path().to_string();
let name = format!("parsing {:?}", folder.name());
thread::Builder::new()
.name(format!("parsing {:?}", folder))
.name(name)
.spawn(move || {
MaildirType::is_valid(&folder)?;
let path = folder.path();
let mut path = PathBuf::from(path);
path.push("cur");
let iter = path.read_dir()?;
@ -310,3 +346,64 @@ impl MaildirType {
w.build(handle)
}
}
#[derive(Debug, Default)]
pub struct MaildirFolder {
hash: u64,
name: String,
path: String,
children: Vec<usize>,
}
impl MaildirFolder {
pub fn new(path: String, file_name: String, children: Vec<usize>) -> Result<Self> {
let mut h = DefaultHasher::new();
h.write(&path.as_bytes());
let ret = MaildirFolder {
hash: h.finish(),
name: file_name,
path: path,
children: children,
};
ret.is_valid()?;
Ok(ret)
}
pub fn path(&self) -> &str {
&self.path
}
fn is_valid(&self) -> Result<()> {
let path = self.path();
let mut p = PathBuf::from(path);
for d in &["cur", "new", "tmp"] {
p.push(d);
if !p.is_dir() {
return Err(MeliError::new(format!(
"{} is not a valid maildir folder",
path
)));
}
p.pop();
}
Ok(())
}
}
impl BackendFolder for MaildirFolder {
fn hash(&self) -> u64 {
self.hash
}
fn name(&self) -> &str {
&self.name
}
fn children(&self) -> &Vec<usize> {
&self.children
}
fn clone(&self) -> Folder {
Box::new(MaildirFolder {
hash: self.hash,
name: self.name.clone(),
path: self.path.clone(),
children: self.children.clone(),
})
}
}

7
melib/src/mailbox/backends/mbox.rs

@ -23,10 +23,10 @@
* https://wiki2.dovecot.org/MailboxFormat/mbox
*/
/*
use async::*;
use conf::Folder;
use error::Result;
use mailbox::backends::{MailBackend, RefreshEventConsumer};
use mailbox::backends::{MailBackend, RefreshEventConsumer, Folder};
use mailbox::email::Envelope;
/// `BackendOp` implementor for Mbox
@ -39,7 +39,6 @@ impl MboxOp {
}
}
/*
impl BackendOp for MboxOp {
fn description(&self) -> String {
unimplemented!();
@ -60,7 +59,6 @@ impl BackendOp for MboxOp {
unimplemented!()
}
}
*/
/// Mbox backend
#[derive(Debug)]
@ -80,3 +78,4 @@ impl MboxType {
MboxType {}
}
}
*/

59
melib/src/mailbox/backends/mod.rs

@ -23,22 +23,25 @@ pub mod maildir;
pub mod mbox;
use async::*;
use conf::Folder;
use conf::AccountSettings;
use error::Result;
use mailbox::backends::imap::ImapType;
//use mailbox::backends::imap::ImapType;
//use mailbox::backends::mbox::MboxType;
use mailbox::backends::maildir::MaildirType;
use mailbox::backends::mbox::MboxType;
use mailbox::email::{Envelope, Flag};
use std::fmt;
use std::fmt::Debug;
extern crate fnv;
use self::fnv::FnvHashMap;
use std;
pub type BackendCreator = Box<Fn(&AccountSettings) -> Box<MailBackend>>;
/// A hashmap containing all available mail backends.
/// An abstraction over any available backends.
pub struct Backends {
map: FnvHashMap<std::string::String, Box<Fn() -> Box<MailBackend>>>,
map: FnvHashMap<std::string::String, Box<Fn() -> BackendCreator>>,
}
impl Backends {
@ -48,20 +51,20 @@ impl Backends {
};
b.register(
"maildir".to_string(),
Box::new(|| Box::new(MaildirType::new("", (0, 0)))),
Box::new(|| Box::new(|f| Box::new(MaildirType::new(f)))),
);
b.register("mbox".to_string(), Box::new(|| Box::new(MboxType::new(""))));
b.register("imap".to_string(), Box::new(|| Box::new(ImapType::new(""))));
//b.register("mbox".to_string(), Box::new(|| Box::new(MboxType::new(""))));
//b.register("imap".to_string(), Box::new(|| Box::new(ImapType::new(""))));
b
}
pub fn get(&self, key: &str) -> Box<MailBackend> {
pub fn get(&self, key: &str) -> BackendCreator {
if !self.map.contains_key(key) {
panic!("{} is not a valid mail backend", key);
}
self.map[key]()
}
pub fn register(&mut self, key: String, backend: Box<Fn() -> Box<MailBackend>>) -> () {
pub fn register(&mut self, key: String, backend: Box<Fn() -> BackendCreator>) -> () {
if self.map.contains_key(&key) {
panic!("{} is an already registered backend", key);
}
@ -90,8 +93,8 @@ impl RefreshEventConsumer {
}
pub trait MailBackend: ::std::fmt::Debug {
fn get(&self, folder: &Folder) -> Async<Result<Vec<Envelope>>>;
fn watch(&self, sender: RefreshEventConsumer, folders: &[Folder]) -> ();
//fn new(folders: &Vec<String>) -> Box<Self>;
fn watch(&self, sender: RefreshEventConsumer) -> Result<()>;
fn folders(&self) -> Vec<Folder>;
//login function
}
@ -168,3 +171,37 @@ impl fmt::Debug for BackendOpGenerator {
write!(f, "BackendOpGenerator: {}", op.description())
}
}
pub trait BackendFolder: Debug {
fn hash(&self) -> u64;
fn name(&self) -> &str;
fn clone(&self) -> Folder;
fn children(&self) -> &Vec<usize>;
}
#[derive(Debug)]
struct DummyFolder {
v: Vec<usize>,
}
impl BackendFolder for DummyFolder {
fn hash(&self) -> u64 {
0
}
fn name(&self) -> &str {
""
}
fn clone(&self) -> Folder {
folder_default()
}
fn children(&self) -> &Vec<usize> {
&self.v
}
}
pub fn folder_default() -> Folder {
Box::new(DummyFolder {
v: Vec::with_capacity(0),
})
}
pub type Folder = Box<BackendFolder>;

8
melib/src/mailbox/email/attachment_types.rs

@ -20,7 +20,7 @@ impl Default for Charset {
}
}
impl<'a> From<&'a[u8]> for Charset {
impl<'a> From<&'a [u8]> for Charset {
fn from(b: &'a [u8]) -> Self {
// TODO: Case insensitivity
match b {
@ -69,9 +69,10 @@ pub enum ContentType {
impl Default for ContentType {
fn default() -> Self {
ContentType::Text{ charset: Charset::UTF8 }
ContentType::Text {
charset: Charset::UTF8,
}
}
}
impl Display for ContentType {
@ -128,4 +129,3 @@ pub enum ContentTransferEncoding {
QuotedPrintable,
Other { tag: Vec<u8> },
}

64
melib/src/mailbox/email/attachments.rs

@ -18,10 +18,10 @@
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use std::fmt;
use std::str;
use data_encoding::BASE64_MIME;
use mailbox::email::parser;
use std::fmt;
use std::str;
pub use mailbox::email::attachment_types::*;
@ -44,7 +44,14 @@ impl fmt::Debug for AttachmentType {
match self {
AttachmentType::Data { .. } => write!(f, "AttachmentType::Data {{ .. }}"),
AttachmentType::Text { .. } => write!(f, "AttachmentType::Text {{ .. }}"),
AttachmentType::Multipart { of_type, subattachments } => write!(f, "AttachmentType::Multipart {{ of_type: {:?},\nsubattachments: {:?} }}", of_type, subattachments),
AttachmentType::Multipart {
of_type,
subattachments,
} => write!(
f,
"AttachmentType::Multipart {{ of_type: {:?},\nsubattachments: {:?} }}",
of_type, subattachments
),
}
}
}
@ -96,7 +103,7 @@ impl fmt::Display for AttachmentType {
impl AttachmentBuilder {
pub fn new(content: &[u8]) -> Self {
AttachmentBuilder {
content_type: (Default::default() , ContentSubType::Plain),
content_type: (Default::default(), ContentSubType::Plain),
content_transfer_encoding: ContentTransferEncoding::_7Bit,
raw: content.to_vec(),
}
@ -124,7 +131,9 @@ impl AttachmentBuilder {
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) };
self.content_type.0 = ContentType::Text {
charset: Charset::from(v),
};
break;
}
}
@ -168,7 +177,7 @@ impl AttachmentBuilder {
fn decode(&self) -> Vec<u8> {
// TODO merge this and standalone decode() function
let charset = match self.content_type.0 {
ContentType::Text{ charset: c } => c,
ContentType::Text { charset: c } => c,
_ => Default::default(),
};
@ -180,13 +189,17 @@ impl AttachmentBuilder {
ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_bytes(&self.raw)
.to_full_result()
.unwrap(),
ContentTransferEncoding::_7Bit
| ContentTransferEncoding::_8Bit
| ContentTransferEncoding::Other { .. } => self.raw.to_vec(),
ContentTransferEncoding::_7Bit
| ContentTransferEncoding::_8Bit
| ContentTransferEncoding::Other { .. } => self.raw.to_vec(),
};
let decoded_result = parser::decode_charset(&bytes, charset);
decoded_result.as_ref().map(|v| v.as_bytes()).unwrap_or_else(|_| &self.raw).to_vec()
decoded_result
.as_ref()
.map(|v| v.as_bytes())
.unwrap_or_else(|_| &self.raw)
.to_vec()
}
pub fn build(self) -> Attachment {
let attachment_type = match self.content_type.0 {
@ -197,7 +210,9 @@ impl AttachmentBuilder {
let multipart_type = match self.content_type.1 {
ContentSubType::Other { ref tag } => match &tag[..] {
b"mixed" | b"Mixed" | b"MIXED" => MultipartType::Mixed,
b"alternative" | b"Alternative" | b"ALTERNATIVE" => MultipartType::Alternative,
b"alternative" | b"Alternative" | b"ALTERNATIVE" => {
MultipartType::Alternative
}
b"digest" | b"Digest" | b"DIGEST" => MultipartType::Digest,
_ => MultipartType::Unsupported { tag: tag.clone() },
},
@ -261,14 +276,15 @@ impl AttachmentBuilder {
}
}
impl fmt::Display for Attachment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.attachment_type {
AttachmentType::Data { .. } => {
write!(f, "Data attachment of type {}", self.mime_type())
}
AttachmentType::Text { .. } => write!(f, "Text attachment of type {}", self.mime_type()),
AttachmentType::Text { .. } => {
write!(f, "Text attachment of type {}", self.mime_type())
}
AttachmentType::Multipart {
of_type: ref multipart_type,
subattachments: ref sub_att_vec,
@ -276,10 +292,16 @@ impl fmt::Display for Attachment {
write!(
f,
"{} attachment with {} subs",
self.mime_type(), sub_att_vec.len()
self.mime_type(),
sub_att_vec.len()
)
} else {
write!(f, "{} attachment with {} subs", self.mime_type(), sub_att_vec.len())
write!(
f,
"{} attachment with {} subs",
self.mime_type(),
sub_att_vec.len()
)
},
}
}
@ -340,7 +362,7 @@ impl Attachment {
for a in sub_att_vec {
count_recursive(a, ret);
}
},
}
}
}
@ -374,7 +396,7 @@ fn decode_rec_helper(a: &Attachment, filter: &Option<Box<Fn(&Attachment) -> Vec<
return filter(a);
}
match a.attachment_type {
AttachmentType::Data { .. } => { Vec::new()},
AttachmentType::Data { .. } => Vec::new(),
AttachmentType::Text { .. } => decode_helper(a, filter),
AttachmentType::Multipart {
of_type: ref multipart_type,
@ -405,7 +427,7 @@ fn decode_helper(a: &Attachment, filter: &Option<Box<Fn(&Attachment) -> Vec<u8>>
}
let charset = match a.content_type.0 {
ContentType::Text{ charset: c } => c,
ContentType::Text { charset: c } => c,
_ => Default::default(),
};
@ -424,7 +446,11 @@ fn decode_helper(a: &Attachment, filter: &Option<Box<Fn(&Attachment) -> Vec<u8>>
if a.content_type().0.is_text() {
let decoded_result = parser::decode_charset(&bytes, charset);
decoded_result.as_ref().map(|v| v.as_bytes()).unwrap_or_else(|_| a.bytes()).to_vec()
decoded_result
.as_ref()
.map(|v| v.as_bytes())
.unwrap_or_else(|_| a.bytes())
.to_vec()
} else {
bytes.to_vec()
}

27
melib/src/mailbox/email/mod.rs

@ -203,6 +203,33 @@ bitflags! {
}
}
#[derive(Debug, Clone, Default)]
pub struct EnvelopeBuilder {
from: Option<Vec<Address>>,
to: Vec<Address>,
body: Option<Attachment>,
in_reply_to: Option<MessageID>,
flags: Flag,
}
impl EnvelopeBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn build(self) -> Envelope {
unimplemented!();
/*
* 1. Check for date. Default is now
* 2.
Envelope {
*/
}
}
/// `Envelope` represents all the data of an email we need to know.
///
/// Attachments (the email's body) is parsed on demand with `body`.

47
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, WINDOWS_1252, GBK};
use encoding::all::{ISO_8859_1, ISO_8859_2, ISO_8859_7, WINDOWS_1252, WINDOWS_1253, GBK};
fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
if input.len() < 5 {
@ -266,33 +266,15 @@ fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
pub fn decode_charset(s: &[u8], charset: Charset) -> Result<String> {
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!()
},
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!(),
}
}
@ -322,12 +304,10 @@ named!(
pub quoted_printable_bytes<Vec<u8>>,
many0!(alt_complete!(
preceded!(quoted_printable_soft_break, quoted_printable_byte) |
preceded!(quoted_printable_soft_break, le_u8)
| quoted_printable_byte | le_u8
preceded!(quoted_printable_soft_break, le_u8) | quoted_printable_byte | le_u8
))
);
named!(
encoded_word_list<Vec<u8>>,
ws!(do_parse!(
@ -397,7 +377,10 @@ fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
IResult::Done(rest, raw) => {
display_name.length = raw.find(b"<").unwrap().saturating_sub(1);
address_spec.offset = display_name.length + 2;
address_spec.length = raw.len().saturating_sub(display_name.length).saturating_sub(3);
address_spec.length = raw
.len()
.saturating_sub(display_name.length)
.saturating_sub(3);
IResult::Done(
rest,
Address::Mailbox(MailboxAddress {

21
melib/src/mailbox/mod.rs

@ -30,18 +30,16 @@ pub use self::email::*;
/* Mail backends. Currently only maildir is supported */
pub mod backends;
use error::Result;
use mailbox::backends::MailBackend;
use mailbox::backends::{folder_default, Folder, MailBackend};
pub mod accounts;
pub use mailbox::accounts::Account;
pub mod thread;
pub use mailbox::thread::{build_threads, Container};
use conf::Folder;
use std::option::Option;
/// `Mailbox` represents a folder of mail.
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct Mailbox {
pub folder: Folder,
pub collection: Vec<Envelope>,
@ -49,10 +47,21 @@ pub struct Mailbox {
pub threads: Vec<Container>,
}
impl Clone for Mailbox {
fn clone(&self) -> Self {
Mailbox {
folder: self.folder.clone(),
collection: self.collection.clone(),
threaded_collection: self.threaded_collection.clone(),
threads: self.threads.clone(),
}
}
}
impl Mailbox {
pub fn new_dummy() -> Self {
Mailbox {
folder: Folder::default(),
folder: folder_default(),
collection: Vec::with_capacity(0),
threaded_collection: Vec::with_capacity(0),
threads: Vec::with_capacity(0),
@ -67,7 +76,7 @@ impl Mailbox {
collection.sort_by(|a, b| a.date().cmp(&b.date()));
let (threads, threaded_collection) = build_threads(&mut collection, sent_folder);
Ok(Mailbox {
folder: folder.clone(),
folder: (*folder).clone(),
collection: collection,
threads: threads,
threaded_collection: threaded_collection,

11
src/bin.rs

@ -59,9 +59,7 @@ fn make_input_thread(
sx.send(ThreadEvent::Input(k));
},
|| {
sx.send(ThreadEvent::UIEvent(UIEventType::ChangeMode(
UIMode::Fork,
)));
sx.send(ThreadEvent::UIEvent(UIEventType::ChangeMode(UIMode::Fork)));
},
&rx,
)
@ -104,9 +102,10 @@ fn main() {
let b = Entity {
component: Box::new(listing),
};
let window = Entity {
component: Box::new(VSplit::new(menu, b, 90, true)),
};
let mut tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true))]));
tabs.add_component(Box::new(Composer {}));
let window = Entity { component: tabs };
let status_bar = Entity {
component: Box::new(StatusBar::new(window)),
};

9
src/python/mod.rs

@ -1,22 +1,21 @@
#![cfg(feature = "python")]
use pyo3::prelude::*;
#[pymodinit(pythonmeli)]
fn pythonmeli(py: Python, m: &PyModule) -> PyResult<()> {
// pyo3 aware function. All of our python interface could be declared in a separate module.
// Note that the `#[pyfn()]` annotation automatically converts the arguments from
// Python objects to Rust values; and the Rust return value back into a Python object.
#[pyfn(m, "sum_as_string")]
fn sum_as_string_py(_py: Python, a:i64, b:i64) -> PyResult<String> {
let out = sum_as_string(a, b);
Ok(out)
fn sum_as_string_py(_py: Python, a: i64, b: i64) -> PyResult<String> {
let out = sum_as_string(a, b);
Ok(out)
}
Ok(())
}
// logic implemented as a normal rust function
fn sum_as_string(a:i64, b:i64) -> String {
fn sum_as_string(a: i64, b: i64) -> String {
format!("{}", a + b).to_string()
}

45
ui/src/components/mail/compose.rs

@ -0,0 +1,45 @@
/*
* meli - ui crate
*
* Copyright 2017-2018 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
pub struct Composer {}
impl fmt::Display for Composer {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// TODO display subject/info
write!(f, "compose")
}
}
impl Component for Composer {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
clear_area(grid, area);
context.dirty_areas.push_back(area);
}
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {}
fn is_dirty(&self) -> bool {
true
}
fn set_dirty(&mut self) {}
}

679
ui/src/components/mail/listing/compact.rs

@ -0,0 +1,679 @@
/*
* meli - ui crate.
*
* Copyright 2017-2018 Manos Pitsidianakis
*
* This file is part of meli.
*
* meli is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* meli is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use super::*;
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
/// `ThreadView`.
pub struct CompactMailListing {
/// (x, y, z): x is accounts, y is folders, z is index inside a folder.
cursor_pos: (usize, usize, usize),
new_cursor_pos: (usize, usize, usize),
length: usize,
sort: (SortField, SortOrder),
//subsort: (SortField, SortOrder),
/// Cache current view.
content: CellBuffer,
/// If we must redraw on next redraw event
dirty: bool,
/// If `self.view` exists or not.
unfocused: bool,
view: Option<ThreadView>,
}
impl Default for CompactMailListing {
fn default() -> Self {
Self::new()
}
}
impl fmt::Display for CompactMailListing {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "mail")
}
}
impl CompactMailListing {
pub fn new() -> Self {
let content = CellBuffer::new(0, 0, Cell::with_char(' '));
CompactMailListing {
cursor_pos: (0, 1, 0),
new_cursor_pos: (0, 0, 0),
length: 0,
sort: (SortField::Date, SortOrder::Desc),
//subsort: (SortField::Date, SortOrder::Asc),
content: content,
dirty: true,
unfocused: false,
view: None,
}
}
/// Fill the `self.content` `CellBuffer` with the contents of the account folder the user has
/// chosen.
fn refresh_mailbox(&mut self, context: &mut Context) {
self.dirty = true;
self.cursor_pos.2 = 0;
self.new_cursor_pos.2 = 0;
self.cursor_pos.1 = self.new_cursor_pos.1;
self.cursor_pos.0 = self.new_cursor_pos.0;
// Inform State that we changed the current folder view.
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::RefreshMailbox((self.cursor_pos.0, self.cursor_pos.1)),
});
// Get mailbox as a reference.
//
loop {
// TODO: Show progress visually
if let Ok(()) = context.accounts[self.cursor_pos.0].status(self.cursor_pos.1) {
break;
}
}
let mailbox = &mut context.accounts[self.cursor_pos.0][self.cursor_pos.1]