More imap async fixes
parent
391058a59c
commit
bbedeed3e3
|
@ -943,6 +943,7 @@ dependencies = [
|
||||||
"nix",
|
"nix",
|
||||||
"nom",
|
"nom",
|
||||||
"notify",
|
"notify",
|
||||||
|
"pin-utils",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
|
@ -329,6 +329,9 @@ pub trait MailBackend: ::std::fmt::Debug + Send + Sync {
|
||||||
sender: RefreshEventConsumer,
|
sender: RefreshEventConsumer,
|
||||||
work_context: WorkContext,
|
work_context: WorkContext,
|
||||||
) -> Result<std::thread::ThreadId>;
|
) -> Result<std::thread::ThreadId>;
|
||||||
|
fn watch_async(&self, sender: RefreshEventConsumer) -> ResultFuture<()> {
|
||||||
|
Err(MeliError::new("Unimplemented."))
|
||||||
|
}
|
||||||
fn mailboxes(&self) -> Result<HashMap<MailboxHash, Mailbox>>;
|
fn mailboxes(&self) -> Result<HashMap<MailboxHash, Mailbox>>;
|
||||||
fn mailboxes_async(&self) -> ResultFuture<HashMap<MailboxHash, Mailbox>> {
|
fn mailboxes_async(&self) -> ResultFuture<HashMap<MailboxHash, Mailbox>> {
|
||||||
Err(MeliError::new("Unimplemented."))
|
Err(MeliError::new("Unimplemented."))
|
||||||
|
|
|
@ -20,8 +20,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use futures::lock::Mutex as FutureMutex;
|
|
||||||
|
|
||||||
use crate::backends::*;
|
use crate::backends::*;
|
||||||
use crate::email::*;
|
use crate::email::*;
|
||||||
use crate::error::{MeliError, Result};
|
use crate::error::{MeliError, Result};
|
||||||
|
|
|
@ -658,13 +658,13 @@ pub fn uid_fetch_response_(
|
||||||
let (input, _) = take_while(is_digit)(input)?;
|
let (input, _) = take_while(is_digit)(input)?;
|
||||||
let (input, result) = permutation((
|
let (input, result) = permutation((
|
||||||
preceded(
|
preceded(
|
||||||
tag("UID "),
|
alt((tag("UID "), tag(" UID "))),
|
||||||
map_res(digit1, |s| {
|
map_res(digit1, |s| {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
opt(preceded(
|
opt(preceded(
|
||||||
tag("FLAGS "),
|
alt((tag("FLAGS "), tag(" FLAGS "))),
|
||||||
delimited(tag("("), byte_flags, tag(")")),
|
delimited(tag("("), byte_flags, tag(")")),
|
||||||
)),
|
)),
|
||||||
length_data(delimited(
|
length_data(delimited(
|
||||||
|
@ -685,13 +685,16 @@ pub fn uid_fetch_flags_response(input: &[u8]) -> IResult<&[u8], Vec<(usize, (Fla
|
||||||
many0(|input| -> IResult<&[u8], (usize, (Flag, Vec<String>))> {
|
many0(|input| -> IResult<&[u8], (usize, (Flag, Vec<String>))> {
|
||||||
let (input, _) = tag("* ")(input)?;
|
let (input, _) = tag("* ")(input)?;
|
||||||
let (input, _) = take_while(is_digit)(input)?;
|
let (input, _) = take_while(is_digit)(input)?;
|
||||||
let (input, _) = tag(" FETCH ( ")(input)?;
|
let (input, _) = tag(" FETCH (")(input)?;
|
||||||
let (input, uid_flags) = permutation((
|
let (input, uid_flags) = permutation((
|
||||||
preceded(
|
preceded(
|
||||||
tag("UID "),
|
alt((tag("UID "), tag(" UID "))),
|
||||||
map_res(digit1, |s| usize::from_str(to_str!(s))),
|
map_res(digit1, |s| usize::from_str(to_str!(s))),
|
||||||
),
|
),
|
||||||
preceded(tag("FLAGS "), delimited(tag("("), byte_flags, tag(")"))),
|
preceded(
|
||||||
|
alt((tag("FLAGS "), tag(" FLAGS "))),
|
||||||
|
delimited(tag("("), byte_flags, tag(")")),
|
||||||
|
),
|
||||||
))(input.ltrim())?;
|
))(input.ltrim())?;
|
||||||
let (input, _) = tag(")\r\n")(input)?;
|
let (input, _) = tag(")\r\n")(input)?;
|
||||||
Ok((input, (uid_flags.0, uid_flags.1)))
|
Ok((input, (uid_flags.0, uid_flags.1)))
|
||||||
|
@ -1356,13 +1359,13 @@ pub fn uid_fetch_envelopes_response(
|
||||||
let (input, _) = tag(" FETCH (")(input)?;
|
let (input, _) = tag(" FETCH (")(input)?;
|
||||||
let (input, uid_flags) = permutation((
|
let (input, uid_flags) = permutation((
|
||||||
preceded(
|
preceded(
|
||||||
tag("UID "),
|
alt((tag("UID "), tag(" UID "))),
|
||||||
map_res(digit1, |s| {
|
map_res(digit1, |s| {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
opt(preceded(
|
opt(preceded(
|
||||||
tag("FLAGS "),
|
alt((tag("FLAGS "), tag(" FLAGS "))),
|
||||||
delimited(tag("("), byte_flags, tag(")")),
|
delimited(tag("("), byte_flags, tag(")")),
|
||||||
)),
|
)),
|
||||||
))(input.ltrim())?;
|
))(input.ltrim())?;
|
||||||
|
@ -1424,31 +1427,31 @@ pub fn status_response(input: &[u8]) -> IResult<&[u8], StatusResponse> {
|
||||||
let (input, _) = tag(" (")(input)?;
|
let (input, _) = tag(" (")(input)?;
|
||||||
let (input, result) = permutation((
|
let (input, result) = permutation((
|
||||||
opt(preceded(
|
opt(preceded(
|
||||||
tag("MESSAGES "),
|
alt((tag("MESSAGES "), tag(" MESSAGES "))),
|
||||||
map_res(digit1, |s| {
|
map_res(digit1, |s| {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
opt(preceded(
|
opt(preceded(
|
||||||
tag("RECENT "),
|
alt((tag("RECENT "), tag(" RECENT "))),
|
||||||
map_res(digit1, |s| {
|
map_res(digit1, |s| {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
opt(preceded(
|
opt(preceded(
|
||||||
tag("UIDNEXT "),
|
alt((tag("UIDNEXT "), tag(" UIDNEXT "))),
|
||||||
map_res(digit1, |s| {
|
map_res(digit1, |s| {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
opt(preceded(
|
opt(preceded(
|
||||||
tag("UIDVALIDITY "),
|
alt((tag("UIDVALIDITY "), tag(" UIDVALIDITY "))),
|
||||||
map_res(digit1, |s| {
|
map_res(digit1, |s| {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
opt(preceded(
|
opt(preceded(
|
||||||
tag("UNSEEN "),
|
alt((tag("UNSEEN "), tag(" UNSEEN "))),
|
||||||
map_res(digit1, |s| {
|
map_res(digit1, |s| {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -135,7 +135,7 @@ pub struct UIDStore {
|
||||||
byte_cache: Arc<Mutex<HashMap<UID, EnvelopeCache>>>,
|
byte_cache: Arc<Mutex<HashMap<UID, EnvelopeCache>>>,
|
||||||
tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
|
tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
|
||||||
|
|
||||||
mailboxes: Arc<RwLock<HashMap<MailboxHash, ImapMailbox>>>,
|
mailboxes: Arc<FutureMutex<HashMap<MailboxHash, ImapMailbox>>>,
|
||||||
is_online: Arc<Mutex<(Instant, Result<()>)>>,
|
is_online: Arc<Mutex<(Instant, Result<()>)>>,
|
||||||
refresh_events: Arc<Mutex<Vec<RefreshEvent>>>,
|
refresh_events: Arc<Mutex<Vec<RefreshEvent>>>,
|
||||||
sender: Arc<RwLock<Option<RefreshEventConsumer>>>,
|
sender: Arc<RwLock<Option<RefreshEventConsumer>>>,
|
||||||
|
@ -151,7 +151,7 @@ impl Default for UIDStore {
|
||||||
uid_index: Default::default(),
|
uid_index: Default::default(),
|
||||||
msn_index: Default::default(),
|
msn_index: Default::default(),
|
||||||
byte_cache: Default::default(),
|
byte_cache: Default::default(),
|
||||||
mailboxes: Arc::new(RwLock::new(Default::default())),
|
mailboxes: Arc::new(FutureMutex::new(Default::default())),
|
||||||
tag_index: Arc::new(RwLock::new(Default::default())),
|
tag_index: Arc::new(RwLock::new(Default::default())),
|
||||||
is_online: Arc::new(Mutex::new((
|
is_online: Arc::new(Mutex::new((
|
||||||
Instant::now(),
|
Instant::now(),
|
||||||
|
@ -204,18 +204,17 @@ impl MailBackend for ImapType {
|
||||||
mailbox_hash: MailboxHash,
|
mailbox_hash: MailboxHash,
|
||||||
sender: RefreshEventConsumer,
|
sender: RefreshEventConsumer,
|
||||||
) -> ResultFuture<()> {
|
) -> ResultFuture<()> {
|
||||||
let inbox = self
|
|
||||||
.uid_store
|
|
||||||
.mailboxes
|
|
||||||
.read()
|
|
||||||
.unwrap()
|
|
||||||
.get(&mailbox_hash)
|
|
||||||
.map(std::clone::Clone::clone)
|
|
||||||
.unwrap();
|
|
||||||
let main_conn = self.connection.clone();
|
let main_conn = self.connection.clone();
|
||||||
*self.uid_store.sender.write().unwrap() = Some(sender);
|
*self.uid_store.sender.write().unwrap() = Some(sender);
|
||||||
let uid_store = self.uid_store.clone();
|
let uid_store = self.uid_store.clone();
|
||||||
Ok(Box::pin(async move {
|
Ok(Box::pin(async move {
|
||||||
|
let inbox = uid_store
|
||||||
|
.mailboxes
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.get(&mailbox_hash)
|
||||||
|
.map(std::clone::Clone::clone)
|
||||||
|
.unwrap();
|
||||||
let mut conn = main_conn.lock().await;
|
let mut conn = main_conn.lock().await;
|
||||||
watch::examine_updates(&inbox, &mut conn, &uid_store).await?;
|
watch::examine_updates(&inbox, &mut conn, &uid_store).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -227,7 +226,7 @@ impl MailBackend for ImapType {
|
||||||
let connection = self.connection.clone();
|
let connection = self.connection.clone();
|
||||||
Ok(Box::pin(async move {
|
Ok(Box::pin(async move {
|
||||||
{
|
{
|
||||||
let mailboxes = uid_store.mailboxes.read().unwrap();
|
let mailboxes = uid_store.mailboxes.lock().await;
|
||||||
if !mailboxes.is_empty() {
|
if !mailboxes.is_empty() {
|
||||||
return Ok(mailboxes
|
return Ok(mailboxes
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -236,7 +235,7 @@ impl MailBackend for ImapType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let new_mailboxes = ImapType::imap_mailboxes(&connection).await?;
|
let new_mailboxes = ImapType::imap_mailboxes(&connection).await?;
|
||||||
let mut mailboxes = uid_store.mailboxes.write()?;
|
let mut mailboxes = uid_store.mailboxes.lock().await;
|
||||||
*mailboxes = new_mailboxes;
|
*mailboxes = new_mailboxes;
|
||||||
/*
|
/*
|
||||||
let mut invalid_configs = vec![];
|
let mut invalid_configs = vec![];
|
||||||
|
@ -314,7 +313,7 @@ impl MailBackend for ImapType {
|
||||||
_mailbox_hash: MailboxHash,
|
_mailbox_hash: MailboxHash,
|
||||||
_sender: RefreshEventConsumer,
|
_sender: RefreshEventConsumer,
|
||||||
) -> Result<Async<()>> {
|
) -> Result<Async<()>> {
|
||||||
unimplemented!()
|
Err(MeliError::new("Unimplemented."))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn watch(
|
fn watch(
|
||||||
|
@ -323,45 +322,39 @@ impl MailBackend for ImapType {
|
||||||
_work_context: WorkContext,
|
_work_context: WorkContext,
|
||||||
) -> Result<std::thread::ThreadId> {
|
) -> Result<std::thread::ThreadId> {
|
||||||
Ok(std::thread::current().id())
|
Ok(std::thread::current().id())
|
||||||
//Err(MeliError::new("Unimplemented."))
|
}
|
||||||
//unimplemented!()
|
|
||||||
/*
|
fn watch_async(&self, sender: RefreshEventConsumer) -> ResultFuture<()> {
|
||||||
|
debug!("watch_async called");
|
||||||
let conn = ImapConnection::new_connection(&self.server_conf, self.uid_store.clone());
|
let conn = ImapConnection::new_connection(&self.server_conf, self.uid_store.clone());
|
||||||
let main_conn = self.connection.clone();
|
let main_conn = self.connection.clone();
|
||||||
*self.uid_store.sender.write().unwrap() = Some(sender);
|
*self.uid_store.sender.write().unwrap() = Some(sender);
|
||||||
let uid_store = self.uid_store.clone();
|
let uid_store = self.uid_store.clone();
|
||||||
let handle = std::thread::Builder::new()
|
Ok(Box::pin(async move {
|
||||||
.name(format!("{} imap connection", self.account_name.as_str(),))
|
let has_idle: bool = main_conn
|
||||||
.spawn(move || {
|
.lock()
|
||||||
let thread = std::thread::current();
|
.await
|
||||||
work_context
|
.capabilities
|
||||||
.set_status
|
.iter()
|
||||||
.send((thread.id(), "watching".to_string()))
|
.any(|cap| cap.eq_ignore_ascii_case(b"IDLE"));
|
||||||
.unwrap();
|
debug!(has_idle);
|
||||||
let has_idle: bool = main_conn
|
let kit = ImapWatchKit {
|
||||||
.lock()
|
conn,
|
||||||
.unwrap()
|
main_conn,
|
||||||
.capabilities
|
uid_store,
|
||||||
.iter()
|
};
|
||||||
.any(|cap| cap.eq_ignore_ascii_case(b"IDLE"));
|
if has_idle {
|
||||||
//let kit = ImapWatchKit {
|
idle(kit).await?;
|
||||||
// conn,
|
} else {
|
||||||
// main_conn,
|
poll_with_examine(kit).await?;
|
||||||
// uid_store,
|
}
|
||||||
// work_context,
|
debug!("watch_async future returning");
|
||||||
//};
|
Ok(())
|
||||||
//if has_idle {
|
}))
|
||||||
// idle(kit).ok().take();
|
|
||||||
//} else {
|
|
||||||
// poll_with_examine(kit).ok().take();
|
|
||||||
//}
|
|
||||||
})?;
|
|
||||||
Ok(handle.thread().id())
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mailboxes(&self) -> Result<HashMap<MailboxHash, Mailbox>> {
|
fn mailboxes(&self) -> Result<HashMap<MailboxHash, Mailbox>> {
|
||||||
unimplemented!()
|
Err(MeliError::new("Unimplemented."))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn operation(&self, hash: EnvelopeHash) -> Result<Box<dyn BackendOp>> {
|
fn operation(&self, hash: EnvelopeHash) -> Result<Box<dyn BackendOp>> {
|
||||||
|
@ -392,7 +385,7 @@ impl MailBackend for ImapType {
|
||||||
let connection = self.connection.clone();
|
let connection = self.connection.clone();
|
||||||
Ok(Box::pin(async move {
|
Ok(Box::pin(async move {
|
||||||
let path = {
|
let path = {
|
||||||
let mailboxes = uid_store.mailboxes.read().unwrap();
|
let mailboxes = uid_store.mailboxes.lock().await;
|
||||||
|
|
||||||
let mailbox = mailboxes.get(&mailbox_hash).ok_or(MeliError::new(format!(
|
let mailbox = mailboxes.get(&mailbox_hash).ok_or(MeliError::new(format!(
|
||||||
"Mailbox with hash {} not found.",
|
"Mailbox with hash {} not found.",
|
||||||
|
@ -468,7 +461,7 @@ impl MailBackend for ImapType {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
{
|
{
|
||||||
let mailboxes = uid_store.mailboxes.write().unwrap();
|
let mailboxes = uid_store.mailboxes.lock().await;
|
||||||
for root_mailbox in mailboxes.values().filter(|f| f.parent.is_none()) {
|
for root_mailbox in mailboxes.values().filter(|f| f.parent.is_none()) {
|
||||||
if path.starts_with(&root_mailbox.name) {
|
if path.starts_with(&root_mailbox.name) {
|
||||||
debug!("path starts with {:?}", &root_mailbox);
|
debug!("path starts with {:?}", &root_mailbox);
|
||||||
|
@ -508,7 +501,7 @@ impl MailBackend for ImapType {
|
||||||
let ret: Result<()> = ImapResponse::from(&response).into();
|
let ret: Result<()> = ImapResponse::from(&response).into();
|
||||||
ret?;
|
ret?;
|
||||||
let new_hash = get_path_hash!(path.as_str());
|
let new_hash = get_path_hash!(path.as_str());
|
||||||
uid_store.mailboxes.write().unwrap().clear();
|
uid_store.mailboxes.lock().await.clear();
|
||||||
Ok((new_hash, new_mailbox_fut?.await.map_err(|err| MeliError::new(format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err)))?))
|
Ok((new_hash, new_mailbox_fut?.await.map_err(|err| MeliError::new(format!("Mailbox create was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err)))?))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -525,7 +518,7 @@ impl MailBackend for ImapType {
|
||||||
let no_select: bool;
|
let no_select: bool;
|
||||||
let is_subscribed: bool;
|
let is_subscribed: bool;
|
||||||
{
|
{
|
||||||
let mailboxes = uid_store.mailboxes.read().unwrap();
|
let mailboxes = uid_store.mailboxes.lock().await;
|
||||||
no_select = mailboxes[&mailbox_hash].no_select;
|
no_select = mailboxes[&mailbox_hash].no_select;
|
||||||
is_subscribed = mailboxes[&mailbox_hash].is_subscribed();
|
is_subscribed = mailboxes[&mailbox_hash].is_subscribed();
|
||||||
imap_path = mailboxes[&mailbox_hash].imap_path().to_string();
|
imap_path = mailboxes[&mailbox_hash].imap_path().to_string();
|
||||||
|
@ -563,7 +556,7 @@ impl MailBackend for ImapType {
|
||||||
}
|
}
|
||||||
let ret: Result<()> = ImapResponse::from(&response).into();
|
let ret: Result<()> = ImapResponse::from(&response).into();
|
||||||
ret?;
|
ret?;
|
||||||
uid_store.mailboxes.write().unwrap().clear();
|
uid_store.mailboxes.lock().await.clear();
|
||||||
new_mailbox_fut?.await.map_err(|err| format!("Mailbox delete was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err).into())
|
new_mailbox_fut?.await.map_err(|err| format!("Mailbox delete was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err).into())
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -578,7 +571,7 @@ impl MailBackend for ImapType {
|
||||||
Ok(Box::pin(async move {
|
Ok(Box::pin(async move {
|
||||||
let command: String;
|
let command: String;
|
||||||
{
|
{
|
||||||
let mailboxes = uid_store.mailboxes.write().unwrap();
|
let mailboxes = uid_store.mailboxes.lock().await;
|
||||||
if mailboxes[&mailbox_hash].is_subscribed() == new_val {
|
if mailboxes[&mailbox_hash].is_subscribed() == new_val {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -604,8 +597,8 @@ impl MailBackend for ImapType {
|
||||||
if ret.is_ok() {
|
if ret.is_ok() {
|
||||||
uid_store
|
uid_store
|
||||||
.mailboxes
|
.mailboxes
|
||||||
.write()
|
.lock()
|
||||||
.unwrap()
|
.await
|
||||||
.entry(mailbox_hash)
|
.entry(mailbox_hash)
|
||||||
.and_modify(|entry| {
|
.and_modify(|entry| {
|
||||||
let _ = entry.set_is_subscribed(new_val);
|
let _ = entry.set_is_subscribed(new_val);
|
||||||
|
@ -627,7 +620,7 @@ impl MailBackend for ImapType {
|
||||||
let command: String;
|
let command: String;
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = String::with_capacity(8 * 1024);
|
||||||
{
|
{
|
||||||
let mailboxes = uid_store.mailboxes.write().unwrap();
|
let mailboxes = uid_store.mailboxes.lock().await;
|
||||||
let permissions = mailboxes[&mailbox_hash].permissions();
|
let permissions = mailboxes[&mailbox_hash].permissions();
|
||||||
if !permissions.delete_mailbox {
|
if !permissions.delete_mailbox {
|
||||||
return Err(MeliError::new(format!("You do not have permission to rename mailbox `{}` (rename is equivalent to delete + create). Set permissions for this mailbox are {}", mailboxes[&mailbox_hash].name(), permissions)));
|
return Err(MeliError::new(format!("You do not have permission to rename mailbox `{}` (rename is equivalent to delete + create). Set permissions for this mailbox are {}", mailboxes[&mailbox_hash].name(), permissions)));
|
||||||
|
@ -654,10 +647,10 @@ impl MailBackend for ImapType {
|
||||||
let new_hash = get_path_hash!(new_path.as_str());
|
let new_hash = get_path_hash!(new_path.as_str());
|
||||||
let ret: Result<()> = ImapResponse::from(&response).into();
|
let ret: Result<()> = ImapResponse::from(&response).into();
|
||||||
ret?;
|
ret?;
|
||||||
uid_store.mailboxes.write().unwrap().clear();
|
uid_store.mailboxes.lock().await.clear();
|
||||||
new_mailbox_fut?.await.map_err(|err| format!("Mailbox rename was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err))?;
|
new_mailbox_fut?.await.map_err(|err| format!("Mailbox rename was succesful (returned `{}`) but listing mailboxes afterwards returned `{}`", response, err))?;
|
||||||
Ok(BackendMailbox::clone(
|
Ok(BackendMailbox::clone(
|
||||||
&uid_store.mailboxes.read().unwrap()[&new_hash],
|
&uid_store.mailboxes.lock().await[&new_hash],
|
||||||
))
|
))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -670,7 +663,7 @@ impl MailBackend for ImapType {
|
||||||
let uid_store = self.uid_store.clone();
|
let uid_store = self.uid_store.clone();
|
||||||
let connection = self.connection.clone();
|
let connection = self.connection.clone();
|
||||||
Ok(Box::pin(async move {
|
Ok(Box::pin(async move {
|
||||||
let mailboxes = uid_store.mailboxes.write().unwrap();
|
let mailboxes = uid_store.mailboxes.lock().await;
|
||||||
let permissions = mailboxes[&mailbox_hash].permissions();
|
let permissions = mailboxes[&mailbox_hash].permissions();
|
||||||
if !permissions.change_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)));
|
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)));
|
||||||
|
@ -1076,7 +1069,7 @@ async fn get_hlpr(
|
||||||
max_uid: &mut Option<usize>,
|
max_uid: &mut Option<usize>,
|
||||||
) -> Result<Vec<Envelope>> {
|
) -> Result<Vec<Envelope>> {
|
||||||
let (permissions, mailbox_path, mailbox_exists, no_select, unseen) = {
|
let (permissions, mailbox_path, mailbox_exists, no_select, unseen) = {
|
||||||
let f = &uid_store.mailboxes.read().unwrap()[&mailbox_hash];
|
let f = &uid_store.mailboxes.lock().await[&mailbox_hash];
|
||||||
(
|
(
|
||||||
f.permissions.clone(),
|
f.permissions.clone(),
|
||||||
f.imap_path().to_string(),
|
f.imap_path().to_string(),
|
||||||
|
|
|
@ -569,7 +569,7 @@ impl ImapConnection {
|
||||||
self.send_command(
|
self.send_command(
|
||||||
format!(
|
format!(
|
||||||
"SELECT \"{}\"",
|
"SELECT \"{}\"",
|
||||||
self.uid_store.mailboxes.read().unwrap()[&mailbox_hash].imap_path()
|
self.uid_store.mailboxes.lock().await[&mailbox_hash].imap_path()
|
||||||
)
|
)
|
||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
)
|
)
|
||||||
|
@ -593,7 +593,7 @@ impl ImapConnection {
|
||||||
self.send_command(
|
self.send_command(
|
||||||
format!(
|
format!(
|
||||||
"EXAMINE \"{}\"",
|
"EXAMINE \"{}\"",
|
||||||
self.uid_store.mailboxes.read().unwrap()[&mailbox_hash].imap_path()
|
self.uid_store.mailboxes.lock().await[&mailbox_hash].imap_path()
|
||||||
)
|
)
|
||||||
.as_bytes(),
|
.as_bytes(),
|
||||||
)
|
)
|
||||||
|
@ -679,27 +679,7 @@ pub struct ImapBlockingConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ImapConnection> for ImapBlockingConnection {
|
impl From<ImapConnection> for ImapBlockingConnection {
|
||||||
fn from(_conn: ImapConnection) -> Self {
|
fn from(conn: ImapConnection) -> Self {
|
||||||
unimplemented!()
|
|
||||||
/*
|
|
||||||
conn.set_nonblocking(false)
|
|
||||||
.expect("set_nonblocking call failed");
|
|
||||||
conn.stream
|
|
||||||
.as_mut()
|
|
||||||
.map(|s| {
|
|
||||||
s.stream
|
|
||||||
.set_write_timeout(Some(std::time::Duration::new(30, 0)))
|
|
||||||
.expect("set_write_timeout call failed")
|
|
||||||
})
|
|
||||||
.expect("set_write_timeout call failed");
|
|
||||||
conn.stream
|
|
||||||
.as_mut()
|
|
||||||
.map(|s| {
|
|
||||||
s.stream
|
|
||||||
.set_read_timeout(Some(std::time::Duration::new(30, 0)))
|
|
||||||
.expect("set_read_timeout call failed")
|
|
||||||
})
|
|
||||||
.expect("set_read_timeout call failed");
|
|
||||||
ImapBlockingConnection {
|
ImapBlockingConnection {
|
||||||
buf: [0; 1024],
|
buf: [0; 1024],
|
||||||
conn,
|
conn,
|
||||||
|
@ -707,7 +687,6 @@ impl From<ImapConnection> for ImapBlockingConnection {
|
||||||
result: Vec::with_capacity(8 * 1024),
|
result: Vec::with_capacity(8 * 1024),
|
||||||
err: None,
|
err: None,
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -719,69 +698,81 @@ impl ImapBlockingConnection {
|
||||||
pub fn err(&self) -> Option<&str> {
|
pub fn err(&self) -> Option<&str> {
|
||||||
self.err.as_ref().map(String::as_str)
|
self.err.as_ref().map(String::as_str)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for ImapBlockingConnection {
|
|
||||||
type Item = Vec<u8>;
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
unimplemented!()
|
|
||||||
/*
|
|
||||||
|
|
||||||
|
pub fn into_stream<'a>(&'a mut self) -> impl Future<Output = Option<Vec<u8>>> + 'a {
|
||||||
self.result.drain(0..self.prev_res_length);
|
self.result.drain(0..self.prev_res_length);
|
||||||
self.prev_res_length = 0;
|
self.prev_res_length = 0;
|
||||||
|
let mut break_flag = false;
|
||||||
let mut prev_failure = None;
|
let mut prev_failure = None;
|
||||||
let ImapBlockingConnection {
|
async move {
|
||||||
ref mut prev_res_length,
|
if self.conn.stream.is_err() {
|
||||||
ref mut result,
|
debug!(&self.conn.stream);
|
||||||
ref mut conn,
|
|
||||||
ref mut buf,
|
|
||||||
ref mut err,
|
|
||||||
} = self;
|
|
||||||
loop {
|
|
||||||
if conn.stream.is_err() {
|
|
||||||
debug!(&conn.stream);
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
match conn.stream.as_mut().unwrap().stream.read(buf) {
|
loop {
|
||||||
Ok(0) => return None,
|
if let Some(y) = read(self, &mut break_flag, &mut prev_failure).await {
|
||||||
Ok(b) => {
|
return Some(y);
|
||||||
result.extend_from_slice(&buf[0..b]);
|
|
||||||
debug!(unsafe { std::str::from_utf8_unchecked(result) });
|
|
||||||
if let Some(pos) = result.find(b"\r\n") {
|
|
||||||
*prev_res_length = pos + b"\r\n".len();
|
|
||||||
return Some(result[0..*prev_res_length].to_vec());
|
|
||||||
}
|
|
||||||
prev_failure = None;
|
|
||||||
}
|
}
|
||||||
Err(e)
|
if break_flag {
|
||||||
if e.kind() == std::io::ErrorKind::WouldBlock
|
|
||||||
|| e.kind() == std::io::ErrorKind::Interrupted =>
|
|
||||||
{
|
|
||||||
debug!(&e);
|
|
||||||
if let Some(prev_failure) = prev_failure.as_ref() {
|
|
||||||
if Instant::now().duration_since(*prev_failure)
|
|
||||||
>= std::time::Duration::new(60 * 5, 0)
|
|
||||||
{
|
|
||||||
*err = Some(e.to_string());
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
prev_failure = Some(Instant::now());
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(&conn.stream);
|
|
||||||
debug!(&e);
|
|
||||||
*err = Some(e.to_string());
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn read(
|
||||||
|
conn: &mut ImapBlockingConnection,
|
||||||
|
break_flag: &mut bool,
|
||||||
|
prev_failure: &mut Option<std::time::Instant>,
|
||||||
|
) -> Option<Vec<u8>> {
|
||||||
|
let ImapBlockingConnection {
|
||||||
|
ref mut prev_res_length,
|
||||||
|
ref mut result,
|
||||||
|
ref mut conn,
|
||||||
|
ref mut buf,
|
||||||
|
ref mut err,
|
||||||
|
} = conn;
|
||||||
|
|
||||||
|
match conn.stream.as_mut().unwrap().stream.read(buf).await {
|
||||||
|
Ok(0) => {
|
||||||
|
*break_flag = true;
|
||||||
|
}
|
||||||
|
Ok(b) => {
|
||||||
|
result.extend_from_slice(&buf[0..b]);
|
||||||
|
debug!(unsafe { std::str::from_utf8_unchecked(result) });
|
||||||
|
if let Some(pos) = result.find(b"\r\n") {
|
||||||
|
*prev_res_length = pos + b"\r\n".len();
|
||||||
|
return Some(result[0..*prev_res_length].to_vec());
|
||||||
|
}
|
||||||
|
*prev_failure = None;
|
||||||
|
}
|
||||||
|
Err(e)
|
||||||
|
if e.kind() == std::io::ErrorKind::WouldBlock
|
||||||
|
|| e.kind() == std::io::ErrorKind::Interrupted =>
|
||||||
|
{
|
||||||
|
debug!(&e);
|
||||||
|
if let Some(prev_failure) = prev_failure.as_ref() {
|
||||||
|
if Instant::now().duration_since(*prev_failure)
|
||||||
|
>= std::time::Duration::new(60 * 5, 0)
|
||||||
|
{
|
||||||
|
*err = Some(e.to_string());
|
||||||
|
*break_flag = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*prev_failure = Some(Instant::now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!(&conn.stream);
|
||||||
|
debug!(&e);
|
||||||
|
*err = Some(e.to_string());
|
||||||
|
*break_flag = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn lookup_ipv4(host: &str, port: u16) -> Result<SocketAddr> {
|
fn lookup_ipv4(host: &str, port: u16) -> Result<SocketAddr> {
|
||||||
use std::net::ToSocketAddrs;
|
use std::net::ToSocketAddrs;
|
||||||
|
|
||||||
|
|
|
@ -126,6 +126,26 @@ impl RequiredResponses {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_imap_required_responses() {
|
||||||
|
let mut ret = String::new();
|
||||||
|
let required_responses = RequiredResponses::FETCH_REQUIRED;
|
||||||
|
let response =
|
||||||
|
&"* 1040 FETCH (UID 1064 FLAGS ())\r\nM15 OK Fetch completed (0.001 + 0.299 secs).\r\n"
|
||||||
|
[0..];
|
||||||
|
for l in response.split_rn() {
|
||||||
|
/*debug!("check line: {}", &l);*/
|
||||||
|
if required_responses.check(l) {
|
||||||
|
ret.push_str(l);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_eq!(&ret, "* 1040 FETCH (UID 1064 FLAGS ())\r\n");
|
||||||
|
let v = protocol_parser::uid_fetch_flags_response(response.as_bytes())
|
||||||
|
.unwrap()
|
||||||
|
.1;
|
||||||
|
assert_eq!(v.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Alert(String);
|
pub struct Alert(String);
|
||||||
pub type ImapParseResult<'a, T> = Result<(&'a str, T, Option<Alert>)>;
|
pub type ImapParseResult<'a, T> = Result<(&'a str, T, Option<Alert>)>;
|
||||||
|
@ -171,18 +191,18 @@ impl std::fmt::Display for ResponseCode {
|
||||||
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
use ResponseCode::*;
|
use ResponseCode::*;
|
||||||
match self {
|
match self {
|
||||||
Alert(s)=> write!(fmt, "ALERT: {}", s),
|
Alert(s)=> write!(fmt, "ALERT: {}", s),
|
||||||
Badcharset(None)=> write!(fmt, "Given charset is not supported by this server."),
|
Badcharset(None)=> write!(fmt, "Given charset is not supported by this server."),
|
||||||
Badcharset(Some(s))=> write!(fmt, "Given charset is not supported by this server. Supported ones are: {}", s),
|
Badcharset(Some(s))=> write!(fmt, "Given charset is not supported by this server. Supported ones are: {}", s),
|
||||||
Capability => write!(fmt, "Capability response"),
|
Capability => write!(fmt, "Capability response"),
|
||||||
Parse(s) => write!(fmt, "Server error in parsing message headers: {}", s),
|
Parse(s) => write!(fmt, "Server error in parsing message headers: {}", s),
|
||||||
Permanentflags(s) => write!(fmt, "Mailbox supports these flags: {}", s),
|
Permanentflags(s) => write!(fmt, "Mailbox supports these flags: {}", s),
|
||||||
ReadOnly=> write!(fmt, "This mailbox is selected read-only."),
|
ReadOnly=> write!(fmt, "This mailbox is selected read-only."),
|
||||||
ReadWrite => write!(fmt, "This mailbox is selected with read-write permissions."),
|
ReadWrite => write!(fmt, "This mailbox is selected with read-write permissions."),
|
||||||
Trycreate => write!(fmt, "Failed to operate on the target mailbox because it doesn't exist. Try creating it first."),
|
Trycreate => write!(fmt, "Failed to operate on the target mailbox because it doesn't exist. Try creating it first."),
|
||||||
Uidnext(uid) => write!(fmt, "Next UID value is {}", uid),
|
Uidnext(uid) => write!(fmt, "Next UID value is {}", uid),
|
||||||
Uidvalidity(uid) => write!(fmt, "Next UIDVALIDITY value is {}", uid),
|
Uidvalidity(uid) => write!(fmt, "Next UIDVALIDITY value is {}", uid),
|
||||||
Unseen(uid) => write!(fmt, "First message without the \\Seen flag is {}", uid),
|
Unseen(uid) => write!(fmt, "First message without the \\Seen flag is {}", uid),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -658,13 +678,13 @@ pub fn uid_fetch_response_(
|
||||||
let (input, _) = take_while(is_digit)(input)?;
|
let (input, _) = take_while(is_digit)(input)?;
|
||||||
let (input, result) = permutation((
|
let (input, result) = permutation((
|
||||||
preceded(
|
preceded(
|
||||||
tag("UID "),
|
alt((tag("UID "), tag(" UID "))),
|
||||||
map_res(digit1, |s| {
|
map_res(digit1, |s| {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
opt(preceded(
|
opt(preceded(
|
||||||
tag("FLAGS "),
|
alt((tag("FLAGS "), tag(" FLAGS "))),
|
||||||
delimited(tag("("), byte_flags, tag(")")),
|
delimited(tag("("), byte_flags, tag(")")),
|
||||||
)),
|
)),
|
||||||
length_data(delimited(
|
length_data(delimited(
|
||||||
|
@ -684,15 +704,18 @@ pub fn uid_fetch_response_(
|
||||||
pub fn uid_fetch_flags_response(input: &[u8]) -> IResult<&[u8], Vec<(usize, (Flag, Vec<String>))>> {
|
pub fn uid_fetch_flags_response(input: &[u8]) -> IResult<&[u8], Vec<(usize, (Flag, Vec<String>))>> {
|
||||||
many0(|input| -> IResult<&[u8], (usize, (Flag, Vec<String>))> {
|
many0(|input| -> IResult<&[u8], (usize, (Flag, Vec<String>))> {
|
||||||
let (input, _) = tag("* ")(input)?;
|
let (input, _) = tag("* ")(input)?;
|
||||||
let (input, _) = take_while(is_digit)(input)?;
|
let (input, _msn) = take_while(is_digit)(input)?;
|
||||||
let (input, _) = tag(" FETCH ( ")(input)?;
|
let (input, _) = tag(" FETCH (")(input)?;
|
||||||
let (input, uid_flags) = permutation((
|
let (input, uid_flags) = permutation((
|
||||||
preceded(
|
preceded(
|
||||||
tag("UID "),
|
alt((tag("UID "), tag(" UID "))),
|
||||||
map_res(digit1, |s| usize::from_str(to_str!(s))),
|
map_res(digit1, |s| usize::from_str(to_str!(s))),
|
||||||
),
|
),
|
||||||
preceded(tag("FLAGS "), delimited(tag("("), byte_flags, tag(")"))),
|
preceded(
|
||||||
))(input.ltrim())?;
|
alt((tag("FLAGS "), tag(" FLAGS "))),
|
||||||
|
delimited(tag("("), byte_flags, tag(")")),
|
||||||
|
),
|
||||||
|
))(input)?;
|
||||||
let (input, _) = tag(")\r\n")(input)?;
|
let (input, _) = tag(")\r\n")(input)?;
|
||||||
Ok((input, (uid_flags.0, uid_flags.1)))
|
Ok((input, (uid_flags.0, uid_flags.1)))
|
||||||
})(input)
|
})(input)
|
||||||
|
@ -1357,13 +1380,13 @@ pub fn uid_fetch_envelopes_response(
|
||||||
let (input, _) = tag(" FETCH (")(input)?;
|
let (input, _) = tag(" FETCH (")(input)?;
|
||||||
let (input, uid_flags) = permutation((
|
let (input, uid_flags) = permutation((
|
||||||
preceded(
|
preceded(
|
||||||
tag("UID "),
|
alt((tag("UID "), tag(" UID "))),
|
||||||
map_res(digit1, |s| {
|
map_res(digit1, |s| {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
opt(preceded(
|
opt(preceded(
|
||||||
tag("FLAGS "),
|
alt((tag("FLAGS "), tag(" FLAGS "))),
|
||||||
delimited(tag("("), byte_flags, tag(")")),
|
delimited(tag("("), byte_flags, tag(")")),
|
||||||
)),
|
)),
|
||||||
))(input.ltrim())?;
|
))(input.ltrim())?;
|
||||||
|
@ -1425,31 +1448,31 @@ pub fn status_response(input: &[u8]) -> IResult<&[u8], StatusResponse> {
|
||||||
let (input, _) = tag(" (")(input)?;
|
let (input, _) = tag(" (")(input)?;
|
||||||
let (input, result) = permutation((
|
let (input, result) = permutation((
|
||||||
opt(preceded(
|
opt(preceded(
|
||||||
tag("MESSAGES "),
|
alt((tag("MESSAGES "), tag(" MESSAGES "))),
|
||||||
map_res(digit1, |s| {
|
map_res(digit1, |s| {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
opt(preceded(
|
opt(preceded(
|
||||||
tag("RECENT "),
|
alt((tag("RECENT "), tag(" RECENT "))),
|
||||||
map_res(digit1, |s| {
|
map_res(digit1, |s| {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
opt(preceded(
|
opt(preceded(
|
||||||
tag("UIDNEXT "),
|
alt((tag("UIDNEXT "), tag(" UIDNEXT "))),
|
||||||
map_res(digit1, |s| {
|
map_res(digit1, |s| {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
opt(preceded(
|
opt(preceded(
|
||||||
tag("UIDVALIDITY "),
|
alt((tag("UIDVALIDITY "), tag(" UIDVALIDITY "))),
|
||||||
map_res(digit1, |s| {
|
map_res(digit1, |s| {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
)),
|
)),
|
||||||
opt(preceded(
|
opt(preceded(
|
||||||
tag("UNSEEN "),
|
alt((tag("UNSEEN "), tag(" UNSEEN "))),
|
||||||
map_res(digit1, |s| {
|
map_res(digit1, |s| {
|
||||||
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
usize::from_str(unsafe { std::str::from_utf8_unchecked(s) })
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -57,7 +57,7 @@ impl ImapConnection {
|
||||||
MailboxSelection::None => return Ok(false),
|
MailboxSelection::None => return Ok(false),
|
||||||
};
|
};
|
||||||
let mailbox =
|
let mailbox =
|
||||||
std::clone::Clone::clone(&self.uid_store.mailboxes.read().unwrap()[&mailbox_hash]);
|
std::clone::Clone::clone(&self.uid_store.mailboxes.lock().await[&mailbox_hash]);
|
||||||
|
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = String::with_capacity(8 * 1024);
|
||||||
let untagged_response =
|
let untagged_response =
|
||||||
|
|
|
@ -60,7 +60,7 @@ pub async fn poll_with_examine(kit: ImapWatchKit) -> Result<()> {
|
||||||
conn.connect().await?;
|
conn.connect().await?;
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = String::with_capacity(8 * 1024);
|
||||||
loop {
|
loop {
|
||||||
let mailboxes = uid_store.mailboxes.read()?;
|
let mailboxes = uid_store.mailboxes.lock().await;
|
||||||
for mailbox in mailboxes.values() {
|
for mailbox in mailboxes.values() {
|
||||||
examine_updates(mailbox, &mut conn, &uid_store).await?;
|
examine_updates(mailbox, &mut conn, &uid_store).await?;
|
||||||
}
|
}
|
||||||
|
@ -84,8 +84,8 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
||||||
conn.connect().await?;
|
conn.connect().await?;
|
||||||
let mailbox: ImapMailbox = match uid_store
|
let mailbox: ImapMailbox = match uid_store
|
||||||
.mailboxes
|
.mailboxes
|
||||||
.read()
|
.lock()
|
||||||
.unwrap()
|
.await
|
||||||
.values()
|
.values()
|
||||||
.find(|f| f.parent.is_none() && (f.special_usage() == SpecialUsageMailbox::Inbox))
|
.find(|f| f.parent.is_none() && (f.special_usage() == SpecialUsageMailbox::Inbox))
|
||||||
.map(std::clone::Clone::clone)
|
.map(std::clone::Clone::clone)
|
||||||
|
@ -158,37 +158,38 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("{:?}", e);
|
debug!("{:?}", e);
|
||||||
panic!("could not select mailbox");
|
return Err(e).chain_err_summary(|| "could not select mailbox");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
exit_on_error!(conn, mailbox_hash, conn.send_command(b"IDLE").await);
|
exit_on_error!(conn, mailbox_hash, conn.send_command(b"IDLE").await);
|
||||||
let mut iter = ImapBlockingConnection::from(conn);
|
let mut blockn = ImapBlockingConnection::from(conn);
|
||||||
let mut beat = std::time::Instant::now();
|
let mut beat = std::time::Instant::now();
|
||||||
let mut watch = std::time::Instant::now();
|
let mut watch = std::time::Instant::now();
|
||||||
/* duration interval to send heartbeat */
|
/* duration interval to send heartbeat */
|
||||||
let _26_mins = std::time::Duration::from_secs(26 * 60);
|
const _26_MINS: std::time::Duration = std::time::Duration::from_secs(26 * 60);
|
||||||
/* duration interval to check other mailboxes for changes */
|
/* duration interval to check other mailboxes for changes */
|
||||||
let _5_mins = std::time::Duration::from_secs(5 * 60);
|
const _5_MINS: std::time::Duration = std::time::Duration::from_secs(5 * 60);
|
||||||
while let Some(line) = iter.next() {
|
while let Some(line) = blockn.into_stream().await {
|
||||||
let now = std::time::Instant::now();
|
let now = std::time::Instant::now();
|
||||||
if now.duration_since(beat) >= _26_mins {
|
if now.duration_since(beat) >= _26_MINS {
|
||||||
let mut main_conn_lck = main_conn.lock().await;
|
let mut main_conn_lck = main_conn.lock().await;
|
||||||
exit_on_error!(
|
exit_on_error!(
|
||||||
iter.conn,
|
blockn.conn,
|
||||||
mailbox_hash,
|
mailbox_hash,
|
||||||
iter.conn.send_raw(b"DONE").await
|
blockn.conn.send_raw(b"DONE").await
|
||||||
iter.conn.read_response(&mut response, RequiredResponses::empty()).await
|
blockn.conn.read_response(&mut response, RequiredResponses::empty()).await
|
||||||
iter.conn.send_command(b"IDLE").await
|
blockn.conn.send_command(b"IDLE").await
|
||||||
main_conn_lck.send_command(b"NOOP").await
|
main_conn_lck.send_command(b"NOOP").await
|
||||||
main_conn_lck.read_response(&mut response, RequiredResponses::empty()).await
|
main_conn_lck.read_response(&mut response, RequiredResponses::empty()).await
|
||||||
);
|
);
|
||||||
beat = now;
|
beat = now;
|
||||||
}
|
}
|
||||||
if now.duration_since(watch) >= _5_mins {
|
if now.duration_since(watch) >= _5_MINS {
|
||||||
/* Time to poll all inboxes */
|
/* Time to poll all inboxes */
|
||||||
let mut conn = main_conn.lock().await;
|
let mut conn = main_conn.lock().await;
|
||||||
for mailbox in uid_store.mailboxes.read().unwrap().values() {
|
let mailboxes = uid_store.mailboxes.lock().await;
|
||||||
|
for mailbox in mailboxes.values() {
|
||||||
exit_on_error!(
|
exit_on_error!(
|
||||||
conn,
|
conn,
|
||||||
mailbox_hash,
|
mailbox_hash,
|
||||||
|
@ -243,6 +244,10 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
||||||
.contains_key(&(mailbox_hash, uid))
|
.contains_key(&(mailbox_hash, uid))
|
||||||
{
|
{
|
||||||
if let Ok(mut env) = Envelope::from_bytes(
|
if let Ok(mut env) = Envelope::from_bytes(
|
||||||
|
/* unwrap() is safe since we ask for RFC822 in the
|
||||||
|
* above FETCH, thus uid_fetch_responses() if
|
||||||
|
* returns a successful parse, it will include the
|
||||||
|
* RFC822 response */
|
||||||
body.unwrap(),
|
body.unwrap(),
|
||||||
flags.as_ref().map(|&(f, _)| f),
|
flags.as_ref().map(|&(f, _)| f),
|
||||||
) {
|
) {
|
||||||
|
@ -346,9 +351,8 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
||||||
let mut conn = main_conn.lock().await;
|
let mut conn = main_conn.lock().await;
|
||||||
/* UID FETCH ALL UID, cross-ref, then FETCH difference headers
|
/* UID FETCH ALL UID, cross-ref, then FETCH difference headers
|
||||||
* */
|
* */
|
||||||
let mut prev_exists = mailbox.exists.lock().unwrap();
|
|
||||||
debug!("exists {}", n);
|
debug!("exists {}", n);
|
||||||
if n > prev_exists.len() {
|
if n > mailbox.exists.lock().unwrap().len() {
|
||||||
exit_on_error!(
|
exit_on_error!(
|
||||||
conn,
|
conn,
|
||||||
mailbox_hash,
|
mailbox_hash,
|
||||||
|
@ -356,7 +360,7 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
||||||
conn.send_command(
|
conn.send_command(
|
||||||
&[
|
&[
|
||||||
b"FETCH",
|
b"FETCH",
|
||||||
format!("{}:{}", prev_exists.len() + 1, n).as_bytes(),
|
format!("{}:{}", mailbox.exists.lock().unwrap().len() + 1, n).as_bytes(),
|
||||||
b"(UID FLAGS RFC822)",
|
b"(UID FLAGS RFC822)",
|
||||||
]
|
]
|
||||||
.join(&b' '),
|
.join(&b' '),
|
||||||
|
@ -418,7 +422,7 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
||||||
&[(uid, &env)],
|
&[(uid, &env)],
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
prev_exists.insert_new(env.hash());
|
mailbox.exists.lock().unwrap().insert_new(env.hash());
|
||||||
|
|
||||||
conn.add_refresh_event(RefreshEvent {
|
conn.add_refresh_event(RefreshEvent {
|
||||||
account_hash: uid_store.account_hash,
|
account_hash: uid_store.account_hash,
|
||||||
|
@ -488,7 +492,7 @@ pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
debug!("IDLE connection dropped");
|
debug!("IDLE connection dropped");
|
||||||
let err: &str = iter.err().unwrap_or("Unknown reason.");
|
let err: &str = blockn.err().unwrap_or("Unknown reason.");
|
||||||
main_conn.lock().await.add_refresh_event(RefreshEvent {
|
main_conn.lock().await.add_refresh_event(RefreshEvent {
|
||||||
account_hash: uid_store.account_hash,
|
account_hash: uid_store.account_hash,
|
||||||
mailbox_hash,
|
mailbox_hash,
|
||||||
|
@ -759,7 +763,7 @@ pub async fn examine_updates(
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!("{:?}", e);
|
debug!("{:?}", e);
|
||||||
panic!("could not select mailbox");
|
return Err(e).chain_err_summary(|| "could not select mailbox");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -20,8 +20,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use crate::error::Result;
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
|
|
@ -21,9 +21,11 @@
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use melib::list_management;
|
use melib::list_management;
|
||||||
|
|
||||||
use crate::terminal::embed::EmbedGrid;
|
|
||||||
use melib::Draft;
|
use melib::Draft;
|
||||||
|
|
||||||
|
use crate::conf::accounts::JobRequest;
|
||||||
|
use crate::jobs::{oneshot, JobId};
|
||||||
|
use crate::terminal::embed::EmbedGrid;
|
||||||
use nix::sys::wait::WaitStatus;
|
use nix::sys::wait::WaitStatus;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
@ -64,6 +66,7 @@ impl std::ops::DerefMut for EmbedStatus {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Composer {
|
pub struct Composer {
|
||||||
reply_context: Option<(MailboxHash, EnvelopeHash)>,
|
reply_context: Option<(MailboxHash, EnvelopeHash)>,
|
||||||
|
reply_bytes_request: Option<(JobId, oneshot::Receiver<Result<Vec<u8>>>)>,
|
||||||
account_cursor: usize,
|
account_cursor: usize,
|
||||||
|
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
|
@ -89,6 +92,7 @@ impl Default for Composer {
|
||||||
pager.set_reflow(text_processing::Reflow::FormatFlowed);
|
pager.set_reflow(text_processing::Reflow::FormatFlowed);
|
||||||
Composer {
|
Composer {
|
||||||
reply_context: None,
|
reply_context: None,
|
||||||
|
reply_bytes_request: None,
|
||||||
account_cursor: 0,
|
account_cursor: 0,
|
||||||
|
|
||||||
cursor: Cursor::Headers,
|
cursor: Cursor::Headers,
|
||||||
|
@ -229,21 +233,6 @@ impl Composer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match account.operation(msg) {
|
|
||||||
Err(err) => {
|
|
||||||
context.replies.push_back(UIEvent::Notification(
|
|
||||||
None,
|
|
||||||
err.to_string(),
|
|
||||||
Some(NotificationType::ERROR),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
Ok(op) => {
|
|
||||||
//FIXME
|
|
||||||
//let parent_bytes = op.as_bytes();
|
|
||||||
//ret.draft = Draft::new_reply(&parent_message, parent_bytes.unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let subject = parent_message.subject();
|
let subject = parent_message.subject();
|
||||||
ret.draft.headers_mut().insert(
|
ret.draft.headers_mut().insert(
|
||||||
"Subject".into(),
|
"Subject".into(),
|
||||||
|
@ -254,6 +243,54 @@ impl Composer {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
drop(parent_message);
|
||||||
|
drop(account);
|
||||||
|
match context.accounts[coordinates.0]
|
||||||
|
.operation(msg)
|
||||||
|
.and_then(|mut op| op.as_bytes())
|
||||||
|
{
|
||||||
|
Err(err) => {
|
||||||
|
context.replies.push_back(UIEvent::Notification(
|
||||||
|
None,
|
||||||
|
err.to_string(),
|
||||||
|
Some(NotificationType::ERROR),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(fut) => {
|
||||||
|
let (mut rcvr, job_id) = context.accounts[coordinates.0]
|
||||||
|
.job_executor
|
||||||
|
.spawn_specialized(fut);
|
||||||
|
context.accounts[coordinates.0]
|
||||||
|
.active_jobs
|
||||||
|
.insert(job_id.clone(), JobRequest::AsBytes);
|
||||||
|
if let Ok(Some(parent_bytes)) = try_recv_timeout!(&mut rcvr) {
|
||||||
|
match parent_bytes {
|
||||||
|
Err(err) => {
|
||||||
|
context.replies.push_back(UIEvent::Notification(
|
||||||
|
None,
|
||||||
|
err.to_string(),
|
||||||
|
Some(NotificationType::ERROR),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(parent_bytes) => {
|
||||||
|
let env_hash = msg;
|
||||||
|
let parent_message =
|
||||||
|
context.accounts[coordinates.0].collection.get_env(env_hash);
|
||||||
|
let mut new_draft = Draft::new_reply(&parent_message, &parent_bytes);
|
||||||
|
new_draft
|
||||||
|
.headers_mut()
|
||||||
|
.extend(ret.draft.headers_mut().drain());
|
||||||
|
new_draft
|
||||||
|
.attachments_mut()
|
||||||
|
.extend(ret.draft.attachments_mut().drain(..));
|
||||||
|
ret.set_draft(new_draft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret.reply_bytes_request = Some((job_id, rcvr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
ret.account_cursor = coordinates.0;
|
ret.account_cursor = coordinates.0;
|
||||||
ret.reply_context = Some((coordinates.1, msg));
|
ret.reply_context = Some((coordinates.1, msg));
|
||||||
ret
|
ret
|
||||||
|
@ -585,6 +622,48 @@ impl Component for Composer {
|
||||||
fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool {
|
fn process_event(&mut self, mut event: &mut UIEvent, context: &mut Context) -> bool {
|
||||||
let shortcuts = self.get_shortcuts(context);
|
let shortcuts = self.get_shortcuts(context);
|
||||||
match (&mut self.mode, &mut event) {
|
match (&mut self.mode, &mut event) {
|
||||||
|
(_, UIEvent::StatusEvent(StatusEvent::JobFinished(ref job_id)))
|
||||||
|
if self
|
||||||
|
.reply_bytes_request
|
||||||
|
.as_ref()
|
||||||
|
.map(|(j, _)| j == job_id)
|
||||||
|
.unwrap_or(false) =>
|
||||||
|
{
|
||||||
|
let bytes = self
|
||||||
|
.reply_bytes_request
|
||||||
|
.take()
|
||||||
|
.unwrap()
|
||||||
|
.1
|
||||||
|
.try_recv()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
match bytes {
|
||||||
|
Ok(parent_bytes) => {
|
||||||
|
let env_hash = self.reply_context.unwrap().1;
|
||||||
|
let parent_message = context.accounts[self.account_cursor]
|
||||||
|
.collection
|
||||||
|
.get_env(env_hash);
|
||||||
|
let mut new_draft = Draft::new_reply(&parent_message, &parent_bytes);
|
||||||
|
new_draft
|
||||||
|
.headers_mut()
|
||||||
|
.extend(self.draft.headers_mut().drain());
|
||||||
|
new_draft
|
||||||
|
.attachments_mut()
|
||||||
|
.extend(self.draft.attachments_mut().drain(..));
|
||||||
|
self.set_draft(new_draft);
|
||||||
|
self.set_dirty(true);
|
||||||
|
self.initialized = false;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
context.replies.push_back(UIEvent::Notification(
|
||||||
|
Some(format!("Failed to load parent envelope")),
|
||||||
|
err.to_string(),
|
||||||
|
Some(NotificationType::ERROR),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
(ViewMode::Edit, _) => {
|
(ViewMode::Edit, _) => {
|
||||||
if self.pager.process_event(event, context) {
|
if self.pager.process_event(event, context) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::conf::accounts::JobRequest;
|
||||||
use crate::types::segment_tree::SegmentTree;
|
use crate::types::segment_tree::SegmentTree;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
@ -175,7 +176,10 @@ pub trait MailListingTrait: ListingTrait {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(fut) => {
|
Ok(fut) => {
|
||||||
//accout.job_executor.spawn_specialized(fut);
|
let (rcvr, job_id) = account.job_executor.spawn_specialized(fut);
|
||||||
|
account
|
||||||
|
.active_jobs
|
||||||
|
.insert(job_id, JobRequest::SetFlags(env_hash, rcvr));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ListingAction::SetUnseen => match envelope.set_unseen(op) {
|
ListingAction::SetUnseen => match envelope.set_unseen(op) {
|
||||||
|
@ -184,7 +188,12 @@ pub trait MailListingTrait: ListingTrait {
|
||||||
StatusEvent::DisplayMessage(e.to_string()),
|
StatusEvent::DisplayMessage(e.to_string()),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Ok(fut) => {}
|
Ok(fut) => {
|
||||||
|
let (rcvr, job_id) = account.job_executor.spawn_specialized(fut);
|
||||||
|
account
|
||||||
|
.active_jobs
|
||||||
|
.insert(job_id, JobRequest::SetFlags(env_hash, rcvr));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
ListingAction::Delete => {
|
ListingAction::Delete => {
|
||||||
drop(envelope);
|
drop(envelope);
|
||||||
|
@ -197,20 +206,21 @@ pub trait MailListingTrait: ListingTrait {
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Ok(fut) => {}
|
Ok(fut) => {
|
||||||
|
let (rcvr, job_id) = account.job_executor.spawn_specialized(fut);
|
||||||
|
account
|
||||||
|
.active_jobs
|
||||||
|
.insert(job_id, JobRequest::DeleteMessage(env_hash, rcvr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ListingAction::CopyTo(ref mailbox_path) => {
|
ListingAction::CopyTo(ref mailbox_path) => {
|
||||||
drop(envelope);
|
drop(envelope);
|
||||||
/*
|
|
||||||
* FIXME
|
|
||||||
match account
|
match account
|
||||||
.mailbox_by_path(mailbox_path)
|
.mailbox_by_path(mailbox_path)
|
||||||
.and_then(|mailbox_hash| {
|
.and_then(|mailbox_hash| op.copy_to(mailbox_hash))
|
||||||
op.as_bytes()
|
{
|
||||||
.and_then(|bytes| account.save(bytes, mailbox_hash, None))
|
|
||||||
}) {
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
context.replies.push_back(UIEvent::Notification(
|
context.replies.push_back(UIEvent::Notification(
|
||||||
Some("Could not copy.".to_string()),
|
Some("Could not copy.".to_string()),
|
||||||
|
@ -219,16 +229,18 @@ pub trait MailListingTrait: ListingTrait {
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Ok(fut) => {}
|
Ok(fut) => {
|
||||||
|
let (rcvr, job_id) = account.job_executor.spawn_specialized(fut);
|
||||||
|
account
|
||||||
|
.active_jobs
|
||||||
|
.insert(job_id, JobRequest::SaveMessage(mailbox_hash, rcvr));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ListingAction::CopyToOtherAccount(ref account_name, ref mailbox_path) => {
|
ListingAction::CopyToOtherAccount(ref account_name, ref mailbox_path) => {
|
||||||
drop(envelope);
|
drop(envelope);
|
||||||
/*
|
if let Err(err) = op.as_bytes().and_then(|bytes_fut| {
|
||||||
* FIXME
|
|
||||||
if let Err(err) = op.as_bytes().map(|b| b.to_vec()).and_then(|bytes| {
|
|
||||||
let account_pos = context
|
let account_pos = context
|
||||||
.accounts
|
.accounts
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -241,7 +253,11 @@ pub trait MailListingTrait: ListingTrait {
|
||||||
})?;
|
})?;
|
||||||
let account = &mut context.accounts[account_pos];
|
let account = &mut context.accounts[account_pos];
|
||||||
let mailbox_hash = account.mailbox_by_path(mailbox_path)?;
|
let mailbox_hash = account.mailbox_by_path(mailbox_path)?;
|
||||||
account.save(&bytes, mailbox_hash, None)
|
let (rcvr, job_id) = account.job_executor.spawn_specialized(bytes_fut);
|
||||||
|
account
|
||||||
|
.active_jobs
|
||||||
|
.insert(job_id, JobRequest::CopyTo(mailbox_hash, rcvr));
|
||||||
|
Ok(())
|
||||||
}) {
|
}) {
|
||||||
context.replies.push_back(UIEvent::Notification(
|
context.replies.push_back(UIEvent::Notification(
|
||||||
Some("Could not copy.".to_string()),
|
Some("Could not copy.".to_string()),
|
||||||
|
@ -250,21 +266,52 @@ pub trait MailListingTrait: ListingTrait {
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ListingAction::MoveTo(ref mailbox_path) => {
|
ListingAction::MoveTo(ref mailbox_path) => {
|
||||||
drop(envelope);
|
drop(envelope);
|
||||||
/*
|
match account
|
||||||
* FIXME
|
.mailbox_by_path(mailbox_path)
|
||||||
if let Err(err) =
|
.and_then(|mailbox_hash| op.copy_to(mailbox_hash))
|
||||||
account
|
|
||||||
.mailbox_by_path(mailbox_path)
|
|
||||||
.and_then(|mailbox_hash| {
|
|
||||||
op.as_bytes()
|
|
||||||
.and_then(|bytes| account.save(bytes, mailbox_hash, None))
|
|
||||||
})
|
|
||||||
{
|
{
|
||||||
|
Err(err) => {
|
||||||
|
context.replies.push_back(UIEvent::Notification(
|
||||||
|
Some("Could not copy.".to_string()),
|
||||||
|
err.to_string(),
|
||||||
|
Some(NotificationType::ERROR),
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Ok(fut) => {
|
||||||
|
let (rcvr, job_id) = account.job_executor.spawn_specialized(fut);
|
||||||
|
account
|
||||||
|
.active_jobs
|
||||||
|
.insert(job_id, JobRequest::SaveMessage(mailbox_hash, rcvr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ListingAction::MoveToOtherAccount(ref account_name, ref mailbox_path) => {
|
||||||
|
drop(envelope);
|
||||||
|
if let Err(err) = op.as_bytes().and_then(|bytes_fut| {
|
||||||
|
let account_pos = context
|
||||||
|
.accounts
|
||||||
|
.iter()
|
||||||
|
.position(|el| el.name() == account_name)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
MeliError::new(format!(
|
||||||
|
"`{}` is not a valid account name",
|
||||||
|
account_name
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
let account = &mut context.accounts[account_pos];
|
||||||
|
let mailbox_hash = account.mailbox_by_path(mailbox_path)?;
|
||||||
|
let (rcvr, job_id) = account.job_executor.spawn_specialized(bytes_fut);
|
||||||
|
account
|
||||||
|
.active_jobs
|
||||||
|
.insert(job_id, JobRequest::CopyTo(mailbox_hash, rcvr));
|
||||||
|
Ok(())
|
||||||
|
}) {
|
||||||
context.replies.push_back(UIEvent::Notification(
|
context.replies.push_back(UIEvent::Notification(
|
||||||
Some("Could not copy.".to_string()),
|
Some("Could not copy.".to_string()),
|
||||||
err.to_string(),
|
err.to_string(),
|
||||||
|
@ -272,46 +319,12 @@ pub trait MailListingTrait: ListingTrait {
|
||||||
));
|
));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*/
|
/*
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ListingAction::MoveToOtherAccount(ref account_name, ref mailbox_path) => {
|
|
||||||
drop(envelope);
|
|
||||||
/* FIXME
|
|
||||||
if let Err(err) = op
|
|
||||||
.as_bytes()
|
|
||||||
.map(|b| b.to_vec())
|
|
||||||
.and_then(|bytes| {
|
|
||||||
let account_pos = context
|
|
||||||
.accounts
|
|
||||||
.iter()
|
|
||||||
.position(|el| el.name() == account_name)
|
|
||||||
.ok_or_else(|| {
|
|
||||||
MeliError::new(format!(
|
|
||||||
"`{}` is not a valid account name",
|
|
||||||
account_name
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
let account = &mut context.accounts[account_pos];
|
|
||||||
let mailbox_hash = account.mailbox_by_path(mailbox_path)?;
|
|
||||||
account.save(&bytes, mailbox_hash, None)
|
|
||||||
})
|
|
||||||
.and_then(|()| {
|
|
||||||
let account = &mut context.accounts[account_pos];
|
|
||||||
account
|
account
|
||||||
.delete(env_hash, mailbox_hash)
|
.delete(env_hash, mailbox_hash)
|
||||||
.chain_err_summary(|| {
|
.chain_err_summary(|| {
|
||||||
"Envelope was copied but removal from original account failed"
|
"Envelope was copied but removal from original account failed"
|
||||||
})
|
})
|
||||||
})
|
|
||||||
{
|
|
||||||
context.replies.push_back(UIEvent::Notification(
|
|
||||||
Some("Could not move.".to_string()),
|
|
||||||
err.to_string(),
|
|
||||||
Some(NotificationType::ERROR),
|
|
||||||
));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
*/
|
*/
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -326,7 +339,10 @@ pub trait MailListingTrait: ListingTrait {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Ok(fut) => {
|
Ok(fut) => {
|
||||||
//FIXME
|
let (rcvr, job_id) = account.job_executor.spawn_specialized(fut);
|
||||||
|
account
|
||||||
|
.active_jobs
|
||||||
|
.insert(job_id, JobRequest::SetFlags(env_hash, rcvr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -341,7 +357,10 @@ pub trait MailListingTrait: ListingTrait {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Ok(fut) => {
|
Ok(fut) => {
|
||||||
// FIXME
|
let (rcvr, job_id) = account.job_executor.spawn_specialized(fut);
|
||||||
|
account
|
||||||
|
.active_jobs
|
||||||
|
.insert(job_id, JobRequest::SetFlags(env_hash, rcvr));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1559,7 +1559,7 @@ impl Component for CompactListing {
|
||||||
.map(|(_, _, j)| j == job_id)
|
.map(|(_, _, j)| j == job_id)
|
||||||
.unwrap_or(false) =>
|
.unwrap_or(false) =>
|
||||||
{
|
{
|
||||||
let (filter_term, mut rcvr, job_id) = self.search_job.take().unwrap();
|
let (filter_term, mut rcvr, _job_id) = self.search_job.take().unwrap();
|
||||||
let results = rcvr.try_recv().unwrap().unwrap();
|
let results = rcvr.try_recv().unwrap().unwrap();
|
||||||
self.filter(filter_term, results, context);
|
self.filter(filter_term, results, context);
|
||||||
self.set_dirty(true);
|
self.set_dirty(true);
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::components::utilities::PageMovement;
|
use crate::components::utilities::PageMovement;
|
||||||
|
use crate::jobs::{oneshot, JobId};
|
||||||
use std::iter::FromIterator;
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
/// 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
|
||||||
|
@ -38,6 +39,11 @@ pub struct ConversationsListing {
|
||||||
/// Cache current view.
|
/// Cache current view.
|
||||||
content: CellBuffer,
|
content: CellBuffer,
|
||||||
|
|
||||||
|
search_job: Option<(
|
||||||
|
String,
|
||||||
|
oneshot::Receiver<Result<SmallVec<[EnvelopeHash; 512]>>>,
|
||||||
|
JobId,
|
||||||
|
)>,
|
||||||
filter_term: String,
|
filter_term: String,
|
||||||
filtered_selection: Vec<ThreadHash>,
|
filtered_selection: Vec<ThreadHash>,
|
||||||
filtered_order: HashMap<ThreadHash, usize>,
|
filtered_order: HashMap<ThreadHash, usize>,
|
||||||
|
@ -693,82 +699,53 @@ impl ListingTrait for ConversationsListing {
|
||||||
results: Result<SmallVec<[EnvelopeHash; 512]>>,
|
results: Result<SmallVec<[EnvelopeHash; 512]>>,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
) {
|
) {
|
||||||
/*
|
if filter_term.is_empty() {
|
||||||
if filter_term.is_empty() {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
self.order.clear();
|
self.order.clear();
|
||||||
self.selection.clear();
|
self.selection.clear();
|
||||||
self.length = 0;
|
self.length = 0;
|
||||||
self.filtered_selection.clear();
|
self.filtered_selection.clear();
|
||||||
self.filtered_order.clear();
|
self.filtered_order.clear();
|
||||||
self.filter_term = filter_term.to_string();
|
self.filter_term = filter_term.to_string();
|
||||||
self.row_updates.clear();
|
self.row_updates.clear();
|
||||||
for v in self.selection.values_mut() {
|
for v in self.selection.values_mut() {
|
||||||
*v = false;
|
*v = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let account = &context.accounts[self.cursor_pos.0];
|
let account = &context.accounts[self.cursor_pos.0];
|
||||||
match account.search(&self.filter_term, self.sort, self.cursor_pos.1) {
|
match results {
|
||||||
Ok(results) => {
|
Ok(results) => {
|
||||||
/*
|
let threads = &account.collection.threads[&self.cursor_pos.1];
|
||||||
let threads = &account.collection.threads[&self.cursor_pos.1];
|
for env_hash in results {
|
||||||
for env_hash in results {
|
if !account.collection.contains_key(&env_hash) {
|
||||||
if !account.collection.contains_key(&env_hash) {
|
continue;
|
||||||
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.filtered_selection.is_empty() {
|
let env_thread_node_hash = account.collection.get_env(env_hash).thread();
|
||||||
threads.group_inner_sort_by(
|
if !threads.thread_nodes.contains_key(&env_thread_node_hash) {
|
||||||
&mut self.filtered_selection,
|
continue;
|
||||||
self.sort,
|
}
|
||||||
&context.accounts[self.cursor_pos.0].collection.envelopes,
|
let thread =
|
||||||
);
|
threads.find_group(threads.thread_nodes[&env_thread_node_hash].group);
|
||||||
self.new_cursor_pos.2 =
|
if self.filtered_order.contains_key(&thread) {
|
||||||
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
|
continue;
|
||||||
} else {
|
}
|
||||||
let default_cell = {
|
if self.all_threads.contains(&thread) {
|
||||||
let mut ret = Cell::with_char(' ');
|
self.filtered_selection.push(thread);
|
||||||
ret.set_fg(self.color_cache.theme_default.fg)
|
self.filtered_order
|
||||||
.set_bg(self.color_cache.theme_default.bg)
|
.insert(thread, self.filtered_selection.len() - 1);
|
||||||
.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>>,
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
if !self.filtered_selection.is_empty() {
|
||||||
self.cursor_pos.2 = 0;
|
threads.group_inner_sort_by(
|
||||||
self.new_cursor_pos.2 = 0;
|
&mut self.filtered_selection,
|
||||||
let message = format!(
|
self.sort,
|
||||||
"Encountered an error while searching for `{}`: {}.",
|
&context.accounts[self.cursor_pos.0].collection.envelopes,
|
||||||
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 default_cell = {
|
||||||
let mut ret = Cell::with_char(' ');
|
let mut ret = Cell::with_char(' ');
|
||||||
ret.set_fg(self.color_cache.theme_default.fg)
|
ret.set_fg(self.color_cache.theme_default.fg)
|
||||||
|
@ -776,20 +753,45 @@ impl ListingTrait for ConversationsListing {
|
||||||
.set_attrs(self.color_cache.theme_default.attrs);
|
.set_attrs(self.color_cache.theme_default.attrs);
|
||||||
ret
|
ret
|
||||||
};
|
};
|
||||||
self.content =
|
self.content = 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.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) {
|
fn set_movement(&mut self, mvm: PageMovement) {
|
||||||
|
@ -815,6 +817,7 @@ impl ConversationsListing {
|
||||||
subsort: (SortField::Date, SortOrder::Desc),
|
subsort: (SortField::Date, SortOrder::Desc),
|
||||||
order: HashMap::default(),
|
order: HashMap::default(),
|
||||||
all_threads: HashSet::default(),
|
all_threads: HashSet::default(),
|
||||||
|
search_job: None,
|
||||||
filter_term: String::new(),
|
filter_term: String::new(),
|
||||||
filtered_selection: Vec::new(),
|
filtered_selection: Vec::new(),
|
||||||
filtered_order: HashMap::default(),
|
filtered_order: HashMap::default(),
|
||||||
|
@ -1345,8 +1348,29 @@ impl Component for ConversationsListing {
|
||||||
}
|
}
|
||||||
UIEvent::Action(ref action) => match action {
|
UIEvent::Action(ref action) => match action {
|
||||||
Action::Listing(Search(ref filter_term)) if !self.unfocused => {
|
Action::Listing(Search(ref filter_term)) if !self.unfocused => {
|
||||||
//self.filter(filter_term, context);
|
match context.accounts[self.cursor_pos.0].search(
|
||||||
self.dirty = true;
|
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);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -1370,6 +1394,18 @@ impl Component for ConversationsListing {
|
||||||
self.set_dirty(true);
|
self.set_dirty(true);
|
||||||
return true;
|
return 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);
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,11 @@ pub struct PlainListing {
|
||||||
/// Cache current view.
|
/// Cache current view.
|
||||||
data_columns: DataColumns,
|
data_columns: DataColumns,
|
||||||
|
|
||||||
|
search_job: Option<(
|
||||||
|
String,
|
||||||
|
oneshot::Receiver<Result<SmallVec<[EnvelopeHash; 512]>>>,
|
||||||
|
JobId,
|
||||||
|
)>,
|
||||||
filter_term: String,
|
filter_term: String,
|
||||||
filtered_selection: Vec<EnvelopeHash>,
|
filtered_selection: Vec<EnvelopeHash>,
|
||||||
filtered_order: HashMap<EnvelopeHash, usize>,
|
filtered_order: HashMap<EnvelopeHash, usize>,
|
||||||
|
@ -587,71 +592,41 @@ impl ListingTrait for PlainListing {
|
||||||
results: Result<SmallVec<[EnvelopeHash; 512]>>,
|
results: Result<SmallVec<[EnvelopeHash; 512]>>,
|
||||||
context: &Context,
|
context: &Context,
|
||||||
) {
|
) {
|
||||||
/*
|
if filter_term.is_empty() {
|
||||||
if filter_term.is_empty() {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
self.order.clear();
|
self.order.clear();
|
||||||
self.selection.clear();
|
self.selection.clear();
|
||||||
self.length = 0;
|
self.length = 0;
|
||||||
self.filtered_selection.clear();
|
self.filtered_selection.clear();
|
||||||
self.filtered_order.clear();
|
self.filtered_order.clear();
|
||||||
self.filter_term = filter_term.to_string();
|
self.filter_term = filter_term.to_string();
|
||||||
self.row_updates.clear();
|
self.row_updates.clear();
|
||||||
for v in self.selection.values_mut() {
|
for v in self.selection.values_mut() {
|
||||||
*v = false;
|
*v = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let account = &context.accounts[self.cursor_pos.0];
|
let account = &context.accounts[self.cursor_pos.0];
|
||||||
match account.search(&self.filter_term, self.sort, self.cursor_pos.1) {
|
match results {
|
||||||
Ok(results) => {
|
Ok(results) => {
|
||||||
/*
|
for env_hash in results {
|
||||||
for env_hash in results {
|
if !account.collection.contains_key(&env_hash) {
|
||||||
if !account.collection.contains_key(&env_hash) {
|
continue;
|
||||||
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_selection.is_empty() {
|
if self.filtered_order.contains_key(&env_hash) {
|
||||||
self.new_cursor_pos.2 =
|
continue;
|
||||||
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
|
}
|
||||||
} else {
|
if self.all_envelopes.contains(&env_hash) {
|
||||||
let default_cell = {
|
self.filtered_selection.push(env_hash);
|
||||||
let mut ret = Cell::with_char(' ');
|
self.filtered_order
|
||||||
ret.set_fg(self.color_cache.theme_default.fg)
|
.insert(env_hash, self.filtered_selection.len() - 1);
|
||||||
.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>>,
|
|
||||||
);
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
if !self.filtered_selection.is_empty() {
|
||||||
self.cursor_pos.2 = 0;
|
self.new_cursor_pos.2 =
|
||||||
self.new_cursor_pos.2 = 0;
|
std::cmp::min(self.filtered_selection.len() - 1, self.cursor_pos.2);
|
||||||
let message = format!(
|
} else {
|
||||||
"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 default_cell = {
|
||||||
let mut ret = Cell::with_char(' ');
|
let mut ret = Cell::with_char(' ');
|
||||||
ret.set_fg(self.color_cache.theme_default.fg)
|
ret.set_fg(self.color_cache.theme_default.fg)
|
||||||
|
@ -660,19 +635,45 @@ impl ListingTrait for PlainListing {
|
||||||
ret
|
ret
|
||||||
};
|
};
|
||||||
self.data_columns.columns[0] =
|
self.data_columns.columns[0] =
|
||||||
CellBuffer::new_with_context(message.len(), 1, default_cell, context);
|
CellBuffer::new_with_context(0, 0, 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) {
|
fn set_movement(&mut self, mvm: PageMovement) {
|
||||||
|
@ -701,6 +702,7 @@ impl PlainListing {
|
||||||
thread_node_hashes: HashMap::default(),
|
thread_node_hashes: HashMap::default(),
|
||||||
order: HashMap::default(),
|
order: HashMap::default(),
|
||||||
filter_term: String::new(),
|
filter_term: String::new(),
|
||||||
|
search_job: None,
|
||||||
filtered_selection: Vec::new(),
|
filtered_selection: Vec::new(),
|
||||||
filtered_order: HashMap::default(),
|
filtered_order: HashMap::default(),
|
||||||
selection: HashMap::default(),
|
selection: HashMap::default(),
|
||||||
|
@ -1276,8 +1278,41 @@ impl Component for PlainListing {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
UIEvent::Action(Action::Listing(Search(ref filter_term))) if !self.unfocused => {
|
UIEvent::Action(Action::Listing(Search(ref filter_term))) if !self.unfocused => {
|
||||||
//self.filter(filter_term, context);
|
match context.accounts[self.cursor_pos.0].search(
|
||||||
self.dirty = true;
|
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);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,11 +193,15 @@ impl MailView {
|
||||||
.and_then(|mut op| op.as_bytes())
|
.and_then(|mut op| op.as_bytes())
|
||||||
{
|
{
|
||||||
Ok(fut) => {
|
Ok(fut) => {
|
||||||
let (chan, job_id) = account.job_executor.spawn_specialized(fut);
|
let (mut chan, job_id) = account.job_executor.spawn_specialized(fut);
|
||||||
debug!(&job_id);
|
debug!(&job_id);
|
||||||
self.active_jobs.insert(job_id.clone());
|
self.active_jobs.insert(job_id.clone());
|
||||||
account.active_jobs.insert(job_id, JobRequest::AsBytes);
|
account.active_jobs.insert(job_id, JobRequest::AsBytes);
|
||||||
self.state = MailViewState::LoadingBody { job_id, chan };
|
if let Ok(Some(bytes_result)) = try_recv_timeout!(&mut chan) {
|
||||||
|
self.state = MailViewState::Loaded { body: bytes_result };
|
||||||
|
} else {
|
||||||
|
self.state = MailViewState::LoadingBody { job_id, chan };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
context.replies.push_back(UIEvent::StatusEvent(
|
context.replies.push_back(UIEvent::StatusEvent(
|
||||||
|
|
|
@ -55,6 +55,22 @@ use std::pin::Pin;
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! try_recv_timeout {
|
||||||
|
($oneshot:expr) => {{
|
||||||
|
const _3_MS: std::time::Duration = std::time::Duration::from_millis(95);
|
||||||
|
let now = std::time::Instant::now();
|
||||||
|
let mut res = Ok(None);
|
||||||
|
while now + _3_MS >= std::time::Instant::now() {
|
||||||
|
res = $oneshot.try_recv().map_err(|_| MeliError::new("canceled"));
|
||||||
|
if res.as_ref().map(|r| r.is_some()).unwrap_or(false) || res.is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
pub type Worker = Option<Async<Result<Vec<Envelope>>>>;
|
pub type Worker = Option<Async<Result<Vec<Envelope>>>>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -151,6 +167,7 @@ pub enum JobRequest {
|
||||||
Refresh(MailboxHash, oneshot::Receiver<Result<()>>),
|
Refresh(MailboxHash, oneshot::Receiver<Result<()>>),
|
||||||
SetFlags(EnvelopeHash, oneshot::Receiver<Result<()>>),
|
SetFlags(EnvelopeHash, oneshot::Receiver<Result<()>>),
|
||||||
SaveMessage(MailboxHash, oneshot::Receiver<Result<()>>),
|
SaveMessage(MailboxHash, oneshot::Receiver<Result<()>>),
|
||||||
|
CopyTo(MailboxHash, oneshot::Receiver<Result<Vec<u8>>>),
|
||||||
DeleteMessage(EnvelopeHash, oneshot::Receiver<Result<()>>),
|
DeleteMessage(EnvelopeHash, oneshot::Receiver<Result<()>>),
|
||||||
CreateMailbox(oneshot::Receiver<Result<(MailboxHash, HashMap<MailboxHash, Mailbox>)>>),
|
CreateMailbox(oneshot::Receiver<Result<(MailboxHash, HashMap<MailboxHash, Mailbox>)>>),
|
||||||
DeleteMailbox(oneshot::Receiver<Result<HashMap<MailboxHash, Mailbox>>>),
|
DeleteMailbox(oneshot::Receiver<Result<HashMap<MailboxHash, Mailbox>>>),
|
||||||
|
@ -171,6 +188,7 @@ impl core::fmt::Debug for JobRequest {
|
||||||
JobRequest::Refresh(_, _) => write!(f, "{}", "JobRequest::Refresh"),
|
JobRequest::Refresh(_, _) => write!(f, "{}", "JobRequest::Refresh"),
|
||||||
JobRequest::SetFlags(_, _) => write!(f, "{}", "JobRequest::SetFlags"),
|
JobRequest::SetFlags(_, _) => write!(f, "{}", "JobRequest::SetFlags"),
|
||||||
JobRequest::SaveMessage(_, _) => write!(f, "{}", "JobRequest::SaveMessage"),
|
JobRequest::SaveMessage(_, _) => write!(f, "{}", "JobRequest::SaveMessage"),
|
||||||
|
JobRequest::CopyTo(_, _) => write!(f, "{}", "JobRequest::CopyTo"),
|
||||||
JobRequest::DeleteMessage(_, _) => write!(f, "{}", "JobRequest::DeleteMessage"),
|
JobRequest::DeleteMessage(_, _) => write!(f, "{}", "JobRequest::DeleteMessage"),
|
||||||
JobRequest::CreateMailbox(_) => write!(f, "{}", "JobRequest::CreateMailbox"),
|
JobRequest::CreateMailbox(_) => write!(f, "{}", "JobRequest::CreateMailbox"),
|
||||||
JobRequest::DeleteMailbox(_) => write!(f, "{}", "JobRequest::DeleteMailbox"),
|
JobRequest::DeleteMailbox(_) => write!(f, "{}", "JobRequest::DeleteMailbox"),
|
||||||
|
@ -189,6 +207,13 @@ impl core::fmt::Debug for JobRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl JobRequest {
|
impl JobRequest {
|
||||||
|
fn is_watch(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
JobRequest::Watch(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn is_get(&self, mailbox_hash: MailboxHash) -> bool {
|
fn is_get(&self, mailbox_hash: MailboxHash) -> bool {
|
||||||
match self {
|
match self {
|
||||||
JobRequest::Get(h, _) if *h == mailbox_hash => true,
|
JobRequest::Get(h, _) if *h == mailbox_hash => true,
|
||||||
|
@ -316,10 +341,6 @@ impl Account {
|
||||||
active_jobs.insert(job_id, JobRequest::Mailboxes(rcvr));
|
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Account {
|
Ok(Account {
|
||||||
|
@ -827,7 +848,7 @@ impl Account {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
pub fn watch(&self) {
|
pub fn watch(&mut self) {
|
||||||
if self.settings.account().manual_refresh {
|
if self.settings.account().manual_refresh {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -836,27 +857,45 @@ impl Account {
|
||||||
let r = RefreshEventConsumer::new(Box::new(move |r| {
|
let r = RefreshEventConsumer::new(Box::new(move |r| {
|
||||||
sender_.send(ThreadEvent::from(r)).unwrap();
|
sender_.send(ThreadEvent::from(r)).unwrap();
|
||||||
}));
|
}));
|
||||||
match self
|
if self.settings.conf.is_async {
|
||||||
.backend
|
if !self.active_jobs.values().any(|j| j.is_watch()) {
|
||||||
.read()
|
match self.backend.read().unwrap().watch_async(r) {
|
||||||
.unwrap()
|
Ok(fut) => {
|
||||||
.watch(r, self.work_context.clone())
|
let (handle, job_id) = self.job_executor.spawn(fut);
|
||||||
{
|
self.active_jobs.insert(job_id, JobRequest::Watch(handle));
|
||||||
Ok(id) => {
|
}
|
||||||
self.sender
|
Err(e) => {
|
||||||
.send(ThreadEvent::NewThread(
|
self.sender
|
||||||
id,
|
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||||
format!("watching {}", self.name()).into(),
|
StatusEvent::DisplayMessage(e.to_string()),
|
||||||
))
|
)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
match self
|
||||||
|
.backend
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.watch(r, self.work_context.clone())
|
||||||
|
{
|
||||||
|
Ok(id) => {
|
||||||
|
self.sender
|
||||||
|
.send(ThreadEvent::NewThread(
|
||||||
|
id,
|
||||||
|
format!("watching {}", self.name()).into(),
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.sender
|
self.sender
|
||||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||||
StatusEvent::DisplayMessage(e.to_string()),
|
StatusEvent::DisplayMessage(e.to_string()),
|
||||||
)))
|
)))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -911,21 +950,35 @@ impl Account {
|
||||||
MailboxStatus::None => {
|
MailboxStatus::None => {
|
||||||
if self.settings.conf.is_async {
|
if self.settings.conf.is_async {
|
||||||
if !self.active_jobs.values().any(|j| j.is_get(mailbox_hash)) {
|
if !self.active_jobs.values().any(|j| j.is_get(mailbox_hash)) {
|
||||||
if let Ok(mailbox_job) =
|
match self.backend.write().unwrap().get_async(
|
||||||
self.backend.write().unwrap().get_async(
|
&&self.mailbox_entries[&mailbox_hash].ref_mailbox,
|
||||||
&&self.mailbox_entries[&mailbox_hash].ref_mailbox,
|
) {
|
||||||
)
|
Ok(mailbox_job) => {
|
||||||
{
|
let mailbox_job = mailbox_job.into_future();
|
||||||
let mailbox_job = mailbox_job.into_future();
|
let (rcvr, job_id) =
|
||||||
let (rcvr, job_id) =
|
self.job_executor.spawn_specialized(mailbox_job);
|
||||||
self.job_executor.spawn_specialized(mailbox_job);
|
self.sender
|
||||||
self.sender
|
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
||||||
.send(ThreadEvent::UIEvent(UIEvent::StatusEvent(
|
StatusEvent::NewJob(job_id.clone()),
|
||||||
StatusEvent::NewJob(job_id.clone()),
|
)))
|
||||||
)))
|
.unwrap();
|
||||||
.unwrap();
|
self.active_jobs.insert(
|
||||||
self.active_jobs
|
job_id,
|
||||||
.insert(job_id, JobRequest::Get(mailbox_hash, rcvr));
|
JobRequest::Get(mailbox_hash, rcvr),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
self.mailbox_entries.entry(mailbox_hash).and_modify(
|
||||||
|
|entry| {
|
||||||
|
entry.status = MailboxStatus::Failed(err);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.sender
|
||||||
|
.send(ThreadEvent::UIEvent(UIEvent::StartupCheck(
|
||||||
|
mailbox_hash,
|
||||||
|
)))
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if self.mailbox_entries[&mailbox_hash].worker.is_none() {
|
} else if self.mailbox_entries[&mailbox_hash].worker.is_none() {
|
||||||
|
@ -945,7 +998,7 @@ impl Account {
|
||||||
Err(0)
|
Err(0)
|
||||||
}
|
}
|
||||||
_ => Err(0),
|
_ => Err(0),
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
Some(ref mut w) => match debug!(w.poll()) {
|
Some(ref mut w) => match debug!(w.poll()) {
|
||||||
Ok(AsyncStatus::NoUpdate) => {
|
Ok(AsyncStatus::NoUpdate) => {
|
||||||
|
@ -1105,27 +1158,18 @@ impl Account {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(&mut self, env_hash: EnvelopeHash, mailbox_hash: MailboxHash) -> Result<()> {
|
pub fn delete(
|
||||||
|
&mut self,
|
||||||
|
env_hash: EnvelopeHash,
|
||||||
|
mailbox_hash: MailboxHash,
|
||||||
|
) -> ResultFuture<()> {
|
||||||
if self.settings.account.read_only() {
|
if self.settings.account.read_only() {
|
||||||
return Err(MeliError::new(format!(
|
return Err(MeliError::new(format!(
|
||||||
"Account {} is read-only.",
|
"Account {} is read-only.",
|
||||||
self.name.as_str()
|
self.name.as_str()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
let job = self
|
self.backend.write().unwrap().delete(env_hash, mailbox_hash)
|
||||||
.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 {
|
pub fn contains_key(&self, h: EnvelopeHash) -> bool {
|
||||||
|
@ -1343,7 +1387,7 @@ impl Account {
|
||||||
pub fn search(
|
pub fn search(
|
||||||
&self,
|
&self,
|
||||||
search_term: &str,
|
search_term: &str,
|
||||||
sort: (SortField, SortOrder),
|
_sort: (SortField, SortOrder),
|
||||||
mailbox_hash: MailboxHash,
|
mailbox_hash: MailboxHash,
|
||||||
) -> ResultFuture<SmallVec<[EnvelopeHash; 512]>> {
|
) -> ResultFuture<SmallVec<[EnvelopeHash; 512]>> {
|
||||||
use melib::parsec::Parser;
|
use melib::parsec::Parser;
|
||||||
|
@ -1545,6 +1589,22 @@ impl Account {
|
||||||
.expect("Could not send event on main channel");
|
.expect("Could not send event on main channel");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
JobRequest::CopyTo(mailbox_hash, mut chan) => {
|
||||||
|
if let Err(err) = chan
|
||||||
|
.try_recv()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.and_then(|bytes| self.save(&bytes, mailbox_hash, None))
|
||||||
|
{
|
||||||
|
self.sender
|
||||||
|
.send(ThreadEvent::UIEvent(UIEvent::Notification(
|
||||||
|
Some(format!("{}: could not save message", &self.name)),
|
||||||
|
err.to_string(),
|
||||||
|
Some(crate::types::NotificationType::ERROR),
|
||||||
|
)))
|
||||||
|
.expect("Could not send event on main channel");
|
||||||
|
}
|
||||||
|
}
|
||||||
JobRequest::DeleteMessage(_, mut chan) => {
|
JobRequest::DeleteMessage(_, mut chan) => {
|
||||||
let r = chan.try_recv().unwrap();
|
let r = chan.try_recv().unwrap();
|
||||||
if let Some(Err(err)) = r {
|
if let Some(Err(err)) = r {
|
||||||
|
@ -1668,7 +1728,9 @@ impl Account {
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JobRequest::Watch(_) => {}
|
JobRequest::Watch(_) => {
|
||||||
|
debug!("JobRequest::Watch finished??? ");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -142,7 +142,7 @@ impl JobExecutor {
|
||||||
let MeliTask { task, id } = meli_task;
|
let MeliTask { task, id } = meli_task;
|
||||||
debug!("Worker {} got task {:?}", i, id);
|
debug!("Worker {} got task {:?}", i, id);
|
||||||
let _ = catch_unwind(|| task.run());
|
let _ = catch_unwind(|| task.run());
|
||||||
debug!("Worker {} got result {:?}", i, id);
|
debug!("Worker {} returned after {:?}", i, id);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
Loading…
Reference in New Issue