Browse Source

MailBackend: change more methods to Futures

tags/alpha-0.6.0
Manos Pitsidianakis 4 months ago
parent
commit
e06308fed2
WARNING! Although there is a key with this ID in the database it does not verify this commit! This commit is SUSPICIOUS. GPG Key ID: 73627C2F690DF710
21 changed files with 611 additions and 392 deletions
  1. +28
    -25
      melib/src/backends.rs
  2. +40
    -26
      melib/src/backends/imap.rs
  3. +0
    -1
      melib/src/backends/imap/protocol_parser.rs
  4. +59
    -51
      melib/src/backends/imap_async.rs
  5. +0
    -1
      melib/src/backends/imap_async/connection.rs
  6. +0
    -1
      melib/src/backends/imap_async/protocol_parser.rs
  7. +8
    -5
      melib/src/backends/jmap.rs
  8. +36
    -11
      melib/src/backends/maildir/backend.rs
  9. +6
    -1
      melib/src/backends/mbox.rs
  10. +8
    -2
      melib/src/backends/notmuch.rs
  11. +7
    -1
      src/components/mail/listing.rs
  12. +50
    -9
      src/components/mail/listing/compact.rs
  13. +89
    -80
      src/components/mail/listing/conversations.rs
  14. +0
    -2
      src/components/mail/listing/offline.rs
  15. +78
    -69
      src/components/mail/listing/plain.rs
  16. +35
    -2
      src/components/utilities.rs
  17. +21
    -2
      src/components/utilities/widgets.rs
  18. +133
    -99
      src/conf/accounts.rs
  19. +7
    -2
      src/plugins/backend.rs
  20. +2
    -0
      src/types.rs
  21. +4
    -2
      src/unix.rs

+ 28
- 25
melib/src/backends.rs View File

