parent
7a6fc1ce94
commit
b98a04f35b
|
@ -9,35 +9,35 @@ use self::test::Bencher;
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_threads_1(b: &mut Bencher) {
|
fn bench_threads_1(b: &mut Bencher) {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let folder = Folder::new(String::from(""), vec![]);
|
let folder = Folder::new(String::from(""), String::from(""), vec![]);
|
||||||
MaildirType::new("").multicore(1, &folder)
|
MaildirType::new("").multicore(1, &folder)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_threads_2(b: &mut Bencher) {
|
fn bench_threads_2(b: &mut Bencher) {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let folder = Folder::new(String::from(""), vec![]);
|
let folder = Folder::new(String::from(""), String::from(""), vec![]);
|
||||||
MaildirType::new("").multicore(2, &folder)
|
MaildirType::new("").multicore(2, &folder)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_threads_3(b: &mut Bencher) {
|
fn bench_threads_3(b: &mut Bencher) {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let folder = Folder::new(String::from(""), vec![]);
|
let folder = Folder::new(String::from(""), String::from(""), vec![]);
|
||||||
MaildirType::new("").multicore(3, &folder)
|
MaildirType::new("").multicore(3, &folder)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_threads_4(b: &mut Bencher) {
|
fn bench_threads_4(b: &mut Bencher) {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let folder = Folder::new(String::from(""), vec![]);
|
let folder = Folder::new(String::from(""), String::from(""), vec![]);
|
||||||
MaildirType::new("").multicore(4, &folder)
|
MaildirType::new("").multicore(4, &folder)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_threads_6(b: &mut Bencher) {
|
fn bench_threads_6(b: &mut Bencher) {
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let folder = Folder::new(String::from(""), vec![]);
|
let folder = Folder::new(String::from(""), String::from(""), vec![]);
|
||||||
MaildirType::new("").multicore(6, &folder)
|
MaildirType::new("").multicore(6, &folder)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,53 +26,22 @@ pub mod pager;
|
||||||
|
|
||||||
use pager::PagerSettings;
|
use pager::PagerSettings;
|
||||||
|
|
||||||
use std::collections::hash_map::DefaultHasher;
|
|
||||||
use std::collections::HashMap;
|
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)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct FileAccount {
|
pub struct FileAccount {
|
||||||
folders: String,
|
root_folder: String,
|
||||||
format: String,
|
format: String,
|
||||||
sent_folder: String,
|
sent_folder: String,
|
||||||
threaded: bool,
|
threaded: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FileAccount {
|
||||||
|
pub fn folder(&self) -> &str {
|
||||||
|
&self.root_folder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct FileSettings {
|
struct FileSettings {
|
||||||
accounts: HashMap<String, FileAccount>,
|
accounts: HashMap<String, FileAccount>,
|
||||||
|
@ -82,7 +51,7 @@ struct FileSettings {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AccountSettings {
|
pub struct AccountSettings {
|
||||||
name: String,
|
name: String,
|
||||||
pub folders: Vec<Folder>,
|
root_folder: String,
|
||||||
format: String,
|
format: String,
|
||||||
pub sent_folder: String,
|
pub sent_folder: String,
|
||||||
pub threaded: bool,
|
pub threaded: bool,
|
||||||
|
@ -95,6 +64,9 @@ impl AccountSettings {
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
pub fn root_folder(&self) -> &str {
|
||||||
|
&self.root_folder
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
@ -126,53 +98,20 @@ impl Settings {
|
||||||
let mut s: HashMap<String, AccountSettings> = HashMap::new();
|
let mut s: HashMap<String, AccountSettings> = HashMap::new();
|
||||||
|
|
||||||
for (id, x) in fs.accounts {
|
for (id, x) in fs.accounts {
|
||||||
let mut folders = Vec::new();
|
let format = x.format.to_lowercase();
|
||||||
fn recurse_folders<P: AsRef<Path>>(folders: &mut Vec<Folder>, p: P) -> Vec<usize> {
|
let sent_folder = x.sent_folder;
|
||||||
let mut children = Vec::new();
|
let threaded = x.threaded;
|
||||||
for mut f in fs::read_dir(p).unwrap() {
|
let root_folder = x.root_folder;
|
||||||
for f in f.iter_mut() {
|
|
||||||
{
|
let acc = AccountSettings {
|
||||||
let path = f.path();
|
name: id.clone(),
|
||||||
if path.ends_with("cur")
|
root_folder,
|
||||||
|| path.ends_with("new")
|
format,
|
||||||
|| path.ends_with("tmp")
|
sent_folder,
|
||||||
{
|
threaded,
|
||||||
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 path = PathBuf::from(&x.folders);
|
|
||||||
let path_children = recurse_folders(&mut folders, &path);
|
s.insert(id, acc);
|
||||||
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,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings {
|
Settings {
|
||||||
|
|
|
@ -23,11 +23,11 @@
|
||||||
* An error object for `melib`
|
* An error object for `melib`
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use nom;
|
use nom;
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use async::*;
|
use async::*;
|
||||||
use conf::{AccountSettings, Folder};
|
use conf::AccountSettings;
|
||||||
use mailbox::backends::{Backends, RefreshEventConsumer};
|
use mailbox::backends::{Backends, RefreshEventConsumer};
|
||||||
use mailbox::*;
|
use mailbox::*;
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
|
@ -47,15 +47,15 @@ pub struct Account {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Account {
|
impl Account {
|
||||||
pub fn new(name: String, settings: AccountSettings, backends: &Backends) -> Self {
|
pub fn new(name: String, settings: AccountSettings, map: &Backends) -> Self {
|
||||||
let sent_folder = settings
|
let backend = map.get(settings.format())(&settings);
|
||||||
.folders
|
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()
|
.iter()
|
||||||
.position(|x| *x.path() == settings.sent_folder);
|
.position(|x: &Folder| x.name() == settings.sent_folder);
|
||||||
let mut folders = Vec::with_capacity(settings.folders.len());
|
for f in ref_folders {
|
||||||
let mut workers = Vec::new();
|
|
||||||
let backend = backends.get(settings.format());
|
|
||||||
for f in &settings.folders {
|
|
||||||
folders.push(None);
|
folders.push(None);
|
||||||
let mut handle = backend.get(&f);
|
let mut handle = backend.get(&f);
|
||||||
workers.push(Some(handle));
|
workers.push(Some(handle));
|
||||||
|
@ -73,14 +73,14 @@ impl Account {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn watch(&self, r: RefreshEventConsumer) -> () {
|
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 */
|
/* This doesn't represent the number of correctly parsed mailboxes though */
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.folders.len()
|
self.folders.len()
|
||||||
}
|
}
|
||||||
pub fn list_folders(&self) -> Vec<Folder> {
|
pub fn list_folders(&self) -> Vec<Folder> {
|
||||||
self.settings.folders.clone()
|
self.backend.folders()
|
||||||
}
|
}
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
&self.name
|
&self.name
|
||||||
|
@ -89,7 +89,8 @@ impl Account {
|
||||||
&mut self.workers
|
&mut self.workers
|
||||||
}
|
}
|
||||||
fn load_mailbox(&mut self, index: usize, envelopes: Result<Vec<Envelope>>) -> () {
|
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() {
|
if self.sent_folder.is_some() {
|
||||||
let id = self.sent_folder.unwrap();
|
let id = self.sent_folder.unwrap();
|
||||||
if id == index {
|
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() {
|
if sent[0].is_none() {
|
||||||
sent[0] = Some(Mailbox::new(sent_path, &None, envelopes.clone()));
|
sent[0] = Some(Mailbox::new(sent_path, &None, envelopes.clone()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,10 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
use async::*;
|
use async::*;
|
||||||
use conf::Folder;
|
|
||||||
use error::Result;
|
use error::Result;
|
||||||
use mailbox::backends::{MailBackend, RefreshEventConsumer};
|
use mailbox::backends::{MailBackend, RefreshEventConsumer, Folder};
|
||||||
use mailbox::email::Envelope;
|
use mailbox::email::Envelope;
|
||||||
|
|
||||||
/// `BackendOp` implementor for Imap
|
/// `BackendOp` implementor for Imap
|
||||||
|
@ -35,7 +35,6 @@ impl ImapOp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
impl BackendOp for ImapOp {
|
impl BackendOp for ImapOp {
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> String {
|
||||||
|
@ -53,7 +52,7 @@ impl BackendOp for ImapOp {
|
||||||
fn fetch_flags(&self) -> Flag {
|
fn fetch_flags(&self) -> Flag {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
|
|
||||||
/// Imap backend
|
/// Imap backend
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -72,4 +71,4 @@ impl ImapType {
|
||||||
pub fn new(_path: &str) -> Self {
|
pub fn new(_path: &str) -> Self {
|
||||||
ImapType {}
|
ImapType {}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
|
@ -20,10 +20,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use async::*;
|
use async::*;
|
||||||
use conf::Folder;
|
use conf::AccountSettings;
|
||||||
use error::{MeliError, Result};
|
use error::{MeliError, Result};
|
||||||
use mailbox::backends::{
|
use mailbox::backends::{
|
||||||
BackendOp, BackendOpGenerator, MailBackend, RefreshEvent, RefreshEventConsumer,
|
BackendFolder, BackendOp, BackendOpGenerator, Folder, MailBackend, RefreshEvent,
|
||||||
|
RefreshEventConsumer,
|
||||||
};
|
};
|
||||||
use mailbox::email::parser;
|
use mailbox::email::parser;
|
||||||
use mailbox::email::{Envelope, Flag};
|
use mailbox::email::{Envelope, Flag};
|
||||||
|
@ -43,7 +44,7 @@ use memmap::{Mmap, Protection};
|
||||||
use std::collections::hash_map::DefaultHasher;
|
use std::collections::hash_map::DefaultHasher;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::hash::Hasher;
|
use std::hash::Hasher;
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
/// `BackendOp` implementor for Maildir
|
/// `BackendOp` implementor for Maildir
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
@ -152,38 +153,42 @@ impl BackendOp for MaildirOp {
|
||||||
/// Maildir backend https://cr.yp.to/proto/maildir.html
|
/// Maildir backend https://cr.yp.to/proto/maildir.html
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct MaildirType {
|
pub struct MaildirType {
|
||||||
|
folders: Vec<MaildirFolder>,
|
||||||
path: String,
|
path: String,
|
||||||
idx: (usize, usize),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MailBackend for MaildirType {
|
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>>> {
|
fn get(&self, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
|
||||||
self.multicore(4, folder)
|
self.multicore(4, folder)
|
||||||
}
|
}
|
||||||
fn watch(&self, sender: RefreshEventConsumer, folders: &[Folder]) -> () {
|
fn watch(&self, sender: RefreshEventConsumer) -> Result<()> {
|
||||||
let folders = folders.to_vec();
|
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()
|
thread::Builder::new()
|
||||||
.name("folder watch".to_string())
|
.name("folder watch".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
let (tx, rx) = channel();
|
// Move `watcher` in the closure's scope so that it doesn't get dropped.
|
||||||
let mut watcher = watcher(tx, Duration::from_secs(1)).unwrap();
|
let _watcher = watcher;
|
||||||
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();
|
|
||||||
}
|
|
||||||
loop {
|
loop {
|
||||||
match rx.recv() {
|
match rx.recv() {
|
||||||
Ok(event) => match event {
|
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() {
|
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.pop();
|
||||||
|
@ -207,45 +212,76 @@ impl MailBackend for MaildirType {
|
||||||
Err(e) => eprintln!("watch error: {:?}", e),
|
Err(e) => eprintln!("watch error: {:?}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})?;
|
||||||
.unwrap();
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaildirType {
|
impl MaildirType {
|
||||||
pub fn new(path: &str, idx: (usize, usize)) -> Self {
|
pub fn new(f: &AccountSettings) -> Self {
|
||||||
MaildirType {
|
let mut folders: Vec<MaildirFolder> = Vec::new();
|
||||||
path: path.to_string(),
|
fn recurse_folders<P: AsRef<Path>>(folders: &mut Vec<MaildirFolder>, p: P) -> Vec<usize> {
|
||||||
idx: idx,
|
let mut children = Vec::new();
|
||||||
}
|
for mut f in fs::read_dir(p).unwrap() {
|
||||||
}
|
for f in f.iter_mut() {
|
||||||
fn is_valid(f: &Folder) -> Result<()> {
|
{
|
||||||
let path = f.path();
|
let path = f.path();
|
||||||
let mut p = PathBuf::from(path);
|
if path.ends_with("cur") || path.ends_with("new") || path.ends_with("tmp") {
|
||||||
for d in &["cur", "new", "tmp"] {
|
continue;
|
||||||
p.push(d);
|
}
|
||||||
if !p.is_dir() {
|
if path.is_dir() {
|
||||||
return Err(MeliError::new(format!(
|
let path_children = recurse_folders(folders, &path);
|
||||||
"{} is not a valid maildir folder",
|
if let Ok(f) = MaildirFolder::new(
|
||||||
path
|
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);
|
||||||
}
|
}
|
||||||
p.pop();
|
|
||||||
}
|
}
|
||||||
Ok(())
|
MaildirType {
|
||||||
|
folders,
|
||||||
|
path: f.root_folder().to_string(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
fn owned_folder_idx(&self, folder: &Folder) -> usize {
|
||||||
|
for (idx, f) in self.folders.iter().enumerate() {
|
||||||
|
if f.hash() == folder.hash() {
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn multicore(&self, cores: usize, folder: &Folder) -> Async<Result<Vec<Envelope>>> {
|
pub fn multicore(&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();
|
||||||
// TODO: Avoid clone
|
// 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()
|
thread::Builder::new()
|
||||||
.name(format!("parsing {:?}", folder))
|
.name(name)
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
MaildirType::is_valid(&folder)?;
|
|
||||||
let path = folder.path();
|
|
||||||
let mut path = PathBuf::from(path);
|
let mut path = PathBuf::from(path);
|
||||||
path.push("cur");
|
path.push("cur");
|
||||||
let iter = path.read_dir()?;
|
let iter = path.read_dir()?;
|
||||||
|
@ -310,3 +346,64 @@ impl MaildirType {
|
||||||
w.build(handle)
|
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(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -23,10 +23,10 @@
|
||||||
* https://wiki2.dovecot.org/MailboxFormat/mbox
|
* https://wiki2.dovecot.org/MailboxFormat/mbox
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
use async::*;
|
use async::*;
|
||||||
use conf::Folder;
|
|
||||||
use error::Result;
|
use error::Result;
|
||||||
use mailbox::backends::{MailBackend, RefreshEventConsumer};
|
use mailbox::backends::{MailBackend, RefreshEventConsumer, Folder};
|
||||||
use mailbox::email::Envelope;
|
use mailbox::email::Envelope;
|
||||||
|
|
||||||
/// `BackendOp` implementor for Mbox
|
/// `BackendOp` implementor for Mbox
|
||||||
|
@ -39,7 +39,6 @@ impl MboxOp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
impl BackendOp for MboxOp {
|
impl BackendOp for MboxOp {
|
||||||
fn description(&self) -> String {
|
fn description(&self) -> String {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
|
@ -60,7 +59,6 @@ impl BackendOp for MboxOp {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
/// Mbox backend
|
/// Mbox backend
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -80,3 +78,4 @@ impl MboxType {
|
||||||
MboxType {}
|
MboxType {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -23,22 +23,25 @@ pub mod maildir;
|
||||||
pub mod mbox;
|
pub mod mbox;
|
||||||
|
|
||||||
use async::*;
|
use async::*;
|
||||||
use conf::Folder;
|
use conf::AccountSettings;
|
||||||
use error::Result;
|
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::maildir::MaildirType;
|
||||||
use mailbox::backends::mbox::MboxType;
|
|
||||||
use mailbox::email::{Envelope, Flag};
|
use mailbox::email::{Envelope, Flag};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
extern crate fnv;
|
extern crate fnv;
|
||||||
use self::fnv::FnvHashMap;
|
use self::fnv::FnvHashMap;
|
||||||
use std;
|
use std;
|
||||||
|
|
||||||
|
pub type BackendCreator = Box<Fn(&AccountSettings) -> Box<MailBackend>>;
|
||||||
|
|
||||||
/// A hashmap containing all available mail backends.
|
/// A hashmap containing all available mail backends.
|
||||||
/// An abstraction over any available backends.
|
/// An abstraction over any available backends.
|
||||||
pub struct Backends {
|
pub struct Backends {
|
||||||
map: FnvHashMap<std::string::String, Box<Fn() -> Box<MailBackend>>>,
|
map: FnvHashMap<std::string::String, Box<Fn() -> BackendCreator>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Backends {
|
impl Backends {
|
||||||
|
@ -48,20 +51,20 @@ impl Backends {
|
||||||
};
|
};
|
||||||
b.register(
|
b.register(
|
||||||
"maildir".to_string(),
|
"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("mbox".to_string(), Box::new(|| Box::new(MboxType::new(""))));
|
||||||
b.register("imap".to_string(), Box::new(|| Box::new(ImapType::new(""))));
|
//b.register("imap".to_string(), Box::new(|| Box::new(ImapType::new(""))));
|
||||||
b
|
b
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(&self, key: &str) -> Box<MailBackend> {
|
pub fn get(&self, key: &str) -> BackendCreator {
|
||||||
if !self.map.contains_key(key) {
|
if !self.map.contains_key(key) {
|
||||||
panic!("{} is not a valid mail backend", key);
|
panic!("{} is not a valid mail backend", key);
|
||||||
}
|
}
|
||||||
self.map[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) {
|
if self.map.contains_key(&key) {
|
||||||
panic!("{} is an already registered backend", key);
|
panic!("{} is an already registered backend", key);
|
||||||
}
|
}
|
||||||
|
@ -90,8 +93,8 @@ 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(&self, folder: &Folder) -> Async<Result<Vec<Envelope>>>;
|
||||||
fn watch(&self, sender: RefreshEventConsumer, folders: &[Folder]) -> ();
|
fn watch(&self, sender: RefreshEventConsumer) -> Result<()>;
|
||||||
//fn new(folders: &Vec<String>) -> Box<Self>;
|
fn folders(&self) -> Vec<Folder>;
|
||||||
//login function
|
//login function
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,3 +171,37 @@ impl fmt::Debug for BackendOpGenerator {
|
||||||
write!(f, "BackendOpGenerator: {}", op.description())
|
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>;
|
||||||
|
|
|
@ -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 {
|
fn from(b: &'a [u8]) -> Self {
|
||||||
// TODO: Case insensitivity
|
// TODO: Case insensitivity
|
||||||
match b {
|
match b {
|
||||||
|
@ -69,9 +69,10 @@ pub enum ContentType {
|
||||||
|
|
||||||
impl Default for ContentType {
|
impl Default for ContentType {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ContentType::Text{ charset: Charset::UTF8 }
|
ContentType::Text {
|
||||||
|
charset: Charset::UTF8,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for ContentType {
|
impl Display for ContentType {
|
||||||
|
@ -128,4 +129,3 @@ pub enum ContentTransferEncoding {
|
||||||
QuotedPrintable,
|
QuotedPrintable,
|
||||||
Other { tag: Vec<u8> },
|
Other { tag: Vec<u8> },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
use std::fmt;
|
|
||||||
use std::str;
|
|
||||||
use data_encoding::BASE64_MIME;
|
use data_encoding::BASE64_MIME;
|
||||||
use mailbox::email::parser;
|
use mailbox::email::parser;
|
||||||
|
use std::fmt;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
pub use mailbox::email::attachment_types::*;
|
pub use mailbox::email::attachment_types::*;
|
||||||
|
|
||||||
|
@ -44,7 +44,14 @@ impl fmt::Debug for AttachmentType {
|
||||||
match self {
|
match self {
|
||||||
AttachmentType::Data { .. } => write!(f, "AttachmentType::Data {{ .. }}"),
|
AttachmentType::Data { .. } => write!(f, "AttachmentType::Data {{ .. }}"),
|
||||||
AttachmentType::Text { .. } => write!(f, "AttachmentType::Text {{ .. }}"),
|
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 {
|
impl AttachmentBuilder {
|
||||||
pub fn new(content: &[u8]) -> Self {
|
pub fn new(content: &[u8]) -> Self {
|
||||||
AttachmentBuilder {
|
AttachmentBuilder {
|
||||||
content_type: (Default::default() , ContentSubType::Plain),
|
content_type: (Default::default(), ContentSubType::Plain),
|
||||||
content_transfer_encoding: ContentTransferEncoding::_7Bit,
|
content_transfer_encoding: ContentTransferEncoding::_7Bit,
|
||||||
raw: content.to_vec(),
|
raw: content.to_vec(),
|
||||||
}
|
}
|
||||||
|
@ -124,7 +131,9 @@ impl AttachmentBuilder {
|
||||||
self.content_type.0 = Default::default();
|
self.content_type.0 = Default::default();
|
||||||
for (n, v) in params {
|
for (n, v) in params {
|
||||||
if n.eq_ignore_ascii_case(b"charset") {
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,7 +177,7 @@ impl AttachmentBuilder {
|
||||||
fn decode(&self) -> Vec<u8> {
|
fn decode(&self) -> Vec<u8> {
|
||||||
// TODO merge this and standalone decode() function
|
// TODO merge this and standalone decode() function
|
||||||
let charset = match self.content_type.0 {
|
let charset = match self.content_type.0 {
|
||||||
ContentType::Text{ charset: c } => c,
|
ContentType::Text { charset: c } => c,
|
||||||
_ => Default::default(),
|
_ => Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -180,13 +189,17 @@ impl AttachmentBuilder {
|
||||||
ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_bytes(&self.raw)
|
ContentTransferEncoding::QuotedPrintable => parser::quoted_printable_bytes(&self.raw)
|
||||||
.to_full_result()
|
.to_full_result()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
ContentTransferEncoding::_7Bit
|
ContentTransferEncoding::_7Bit
|
||||||
| ContentTransferEncoding::_8Bit
|
| ContentTransferEncoding::_8Bit
|
||||||
| ContentTransferEncoding::Other { .. } => self.raw.to_vec(),
|
| ContentTransferEncoding::Other { .. } => self.raw.to_vec(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let decoded_result = parser::decode_charset(&bytes, charset);
|
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 {
|
pub fn build(self) -> Attachment {
|
||||||
let attachment_type = match self.content_type.0 {
|
let attachment_type = match self.content_type.0 {
|
||||||
|
@ -197,7 +210,9 @@ impl AttachmentBuilder {
|
||||||
let multipart_type = match self.content_type.1 {
|
let multipart_type = match self.content_type.1 {
|
||||||
ContentSubType::Other { ref tag } => match &tag[..] {
|
ContentSubType::Other { ref tag } => match &tag[..] {
|
||||||
b"mixed" | b"Mixed" | b"MIXED" => MultipartType::Mixed,
|
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,
|
b"digest" | b"Digest" | b"DIGEST" => MultipartType::Digest,
|
||||||
_ => MultipartType::Unsupported { tag: tag.clone() },
|
_ => MultipartType::Unsupported { tag: tag.clone() },
|
||||||
},
|
},
|
||||||
|
@ -261,14 +276,15 @@ impl AttachmentBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl fmt::Display for Attachment {
|
impl fmt::Display for Attachment {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match self.attachment_type {
|
match self.attachment_type {
|
||||||
AttachmentType::Data { .. } => {
|
AttachmentType::Data { .. } => {
|
||||||
write!(f, "Data attachment of type {}", self.mime_type())
|
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 {
|
AttachmentType::Multipart {
|
||||||
of_type: ref multipart_type,
|
of_type: ref multipart_type,
|
||||||
subattachments: ref sub_att_vec,
|
subattachments: ref sub_att_vec,
|
||||||
|
@ -276,10 +292,16 @@ impl fmt::Display for Attachment {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{} attachment with {} subs",
|
"{} attachment with {} subs",
|
||||||
self.mime_type(), sub_att_vec.len()
|
self.mime_type(),
|
||||||
|
sub_att_vec.len()
|
||||||
)
|
)
|
||||||
} else {
|
} 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 {
|
for a in sub_att_vec {
|
||||||
count_recursive(a, ret);
|
count_recursive(a, ret);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,7 +396,7 @@ fn decode_rec_helper(a: &Attachment, filter: &Option<Box<Fn(&Attachment) -> Vec<
|
||||||
return filter(a);
|
return filter(a);
|
||||||
}
|
}
|
||||||
match a.attachment_type {
|
match a.attachment_type {
|
||||||
AttachmentType::Data { .. } => { Vec::new()},
|
AttachmentType::Data { .. } => Vec::new(),
|
||||||
AttachmentType::Text { .. } => decode_helper(a, filter),
|
AttachmentType::Text { .. } => decode_helper(a, filter),
|
||||||
AttachmentType::Multipart {
|
AttachmentType::Multipart {
|
||||||
of_type: ref multipart_type,
|
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 {
|
let charset = match a.content_type.0 {
|
||||||
ContentType::Text{ charset: c } => c,
|
ContentType::Text { charset: c } => c,
|
||||||
_ => Default::default(),
|
_ => 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() {
|
if a.content_type().0.is_text() {
|
||||||
let decoded_result = parser::decode_charset(&bytes, charset);
|
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 {
|
} else {
|
||||||
bytes.to_vec()
|
bytes.to_vec()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
/// `Envelope` represents all the data of an email we need to know.
|
||||||
///
|
///
|
||||||
/// Attachments (the email's body) is parsed on demand with `body`.
|
/// Attachments (the email's body) is parsed on demand with `body`.
|
||||||
|
|
|
@ -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
|
/* TODO: make a map of encodings and decoding functions so that they can be reused and easily
|
||||||
* extended */
|
* extended */
|
||||||
use encoding::all::{ISO_8859_1, ISO_8859_2, ISO_8859_7, WINDOWS_1253, 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>> {
|
fn encoded_word(input: &[u8]) -> IResult<&[u8], Vec<u8>> {
|
||||||
if input.len() < 5 {
|
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> {
|
pub fn decode_charset(s: &[u8], charset: Charset) -> Result<String> {
|
||||||
match charset {
|
match charset {
|
||||||
Charset::UTF8 | Charset::Ascii => {
|
Charset::UTF8 | Charset::Ascii => Ok(String::from_utf8(s.to_vec()).unwrap()),
|
||||||
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_7 => {
|
Charset::ISO8859_2 => Ok(ISO_8859_2.decode(s, DecoderTrap::Strict)?),
|
||||||
Ok(ISO_8859_7.decode(s, DecoderTrap::Strict)?)
|
Charset::GBK => Ok(GBK.decode(s, DecoderTrap::Strict)?),
|
||||||
}
|
Charset::Windows1252 => Ok(WINDOWS_1252.decode(s, DecoderTrap::Strict)?),
|
||||||
Charset::ISO8859_1 => {
|
Charset::Windows1253 => Ok(WINDOWS_1253.decode(s, DecoderTrap::Strict)?),
|
||||||
Ok(ISO_8859_1.decode(s, DecoderTrap::Strict)?)
|
Charset::GB2312 => unimplemented!(),
|
||||||
}
|
Charset::UTF16 => unimplemented!(),
|
||||||
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>>,
|
pub quoted_printable_bytes<Vec<u8>>,
|
||||||
many0!(alt_complete!(
|
many0!(alt_complete!(
|
||||||
preceded!(quoted_printable_soft_break, quoted_printable_byte) |
|
preceded!(quoted_printable_soft_break, quoted_printable_byte) |
|
||||||
preceded!(quoted_printable_soft_break, le_u8)
|
preceded!(quoted_printable_soft_break, le_u8) | quoted_printable_byte | le_u8
|
||||||
| quoted_printable_byte | le_u8
|
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
named!(
|
named!(
|
||||||
encoded_word_list<Vec<u8>>,
|
encoded_word_list<Vec<u8>>,
|
||||||
ws!(do_parse!(
|
ws!(do_parse!(
|
||||||
|
@ -397,7 +377,10 @@ fn display_addr(input: &[u8]) -> IResult<&[u8], Address> {
|
||||||
IResult::Done(rest, raw) => {
|
IResult::Done(rest, raw) => {
|
||||||
display_name.length = raw.find(b"<").unwrap().saturating_sub(1);
|
display_name.length = raw.find(b"<").unwrap().saturating_sub(1);
|
||||||
address_spec.offset = display_name.length + 2;
|
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(
|
IResult::Done(
|
||||||
rest,
|
rest,
|
||||||
Address::Mailbox(MailboxAddress {
|
Address::Mailbox(MailboxAddress {
|
||||||
|
|
|
@ -30,18 +30,16 @@ pub use self::email::*;
|
||||||
/* Mail backends. Currently only maildir is supported */
|
/* Mail backends. Currently only maildir is supported */
|
||||||
pub mod backends;
|
pub mod backends;
|
||||||
use error::Result;
|
use error::Result;
|
||||||
use mailbox::backends::MailBackend;
|
use mailbox::backends::{folder_default, Folder, MailBackend};
|
||||||
pub mod accounts;
|
pub mod accounts;
|
||||||
pub use mailbox::accounts::Account;
|
pub use mailbox::accounts::Account;
|
||||||
pub mod thread;
|
pub mod thread;
|
||||||
pub use mailbox::thread::{build_threads, Container};
|
pub use mailbox::thread::{build_threads, Container};
|
||||||
|
|
||||||
use conf::Folder;
|
|
||||||
|
|
||||||
use std::option::Option;
|
use std::option::Option;
|
||||||
|
|
||||||
/// `Mailbox` represents a folder of mail.
|
/// `Mailbox` represents a folder of mail.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug)]
|
||||||
pub struct Mailbox {
|
pub struct Mailbox {
|
||||||
pub folder: Folder,
|
pub folder: Folder,
|
||||||
pub collection: Vec<Envelope>,
|
pub collection: Vec<Envelope>,
|
||||||
|
@ -49,10 +47,21 @@ pub struct Mailbox {
|
||||||
pub threads: Vec<Container>,
|
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 {
|
impl Mailbox {
|
||||||
pub fn new_dummy() -> Self {
|
pub fn new_dummy() -> Self {
|
||||||
Mailbox {
|
Mailbox {
|
||||||
folder: Folder::default(),
|
folder: folder_default(),
|
||||||
collection: Vec::with_capacity(0),
|
collection: Vec::with_capacity(0),
|
||||||
threaded_collection: Vec::with_capacity(0),
|
threaded_collection: Vec::with_capacity(0),
|
||||||
threads: 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()));
|
collection.sort_by(|a, b| a.date().cmp(&b.date()));
|
||||||
let (threads, threaded_collection) = build_threads(&mut collection, sent_folder);
|
let (threads, threaded_collection) = build_threads(&mut collection, sent_folder);
|
||||||
Ok(Mailbox {
|
Ok(Mailbox {
|
||||||
folder: folder.clone(),
|
folder: (*folder).clone(),
|
||||||
collection: collection,
|
collection: collection,
|
||||||
threads: threads,
|
threads: threads,
|
||||||
threaded_collection: threaded_collection,
|
threaded_collection: threaded_collection,
|
||||||
|
|
11
src/bin.rs
11
src/bin.rs
|
@ -59,9 +59,7 @@ fn make_input_thread(
|
||||||
sx.send(ThreadEvent::Input(k));
|
sx.send(ThreadEvent::Input(k));
|
||||||
},
|
},
|
||||||
|| {
|
|| {
|
||||||
sx.send(ThreadEvent::UIEvent(UIEventType::ChangeMode(
|
sx.send(ThreadEvent::UIEvent(UIEventType::ChangeMode(UIMode::Fork)));
|
||||||
UIMode::Fork,
|
|
||||||
)));
|
|
||||||
},
|
},
|
||||||
&rx,
|
&rx,
|
||||||
)
|
)
|
||||||
|
@ -104,9 +102,10 @@ fn main() {
|
||||||
let b = Entity {
|
let b = Entity {
|
||||||
component: Box::new(listing),
|
component: Box::new(listing),
|
||||||
};
|
};
|
||||||
let window = Entity {
|
let mut tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true))]));
|
||||||
component: Box::new(VSplit::new(menu, b, 90, true)),
|
tabs.add_component(Box::new(Composer {}));
|
||||||
};
|
let window = Entity { component: tabs };
|
||||||
|
|
||||||
let status_bar = Entity {
|
let status_bar = Entity {
|
||||||
component: Box::new(StatusBar::new(window)),
|
component: Box::new(StatusBar::new(window)),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,22 +1,21 @@
|
||||||
#![cfg(feature = "python")]
|
#![cfg(feature = "python")]
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
|
|
||||||
#[pymodinit(pythonmeli)]
|
#[pymodinit(pythonmeli)]
|
||||||
fn pythonmeli(py: Python, m: &PyModule) -> PyResult<()> {
|
fn pythonmeli(py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
// pyo3 aware function. All of our python interface could be declared in a separate module.
|
// 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
|
// 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.
|
// Python objects to Rust values; and the Rust return value back into a Python object.
|
||||||
#[pyfn(m, "sum_as_string")]
|
#[pyfn(m, "sum_as_string")]
|
||||||
fn sum_as_string_py(_py: Python, a:i64, b:i64) -> PyResult<String> {
|
fn sum_as_string_py(_py: Python, a: i64, b: i64) -> PyResult<String> {
|
||||||
let out = sum_as_string(a, b);
|
let out = sum_as_string(a, b);
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// logic implemented as a normal rust function
|
// 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()
|
format!("{}", a + b).to_string()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {}
|
||||||
|
}
|
|
@ -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]
|
||||||
|
.as_ref()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.length = mailbox.threads.len();
|
||||||
|
let mut content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
|
||||||
|
if self.length == 0 {
|
||||||
|
write_string_to_grid(
|
||||||
|
&format!("Folder `{}` is empty.", mailbox.folder.name()),
|
||||||
|
&mut content,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((0, 0), (MAX_COLS - 1, 0)),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
self.content = content;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Fix the threaded hell and refactor stuff into seperate functions and/or modules.
|
||||||
|
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
||||||
|
let mut thread_idx = 0; // needed for alternate thread colors
|
||||||
|
/* Draw threaded view. */
|
||||||
|
let mut local_collection: Vec<usize> = mailbox.threaded_collection.clone();
|
||||||
|
let threads: &Vec<Container> = &mailbox.threads;
|
||||||
|
local_collection.sort_by(|a, b| match self.sort {
|
||||||
|
(SortField::Date, SortOrder::Desc) => {
|
||||||
|
mailbox.thread(*b).date().cmp(&mailbox.thread(*a).date())
|
||||||
|
}
|
||||||
|
(SortField::Date, SortOrder::Asc) => {
|
||||||
|
mailbox.thread(*a).date().cmp(&mailbox.thread(*b).date())
|
||||||
|
}
|
||||||
|
(SortField::Subject, SortOrder::Desc) => {
|
||||||
|
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.subject().cmp(&mb.subject())
|
||||||
|
}
|
||||||
|
(SortField::Subject, SortOrder::Asc) => {
|
||||||
|
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.subject().cmp(&ma.subject())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let mut iter = local_collection.iter().enumerate().peekable();
|
||||||
|
let len = mailbox
|
||||||
|
.threaded_collection
|
||||||
|
.len()
|
||||||
|
.to_string()
|
||||||
|
.chars()
|
||||||
|
.count();
|
||||||
|
/* This is just a desugared for loop so that we can use .peek() */
|
||||||
|
while let Some((idx, i)) = iter.next() {
|
||||||
|
let container = &threads[*i];
|
||||||
|
let indentation = container.indentation();
|
||||||
|
|
||||||
|
if indentation == 0 {
|
||||||
|
thread_idx += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(container.has_message());
|
||||||
|
match iter.peek() {
|
||||||
|
Some(&(_, x)) if threads[*x].indentation() == indentation => {
|
||||||
|
indentations.pop();
|
||||||
|
indentations.push(true);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
indentations.pop();
|
||||||
|
indentations.push(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if container.has_sibling() {
|
||||||
|
indentations.pop();
|
||||||
|
indentations.push(true);
|
||||||
|
}
|
||||||
|
let envelope: &Envelope = &mailbox.collection[container.message().unwrap()];
|
||||||
|
let fg_color = if !envelope.is_seen() {
|
||||||
|
Color::Byte(0)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
let bg_color = if !envelope.is_seen() {
|
||||||
|
Color::Byte(251)
|
||||||
|
} else if thread_idx % 2 == 0 {
|
||||||
|
Color::Byte(236)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
let (x, _) = write_string_to_grid(
|
||||||
|
&CompactMailListing::make_thread_entry(
|
||||||
|
envelope,
|
||||||
|
idx,
|
||||||
|
indentation,
|
||||||
|
container,
|
||||||
|
&indentations,
|
||||||
|
len,
|
||||||
|
),
|
||||||
|
&mut content,
|
||||||
|
fg_color,
|
||||||
|
bg_color,
|
||||||
|
((0, idx), (MAX_COLS - 1, idx)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
for x in x..MAX_COLS {
|
||||||
|
content[(x, idx)].set_ch(' ');
|
||||||
|
content[(x, idx)].set_bg(bg_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
match iter.peek() {
|
||||||
|
Some(&(_, x)) if threads[*x].indentation() > indentation => {
|
||||||
|
indentations.push(false);
|
||||||
|
}
|
||||||
|
Some(&(_, x)) if threads[*x].indentation() < indentation => {
|
||||||
|
for _ in 0..(indentation - threads[*x].indentation()) {
|
||||||
|
indentations.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn highlight_line_self(&mut self, idx: usize, context: &Context) {
|
||||||
|
let threaded = context.accounts[self.cursor_pos.0]
|
||||||
|
.runtime_settings
|
||||||
|
.threaded;
|
||||||
|
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||||
|
.as_ref()
|
||||||
|
.unwrap();
|
||||||
|
let envelope: &Envelope = if threaded {
|
||||||
|
let i = mailbox.threaded_mail(idx);
|
||||||
|
&mailbox.collection[i]
|
||||||
|
} else {
|
||||||
|
&mailbox.collection[idx]
|
||||||
|
};
|
||||||
|
|
||||||
|
let fg_color = if !envelope.is_seen() {
|
||||||
|
Color::Byte(0)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
let bg_color = if !envelope.is_seen() {
|
||||||
|
Color::Byte(251)
|
||||||
|
} else if idx % 2 == 0 {
|
||||||
|
Color::Byte(236)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
change_colors(
|
||||||
|
&mut self.content,
|
||||||
|
((0, idx), (MAX_COLS - 1, idx)),
|
||||||
|
fg_color,
|
||||||
|
bg_color,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn highlight_line(&self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context) {
|
||||||
|
let threaded = context.accounts[self.cursor_pos.0]
|
||||||
|
.runtime_settings
|
||||||
|
.threaded;
|
||||||
|
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||||
|
.as_ref()
|
||||||
|
.unwrap();
|
||||||
|
let envelope: &Envelope = if threaded {
|
||||||
|
let i = mailbox.threaded_mail(idx);
|
||||||
|
&mailbox.collection[i]
|
||||||
|
} else {
|
||||||
|
&mailbox.collection[idx]
|
||||||
|
};
|
||||||
|
|
||||||
|
let fg_color = if !envelope.is_seen() {
|
||||||
|
Color::Byte(0)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
let bg_color = if self.cursor_pos.2 == idx {
|
||||||
|
Color::Byte(246)
|
||||||
|
} else if !envelope.is_seen() {
|
||||||
|
Color::Byte(251)
|
||||||
|
} else if idx % 2 == 0 {
|
||||||
|
Color::Byte(236)
|
||||||
|
} else {
|
||||||
|
Color::Default
|
||||||
|
};
|
||||||
|
change_colors(grid, area, fg_color, bg_color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draw the list of `Envelope`s.
|
||||||
|
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
if self.cursor_pos.1 != self.new_cursor_pos.1 {
|
||||||
|
self.refresh_mailbox(context);
|
||||||
|
}
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
|
if self.length == 0 {
|
||||||
|
clear_area(grid, area);
|
||||||
|
copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, 0)));
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let rows = get_y(bottom_right) - get_y(upper_left) + 1;
|
||||||
|
let prev_page_no = (self.cursor_pos.2).wrapping_div(rows);
|
||||||
|
let page_no = (self.new_cursor_pos.2).wrapping_div(rows);
|
||||||
|
|
||||||
|
let top_idx = page_no * rows;
|
||||||
|
|
||||||
|
/* If cursor position has changed, remove the highlight from the previous position and
|
||||||
|
* apply it in the new one. */
|
||||||
|
if self.cursor_pos.2 != self.new_cursor_pos.2 && prev_page_no == page_no {
|
||||||
|
let old_cursor_pos = self.cursor_pos;
|
||||||
|
self.cursor_pos = self.new_cursor_pos;
|
||||||
|
for idx in &[old_cursor_pos.2, self.new_cursor_pos.2] {
|
||||||
|
if *idx >= self.length {
|
||||||
|
continue; //bounds check
|
||||||
|
}
|
||||||
|
let new_area = (
|
||||||
|
set_y(upper_left, get_y(upper_left) + (*idx % rows)),
|
||||||
|
set_y(bottom_right, get_y(upper_left) + (*idx % rows)),
|
||||||
|
);
|
||||||
|
self.highlight_line(grid, new_area, *idx, context);
|
||||||
|
context.dirty_areas.push_back(new_area);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} else if self.cursor_pos != self.new_cursor_pos {
|
||||||
|
self.cursor_pos = self.new_cursor_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Page_no has changed, so draw new page */
|
||||||
|
copy_area(
|
||||||
|
grid,
|
||||||
|
&self.content,
|
||||||
|
area,
|
||||||
|
((0, top_idx), (MAX_COLS - 1, self.length)),
|
||||||
|
);
|
||||||
|
self.highlight_line(
|
||||||
|
grid,
|
||||||
|
(
|
||||||
|
set_y(upper_left, get_y(upper_left) + (self.cursor_pos.2 % rows)),
|
||||||
|
set_y(bottom_right, get_y(upper_left) + (self.cursor_pos.2 % rows)),
|
||||||
|
),
|
||||||
|
self.cursor_pos.2,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_thread_entry(
|
||||||
|
envelope: &Envelope,
|
||||||
|
idx: usize,
|
||||||
|
indent: usize,
|
||||||
|
container: &Container,
|
||||||
|
indentations: &[bool],
|
||||||
|
idx_width: usize,
|
||||||
|
) -> String {
|
||||||
|
let has_sibling = container.has_sibling();
|
||||||
|
let has_parent = container.has_parent();
|
||||||
|
let show_subject = container.show_subject();
|
||||||
|
|
||||||
|
let mut s = format!(
|
||||||
|
"{}{}{} ",
|
||||||
|
idx,
|
||||||
|
" ".repeat(idx_width + 2 - (idx.to_string().chars().count())),
|
||||||
|
CompactMailListing::format_date(&envelope)
|
||||||
|
);
|
||||||
|
for i in 0..indent {
|
||||||
|
if indentations.len() > i && indentations[i] {
|
||||||
|
s.push('│');
|
||||||
|
} else {
|
||||||
|
s.push(' ');
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
s.push(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if indent > 0 {
|
||||||
|
if has_sibling && has_parent {
|
||||||
|
s.push('├');
|
||||||
|
} else if has_sibling {
|
||||||
|
s.push('┬');
|
||||||
|
} else {
|
||||||
|
s.push('└');
|
||||||
|
}
|
||||||
|
s.push('─');
|
||||||
|
s.push('>');
|
||||||
|
}
|
||||||
|
|
||||||
|
if show_subject {
|
||||||
|
s.push_str(&format!("{:.85}", envelope.subject()));
|
||||||
|
}
|
||||||
|
let attach_count = envelope.body().count_attachments();
|
||||||
|
if attach_count > 1 {
|
||||||
|
s.push_str(&format!(" {}∞ ", attach_count - 1));
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
fn format_date(envelope: &Envelope) -> String {
|
||||||
|
let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date());
|
||||||
|
let now: std::time::Duration = std::time::SystemTime::now().duration_since(d).unwrap();
|
||||||
|
match now.as_secs() {
|
||||||
|
n if n < 10 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(8)),
|
||||||
|
n if n < 24 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(7)),
|
||||||
|
n if n < 4 * 24 * 60 * 60 => {
|
||||||
|
format!("{} days ago{}", n / (24 * 60 * 60), " ".repeat(9))
|
||||||
|
}
|
||||||
|
_ => envelope.datetime().format("%Y-%m-%d %H:%M:%S").to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for CompactMailListing {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
if !self.unfocused {
|
||||||
|
if !self.is_dirty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.dirty = false;
|
||||||
|
/* Draw the entire list */
|
||||||
|
self.draw_list(grid, area, context);
|
||||||
|
} else {
|
||||||
|
let upper_left = upper_left!(area);
|
||||||
|
let bottom_right = bottom_right!(area);
|
||||||
|
if self.length == 0 && self.dirty {
|
||||||
|
clear_area(grid, area);
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Render the mail body in a pager, basically copy what HSplit does */
|
||||||
|
let total_rows = get_y(bottom_right) - get_y(upper_left);
|
||||||
|
let pager_ratio = context.runtime_settings.pager.pager_ratio;
|
||||||
|
let bottom_entity_rows = (pager_ratio * total_rows) / 100;
|
||||||
|
|
||||||
|
if bottom_entity_rows > total_rows {
|
||||||
|
clear_area(grid, area);
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* Mark message as read */
|
||||||
|
let idx = self.cursor_pos.2;
|
||||||
|
let must_highlight = {
|
||||||
|
if self.length == 0 {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
let threaded = context.accounts[self.cursor_pos.0]
|
||||||
|
.runtime_settings
|
||||||
|
.threaded;
|
||||||
|
let mailbox = &mut context.accounts[self.cursor_pos.0][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]
|
||||||
|
};
|
||||||
|
if !envelope.is_seen() {
|
||||||
|
envelope.set_seen().unwrap();
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if must_highlight {
|
||||||
|
self.highlight_line_self(idx, context);
|
||||||
|
}
|
||||||
|
let mid = get_y(upper_left) + total_rows - bottom_entity_rows;
|
||||||
|
self.draw_list(
|
||||||
|
grid,
|
||||||
|
(
|
||||||
|
upper_left,
|
||||||
|
(get_x(bottom_right), get_y(upper_left) + mid - 1),
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
if self.length == 0 {
|
||||||
|
self.dirty = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
/* TODO: Move the box drawing business in separate functions */
|
||||||
|
if get_x(upper_left) > 0 && grid[(get_x(upper_left) - 1, mid)].ch() == VERT_BOUNDARY
|
||||||
|
{
|
||||||
|
grid[(get_x(upper_left) - 1, mid)].set_ch(LIGHT_VERTICAL_AND_RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in get_x(upper_left)..=get_x(bottom_right) {
|
||||||
|
grid[(i, mid)].set_ch('─');
|
||||||
|
}
|
||||||
|
context
|
||||||
|
.dirty_areas
|
||||||
|
.push_back((set_y(upper_left, mid), set_y(bottom_right, mid)));
|
||||||
|
}
|
||||||
|
// TODO: Make headers view configurable
|
||||||
|
|
||||||
|
if !self.dirty {
|
||||||
|
if let Some(v) = self.view.as_mut() {
|
||||||
|
v.draw(grid, (set_y(upper_left, mid + 1), bottom_right), context);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.view = Some(ThreadView::new(Vec::new()));
|
||||||
|
self.view.as_mut().unwrap().draw(
|
||||||
|
grid,
|
||||||
|
(set_y(upper_left, mid + 1), bottom_right),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
self.dirty = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||||
|
match event.event_type {
|
||||||
|
UIEventType::Input(Key::Up) => {
|
||||||
|
if self.cursor_pos.2 > 0 {
|
||||||
|
self.new_cursor_pos.2 -= 1;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UIEventType::Input(Key::Down) => {
|
||||||
|
if self.length > 0 && self.new_cursor_pos.2 < self.length - 1 {
|
||||||
|
self.new_cursor_pos.2 += 1;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UIEventType::Input(Key::Char('\n')) if !self.unfocused => {
|
||||||
|
self.unfocused = true;
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
UIEventType::Input(Key::Char('m')) if !self.unfocused => {
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
/* Kill input thread so that spawned command can be sole receiver of stdin */
|
||||||
|
{
|
||||||
|
/* I tried thread::park() here but for some reason it never blocked and always
|
||||||
|
* returned. Spinlocks are also useless because you have to keep the mutex
|
||||||
|
* guard alive til the child process exits, which requires some effort.
|
||||||
|
*
|
||||||
|
* The only problem with this approach is tht the user has to send some input
|
||||||
|
* in order for the input-thread to wake up and realise it should kill itself.
|
||||||
|
*
|
||||||
|
* I tried writing to stdin/tty manually but for some reason rustty didn't
|
||||||
|
* acknowledge it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* tx sends to input-thread and it kills itself.
|
||||||
|
*/
|
||||||
|
let tx = context.input_thread();
|
||||||
|
tx.send(true);
|
||||||
|
}
|
||||||
|
let mut f = create_temp_file(&new_draft(context), None);
|
||||||
|
//let mut f = Box::new(std::fs::File::create(&dir).unwrap());
|
||||||
|
|
||||||
|
// TODO: check exit status
|
||||||
|
let mut output = Command::new("vim")
|
||||||
|
.arg("+/^$")
|
||||||
|
.arg(&f.path())
|
||||||
|
.stdin(Stdio::inherit())
|
||||||
|
.stdout(Stdio::inherit())
|
||||||
|
.spawn()
|
||||||
|
.expect("failed to execute process");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Main loop will wait on children and when they reap them the loop spawns a new
|
||||||
|
* input-thread
|
||||||
|
*/
|
||||||
|
context.replies.push_back(UIEvent {
|
||||||
|
id: 0,
|
||||||
|
event_type: UIEventType::Fork(ForkType::NewDraft(f, output)),
|
||||||
|
});
|
||||||
|
context.replies.push_back(UIEvent {
|
||||||
|
id: 0,
|
||||||
|
event_type: UIEventType::ChangeMode(UIMode::Fork),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UIEventType::Input(Key::Char('i')) if self.unfocused => {
|
||||||
|
self.unfocused = false;
|
||||||
|
self.dirty = true;
|
||||||
|
self.view = None;
|
||||||
|
}
|
||||||
|
UIEventType::Input(Key::Char(k @ 'J')) | UIEventType::Input(Key::Char(k @ 'K')) => {
|
||||||
|
let folder_length = context.accounts[self.cursor_pos.0].len();
|
||||||
|
let accounts_length = context.accounts.len();
|
||||||
|
match k {
|
||||||
|
'J' if folder_length > 0 => {
|
||||||
|
if self.new_cursor_pos.1 < folder_length - 1 {
|
||||||
|
self.new_cursor_pos.1 = self.cursor_pos.1 + 1;
|
||||||
|
self.dirty = true;
|
||||||
|
self.refresh_mailbox(context);
|
||||||
|
} else if accounts_length > 0 && self.new_cursor_pos.0 < accounts_length - 1
|
||||||
|
{
|
||||||
|
self.new_cursor_pos.0 = self.cursor_pos.0 + 1;
|
||||||
|
self.new_cursor_pos.1 = 0;
|
||||||
|
self.dirty = true;
|
||||||
|
self.refresh_mailbox(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
'K' => {
|
||||||
|
if self.cursor_pos.1 > 0 {
|
||||||
|
self.new_cursor_pos.1 = self.cursor_pos.1 - 1;
|
||||||
|
self.dirty = true;
|
||||||
|
self.refresh_mailbox(context);
|
||||||
|
} else if self.cursor_pos.0 > 0 {
|
||||||
|
self.new_cursor_pos.0 = self.cursor_pos.0 - 1;
|
||||||
|
self.new_cursor_pos.1 = 0;
|
||||||
|
self.dirty = true;
|
||||||
|
self.refresh_mailbox(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UIEventType::Input(Key::Char(k @ 'h')) | UIEventType::Input(Key::Char(k @ 'l')) => {
|
||||||
|
let accounts_length = context.accounts.len();
|
||||||
|
match k {
|
||||||
|
'h' if accounts_length > 0 && self.new_cursor_pos.0 < accounts_length - 1 => {
|
||||||
|
self.new_cursor_pos.0 = self.cursor_pos.0 + 1;
|
||||||
|
self.new_cursor_pos.1 = 0;
|
||||||
|
self.dirty = true;
|
||||||
|
self.refresh_mailbox(context);
|
||||||
|
}
|
||||||
|
'l' if self.cursor_pos.0 > 0 => {
|
||||||
|
self.new_cursor_pos.0 = self.cursor_pos.0 - 1;
|
||||||
|
self.new_cursor_pos.1 = 0;
|
||||||
|
self.dirty = true;
|
||||||
|
self.refresh_mailbox(context);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UIEventType::RefreshMailbox(_) => {
|
||||||
|
self.dirty = true;
|
||||||
|
self.view = None;
|
||||||
|
}
|
||||||
|
UIEventType::MailboxUpdate((ref idxa, ref idxf)) => {
|
||||||
|
if *idxa == self.new_cursor_pos.1 && *idxf == self.new_cursor_pos.0 {
|
||||||
|
self.refresh_mailbox(context);
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UIEventType::ChangeMode(UIMode::Normal) => {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
UIEventType::Resize => {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
UIEventType::Action(ref action) => match action {
|
||||||
|
Action::MailListing(MailListingAction::ToggleThreaded) => {
|
||||||
|
context.accounts[self.cursor_pos.0]
|
||||||
|
.runtime_settings
|
||||||
|
.threaded = !context.accounts[self.cursor_pos.0]
|
||||||
|
.runtime_settings
|
||||||
|
.threaded;
|
||||||
|
self.refresh_mailbox(context);
|
||||||
|
self.dirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Action::ViewMailbox(idx) => {
|
||||||
|
self.new_cursor_pos.1 = *idx;
|
||||||
|
self.dirty = true;
|
||||||
|
self.refresh_mailbox(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Action::Sort(field, order) => {
|
||||||
|
self.sort = (field.clone(), order.clone());
|
||||||
|
self.dirty = true;
|
||||||
|
self.refresh_mailbox(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if let Some(ref mut v) = self.view {
|
||||||
|
v.process_event(event, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||||
|
}
|
||||||
|
fn set_dirty(&mut self) {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,9 @@
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
mod compact;
|
||||||
|
pub use self::compact::*;
|
||||||
|
|
||||||
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 `Envelope` content in a
|
/// A list of all mail (`Envelope`s) in a `Mailbox`. On `\n` it opens the `Envelope` content in a
|
||||||
|
@ -31,7 +34,7 @@ pub struct MailListing {
|
||||||
new_cursor_pos: (usize, usize, usize),
|
new_cursor_pos: (usize, usize, usize),
|
||||||
length: usize,
|
length: usize,
|
||||||
sort: (SortField, SortOrder),
|
sort: (SortField, SortOrder),
|
||||||
subsort: (SortField, SortOrder),
|
//subsort: (SortField, SortOrder),
|
||||||
/// Cache current view.
|
/// Cache current view.
|
||||||
content: CellBuffer,
|
content: CellBuffer,
|
||||||
/// If we must redraw on next redraw event
|
/// If we must redraw on next redraw event
|
||||||
|
@ -47,6 +50,12 @@ impl Default for MailListing {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for MailListing {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "mail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MailListing {
|
impl MailListing {
|
||||||
/// Helper function to format entry strings for MailListing */
|
/// Helper function to format entry strings for MailListing */
|
||||||
/* TODO: Make this configurable */
|
/* TODO: Make this configurable */
|
||||||
|
@ -66,7 +75,7 @@ impl MailListing {
|
||||||
new_cursor_pos: (0, 0, 0),
|
new_cursor_pos: (0, 0, 0),
|
||||||
length: 0,
|
length: 0,
|
||||||
sort: (SortField::Date, SortOrder::Desc),
|
sort: (SortField::Date, SortOrder::Desc),
|
||||||
subsort: (SortField::Date, SortOrder::Asc),
|
//subsort: (SortField::Date, SortOrder::Asc),
|
||||||
content: content,
|
content: content,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
unfocused: false,
|
unfocused: false,
|
||||||
|
@ -728,4 +737,7 @@ impl Component for MailListing {
|
||||||
fn is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
self.dirty || self.view.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
fn set_dirty(&mut self) {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -22,11 +22,14 @@
|
||||||
/*! Entities that handle Mail specific functions.
|
/*! Entities that handle Mail specific functions.
|
||||||
*/
|
*/
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use melib::backends::Folder;
|
||||||
|
|
||||||
pub mod listing;
|
pub mod listing;
|
||||||
pub mod view;
|
|
||||||
pub use listing::*;
|
pub use listing::*;
|
||||||
|
pub mod view;
|
||||||
pub use view::*;
|
pub use view::*;
|
||||||
|
mod compose;
|
||||||
|
pub use self::compose::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct AccountMenuEntry {
|
struct AccountMenuEntry {
|
||||||
|
@ -43,6 +46,13 @@ pub struct AccountMenu {
|
||||||
cursor: Option<(usize, usize)>,
|
cursor: Option<(usize, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for AccountMenu {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// TODO display subject/info
|
||||||
|
write!(f, "menu")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl AccountMenu {
|
impl AccountMenu {
|
||||||
pub fn new(accounts: &[Account]) -> Self {
|
pub fn new(accounts: &[Account]) -> Self {
|
||||||
let accounts = accounts
|
let accounts = accounts
|
||||||
|
@ -53,8 +63,10 @@ impl AccountMenu {
|
||||||
index: i,
|
index: i,
|
||||||
entries: {
|
entries: {
|
||||||
let mut entries = Vec::with_capacity(a.len());
|
let mut entries = Vec::with_capacity(a.len());
|
||||||
for (idx, acc) in a.list_folders().iter().enumerate() {
|
let mut idx = 0;
|
||||||
entries.push((idx, acc.clone()));
|
for acc in a.list_folders() {
|
||||||
|
entries.push((idx, acc));
|
||||||
|
idx += 1;
|
||||||
}
|
}
|
||||||
entries
|
entries
|
||||||
},
|
},
|
||||||
|
@ -187,12 +199,7 @@ impl AccountMenu {
|
||||||
);
|
);
|
||||||
|
|
||||||
if highlight && idx > 1 && self.cursor.unwrap().1 == idx - 2 {
|
if highlight && idx > 1 && self.cursor.unwrap().1 == idx - 2 {
|
||||||
change_colors(
|
change_colors(grid, ((x, y), (get_x(bottom_right), y)), color_fg, color_bg);
|
||||||
grid,
|
|
||||||
((x, y), (get_x(bottom_right), y)),
|
|
||||||
color_fg,
|
|
||||||
color_bg,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
change_colors(grid, ((x, y), set_y(bottom_right, y)), color_fg, color_bg);
|
change_colors(grid, ((x, y), set_y(bottom_right, y)), color_fg, color_bg);
|
||||||
}
|
}
|
||||||
|
@ -240,4 +247,7 @@ impl Component for AccountMenu {
|
||||||
fn is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
self.dirty
|
self.dirty
|
||||||
}
|
}
|
||||||
|
fn set_dirty(&mut self) {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,13 @@ impl HtmlView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for HtmlView {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// TODO display subject/info
|
||||||
|
write!(f, "view")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Component for HtmlView {
|
impl Component for HtmlView {
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
self.pager.draw(grid, area, context);
|
self.pager.draw(grid, area, context);
|
||||||
|
@ -90,4 +97,5 @@ impl Component for HtmlView {
|
||||||
fn is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
self.pager.is_dirty()
|
self.pager.is_dirty()
|
||||||
}
|
}
|
||||||
|
fn set_dirty(&mut self) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,9 @@ use linkify::{Link, LinkFinder};
|
||||||
use std::process::{Command, Stdio};
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
mod html;
|
mod html;
|
||||||
|
|
||||||
pub use self::html::*;
|
pub use self::html::*;
|
||||||
|
mod thread;
|
||||||
|
pub use self::thread::*;
|
||||||
|
|
||||||
use mime_apps::query_default_app;
|
use mime_apps::query_default_app;
|
||||||
|
|
||||||
|
@ -59,6 +60,13 @@ pub struct MailView {
|
||||||
cmd_buf: String,
|
cmd_buf: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for MailView {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// TODO display subject/info
|
||||||
|
write!(f, "view mail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MailView {
|
impl MailView {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
coordinates: (usize, usize, usize),
|
coordinates: (usize, usize, usize),
|
||||||
|
@ -80,23 +88,34 @@ impl MailView {
|
||||||
fn attachment_to_text(&self, body: Attachment) -> String {
|
fn attachment_to_text(&self, body: Attachment) -> String {
|
||||||
let finder = LinkFinder::new();
|
let finder = LinkFinder::new();
|
||||||
let body_text = if body.content_type().0.is_text() && body.content_type().1.is_html() {
|
let body_text = if body.content_type().0.is_text() && body.content_type().1.is_html() {
|
||||||
let mut s = String::from("Text piped through `w3m`. Press `v` to open in web browser. \n\n");
|
let mut s =
|
||||||
|
String::from("Text piped through `w3m`. Press `v` to open in web browser. \n\n");
|
||||||
s.extend(
|
s.extend(
|
||||||
String::from_utf8_lossy(&decode(&body, Some(Box::new(|a: &Attachment| {
|
String::from_utf8_lossy(&decode(
|
||||||
use std::io::Write;
|
&body,
|
||||||
use std::process::{Command, Stdio};
|
Some(Box::new(|a: &Attachment| {
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
let raw = decode(a, None);
|
let raw = decode(a, None);
|
||||||
let mut html_filter = Command::new("w3m")
|
let mut html_filter = Command::new("w3m")
|
||||||
.args(&["-I", "utf-8", "-T", "text/html"])
|
.args(&["-I", "utf-8", "-T", "text/html"])
|
||||||
.stdin(Stdio::piped())
|
.stdin(Stdio::piped())
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("Failed to start html filter process");
|
.expect("Failed to start html filter process");
|
||||||
|
|
||||||
html_filter.stdin.as_mut().unwrap().write_all(&raw).expect("Failed to write to w3m stdin");
|
html_filter
|
||||||
html_filter.wait_with_output().unwrap().stdout
|
.stdin
|
||||||
})))).into_owned().chars());
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.write_all(&raw)
|
||||||
|
.expect("Failed to write to w3m stdin");
|
||||||
|
html_filter.wait_with_output().unwrap().stdout
|
||||||
|
})),
|
||||||
|
)).into_owned()
|
||||||
|
.chars(),
|
||||||
|
);
|
||||||
s
|
s
|
||||||
} else {
|
} else {
|
||||||
String::from_utf8_lossy(&decode_rec(&body, None)).into()
|
String::from_utf8_lossy(&decode_rec(&body, None)).into()
|
||||||
|
@ -105,13 +124,14 @@ impl MailView {
|
||||||
ViewMode::Normal | ViewMode::Subview => {
|
ViewMode::Normal | ViewMode::Subview => {
|
||||||
let mut t = body_text.to_string();
|
let mut t = body_text.to_string();
|
||||||
if body.count_attachments() > 1 {
|
if body.count_attachments() > 1 {
|
||||||
t = body.attachments().iter().enumerate().fold(
|
t = body
|
||||||
t,
|
.attachments()
|
||||||
|mut s, (idx, a)| {
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.fold(t, |mut s, (idx, a)| {
|
||||||
s.push_str(&format!("[{}] {}\n\n", idx, a));
|
s.push_str(&format!("[{}] {}\n\n", idx, a));
|
||||||
s
|
s
|
||||||
},
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
t
|
t
|
||||||
}
|
}
|
||||||
|
@ -131,13 +151,14 @@ impl MailView {
|
||||||
t.insert_str(l.start() + offset, &format!("[{}]", lidx));
|
t.insert_str(l.start() + offset, &format!("[{}]", lidx));
|
||||||
}
|
}
|
||||||
if body.count_attachments() > 1 {
|
if body.count_attachments() > 1 {
|
||||||
t = body.attachments().iter().enumerate().fold(
|
t = body
|
||||||
t,
|
.attachments()
|
||||||
|mut s, (idx, a)| {
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.fold(t, |mut s, (idx, a)| {
|
||||||
s.push_str(&format!("[{}] {}\n\n", idx, a));
|
s.push_str(&format!("[{}] {}\n\n", idx, a));
|
||||||
s
|
s
|
||||||
},
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
t
|
t
|
||||||
}
|
}
|
||||||
|
@ -190,9 +211,7 @@ impl Component for MailView {
|
||||||
|
|
||||||
let (envelope_idx, y): (usize, usize) = {
|
let (envelope_idx, y): (usize, usize) = {
|
||||||
let accounts = &mut context.accounts;
|
let accounts = &mut context.accounts;
|
||||||
let threaded = accounts[self.coordinates.0]
|
let threaded = accounts[self.coordinates.0].runtime_settings.threaded;
|
||||||
.runtime_settings
|
|
||||||
.threaded;
|
|
||||||
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1]
|
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -291,12 +310,15 @@ impl Component for MailView {
|
||||||
let body = envelope.body();
|
let body = envelope.body();
|
||||||
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(&body.attachments()[aidx], None))));
|
self.subview = Some(Box::new(HtmlView::new(decode(
|
||||||
},
|
&body.attachments()[aidx],
|
||||||
|
None,
|
||||||
|
))));
|
||||||
|
}
|
||||||
ViewMode::Normal if body.is_html() => {
|
ViewMode::Normal if body.is_html() => {
|
||||||
self.subview = Some(Box::new(HtmlView::new(decode(&body, None))));
|
self.subview = Some(Box::new(HtmlView::new(decode(&body, None))));
|
||||||
self.mode = ViewMode::Subview;
|
self.mode = ViewMode::Subview;
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let buf = {
|
let buf = {
|
||||||
let text = self.attachment_to_text(body);
|
let text = self.attachment_to_text(body);
|
||||||
|
@ -309,7 +331,7 @@ impl Component for MailView {
|
||||||
self.pager.as_mut().map(|p| p.cursor_pos())
|
self.pager.as_mut().map(|p| p.cursor_pos())
|
||||||
};
|
};
|
||||||
self.pager = Some(Pager::from_buf(&buf, cursor_pos));
|
self.pager = Some(Pager::from_buf(&buf, cursor_pos));
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
self.dirty = false;
|
self.dirty = false;
|
||||||
}
|
}
|
||||||
|
@ -351,9 +373,7 @@ impl Component for MailView {
|
||||||
|
|
||||||
{
|
{
|
||||||
let accounts = &mut context.accounts;
|
let accounts = &mut context.accounts;
|
||||||
let threaded = accounts[self.coordinates.0]
|
let threaded = accounts[self.coordinates.0].runtime_settings.threaded;
|
||||||
.runtime_settings
|
|
||||||
.threaded;
|
|
||||||
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1]
|
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -424,9 +444,7 @@ impl Component for MailView {
|
||||||
self.cmd_buf.clear();
|
self.cmd_buf.clear();
|
||||||
let url = {
|
let url = {
|
||||||
let accounts = &mut context.accounts;
|
let accounts = &mut context.accounts;
|
||||||
let threaded = accounts[self.coordinates.0]
|
let threaded = accounts[self.coordinates.0].runtime_settings.threaded;
|
||||||
.runtime_settings
|
|
||||||
.threaded;
|
|
||||||
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1]
|
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1]
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -482,4 +500,7 @@ impl Component for MailView {
|
||||||
|| self.pager.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)
|
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
fn set_dirty(&mut self) {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
* 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::*;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::process::{Command, Stdio};
|
||||||
|
|
||||||
|
pub struct ThreadView {
|
||||||
|
pager: Pager,
|
||||||
|
bytes: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadView {
|
||||||
|
pub fn new(bytes: Vec<u8>) -> Self {
|
||||||
|
let mut html_filter = Command::new("w3m")
|
||||||
|
.args(&["-I", "utf-8", "-T", "text/html"])
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.expect("Failed to start html filter process");
|
||||||
|
html_filter
|
||||||
|
.stdin
|
||||||
|
.as_mut()
|
||||||
|
.unwrap()
|
||||||
|
.write_all(&bytes)
|
||||||
|
.expect("Failed to write to w3m stdin");
|
||||||
|
let mut display_text =
|
||||||
|
String::from("Text piped through `w3m`. Press `v` to open in web browser. \n\n");
|
||||||
|
display_text.push_str(&String::from_utf8_lossy(
|
||||||
|
&html_filter.wait_with_output().unwrap().stdout,
|
||||||
|
));
|
||||||
|
|
||||||
|
let buf = MailView::plain_text_to_buf(&display_text, true);
|
||||||
|
let pager = Pager::from_buf(&buf, None);
|
||||||
|
ThreadView { pager, bytes }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ThreadView {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// TODO display subject/info
|
||||||
|
write!(f, "view thread")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for ThreadView {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
self.pager.draw(grid, area, context);
|
||||||
|
}
|
||||||
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||||
|
match event.event_type {
|
||||||
|
UIEventType::Input(Key::Char('v')) => {
|
||||||
|
// TODO: Optional filter that removes outgoing resource requests (images and
|
||||||
|
// scripts)
|
||||||
|
let binary = query_default_app("text/html");
|
||||||
|
if let Ok(binary) = binary {
|
||||||
|
let mut p = create_temp_file(&self.bytes, None);
|
||||||
|
Command::new(&binary)
|
||||||
|
.arg(p.path())
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
.unwrap_or_else(|_| panic!("Failed to start {}", binary.display()));
|
||||||
|
context.temp_files.push(p);
|
||||||
|
} else {
|
||||||
|
context.replies.push_back(UIEvent {
|
||||||
|
id: 0,
|
||||||
|
event_type: UIEventType::StatusNotification(format!(
|
||||||
|
"Couldn't find a default application for html files."
|
||||||
|
)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
self.pager.process_event(event, context);
|
||||||
|
}
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.pager.is_dirty()
|
||||||
|
}
|
||||||
|
fn set_dirty(&mut self) {}
|
||||||
|
}
|
|
@ -35,6 +35,10 @@ pub mod notifications;
|
||||||
pub mod utilities;
|
pub mod utilities;
|
||||||
pub use self::utilities::*;
|
pub use self::utilities::*;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use super::{Key, UIEvent, UIEventType};
|
use super::{Key, UIEvent, UIEventType};
|
||||||
/// The upper and lower boundary char.
|
/// The upper and lower boundary char.
|
||||||
const HORZ_BOUNDARY: char = '─';
|
const HORZ_BOUNDARY: char = '─';
|
||||||
|
@ -65,6 +69,14 @@ pub struct Entity {
|
||||||
pub component: Box<Component>, // more than one?
|
pub component: Box<Component>, // more than one?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for Entity {
|
||||||
|
type Target = Box<Component>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Box<Component> {
|
||||||
|
&self.component
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Entity {
|
impl Entity {
|
||||||
/// Pass events to child component.
|
/// Pass events to child component.
|
||||||
pub fn rcv_event(&mut self, event: &UIEvent, context: &mut Context) {
|
pub fn rcv_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||||
|
@ -75,12 +87,13 @@ impl Entity {
|
||||||
/// Types implementing this Trait can draw on the terminal and receive events.
|
/// Types implementing this Trait can draw on the terminal and receive events.
|
||||||
/// If a type wants to skip drawing if it has not changed anything, it can hold some flag in its
|
/// If a type wants to skip drawing if it has not changed anything, it can hold some flag in its
|
||||||
/// fields (eg self.dirty = false) and act upon that in their `draw` implementation.
|
/// fields (eg self.dirty = false) and act upon that in their `draw` implementation.
|
||||||
pub trait Component {
|
pub trait Component: Display {
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context);
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context);
|
||||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context);
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context);
|
||||||
fn is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
fn set_dirty(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: word break.
|
// TODO: word break.
|
||||||
|
|
|
@ -29,6 +29,13 @@ use super::*;
|
||||||
/// Passes notifications to the OS using the XDG specifications.
|
/// Passes notifications to the OS using the XDG specifications.
|
||||||
pub struct XDGNotifications {}
|
pub struct XDGNotifications {}
|
||||||
|
|
||||||
|
impl fmt::Display for XDGNotifications {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// TODO display subject/info
|
||||||
|
write!(f, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Component for XDGNotifications {
|
impl Component for XDGNotifications {
|
||||||
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {}
|
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {}
|
||||||
fn process_event(&mut self, event: &UIEvent, _context: &mut Context) {
|
fn process_event(&mut self, event: &UIEvent, _context: &mut Context) {
|
||||||
|
@ -41,4 +48,5 @@ impl Component for XDGNotifications {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fn set_dirty(&mut self) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,13 @@ pub struct HSplit {
|
||||||
ratio: usize, // bottom/whole height * 100
|
ratio: usize, // bottom/whole height * 100
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for HSplit {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// TODO display subject/info
|
||||||
|
self.top.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl HSplit {
|
impl HSplit {
|
||||||
pub fn new(top: Entity, bottom: Entity, ratio: usize, show_divider: bool) -> Self {
|
pub fn new(top: Entity, bottom: Entity, ratio: usize, show_divider: bool) -> Self {
|
||||||
HSplit {
|
HSplit {
|
||||||
|
@ -79,6 +86,10 @@ impl Component for HSplit {
|
||||||
fn is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
self.top.component.is_dirty() || self.bottom.component.is_dirty()
|
self.top.component.is_dirty() || self.bottom.component.is_dirty()
|
||||||
}
|
}
|
||||||
|
fn set_dirty(&mut self) {
|
||||||
|
self.top.component.set_dirty();
|
||||||
|
self.bottom.component.set_dirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A vertically split in half container.
|
/// A vertically split in half container.
|
||||||
|
@ -90,6 +101,13 @@ pub struct VSplit {
|
||||||
ratio: usize, // right/(container width) * 100
|
ratio: usize, // right/(container width) * 100
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for VSplit {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// TODO display focused entity
|
||||||
|
self.right.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl VSplit {
|
impl VSplit {
|
||||||
pub fn new(left: Entity, right: Entity, ratio: usize, show_divider: bool) -> Self {
|
pub fn new(left: Entity, right: Entity, ratio: usize, show_divider: bool) -> Self {
|
||||||
VSplit {
|
VSplit {
|
||||||
|
@ -155,6 +173,10 @@ impl Component for VSplit {
|
||||||
fn is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
self.left.component.is_dirty() || self.right.component.is_dirty()
|
self.left.component.is_dirty() || self.right.component.is_dirty()
|
||||||
}
|
}
|
||||||
|
fn set_dirty(&mut self) {
|
||||||
|
self.left.component.set_dirty();
|
||||||
|
self.right.component.set_dirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A pager for text.
|
/// A pager for text.
|
||||||
|
@ -168,6 +190,13 @@ pub struct Pager {
|
||||||
content: CellBuffer,
|
content: CellBuffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Pager {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// TODO display info
|
||||||
|
write!(f, "pager")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Pager {
|
impl Pager {
|
||||||
pub fn from_string(mut text: String, context: &mut Context, cursor_pos: Option<usize>) -> Self {
|
pub fn from_string(mut text: String, context: &mut Context, cursor_pos: Option<usize>) -> Self {
|
||||||
let pager_filter: Option<&String> = context.settings.pager.filter.as_ref();
|
let pager_filter: Option<&String> = context.settings.pager.filter.as_ref();
|
||||||
|
@ -325,6 +354,9 @@ impl Component for Pager {
|
||||||
fn is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
self.dirty
|
self.dirty
|
||||||
}
|
}
|
||||||
|
fn set_dirty(&mut self) {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Status bar.
|
/// Status bar.
|
||||||
|
@ -338,6 +370,13 @@ pub struct StatusBar {
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for StatusBar {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// TODO display info
|
||||||
|
write!(f, "status bar")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl StatusBar {
|
impl StatusBar {
|
||||||
pub fn new(container: Entity) -> Self {
|
pub fn new(container: Entity) -> Self {
|
||||||
StatusBar {
|
StatusBar {
|
||||||
|
@ -497,6 +536,9 @@ impl Component for StatusBar {
|
||||||
fn is_dirty(&self) -> bool {
|
fn is_dirty(&self) -> bool {
|
||||||
self.dirty || self.container.component.is_dirty()
|
self.dirty || self.container.component.is_dirty()
|
||||||
}
|
}
|
||||||
|
fn set_dirty(&mut self) {
|
||||||
|
self.dirty = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A box with a text content.
|
// A box with a text content.
|
||||||
|
@ -510,11 +552,17 @@ impl TextBox {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for TextBox {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// TODO display info
|
||||||
|
write!(f, "text box")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Component for TextBox {
|
impl Component for TextBox {
|
||||||
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {}
|
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {}
|
||||||
fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) {
|
fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) {}
|
||||||
return;
|
fn set_dirty(&mut self) {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Progress {
|
pub struct Progress {
|
||||||
|
@ -552,6 +600,13 @@ impl Progress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Progress {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// TODO display info
|
||||||
|
write!(f, "progress bar")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Component for Progress {
|
impl Component for Progress {
|
||||||
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {
|
fn draw(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
|
@ -559,4 +614,97 @@ impl Component for Progress {
|
||||||
fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) {
|
fn process_event(&mut self, _event: &UIEvent, _context: &mut Context) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
fn set_dirty(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Tabbed {
|
||||||
|
children: Vec<Box<Component>>,
|
||||||
|
cursor_pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Tabbed {
|
||||||
|
pub fn new(children: Vec<Box<Component>>) -> Self {
|
||||||
|
Tabbed {
|
||||||
|
children,
|
||||||
|
cursor_pos: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn draw_tabs(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
let mut x = get_x(upper_left!(area));
|
||||||
|
let mut y: usize = get_y(upper_left!(area));
|
||||||
|
for (idx, c) in self.children.iter().enumerate() {
|
||||||
|
let (fg, bg) = if idx == self.cursor_pos {
|
||||||
|
(Color::Default, Color::Default)
|
||||||
|
} else {
|
||||||
|
(Color::Byte(15), Color::Byte(8))
|
||||||
|
};
|
||||||
|
let (x_, _y_) = write_string_to_grid(
|
||||||
|
&format!(" {} ", c),
|
||||||
|
grid,
|
||||||
|
fg,
|
||||||
|
bg,
|
||||||
|
(set_x(upper_left!(area), x), bottom_right!(area)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
x = x_ + 1;
|
||||||
|
if y != _y_ {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
y = _y_;
|
||||||
|
}
|
||||||
|
let (cols, _) = grid.size();
|
||||||
|
let cslice: &mut [Cell] = grid;
|
||||||
|
for c in cslice[(y * cols) + x..(y * cols) + cols].iter_mut() {
|
||||||
|
c.set_bg(Color::Byte(7));
|
||||||
|
}
|
||||||
|
context.dirty_areas.push_back(area);
|
||||||
|
}
|
||||||
|
pub fn add_component(&mut self, new: Box<Component>) {
|
||||||
|
self.children.push(new);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Tabbed {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// TODO display info
|
||||||
|
write!(f, "tabs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Tabbed {
|
||||||
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
|
if self.children.len() > 1 {
|
||||||
|
self.draw_tabs(
|
||||||
|
grid,
|
||||||
|
(
|
||||||
|
upper_left!(area),
|
||||||
|
set_x(upper_left!(area), get_x(bottom_right!(area))),
|
||||||
|
),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
let y = get_y(upper_left!(area));
|
||||||
|
self.children[self.cursor_pos].draw(
|
||||||
|
grid,
|
||||||
|
(set_y(upper_left!(area), y + 1), bottom_right!(area)),
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
self.children[self.cursor_pos].draw(grid, area, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn process_event(&mut self, event: &UIEvent, context: &mut Context) {
|
||||||
|
match &event.event_type {
|
||||||
|
UIEventType::Input(Key::Char('T')) => {
|
||||||
|
self.cursor_pos = (self.cursor_pos + 1) % self.children.len();
|
||||||
|
self.children[self.cursor_pos].set_dirty();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
self.children[self.cursor_pos].process_event(event, context);
|
||||||
|
}
|
||||||
|
fn is_dirty(&self) -> bool {
|
||||||
|
self.children[self.cursor_pos].is_dirty()
|
||||||
|
}
|
||||||
|
fn set_dirty(&mut self) {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
|
|
@ -50,7 +50,7 @@ pub struct Context {
|
||||||
|
|
||||||
/// Events queue that components send back to the state
|
/// Events queue that components send back to the state
|
||||||
pub replies: VecDeque<UIEvent>,
|
pub replies: VecDeque<UIEvent>,
|
||||||
_backends: Backends,
|
backends: Backends,
|
||||||
|
|
||||||
input_thread: chan::Sender<bool>,
|
input_thread: chan::Sender<bool>,
|
||||||
pub temp_files: Vec<File>,
|
pub temp_files: Vec<File>,
|
||||||
|
@ -103,8 +103,8 @@ impl State<std::io::Stdout> {
|
||||||
pub fn new(sender: Sender<ThreadEvent>, input_thread: chan::Sender<bool>) -> Self {
|
pub fn new(sender: Sender<ThreadEvent>, input_thread: chan::Sender<bool>) -> Self {
|
||||||
let _stdout = std::io::stdout();
|
let _stdout = std::io::stdout();
|
||||||
_stdout.lock();
|
_stdout.lock();
|
||||||
let settings = Settings::new();
|
|
||||||
let backends = Backends::new();
|
let backends = Backends::new();
|
||||||
|
let settings = Settings::new();
|
||||||
let stdout = AlternateScreen::from(_stdout.into_raw_mode().unwrap());
|
let stdout = AlternateScreen::from(_stdout.into_raw_mode().unwrap());
|
||||||
|
|
||||||
let termsize = termion::terminal_size().ok();
|
let termsize = termion::terminal_size().ok();
|
||||||
|
@ -155,7 +155,7 @@ impl State<std::io::Stdout> {
|
||||||
accounts,
|
accounts,
|
||||||
mailbox_hashes: FnvHashMap::with_capacity_and_hasher(1, Default::default()),
|
mailbox_hashes: FnvHashMap::with_capacity_and_hasher(1, Default::default()),
|
||||||
|
|
||||||
_backends: backends,
|
backends,
|
||||||
settings: settings.clone(),
|
settings: settings.clone(),
|
||||||
runtime_settings: settings,
|
runtime_settings: settings,
|
||||||
dirty_areas: VecDeque::with_capacity(5),
|
dirty_areas: VecDeque::with_capacity(5),
|
||||||
|
@ -178,7 +178,7 @@ impl State<std::io::Stdout> {
|
||||||
).unwrap();
|
).unwrap();
|
||||||
s.flush();
|
s.flush();
|
||||||
for (x, account) in s.context.accounts.iter_mut().enumerate() {
|
for (x, account) in s.context.accounts.iter_mut().enumerate() {
|
||||||
for (y, folder) in account.settings.folders.iter().enumerate() {
|
for (y, folder) in account.backend.folders().iter().enumerate() {
|
||||||
s.context.mailbox_hashes.insert(folder.hash(), (x, y));
|
s.context.mailbox_hashes.insert(folder.hash(), (x, y));
|
||||||
}
|
}
|
||||||
let sender = s.sender.clone();
|
let sender = s.sender.clone();
|
||||||
|
@ -463,8 +463,7 @@ impl<W: Write> State<W> {
|
||||||
_ => {
|
_ => {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
} {
|
||||||
{
|
|
||||||
if let Some(ForkType::NewDraft(f, _)) = std::mem::replace(&mut self.child, None) {
|
if let Some(ForkType::NewDraft(f, _)) = std::mem::replace(&mut self.child, None) {
|
||||||
self.rcv_event(UIEvent {
|
self.rcv_event(UIEvent {
|
||||||
id: 0,
|
id: 0,
|
||||||
|
@ -476,7 +475,9 @@ impl<W: Write> State<W> {
|
||||||
Some(false)
|
Some(false)
|
||||||
}
|
}
|
||||||
fn flush(&mut self) {
|
fn flush(&mut self) {
|
||||||
if let Some(s) = self.stdout.as_mut() { s.flush().unwrap(); }
|
if let Some(s) = self.stdout.as_mut() {
|
||||||
|
s.flush().unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn stdout(&mut self) -> &mut termion::screen::AlternateScreen<termion::raw::RawTerminal<W>> {
|
fn stdout(&mut self) -> &mut termion::screen::AlternateScreen<termion::raw::RawTerminal<W>> {
|
||||||
self.stdout.as_mut().unwrap()
|
self.stdout.as_mut().unwrap()
|
||||||
|
|
|
@ -20,9 +20,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use std;
|
use std;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::fs::OpenOptions;
|
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -39,7 +39,12 @@ impl Drop for File {
|
||||||
|
|
||||||
impl File {
|
impl File {
|
||||||
pub fn file(&mut self) -> std::fs::File {
|
pub fn file(&mut self) -> std::fs::File {
|
||||||
OpenOptions::new().read(true).write(true).create(true).open(&self.path).unwrap()
|
OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.write(true)
|
||||||
|
.create(true)
|
||||||
|
.open(&self.path)
|
||||||
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path(&self) -> &PathBuf {
|
pub fn path(&self) -> &PathBuf {
|
||||||
|
|
Loading…
Reference in New Issue