From e06308fed26e0730b2f8a15880a371cd38bfdf20 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Tue, 30 Jun 2020 11:40:26 +0300 Subject: [PATCH] MailBackend: change more methods to Futures --- melib/src/backends.rs | 53 ++-- melib/src/backends/imap.rs | 66 +++-- melib/src/backends/imap/protocol_parser.rs | 1 - melib/src/backends/imap_async.rs | 108 ++++---- melib/src/backends/imap_async/connection.rs | 1 - .../backends/imap_async/protocol_parser.rs | 1 - melib/src/backends/jmap.rs | 13 +- melib/src/backends/maildir/backend.rs | 47 +++- melib/src/backends/mbox.rs | 7 +- melib/src/backends/notmuch.rs | 10 +- src/components/mail/listing.rs | 8 +- src/components/mail/listing/compact.rs | 59 ++++- src/components/mail/listing/conversations.rs | 171 +++++++------ src/components/mail/listing/offline.rs | 2 - src/components/mail/listing/plain.rs | 147 +++++------ src/components/utilities.rs | 37 ++- src/components/utilities/widgets.rs | 23 +- src/conf/accounts.rs | 232 ++++++++++-------- src/plugins/backend.rs | 9 +- src/types.rs | 2 + src/unix.rs | 6 +- 21 files changed, 611 insertions(+), 392 deletions(-) diff --git a/melib/src/backends.rs b/melib/src/backends.rs index 4fe74859..6d0ce570 100644 --- a/melib/src/backends.rs +++ b/melib/src/backends.rs @@ -295,11 +295,11 @@ impl NotifyFn { } } +pub type ResultFuture = Result> + Send + 'static>>>; + pub trait MailBackend: ::std::fmt::Debug + Send + Sync { fn is_online(&self) -> Result<()>; - fn is_online_async( - &self, - ) -> 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> + 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; fn mailboxes(&self) -> Result>; - fn mailboxes_async( - &self, - ) -> Result>> + Send>>> { + fn mailboxes_async(&self) -> ResultFuture> { Err(MeliError::new("Unimplemented.")) } fn operation(&self, hash: EnvelopeHash) -> Result>; - fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option) -> Result<()>; - fn delete(&self, _env_hash: EnvelopeHash, _mailbox_hash: MailboxHash) -> Result<()> { + fn save( + &self, + bytes: Vec, + mailbox_hash: MailboxHash, + flags: Option, + ) -> ResultFuture<()>; + fn delete(&self, _env_hash: EnvelopeHash, _mailbox_hash: MailboxHash) -> ResultFuture<()> { Err(MeliError::new("Unimplemented.")) } fn tags(&self) -> Option>>> { @@ -353,22 +356,30 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync { fn create_mailbox( &mut self, _path: String, - ) -> Result<(MailboxHash, HashMap)> { + ) -> ResultFuture<(MailboxHash, HashMap)> { Err(MeliError::new("Unimplemented.")) } fn delete_mailbox( &mut self, _mailbox_hash: MailboxHash, - ) -> Result> { + ) -> ResultFuture> { 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 { + fn rename_mailbox( + &mut self, + _mailbox_hash: MailboxHash, + _new_path: String, + ) -> ResultFuture { 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, - ) -> Result> { + ) -> ResultFuture> { Err(MeliError::new("Unimplemented.")) } } @@ -469,18 +480,10 @@ impl BackendOp for ReadOnlyOp { fn fetch_flags(&self) -> Result { self.op.fetch_flags() } - fn set_flag( - &mut self, - _flag: Flag, - _value: bool, - ) -> 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> + Send>>> { + fn set_tag(&mut self, _tag: String, _value: bool) -> ResultFuture<()> { Err(MeliError::new("read-only set.")) } } diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index ee748e7c..8c9f37f7 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -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) -> Result<()> { + fn save( + &self, + bytes: Vec, + mailbox_hash: MailboxHash, + flags: Option, + ) -> 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)> { + ) -> ResultFuture<(MailboxHash, HashMap)> { /* 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> { + ) -> ResultFuture> { 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 { + ) -> ResultFuture { 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, - ) -> Result> { + ) -> ResultFuture> { 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)) diff --git a/melib/src/backends/imap/protocol_parser.rs b/melib/src/backends/imap/protocol_parser.rs index ec673c81..bd1545f4 100644 --- a/melib/src/backends/imap/protocol_parser.rs +++ b/melib/src/backends/imap/protocol_parser.rs @@ -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") { diff --git a/melib/src/backends/imap_async.rs b/melib/src/backends/imap_async.rs index 368777d8..717b3c67 100644 --- a/melib/src/backends/imap_async.rs +++ b/melib/src/backends/imap_async.rs @@ -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> + 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>> + Send>>, - > { + fn mailboxes_async(&self) -> ResultFuture> { 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> + 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) -> Result<()> { + fn save( + &self, + _bytes: Vec, + _mailbox_hash: MailboxHash, + _flags: Option, + ) -> ResultFuture<()> { unimplemented!() /* let path = { @@ -450,7 +448,7 @@ impl MailBackend for ImapType { fn create_mailbox( &mut self, _path: String, - ) -> Result<(MailboxHash, HashMap)> { + ) -> ResultFuture<(MailboxHash, HashMap)> { 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> { + ) -> ResultFuture> { 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 { + fn rename_mailbox( + &mut self, + _mailbox_hash: MailboxHash, + _new_path: String, + ) -> ResultFuture { 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, - ) -> Result> { - unimplemented!() - /* + query: crate::search::Query, + mailbox_hash: Option, + ) -> ResultFuture> { 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); + 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 = 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), - )); + 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( diff --git a/melib/src/backends/imap_async/connection.rs b/melib/src/backends/imap_async/connection.rs index 362f9c63..e1653c1b 100644 --- a/melib/src/backends/imap_async/connection.rs +++ b/melib/src/backends/imap_async/connection.rs @@ -508,7 +508,6 @@ impl ImapConnection { if required_responses.check(l) || !self.process_untagged(l).await? { ret.push_str(l); } - ret.push_str(l); } } } diff --git a/melib/src/backends/imap_async/protocol_parser.rs b/melib/src/backends/imap_async/protocol_parser.rs index 5823d69e..dbcc29d9 100644 --- a/melib/src/backends/imap_async/protocol_parser.rs +++ b/melib/src/backends/imap_async/protocol_parser.rs @@ -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") { diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index 2999fcb0..5228c452 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -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) -> Result<()> { + fn save( + &self, + _bytes: Vec, + _mailbox_hash: MailboxHash, + _flags: Option, + ) -> ResultFuture<()> { Err(MeliError::new("Unimplemented.")) } - fn as_any(&self) -> &dyn::std::any::Any { + fn as_any(&self) -> &dyn ::std::any::Any { self } diff --git a/melib/src/backends/maildir/backend.rs b/melib/src/backends/maildir/backend.rs index 5a463cd2..d24cdee8 100644 --- a/melib/src/backends/maildir/backend.rs +++ b/melib/src/backends/maildir/backend.rs @@ -178,6 +178,10 @@ impl MailBackend for MaildirType { Ok(()) } + fn is_online_async(&self) -> ResultFuture<()> { + Ok(Box::pin(async { Ok(()) })) + } + fn mailboxes(&self) -> Result> { Ok(self .mailboxes @@ -185,6 +189,10 @@ impl MailBackend for MaildirType { .map(|(h, f)| (*h, BackendMailbox::clone(f))) .collect()) } + fn mailboxes_async(&self) -> ResultFuture> { + let res = self.mailboxes(); + Ok(Box::pin(async { res })) + } fn get(&mut self, mailbox: &Mailbox) -> Async>> { self.multicore(4, mailbox) @@ -670,18 +678,26 @@ impl MailBackend for MaildirType { ))) } - fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option) -> Result<()> { - MaildirType::save_to_mailbox(self.mailboxes[&mailbox_hash].fs_path.clone(), bytes, flags) + fn save( + &self, + bytes: Vec, + mailbox_hash: MailboxHash, + flags: Option, + ) -> 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)> { + ) -> ResultFuture<(MailboxHash, HashMap)> { 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> { + ) -> ResultFuture> { 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 { + fn rename_mailbox( + &mut self, + _mailbox_hash: MailboxHash, + _new_path: String, + ) -> ResultFuture { 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) -> Result<()> { + pub fn save_to_mailbox(mut path: PathBuf, bytes: Vec, flags: Option) -> 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(()); } diff --git a/melib/src/backends/mbox.rs b/melib/src/backends/mbox.rs index b5b26f64..4239c0f5 100644 --- a/melib/src/backends/mbox.rs +++ b/melib/src/backends/mbox.rs @@ -902,7 +902,12 @@ impl MailBackend for MboxType { ))) } - fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option) -> Result<()> { + fn save( + &self, + _bytes: Vec, + _mailbox_hash: MailboxHash, + _flags: Option, + ) -> ResultFuture<()> { Err(MeliError::new("Unimplemented.")) } diff --git a/melib/src/backends/notmuch.rs b/melib/src/backends/notmuch.rs index b8e4d701..fb755ec0 100644 --- a/melib/src/backends/notmuch.rs +++ b/melib/src/backends/notmuch.rs @@ -611,13 +611,19 @@ impl MailBackend for NotmuchDb { })) } - fn save(&self, bytes: &[u8], _mailbox_hash: MailboxHash, flags: Option) -> Result<()> { + fn save( + &self, + bytes: Vec, + _mailbox_hash: MailboxHash, + flags: Option, + ) -> 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 { diff --git a/src/components/mail/listing.rs b/src/components/mail/listing.rs index afead265..f27b800e 100644 --- a/src/components/mail/listing.rs +++ b/src/components/mail/listing.rs @@ -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>, + _context: &Context, + ) { + } fn set_movement(&mut self, mvm: PageMovement); } diff --git a/src/components/mail/listing/compact.rs b/src/components/mail/listing/compact.rs index c4f48252..6543d97e 100644 --- a/src/components/mail/listing/compact.rs +++ b/src/components/mail/listing/compact.rs @@ -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>>, + JobId, + )>, filter_term: String, filtered_selection: Vec, filtered_order: HashMap, @@ -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>, + 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); } _ => {} } diff --git a/src/components/mail/listing/conversations.rs b/src/components/mail/listing/conversations.rs index b8efe0a0..f7dc2146 100644 --- a/src/components/mail/listing/conversations.rs +++ b/src/components/mail/listing/conversations.rs @@ -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>, + 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 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); + } } - let env_thread_node_hash = account.collection.get_env(env_hash).thread(); - if !threads.thread_nodes.contains_key(&env_thread_node_hash) { - continue; + 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); } - 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.filtered_selection.is_empty() { - threads.group_inner_sort_by( - &mut self.filtered_selection, - self.sort, - &context.accounts[self.cursor_pos.0].collection.envelopes, + self.redraw_threads_list( + context, + Box::new(self.filtered_selection.clone().into_iter()) + as Box>, + ); + */ + } + 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>, - ); } - 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; } diff --git a/src/components/mail/listing/offline.rs b/src/components/mail/listing/offline.rs index d898a67e..b3102185 100644 --- a/src/components/mail/listing/offline.rs +++ b/src/components/mail/listing/offline.rs @@ -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) {} } diff --git a/src/components/mail/listing/plain.rs b/src/components/mail/listing/plain.rs index e6677847..33d8557e 100644 --- a/src/components/mail/listing/plain.rs +++ b/src/components/mail/listing/plain.rs @@ -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>, + 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; + 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.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.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>, + ); + */ } - 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>, - ); } - 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 { return true; } UIEvent::Action(Action::Listing(Search(ref filter_term))) if !self.unfocused => { - self.filter(filter_term, context); + //self.filter(filter_term, context); self.dirty = true; } _ => {} diff --git a/src/components/utilities.rs b/src/components/utilities.rs index c448e83b..32f5c997 100644 --- a/src/components/utilities.rs +++ b/src/components/utilities.rs @@ -30,6 +30,7 @@ pub use self::widgets::*; mod layouts; pub use self::layouts::*; +use crate::jobs1::JobId; use std::collections::HashSet; #[derive(Debug, Clone, Copy)] @@ -636,6 +637,8 @@ pub struct StatusBar { height: usize, dirty: bool, id: ComponentId, + progress_spinner: ProgressSpinner, + in_progress_jobs: HashSet, auto_complete: AutoComplete, cmd_history: Vec, @@ -661,7 +664,8 @@ impl StatusBar { height: 1, id: ComponentId::new_v4(), auto_complete: AutoComplete::new(Vec::new()), - + progress_spinner: ProgressSpinner::new(3), + in_progress_jobs: HashSet::default(), cmd_history: crate::execute::history::old_cmd_history(), } } @@ -755,6 +759,17 @@ impl Component for StatusBar { context, ); + if self.progress_spinner.is_dirty() { + self.progress_spinner.draw( + grid, + ( + (get_x(bottom_right).saturating_sub(1), get_y(bottom_right)), + bottom_right, + ), + context, + ); + } + if self.mode != UIMode::Execute && !self.is_dirty() { return; } @@ -1148,12 +1163,30 @@ impl Component for StatusBar { self.status = format!("{} | {}", self.mode, std::mem::replace(s, String::new())); self.dirty = true; } + UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id)) => { + self.in_progress_jobs.remove(job_id); + if self.in_progress_jobs.is_empty() { + self.progress_spinner.stop(); + self.set_dirty(true); + } + } + UIEvent::StatusEvent(StatusEvent::NewJob(ref job_id)) => { + if self.in_progress_jobs.is_empty() { + self.progress_spinner.start(); + } + self.in_progress_jobs.insert(job_id.clone()); + } + UIEvent::Timer(_) => { + if self.progress_spinner.process_event(event, context) { + return true; + } + } _ => {} } false } fn is_dirty(&self) -> bool { - self.dirty || self.container.is_dirty() + self.dirty || self.container.is_dirty() || self.progress_spinner.is_dirty() } fn set_dirty(&mut self, value: bool) { self.dirty = value; diff --git a/src/components/utilities/widgets.rs b/src/components/utilities/widgets.rs index 44e4c2cd..9484778e 100644 --- a/src/components/utilities/widgets.rs +++ b/src/components/utilities/widgets.rs @@ -992,6 +992,8 @@ impl ScrollBar { #[derive(Debug)] pub struct ProgressSpinner { + //total_work: usize, + //finished: usize, timer: crate::timer::PosixTimer, stage: usize, kind: usize, @@ -1018,10 +1020,13 @@ impl ProgressSpinner { &["⚪", "⚫"], ]; + const INTERVAL: std::time::Duration = std::time::Duration::from_millis(50); + const VALUE: std::time::Duration = std::time::Duration::from_millis(500); + pub fn new(kind: usize) -> Self { let timer = crate::timer::PosixTimer::new_with_signal( - std::time::Duration::from_millis(50), - std::time::Duration::from_millis(500), + std::time::Duration::from_millis(0), + std::time::Duration::from_millis(0), nix::sys::signal::Signal::SIGALRM, ) .unwrap(); @@ -1033,6 +1038,20 @@ impl ProgressSpinner { id: ComponentId::new_v4(), } } + + pub fn start(&mut self) { + self.timer + .set_interval(Self::INTERVAL) + .set_value(Self::VALUE) + .rearm() + } + + pub fn stop(&mut self) { + self.timer + .set_interval(std::time::Duration::from_millis(0)) + .set_value(std::time::Duration::from_millis(0)) + .rearm() + } } impl fmt::Display for ProgressSpinner { diff --git a/src/conf/accounts.rs b/src/conf/accounts.rs index fd0f7049..5e891be1 100644 --- a/src/conf/accounts.rs +++ b/src/conf/accounts.rs @@ -28,7 +28,7 @@ use crate::jobs1::{JobExecutor, JobId, JoinHandle}; use melib::async_workers::{Async, AsyncBuilder, AsyncStatus, WorkContext}; use melib::backends::{ AccountHash, BackendOp, Backends, MailBackend, Mailbox, MailboxHash, NotifyFn, ReadOnlyOp, - RefreshEvent, RefreshEventConsumer, RefreshEventKind, SpecialUsageMailbox, + RefreshEvent, RefreshEventConsumer, RefreshEventKind, ResultFuture, SpecialUsageMailbox, }; use melib::email::*; use melib::error::{MeliError, Result}; @@ -43,6 +43,7 @@ use crate::types::UIEvent::{self, EnvelopeRemove, EnvelopeRename, EnvelopeUpdate use crate::{StatusEvent, ThreadEvent}; use crossbeam::Sender; use futures::channel::oneshot; +use futures::future::FutureExt; pub use futures::stream::Stream; use futures::stream::StreamExt; use std::collections::VecDeque; @@ -131,13 +132,13 @@ pub struct Account { pub(crate) backend: Arc>>, pub job_executor: Arc, - active_jobs: HashMap, + pub active_jobs: HashMap, sender: Sender, event_queue: VecDeque<(MailboxHash, RefreshEvent)>, notify_fn: Arc, } -enum JobRequest { +pub enum JobRequest { Mailboxes(oneshot::Receiver>>), Get( MailboxHash, @@ -154,11 +155,7 @@ enum JobRequest { CreateMailbox(oneshot::Receiver)>>), DeleteMailbox(oneshot::Receiver>>), //RenameMailbox, - Search( - crate::search::Query, - Option, - oneshot::Receiver>>, - ), + Search, SetMailboxPermissions(MailboxHash, oneshot::Receiver>), SetMailboxSubscription(MailboxHash, oneshot::Receiver>), Watch(JoinHandle), @@ -177,7 +174,7 @@ impl core::fmt::Debug for JobRequest { JobRequest::CreateMailbox(_) => write!(f, "{}", "JobRequest::CreateMailbox"), JobRequest::DeleteMailbox(_) => write!(f, "{}", "JobRequest::DeleteMailbox"), //JobRequest::RenameMailbox, - JobRequest::Search(_, _, _) => write!(f, "{}", "JobRequest::Search"), + JobRequest::Search => write!(f, "{}", "JobRequest::Search"), JobRequest::SetMailboxPermissions(_, _) => { write!(f, "{}", "JobRequest::SetMailboxPermissions") } @@ -189,6 +186,22 @@ impl core::fmt::Debug for JobRequest { } } +impl JobRequest { + fn is_get(&self, mailbox_hash: MailboxHash) -> bool { + match self { + JobRequest::Get(h, _) if *h == mailbox_hash => true, + _ => false, + } + } + + fn is_online(&self) -> bool { + match self { + JobRequest::IsOnline(_) => true, + _ => false, + } + } +} + impl Drop for Account { fn drop(&mut self) { if let Ok(data_dir) = xdg::BaseDirectories::with_profile("meli", &self.name) { @@ -294,14 +307,17 @@ impl Account { let mut active_jobs = HashMap::default(); if settings.conf.is_async { + if let Ok(mailboxes_job) = backend.mailboxes_async() { + if let Ok(online_job) = backend.is_online_async() { + let (rcvr, job_id) = + job_executor.spawn_specialized(online_job.then(|_| mailboxes_job)); + active_jobs.insert(job_id, JobRequest::Mailboxes(rcvr)); + } + } if let Ok(online_job) = backend.is_online_async() { let (rcvr, job_id) = job_executor.spawn_specialized(online_job); active_jobs.insert(job_id, JobRequest::IsOnline(rcvr)); } - if let Ok(mailboxes_job) = backend.mailboxes_async() { - let (rcvr, job_id) = job_executor.spawn_specialized(mailboxes_job); - active_jobs.insert(job_id, JobRequest::Mailboxes(rcvr)); - } } Ok(Account { @@ -460,6 +476,11 @@ impl Account { if let Ok(mailbox_job) = self.backend.write().unwrap().get_async(&f) { let mailbox_job = mailbox_job.into_future(); let (rcvr, job_id) = self.job_executor.spawn_specialized(mailbox_job); + self.sender + .send(ThreadEvent::UIEvent(UIEvent::StatusEvent( + StatusEvent::NewJob(job_id.clone()), + ))) + .unwrap(); self.active_jobs.insert(job_id, JobRequest::Get(*h, rcvr)); } } else { @@ -790,6 +811,11 @@ impl Account { if self.settings.conf.is_async { if let Ok(refresh_job) = self.backend.write().unwrap().refresh_async(mailbox_hash, r) { let (rcvr, job_id) = self.job_executor.spawn_specialized(refresh_job); + self.sender + .send(ThreadEvent::UIEvent(UIEvent::StatusEvent( + StatusEvent::NewJob(job_id.clone()), + ))) + .unwrap(); self.active_jobs .insert(job_id, JobRequest::Refresh(mailbox_hash, rcvr)); } @@ -882,18 +908,25 @@ impl Account { } MailboxStatus::None => { if self.settings.conf.is_async { - if let Ok(mailbox_job) = - self.backend.write().unwrap().get_async( - &&self.mailbox_entries[&mailbox_hash].ref_mailbox, - ) - { - let mailbox_job = mailbox_job.into_future(); - let (rcvr, job_id) = - self.job_executor.spawn_specialized(mailbox_job); - self.active_jobs - .insert(job_id, JobRequest::Get(mailbox_hash, rcvr)); + if !self.active_jobs.values().any(|j| j.is_get(mailbox_hash)) { + if let Ok(mailbox_job) = + self.backend.write().unwrap().get_async( + &&self.mailbox_entries[&mailbox_hash].ref_mailbox, + ) + { + let mailbox_job = mailbox_job.into_future(); + let (rcvr, job_id) = + self.job_executor.spawn_specialized(mailbox_job); + self.sender + .send(ThreadEvent::UIEvent(UIEvent::StatusEvent( + StatusEvent::NewJob(job_id.clone()), + ))) + .unwrap(); + self.active_jobs + .insert(job_id, JobRequest::Get(mailbox_hash, rcvr)); + } } - } else { + } else if self.mailbox_entries[&mailbox_hash].worker.is_none() { let handle = Account::new_worker( self.mailbox_entries[&mailbox_hash].ref_mailbox.clone(), &mut self.backend, @@ -991,7 +1024,7 @@ impl Account { } pub fn save_special( - &self, + &mut self, bytes: &[u8], mailbox_type: SpecialUsageMailbox, flags: Flag, @@ -1042,27 +1075,55 @@ impl Account { } } - pub fn save(&self, bytes: &[u8], mailbox_hash: MailboxHash, flags: Option) -> Result<()> { + pub fn save( + &mut self, + bytes: &[u8], + mailbox_hash: MailboxHash, + flags: Option, + ) -> Result<()> { if self.settings.account.read_only() { return Err(MeliError::new(format!( "Account {} is read-only.", self.name.as_str() ))); } - self.backend + let job = self + .backend .write() .unwrap() - .save(bytes, mailbox_hash, flags) + .save(bytes.to_vec(), mailbox_hash, flags)?; + let (rcvr, job_id) = self.job_executor.spawn_specialized(job); + self.sender + .send(ThreadEvent::UIEvent(UIEvent::StatusEvent( + StatusEvent::NewJob(job_id.clone()), + ))) + .unwrap(); + self.active_jobs + .insert(job_id, JobRequest::SaveMessage(mailbox_hash, rcvr)); + Ok(()) } - pub fn delete(&self, env_hash: EnvelopeHash, mailbox_hash: MailboxHash) -> Result<()> { + pub fn delete(&mut self, env_hash: EnvelopeHash, mailbox_hash: MailboxHash) -> Result<()> { if self.settings.account.read_only() { return Err(MeliError::new(format!( "Account {} is read-only.", self.name.as_str() ))); } - self.backend.write().unwrap().delete(env_hash, mailbox_hash) + let job = self + .backend + .write() + .unwrap() + .delete(env_hash, mailbox_hash)?; + let (rcvr, job_id) = self.job_executor.spawn_specialized(job); + self.sender + .send(ThreadEvent::UIEvent(UIEvent::StatusEvent( + StatusEvent::NewJob(job_id.clone()), + ))) + .unwrap(); + self.active_jobs + .insert(job_id, JobRequest::DeleteMessage(env_hash, rcvr)); + Ok(()) } pub fn contains_key(&self, h: EnvelopeHash) -> bool { @@ -1091,11 +1152,12 @@ impl Account { } match op { MailboxOperation::Create(path) => { - let (mailbox_hash, mut mailboxes) = self - .backend - .write() - .unwrap() - .create_mailbox(path.to_string())?; + let (mailbox_hash, mut mailboxes) = futures::executor::block_on( + self.backend + .write() + .unwrap() + .create_mailbox(path.to_string())?, + )?; self.sender .send(ThreadEvent::UIEvent(UIEvent::MailboxCreate(( self.index, @@ -1159,7 +1221,9 @@ impl Account { return Err(MeliError::new("Cannot delete only mailbox.")); } let mailbox_hash = self.mailbox_by_path(&path)?; - let mut mailboxes = self.backend.write().unwrap().delete_mailbox(mailbox_hash)?; + let mut mailboxes = futures::executor::block_on( + self.backend.write().unwrap().delete_mailbox(mailbox_hash)?, + )?; self.sender .send(ThreadEvent::UIEvent(UIEvent::MailboxDelete(( self.index, @@ -1254,13 +1318,7 @@ impl Account { if self.is_online { return Ok(()); } - if !self.active_jobs.values().any(|j| { - if let JobRequest::IsOnline(_) = j { - true - } else { - false - } - }) { + if !self.active_jobs.values().any(JobRequest::is_online) { if let Ok(online_job) = self.backend.read().unwrap().is_online_async() { let (rcvr, job_id) = self.job_executor.spawn_specialized(online_job); self.active_jobs.insert(job_id, JobRequest::IsOnline(rcvr)); @@ -1270,7 +1328,7 @@ impl Account { } else { let ret = self.backend.read().unwrap().is_online(); if ret.is_ok() != self.is_online && ret.is_ok() { - //self.init()?; + self.init(None)?; } self.is_online = ret.is_ok(); if !self.is_online { @@ -1285,33 +1343,14 @@ impl Account { search_term: &str, sort: (SortField, SortOrder), mailbox_hash: MailboxHash, - ) -> Result> { - if self.settings.account().format() == "imap" { - use melib::parsec::Parser; - let query = melib::search::query().parse(search_term)?.1; - return self - .backend - .read() - .unwrap() - .search(query, Some(mailbox_hash)); - } - - #[cfg(feature = "notmuch")] - { - if self.settings.account().format() == "notmuch" { - let backend_lck = self.backend.read().unwrap(); - let b = (*backend_lck).as_any(); - return if let Some(notmuch_backend) = b.downcast_ref::() - { - notmuch_backend.search(search_term) - } else { - Err(MeliError::new( - "Internal error: Could not downcast backend to NotmuchDb", - )) - }; - } - } - + ) -> ResultFuture> { + use melib::parsec::Parser; + let query = melib::search::query().parse(search_term)?.1; + self.backend + .read() + .unwrap() + .search(query, Some(mailbox_hash)) + /* #[cfg(feature = "sqlite3")] { crate::sqlite3::search(search_term, sort) @@ -1346,6 +1385,7 @@ impl Account { } Ok(ret) } + */ } pub fn mailbox_by_path(&self, path: &str) -> Result { @@ -1379,6 +1419,12 @@ impl Account { } } JobRequest::Get(mailbox_hash, mut chan) => { + self.sender + .send(ThreadEvent::UIEvent(UIEvent::StatusEvent( + StatusEvent::JobFinished(job_id.clone()), + ))) + .unwrap(); + let (payload, rest): (Option>>, _) = chan.try_recv().unwrap().unwrap(); debug!("got payload in status for {}", mailbox_hash); @@ -1398,8 +1444,12 @@ impl Account { .unwrap(); return true; } - let (rcvr, job_id) = self.job_executor.spawn_specialized(rest.into_future()); + self.sender + .send(ThreadEvent::UIEvent(UIEvent::StatusEvent( + StatusEvent::NewJob(job_id.clone()), + ))) + .unwrap(); self.active_jobs .insert(job_id, JobRequest::Get(mailbox_hash, rcvr)); let payload = payload.unwrap(); @@ -1465,18 +1515,11 @@ impl Account { if r.is_some() && r.unwrap().is_ok() { self.is_online = true; } - } - JobRequest::Refresh(_, mut chan) => { - let r = chan.try_recv().unwrap(); - if let Some(Err(err)) = r { - self.sender - .send(ThreadEvent::UIEvent(UIEvent::Notification( - Some(format!("{} refresh exited with error", &self.name)), - err.to_string(), - Some(crate::types::NotificationType::ERROR), - ))) - .expect("Could not send event on main channel"); - } + self.sender + .send(ThreadEvent::UIEvent(UIEvent::StatusEvent( + StatusEvent::JobFinished(job_id.clone()), + ))) + .unwrap(); } JobRequest::SetFlags(_, mut chan) => { let r = chan.try_recv().unwrap(); @@ -1558,21 +1601,12 @@ impl Account { } } //JobRequest::RenameMailbox, - JobRequest::Search(_, _, mut chan) => { - let r = chan.try_recv().unwrap(); - match r { - Some(Err(err)) => { - self.sender - .send(ThreadEvent::UIEvent(UIEvent::Notification( - Some(format!("{}: could not perform search", &self.name)), - err.to_string(), - Some(crate::types::NotificationType::ERROR), - ))) - .expect("Could not send event on main channel"); - } - Some(Ok(v)) => unimplemented!(), - None => {} - } + JobRequest::Search => { + self.sender + .send(ThreadEvent::UIEvent(UIEvent::StatusEvent( + StatusEvent::JobFinished(job_id.clone()), + ))) + .unwrap(); } JobRequest::SetMailboxPermissions(_, mut chan) => { let r = chan.try_recv().unwrap(); diff --git a/src/plugins/backend.rs b/src/plugins/backend.rs index 3c96fb6c..0a58964a 100644 --- a/src/plugins/backend.rs +++ b/src/plugins/backend.rs @@ -213,13 +213,18 @@ impl MailBackend for PluginBackend { })) } - fn save(&self, _bytes: &[u8], _mailbox_hash: MailboxHash, _flags: Option) -> Result<()> { + fn save( + &self, + _bytes: Vec, + _mailbox_hash: MailboxHash, + _flags: Option, + ) -> ResultFuture<()> { Err(MeliError::new("Unimplemented.")) } fn create_mailbox( &mut self, _name: String, - ) -> Result<(MailboxHash, HashMap)> { + ) -> ResultFuture<(MailboxHash, HashMap)> { Err(MeliError::new("Unimplemented.")) } fn tags(&self) -> Option>>> { diff --git a/src/types.rs b/src/types.rs index a9b08e06..c56c8c53 100644 --- a/src/types.rs +++ b/src/types.rs @@ -53,6 +53,8 @@ pub enum StatusEvent { BufClear, BufSet(String), UpdateStatus(String), + NewJob(JobId), + JobFinished(JobId), } /// `ThreadEvent` encapsulates all of the possible values we need to transfer between our threads diff --git a/src/unix.rs b/src/unix.rs index f6498fe3..b473fbb1 100644 --- a/src/unix.rs +++ b/src/unix.rs @@ -115,13 +115,15 @@ pub mod timer { } /// Sets value without arming timer - pub fn set_value(&mut self, value: Duration) { + pub fn set_value(&mut self, value: Duration) -> &mut Self { self.value = value; + self } /// Sets interval without arming timer - pub fn set_interval(&mut self, interval: Duration) { + pub fn set_interval(&mut self, interval: Duration) -> &mut Self { self.interval = interval; + self } pub fn new_with_signal(