@ -295,11 +295,11 @@ impl NotifyFn {
}
}
pub type ResultFuture<T> = Result<Pin<Box<dyn Future<Output = Result<T>> + Send + 'static>>>;
pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
fn is_online(&self) -> Result<()>;
fn is_online_async(
&self,
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>> {
fn is_online_async(&self) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}
fn connect(&mut self) {}
@ -321,7 +321,7 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
&mut self,
_mailbox_hash: MailboxHash,
_sender: RefreshEventConsumer,
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>> {
) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}
fn watch(
@ -330,15 +330,18 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
work_context: WorkContext,
) -> Result<std::thread::ThreadId>;
fn mailboxes(&self) -> Result<HashMap<MailboxHash, Mailbox>>;
fn mailboxes_async(
&self,
) -> Result<Pin<Box<dyn Future<Output = Result<HashMap<MailboxHash, Mailbox>>> + Send>>> {
fn mailboxes_async(&self) -> ResultFuture<HashMap<MailboxHash, Mailbox>> {
Err(MeliError::new("Unimplemented."))
}
fn operation(&self, hash: EnvelopeHash) -> Result<Box<dyn BackendOp>>;
fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option<Flag>) -> Result<()>;
fn delete(&self, _env_hash: EnvelopeHash, _mailbox_hash: MailboxHash) -> Result<()> {
fn save(
&self,
bytes: Vec<u8>,
mailbox_hash: MailboxHash,
flags: Option<Flag>,
) -> ResultFuture<()>;
fn delete(&self, _env_hash: EnvelopeHash, _mailbox_hash: MailboxHash) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {
@ -353,22 +356,30 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
fn create_mailbox(
&mut self,
_path: String,
) -> Result<(MailboxHash, HashMap<MailboxHash, Mailbox>)> {
) -> ResultFuture<(MailboxHash, HashMap<MailboxHash, Mailbox>)> {
Err(MeliError::new("Unimplemented."))
}
fn delete_mailbox(
&mut self,
_mailbox_hash: MailboxHash,
) -> Result<HashMap<MailboxHash, Mailbox>> {
) -> ResultFuture<HashMap<MailboxHash, Mailbox>> {
Err(MeliError::new("Unimplemented."))
}
fn set_mailbox_subscription(&mut self, _mailbox_hash: MailboxHash, _val: bool) -> Result<()> {
fn set_mailbox_subscription(
&mut self,
_mailbox_hash: MailboxHash,
_val: bool,
) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}
fn rename_mailbox(&mut self, _mailbox_hash: MailboxHash, _new_path: String) -> Result<Mailbox> {
fn rename_mailbox(
&mut self,
_mailbox_hash: MailboxHash,
_new_path: String,
) -> ResultFuture<Mailbox> {
Err(MeliError::new("Unimplemented."))
}
@ -376,7 +387,7 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
&mut self,
_mailbox_hash: MailboxHash,
_val: MailboxPermissions,
) -> Result<()> {
) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}
@ -384,7 +395,7 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
&self,
_query: crate::search::Query,
_mailbox_hash: Option<MailboxHash>,
) -> Result<SmallVec<[EnvelopeHash; 512]>> {
) -> ResultFuture<SmallVec<[EnvelopeHash; 512]>> {
Err(MeliError::new("Unimplemented."))
}
}
@ -469,18 +480,10 @@ impl BackendOp for ReadOnlyOp {
fn fetch_flags(&self) -> Result<Flag> {
self.op.fetch_flags()
}
fn set_flag(
&mut self,
_flag: Flag,
_value: bool,
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send>>> {
fn set_flag(&mut self, _flag: Flag, _value: bool) -> ResultFuture<()> {
Err(MeliError::new("read-only set."))
}
fn set_tag(
&mut self,
_tag: String,
_value: bool,
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send>>> {
fn set_tag(&mut self, _tag: String, _value: bool) -> ResultFuture<()> {
Err(MeliError::new("read-only set."))
}
}

+ 40
- 26
melib/src/backends/imap.rs View File

@ -37,11 +37,10 @@ pub mod managesieve;
mod untagged;
use crate::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
use crate::backends::BackendOp;
use crate::backends::RefreshEvent;
use crate::backends::RefreshEventKind::{self, *};
use crate::backends::{AccountHash, MailboxHash};
use crate::backends::{BackendMailbox, MailBackend, Mailbox, RefreshEventConsumer};
use crate::backends::{
RefreshEventKind::{self, *},
*,
};
use crate::conf::AccountSettings;
use crate::email::*;
use crate::error::{MeliError, Result, ResultIntoMeliError};
@ -665,7 +664,12 @@ impl MailBackend for ImapType {
)))
}
fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option<Flag>) -> Result<()> {
fn save(
&self,
bytes: Vec<u8>,
mailbox_hash: MailboxHash,
flags: Option<Flag>,
) -> ResultFuture<()> {
let path = {
let mailboxes = self.uid_store.mailboxes.read().unwrap();
@ -696,16 +700,16 @@ impl MailBackend for ImapType {
)?;
// wait for "+ Ready for literal data" reply
conn.wait_for_continuation_request()?;
conn.send_literal(bytes)?;
conn.send_literal(&bytes)?;
conn.read_response(&mut response, RequiredResponses::empty())?;
Ok(())
Ok(Box::pin(async { Ok(()) }))
}
fn as_any(&self) -> &dyn::std::any::Any {
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn::std::any::Any {
fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any {
self
}
@ -720,7 +724,7 @@ impl MailBackend for ImapType {
fn create_mailbox(
&mut self,
mut path: String,
) -> Result<(MailboxHash, HashMap<MailboxHash, Mailbox>)> {
) -> ResultFuture<(MailboxHash, HashMap<MailboxHash, Mailbox>)> {
/* Must transform path to something the IMAP server will accept
*
* Each root mailbox has a hierarchy delimeter reported by the LIST entry. All paths
@ -768,13 +772,15 @@ impl MailBackend for ImapType {
let new_hash = get_path_hash!(path.as_str());
mailboxes.clear();
drop(mailboxes);
Ok((new_hash, self.mailboxes().map_err(|err| MeliError::new(format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err)))?))
let ret =
Ok((new_hash, self.mailboxes().map_err(|err| MeliError::new(format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err)))?));
Ok(Box::pin(async { ret }))
}
fn delete_mailbox(
&mut self,
mailbox_hash: MailboxHash,
) -> Result<HashMap<MailboxHash, Mailbox>> {
) -> ResultFuture<HashMap<MailboxHash, Mailbox>> {
let mailboxes = self.uid_store.mailboxes.read().unwrap();
let permissions = mailboxes[&mailbox_hash].permissions();
if !permissions.delete_mailbox {
@ -812,13 +818,19 @@ impl MailBackend for ImapType {
let mut mailboxes = self.uid_store.mailboxes.write().unwrap();
mailboxes.clear();
drop(mailboxes);
self.mailboxes().map_err(|err| format!("Mailbox delete was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err).into())
let res = self.mailboxes().map_err(|err| format!("Mailbox delete was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err).into());
Ok(Box::pin(async { res }))
}
fn set_mailbox_subscription(&mut self, mailbox_hash: MailboxHash, new_val: bool) -> Result<()> {
fn set_mailbox_subscription(
&mut self,
mailbox_hash: MailboxHash,
new_val: bool,
) -> ResultFuture<()> {
let mut mailboxes = self.uid_store.mailboxes.write().unwrap();
if mailboxes[&mailbox_hash].is_subscribed() == new_val {
return Ok(());
return Ok(Box::pin(async { Ok(()) }));
}
let mut response = String::with_capacity(8 * 1024);
@ -842,14 +854,14 @@ impl MailBackend for ImapType {
let _ = entry.set_is_subscribed(new_val);
});
}
ret
Ok(Box::pin(async { ret }))
}
fn rename_mailbox(
&mut self,
mailbox_hash: MailboxHash,
mut new_path: String,
) -> Result<Mailbox> {
) -> ResultFuture<Mailbox> {
let mut mailboxes = self.uid_store.mailboxes.write().unwrap();
let permissions = mailboxes[&mailbox_hash].permissions();
if !permissions.delete_mailbox {
@ -880,30 +892,31 @@ impl MailBackend for ImapType {
mailboxes.clear();
drop(mailboxes);
self.mailboxes().map_err(|err| format!("Mailbox rename was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err))?;
Ok(BackendMailbox::clone(
let ret = Ok(BackendMailbox::clone(
&self.uid_store.mailboxes.read().unwrap()[&new_hash],
))
));
Ok(Box::pin(async { ret }))
}
fn set_mailbox_permissions(
&mut self,
mailbox_hash: MailboxHash,
_val: crate::backends::MailboxPermissions,
) -> Result<()> {
_val: MailboxPermissions,
) -> ResultFuture<()> {
let mailboxes = self.uid_store.mailboxes.write().unwrap();
let permissions = mailboxes[&mailbox_hash].permissions();
if !permissions.change_permissions {
return Err(MeliError::new(format!("You do not have permission to change permissions for mailbox `{}`. Set permissions for this mailbox are {}", mailboxes[&mailbox_hash].name(), permissions)));
}
Err(MeliError::new("Unimplemented."))
Ok(Box::pin(async { Err(MeliError::new("Unimplemented.")) }))
}
fn search(
&self,
query: crate::search::Query,
mailbox_hash: Option<MailboxHash>,
) -> Result<SmallVec<[EnvelopeHash; 512]>> {
) -> ResultFuture<SmallVec<[EnvelopeHash; 512]>> {
if mailbox_hash.is_none() {
return Err(MeliError::new(
"Cannot search without specifying mailbox on IMAP",
@ -1011,7 +1024,7 @@ impl MailBackend for ImapType {
if l.starts_with("* SEARCH") {
use std::iter::FromIterator;
let uid_index = self.uid_store.uid_index.lock()?;
return Ok(SmallVec::from_iter(
let ret = Ok(SmallVec::from_iter(
l["* SEARCH".len()..]
.trim()
.split_whitespace()
@ -1020,6 +1033,7 @@ impl MailBackend for ImapType {
.filter_map(|uid| uid_index.get(&(mailbox_hash, uid)))
.map(|env_hash_ref| *env_hash_ref),
));
return Ok(Box::pin(async { ret }));
}
}
Err(MeliError::new(response))

+ 0
- 1
melib/src/backends/imap/protocol_parser.rs View File

@ -355,7 +355,6 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
f.no_select = false;
f.is_subscribed = false;
for p in properties.split(|&b| b == b' ') {
use crate::backends::SpecialUsageMailbox;
if p.eq_ignore_ascii_case(b"\\NoSelect") {
f.no_select = true;
} else if p.eq_ignore_ascii_case(b"\\Sent") {

+ 59
- 51
melib/src/backends/imap_async.rs View File

@ -37,11 +37,10 @@ pub mod managesieve;
mod untagged;
use crate::async_workers::{Async, WorkContext};
use crate::backends::BackendOp;
use crate::backends::RefreshEvent;
use crate::backends::RefreshEventKind::{self, *};
use crate::backends::{AccountHash, MailboxHash};
use crate::backends::{BackendMailbox, MailBackend, Mailbox, RefreshEventConsumer};
use crate::backends::{
RefreshEventKind::{self, *},
*,
};
use crate::conf::AccountSettings;
use crate::email::*;
use crate::error::{MeliError, Result, ResultIntoMeliError};
@ -204,7 +203,7 @@ impl MailBackend for ImapType {
&mut self,
mailbox_hash: MailboxHash,
sender: RefreshEventConsumer,
) -> Result<Pin<Box<dyn Future<Output = Result<()>> + Send + 'static>>> {
) -> ResultFuture<()> {
let inbox = self
.uid_store
.mailboxes
@ -223,11 +222,7 @@ impl MailBackend for ImapType {
}))
}
fn mailboxes_async(
&self,
) -> Result<
core::pin::Pin<Box<dyn Future<Output = Result<HashMap<MailboxHash, Mailbox>>> + Send>>,
> {
fn mailboxes_async(&self) -> ResultFuture<HashMap<MailboxHash, Mailbox>> {
let uid_store = self.uid_store.clone();
let connection = self.connection.clone();
Ok(Box::pin(async move {
@ -279,9 +274,7 @@ impl MailBackend for ImapType {
}))
}
fn is_online_async(
&self,
) -> Result<core::pin::Pin<Box<dyn Future<Output = Result<()>> + Send>>> {
fn is_online_async(&self) -> ResultFuture<()> {
let connection = self.connection.clone();
Ok(Box::pin(async move {
debug!("INSIDE is_online_async()");
@ -392,7 +385,12 @@ impl MailBackend for ImapType {
)))
}
fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option<Flag>) -> Result<()> {
fn save(
&self,
_bytes: Vec<u8>,
_mailbox_hash: MailboxHash,
_flags: Option<Flag>,
) -> ResultFuture<()> {
unimplemented!()
/*
let path = {
@ -450,7 +448,7 @@ impl MailBackend for ImapType {
fn create_mailbox(
&mut self,
_path: String,
) -> Result<(MailboxHash, HashMap<MailboxHash, Mailbox>)> {
) -> ResultFuture<(MailboxHash, HashMap<MailboxHash, Mailbox>)> {
unimplemented!()
/*
/* Must transform path to something the IMAP server will accept
@ -507,7 +505,7 @@ impl MailBackend for ImapType {
fn delete_mailbox(
&mut self,
_mailbox_hash: MailboxHash,
) -> Result<HashMap<MailboxHash, Mailbox>> {
) -> ResultFuture<HashMap<MailboxHash, Mailbox>> {
unimplemented!()
/*
let mailboxes = self.uid_store.mailboxes.read().unwrap();
@ -553,7 +551,7 @@ impl MailBackend for ImapType {
&mut self,
_mailbox_hash: MailboxHash,
_new_val: bool,
) -> Result<()> {
) -> ResultFuture<()> {
unimplemented!()
/*
let mut mailboxes = self.uid_store.mailboxes.write().unwrap();
@ -586,7 +584,11 @@ impl MailBackend for ImapType {
*/
}
fn rename_mailbox(&mut self, _mailbox_hash: MailboxHash, _new_path: String) -> Result<Mailbox> {
fn rename_mailbox(
&mut self,
_mailbox_hash: MailboxHash,
_new_path: String,
) -> ResultFuture<Mailbox> {
unimplemented!()
/*
let mut mailboxes = self.uid_store.mailboxes.write().unwrap();
@ -629,7 +631,7 @@ impl MailBackend for ImapType {
&mut self,
_mailbox_hash: MailboxHash,
_val: crate::backends::MailboxPermissions,
) -> Result<()> {
) -> ResultFuture<()> {
unimplemented!()
/*
let mailboxes = self.uid_store.mailboxes.write().unwrap();
@ -644,11 +646,9 @@ impl MailBackend for ImapType {
fn search(
&self,
_query: crate::search::Query,
_mailbox_hash: Option<MailboxHash>,
) -> Result<SmallVec<[EnvelopeHash; 512]>> {
unimplemented!()
/*
query: crate::search::Query,
mailbox_hash: Option<MailboxHash>,
) -> ResultFuture<SmallVec<[EnvelopeHash; 512]>> {
if mailbox_hash.is_none() {
return Err(MeliError::new(
"Cannot search without specifying mailbox on IMAP",
@ -743,32 +743,38 @@ impl MailBackend for ImapType {
}
let mut query_str = String::new();
rec(&query, &mut query_str);
let connection = self.connection.clone();
let uid_store = self.uid_store.clone();
let mut response = String::with_capacity(8 * 1024);
let mut conn = try_lock(&self.connection, Some(std::time::Duration::new(2, 0)))?;
conn.examine_mailbox(mailbox_hash, &mut response)?;
conn.send_command(format!("UID SEARCH CHARSET UTF-8 {}", query_str).as_bytes())?;
conn.read_response(&mut response, RequiredResponses::SEARCH)?;
debug!(&response);
let mut lines = response.lines();
for l in lines.by_ref() {
if l.starts_with("* SEARCH") {
use std::iter::FromIterator;
let uid_index = self.uid_store.uid_index.lock()?;
return Ok(SmallVec::from_iter(
l["* SEARCH".len()..]
.trim()
.split_whitespace()
.map(usize::from_str)
.filter_map(std::result::Result::ok)
.filter_map(|uid| uid_index.get(&(mailbox_hash, uid)))
.map(|env_hash_ref| *env_hash_ref),
));
Ok(Box::pin(async move {
let mut response = String::with_capacity(8 * 1024);
let mut conn = connection.lock().await;
conn.examine_mailbox(mailbox_hash, &mut response, false)
.await?;
conn.send_command(format!("UID SEARCH CHARSET UTF-8 {}", query_str).as_bytes())
.await?;
conn.read_response(&mut response, RequiredResponses::SEARCH)
.await?;
debug!(&response);
let mut lines = response.lines();
for l in lines.by_ref() {
if l.starts_with("* SEARCH") {
use std::iter::FromIterator;
let uid_index = uid_store.uid_index.lock()?;
return Ok(SmallVec::from_iter(
l["* SEARCH".len()..]
.trim()
.split_whitespace()
.map(usize::from_str)
.filter_map(std::result::Result::ok)
.filter_map(|uid| uid_index.get(&(mailbox_hash, uid)))
.map(|env_hash_ref| *env_hash_ref),
));
}
}
}
Err(MeliError::new(response))
*/
Err(MeliError::new(response))
}))
}
}
@ -1206,6 +1212,7 @@ async fn get_hlpr(
h.write_usize(uid);
h.write(mailbox_path.as_bytes());
env.set_hash(h.finish());
/*
debug!(
"env hash {} {} UID = {} MSN = {}",
env.hash(),
@ -1213,6 +1220,7 @@ async fn get_hlpr(
uid,
message_sequence_number
);
*/
valid_hash_set.insert(env.hash());
let mut tag_lck = uid_store.tag_index.write().unwrap();
if let Some((flags, keywords)) = flags {
@ -1273,7 +1281,6 @@ async fn get_hlpr(
kind: RefreshEventKind::Remove(env_hash),
});
}
drop(conn);
unseen
.lock()
.unwrap()
@ -1282,9 +1289,10 @@ async fn get_hlpr(
.lock()
.unwrap()
.insert_existing_set(envelopes.iter().map(|(_, env)| env.hash()).collect::<_>());
drop(conn);
payload.extend(envelopes.into_iter().map(|(_, env)| env));
}
*max_uid = if max_uid_left == 1 {
*max_uid = if max_uid_left <= 1 {
Some(0)
} else {
Some(std::cmp::max(

+ 0
- 1
melib/src/backends/imap_async/connection.rs View File

@ -508,7 +508,6 @@ impl ImapConnection {
if required_responses.check(l) || !self.process_untagged(l).await? {
ret.push_str(l);
}
ret.push_str(l);
}
}
}

+ 0
- 1
melib/src/backends/imap_async/protocol_parser.rs View File

@ -355,7 +355,6 @@ pub fn list_mailbox_result(input: &[u8]) -> IResult<&[u8], ImapMailbox> {
f.no_select = false;
f.is_subscribed = false;
for p in properties.split(|&b| b == b' ') {
use crate::backends::SpecialUsageMailbox;
if p.eq_ignore_ascii_case(b"\\NoSelect") {
f.no_select = true;
} else if p.eq_ignore_ascii_case(b"\\Sent") {

+ 8
- 5
melib/src/backends/jmap.rs View File

@ -20,9 +20,7 @@
*/
use crate::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext};
use crate::backends::BackendOp;
use crate::backends::MailboxHash;
use crate::backends::{BackendMailbox, MailBackend, Mailbox, RefreshEventConsumer};
use crate::backends::*;
use crate::conf::AccountSettings;
use crate::email::*;
use crate::error::{MeliError, Result};
@ -262,11 +260,16 @@ impl MailBackend for JmapType {
)))
}
fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option<Flag>) -> Result<()> {
fn save(
&self,
_bytes: Vec<u8>,
_mailbox_hash: MailboxHash,
_flags: Option<Flag>,
) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}
fn as_any(&self) -> &dyn::std::any::Any {
fn as_any(&self) -> &dyn ::std::any::Any {
self
}

+ 36
- 11
melib/src/backends/maildir/backend.rs View File

@ -178,6 +178,10 @@ impl MailBackend for MaildirType {
Ok(())
}
fn is_online_async(&self) -> ResultFuture<()> {
Ok(Box::pin(async { Ok(()) }))
}
fn mailboxes(&self) -> Result<HashMap<MailboxHash, Mailbox>> {
Ok(self
.mailboxes
@ -185,6 +189,10 @@ impl MailBackend for MaildirType {
.map(|(h, f)| (*h, BackendMailbox::clone(f)))
.collect())
}
fn mailboxes_async(&self) -> ResultFuture<HashMap<MailboxHash, Mailbox>> {
let res = self.mailboxes();
Ok(Box::pin(async { res }))
}
fn get(&mut self, mailbox: &Mailbox) -> Async<Result<Vec<Envelope>>> {
self.multicore(4, mailbox)
@ -670,18 +678,26 @@ impl MailBackend for MaildirType {
)))
}
fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option<Flag>) -> Result<()> {
MaildirType::save_to_mailbox(self.mailboxes[&mailbox_hash].fs_path.clone(), bytes, flags)
fn save(
&self,
bytes: Vec<u8>,
mailbox_hash: MailboxHash,
flags: Option<Flag>,
) -> ResultFuture<()> {
let path = self.mailboxes[&mailbox_hash].fs_path.clone();
Ok(Box::pin(async move {
MaildirType::save_to_mailbox(path, bytes, flags)
}))
}
fn as_any(&self) -> &dyn::std::any::Any {
fn as_any(&self) -> &dyn ::std::any::Any {
self
}
fn create_mailbox(
&mut self,
new_path: String,
) -> Result<(MailboxHash, HashMap<MailboxHash, Mailbox>)> {
) -> ResultFuture<(MailboxHash, HashMap<MailboxHash, Mailbox>)> {
let mut path = self.path.clone();
path.push(&new_path);
if !path.starts_with(&self.path) {
@ -720,21 +736,30 @@ impl MailBackend for MaildirType {
};
self.mailboxes.insert(mailbox_hash, new_mailbox);
Ok((mailbox_hash, self.mailboxes()?))
let ret = Ok((mailbox_hash, self.mailboxes()?));
Ok(Box::pin(async { ret }))
}
fn delete_mailbox(
&mut self,
_mailbox_hash: MailboxHash,
) -> Result<HashMap<MailboxHash, Mailbox>> {
) -> ResultFuture<HashMap<MailboxHash, Mailbox>> {
Err(MeliError::new("Unimplemented."))
}
fn set_mailbox_subscription(&mut self, _mailbox_hash: MailboxHash, _val: bool) -> Result<()> {
fn set_mailbox_subscription(
&mut self,
_mailbox_hash: MailboxHash,
_val: bool,
) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}
fn rename_mailbox(&mut self, _mailbox_hash: MailboxHash, _new_path: String) -> Result<Mailbox> {
fn rename_mailbox(
&mut self,
_mailbox_hash: MailboxHash,
_new_path: String,
) -> ResultFuture<Mailbox> {
Err(MeliError::new("Unimplemented."))
}
@ -742,7 +767,7 @@ impl MailBackend for MaildirType {
&mut self,
_mailbox_hash: MailboxHash,
_val: crate::backends::MailboxPermissions,
) -> Result<()> {
) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}
}
@ -1084,7 +1109,7 @@ impl MaildirType {
w.build(handle)
}
pub fn save_to_mailbox(mut path: PathBuf, bytes: &[u8], flags: Option<Flag>) -> Result<()> {
pub fn save_to_mailbox(mut path: PathBuf, bytes: Vec<u8>, flags: Option<Flag>) -> Result<()> {
for d in &["cur", "new", "tmp"] {
path.push(d);
if !path.is_dir() {
@ -1149,7 +1174,7 @@ impl MaildirType {
file.set_permissions(permissions)?;
let mut writer = io::BufWriter::new(file);
writer.write_all(bytes).unwrap();
writer.write_all(&bytes).unwrap();
return Ok(());
}

+ 6
- 1
melib/src/backends/mbox.rs View File

@ -902,7 +902,12 @@ impl MailBackend for MboxType {
)))
}
fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option<Flag>) -> Result<()> {
fn save(
&self,
_bytes: Vec<u8>,
_mailbox_hash: MailboxHash,
_flags: Option<Flag>,
) -> ResultFuture<()> {
Err(MeliError::new("Unimplemented."))
}

+ 8
- 2
melib/src/backends/notmuch.rs View File

@ -611,13 +611,19 @@ impl MailBackend for NotmuchDb {
}))
}
fn save(&self, bytes: &[u8], _mailbox_hash: MailboxHash, flags: Option<Flag>) -> Result<()> {
fn save(
&self,
bytes: Vec<u8>,
_mailbox_hash: MailboxHash,
flags: Option<Flag>,
) -> ResultFuture<()> {
let path = self
.save_messages_to
.as_ref()
.unwrap_or(&self.path)
.to_path_buf();
MaildirType::save_to_mailbox(path, bytes, flags)
MaildirType::save_to_mailbox(path, bytes, flags)?;
Ok(Box::pin(async { Ok(()) }))
}
fn as_any(&self) -> &dyn ::std::any::Any {

+ 7
- 1
src/components/mail/listing.rs View File

@ -356,7 +356,13 @@ pub trait ListingTrait: Component {
fn set_coordinates(&mut self, _: (usize, MailboxHash));
fn draw_list(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context);
fn highlight_line(&mut self, grid: &mut CellBuffer, area: Area, idx: usize, context: &Context);
fn filter(&mut self, _filter_term: &str, _context: &Context) {}
fn filter(
&mut self,
_filter_term: String,
_results: Result<SmallVec<[EnvelopeHash; 512]>>,
_context: &Context,
) {
}
fn set_movement(&mut self, mvm: PageMovement);
}

+ 50
- 9
src/components/mail/listing/compact.rs View File

@ -22,6 +22,7 @@
use super::EntryStrings;
use super::*;
use crate::components::utilities::PageMovement;
use crate::jobs1::{oneshot, JobId};
use std::cmp;
use std::convert::TryInto;
use std::iter::FromIterator;
@ -61,6 +62,11 @@ pub struct CompactListing {
rows_drawn: SegmentTree,
rows: Vec<((usize, (ThreadHash, EnvelopeHash)), EntryStrings)>,
search_job: Option<(
String,
oneshot::Receiver<Result<SmallVec<[EnvelopeHash; 512]>>>,
JobId,
)>,
filter_term: String,
filtered_selection: Vec<ThreadHash>,
filtered_order: HashMap<ThreadHash, usize>,
@ -724,21 +730,22 @@ impl ListingTrait for CompactListing {
context.dirty_areas.push_back(area);
}
fn filter(&mut self, filter_term: &str, context: &Context) {
if filter_term.is_empty() {
return;
}
fn filter(
&mut self,
filter_term: String,
results: Result<SmallVec<[EnvelopeHash; 512]>>,
context: &Context,
) {
self.order.clear();
self.selection.clear();
self.length = 0;
self.filtered_selection.clear();
self.filtered_order.clear();
self.filter_term = filter_term.to_string();
self.filter_term = filter_term;
self.row_updates.clear();
let account = &context.accounts[self.cursor_pos.0];
match account.search(&self.filter_term, self.sort, self.cursor_pos.1) {
match results {
Ok(results) => {
let threads = &account.collection.threads[&self.cursor_pos.1];
for env_hash in results {
@ -841,6 +848,7 @@ impl CompactListing {
subsort: (SortField::Date, SortOrder::Desc),
all_threads: HashSet::default(),
order: HashMap::default(),
search_job: None,
filter_term: String::new(),
filtered_selection: Vec::new(),
filtered_order: HashMap::default(),
@ -1520,8 +1528,41 @@ impl Component for CompactListing {
return true;
}
UIEvent::Action(Action::Listing(Search(ref filter_term))) if !self.unfocused => {
self.filter(filter_term, context);
self.dirty = true;
match context.accounts[self.cursor_pos.0].search(
filter_term,
self.sort,
self.cursor_pos.1,
) {
Ok(job) => {
let (chan, job_id) = context.accounts[self.cursor_pos.0]
.job_executor
.spawn_specialized(job);
context.accounts[self.cursor_pos.0]
.active_jobs
.insert(job_id.clone(), crate::conf::accounts::JobRequest::Search);
self.search_job = Some((filter_term.to_string(), chan, job_id));
}
Err(err) => {
context.replies.push_back(UIEvent::Notification(
Some("Could not perform search".to_string()),
err.to_string(),
Some(crate::types::NotificationType::ERROR),
));
}
};
self.set_dirty(true);
}
UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id))
if self
.search_job
.as_ref()
.map(|(_, _, j)| j == job_id)
.unwrap_or(false) =>
{
let (filter_term, mut rcvr, job_id) = self.search_job.take().unwrap();
let results = rcvr.try_recv().unwrap().unwrap();
self.filter(filter_term, results, context);
self.set_dirty(true);
}
_ => {}
}

+ 89
- 80
src/components/mail/listing/conversations.rs View File

@ -687,54 +687,88 @@ impl ListingTrait for ConversationsListing {
context.dirty_areas.push_back(area);
}
fn filter(&mut self, filter_term: &str, context: &Context) {
if filter_term.is_empty() {
return;
}
fn filter(
&mut self,
filter_term: String,
results: Result<SmallVec<[EnvelopeHash; 512]>>,
context: &Context,
) {
/*
if filter_term.is_empty() {
return;
}
self.order.clear();
self.selection.clear();
self.length = 0;
self.filtered_selection.clear();
self.filtered_order.clear();
self.filter_term = filter_term.to_string();
self.row_updates.clear();
for v in self.selection.values_mut() {
*v = false;
}
self.order.clear();
self.selection.clear();
self.length = 0;
self.filtered_selection.clear();
self.filtered_order.clear();
self.filter_term = filter_term.to_string();
self.row_updates.clear();
for v in self.selection.values_mut() {
*v = false;
}
let account = &context.accounts[self.cursor_pos.0];
match account.search(&self.filter_term, self.sort, self.cursor_pos.1) {
Ok(results) => {
let threads = &account.collection.threads[&self.cursor_pos.1];
for env_hash in results {
if !account.collection.contains_key(&env_hash) {
continue;
}
let env_thread_node_hash = account.collection.get_env(env_hash).thread();
if !threads.thread_nodes.contains_key(&env_thread_node_hash) {
continue;
}
let thread =
threads.find_group(threads.thread_nodes[&env_thread_node_hash].group);
if self.filtered_order.contains_key(&thread) {
continue;
let account = &context.accounts[self.cursor_pos.0];
match account.search(&self.filter_term, self.sort, self.cursor_pos.1) {
Ok(results) => {
/*
let threads = &account.collection.threads[&self.cursor_pos.1];
for env_hash in results {
if !account.collection.contains_key(&env_hash) {
continue;
}
let env_thread_node_hash = account.collection.get_env(env_hash).thread();
if !threads.thread_nodes.contains_key(&env_thread_node_hash) {
continue;
}
let thread =
threads.find_group(threads.thread_nodes[&env_thread_node_hash].group);
if self.filtered_order.contains_key(&thread) {
continue;
}
if self.all_threads.contains(&thread) {
self.filtered_selection.push(thread);
self.filtered_order
.insert(thread, self.filtered_selection.len() - 1);
}
}
if self.all_threads.contains(&thread) {
self.filtered_selection.push(thread);
self.filtered_order
.insert(thread, self.filtered_selection.len() - 1);
if !self.filtered_selection.is_empty() {
threads.group_inner_sort_by(
&mut self.filtered_selection,
self.sort,
&context.accounts[self.cursor_pos.0].collection.envelopes,
);
self.new_cursor_pos.2 =
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
} else {
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
self.content = CellBuffer::new_with_context(0, 0, default_cell, context);
}
self.redraw_threads_list(
context,
Box::new(self.filtered_selection.clone().into_iter())
as Box<dyn Iterator<Item = ThreadHash>>,
);
*/
}
if !self.filtered_selection.is_empty() {
threads.group_inner_sort_by(
&mut self.filtered_selection,
self.sort,
&context.accounts[self.cursor_pos.0].collection.envelopes,
Err(e) => {
self.cursor_pos.2 = 0;
self.new_cursor_pos.2 = 0;
let message = format!(
"Encountered an error while searching for `{}`: {}.",
self.filter_term, e
);
log(
format!("Failed to search for term {}: {}", self.filter_term, e),
ERROR,
);
self.new_cursor_pos.2 =
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
} else {
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
@ -742,45 +776,20 @@ impl ListingTrait for ConversationsListing {
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
self.content = CellBuffer::new_with_context(0, 0, default_cell, context);
self.content =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
write_string_to_grid(
&message,
&mut self.content,
self.color_cache.theme_default.fg,
self.color_cache.theme_default.bg,
self.color_cache.theme_default.attrs,
((0, 0), (message.len() - 1, 0)),
None,
);
}
self.redraw_threads_list(
context,
Box::new(self.filtered_selection.clone().into_iter())
as Box<dyn Iterator<Item = ThreadHash>>,
);
}
Err(e) => {
self.cursor_pos.2 = 0;
self.new_cursor_pos.2 = 0;
let message = format!(
"Encountered an error while searching for `{}`: {}.",
self.filter_term, e
);
log(
format!("Failed to search for term {}: {}", self.filter_term, e),
ERROR,
);
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
self.content =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
write_string_to_grid(
&message,
&mut self.content,
self.color_cache.theme_default.fg,
self.color_cache.theme_default.bg,
self.color_cache.theme_default.attrs,
((0, 0), (message.len() - 1, 0)),
None,
);
}
}
*/
}
fn set_movement(&mut self, mvm: PageMovement) {
@ -1336,7 +1345,7 @@ impl Component for ConversationsListing {
}
UIEvent::Action(ref action) => match action {
Action::Listing(Search(ref filter_term)) if !self.unfocused => {
self.filter(filter_term, context);
//self.filter(filter_term, context);
self.dirty = true;
return true;
}

+ 0
- 2
src/components/mail/listing/offline.rs View File

@ -74,8 +74,6 @@ impl ListingTrait for OfflineListing {
fn draw_list(&mut self, _: &mut CellBuffer, _: Area, _: &mut Context) {}
fn filter(&mut self, _: &str, _: &Context) {}
fn set_movement(&mut self, _: PageMovement) {}
}

+ 78
- 69
src/components/mail/listing/plain.rs View File

@ -581,42 +581,77 @@ impl ListingTrait for PlainListing {
context.dirty_areas.push_back(area);
}
fn filter(&mut self, filter_term: &str, context: &Context) {
if filter_term.is_empty() {
return;
}
fn filter(
&mut self,
filter_term: String,
results: Result<SmallVec<[EnvelopeHash; 512]>>,
context: &Context,
) {
/*
if filter_term.is_empty() {
return;
}
self.order.clear();
self.selection.clear();
self.length = 0;
self.filtered_selection.clear();
self.filtered_order.clear();
self.filter_term = filter_term.to_string();
self.row_updates.clear();
for v in self.selection.values_mut() {
*v = false;
}
self.order.clear();
self.selection.clear();
self.length = 0;
self.filtered_selection.clear();
self.filtered_order.clear();
self.filter_term = filter_term.to_string();
self.row_updates.clear();
for v in self.selection.values_mut() {
*v = false;
}
let account = &context.accounts[self.cursor_pos.0];
match account.search(&self.filter_term, self.sort, self.cursor_pos.1) {
Ok(results) => {
for env_hash in results {
if !account.collection.contains_key(&env_hash) {
continue;
}
if self.filtered_order.contains_key(&env_hash) {
continue;
let account = &context.accounts[self.cursor_pos.0];
match account.search(&self.filter_term, self.sort, self.cursor_pos.1) {
Ok(results) => {
/*
for env_hash in results {
if !account.collection.contains_key(&env_hash) {
continue;
}
if self.filtered_order.contains_key(&env_hash) {
continue;
}
if self.all_envelopes.contains(&env_hash) {
self.filtered_selection.push(env_hash);
self.filtered_order
.insert(env_hash, self.filtered_selection.len() - 1);
}
}
if self.all_envelopes.contains(&env_hash) {
self.filtered_selection.push(env_hash);
self.filtered_order
.insert(env_hash, self.filtered_selection.len() - 1);
if !self.filtered_selection.is_empty() {
self.new_cursor_pos.2 =
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
} else {
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
self.data_columns.columns[0] =
CellBuffer::new_with_context(0, 0, default_cell, context);
}
self.redraw_list(
context,
Box::new(self.filtered_selection.clone().into_iter())
as Box<dyn Iterator<Item = EnvelopeHash>>,
);
*/
}
if !self.filtered_selection.is_empty() {
self.new_cursor_pos.2 =
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
} else {
Err(e) => {
self.cursor_pos.2 = 0;
self.new_cursor_pos.2 = 0;
let message = format!(
"Encountered an error while searching for `{}`: {}.",
&self.filter_term, e
);
log(
format!("Failed to search for term {}: {}", &self.filter_term, e),
ERROR,
);
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
@ -625,45 +660,19 @@ impl ListingTrait for PlainListing {
ret
};
self.data_columns.columns[0] =
CellBuffer::new_with_context(0, 0, default_cell, context);
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
write_string_to_grid(
&message,
&mut self.data_columns.columns[0],
self.color_cache.theme_default.fg,
self.color_cache.theme_default.bg,
self.color_cache.theme_default.attrs,
((0, 0), (message.len() - 1, 0)),
None,
);
}
self.redraw_list(
context,
Box::new(self.filtered_selection.clone().into_iter())
as Box<dyn Iterator<Item = EnvelopeHash>>,
);
}
Err(e) => {
self.cursor_pos.2 = 0;
self.new_cursor_pos.2 = 0;
let message = format!(
"Encountered an error while searching for `{}`: {}.",
&self.filter_term, e
);
log(
format!("Failed to search for term {}: {}", &self.filter_term, e),
ERROR,
);
let default_cell = {
let mut ret = Cell::with_char(' ');
ret.set_fg(self.color_cache.theme_default.fg)
.set_bg(self.color_cache.theme_default.bg)
.set_attrs(self.color_cache.theme_default.attrs);
ret
};
self.data_columns.columns[0] =
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
write_string_to_grid(
&message,
&mut self.data_columns.columns[0],
self.color_cache.theme_default.fg,
self.color_cache.theme_default.bg,
self.color_cache.theme_default.attrs,
((0, 0), (message.len() - 1, 0)),
None,
);
}
}
*/
}
fn set_movement(&mut self, mvm: PageMovement) {
@ -1267,7 +1276,7 @@ impl Component for PlainListing {