From 335a1011de42bd1ae450a07ffafc57312bf794f8 Mon Sep 17 00:00:00 2001 From: Manos Pitsidianakis Date: Fri, 6 Sep 2019 12:54:12 +0300 Subject: [PATCH] imap: add watch --- melib/src/backends/imap.rs | 345 +++---------------- melib/src/backends/imap/folder.rs | 4 + melib/src/backends/imap/watch.rs | 479 ++++++++++++++++++++++++++ ui/src/components/mail/view/thread.rs | 3 + ui/src/conf/accounts.rs | 1 - 5 files changed, 531 insertions(+), 301 deletions(-) create mode 100644 melib/src/backends/imap/watch.rs diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index 02d50546..bf4ec7b2 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -28,6 +28,8 @@ mod operations; pub use operations::*; mod connection; pub use connection::*; +mod watch; +pub use watch::*; extern crate native_tls; @@ -38,6 +40,7 @@ use crate::backends::RefreshEvent; use crate::backends::RefreshEventKind::{self, *}; use crate::backends::{BackendFolder, Folder, FolderOperation, MailBackend, RefreshEventConsumer}; use crate::conf::AccountSettings; +use crate::email::parser::BytesExt; use crate::email::*; use crate::error::{MeliError, Result}; use fnv::{FnvHashMap, FnvHashSet}; @@ -82,22 +85,21 @@ impl MailBackend for ImapType { let uid_index = self.uid_index.clone(); let folder_path = folder.path().to_string(); let folder_hash = folder.hash(); - let connection = self.folder_connections[&folder_hash].clone(); + let folder_exists = self.folders[&folder_hash].exists.clone(); + let connection = self.connection.clone(); let closure = move || { let connection = connection.clone(); let tx = tx.clone(); let mut response = String::with_capacity(8 * 1024); - { - let conn = connection.lock(); - exit_on_error!(&tx, conn); - let mut conn = conn.unwrap(); + let conn = connection.lock(); + exit_on_error!(&tx, conn); + let mut conn = conn.unwrap(); + debug!("locked for get {}", folder_path); - debug!("locked for get {}", folder_path); - exit_on_error!(&tx, - conn.send_command(format!("EXAMINE {}", folder_path).as_bytes()) - conn.read_response(&mut response) - ); - } + exit_on_error!(&tx, + conn.send_command(format!("EXAMINE {}", folder_path).as_bytes()) + conn.read_response(&mut response) + ); let examine_response = protocol_parser::select_response(&response) .to_full_result() .map_err(MeliError::from); @@ -106,18 +108,17 @@ impl MailBackend for ImapType { SelectResponse::Ok(ok) => ok.exists, SelectResponse::Bad(b) => b.exists, }; + { + let mut folder_exists = folder_exists.lock().unwrap(); + *folder_exists = exists; + } while exists > 1 { let mut envelopes = vec![]; - { - let conn = connection.lock(); - exit_on_error!(&tx, conn); - let mut conn = conn.unwrap(); - exit_on_error!(&tx, - conn.send_command(format!("UID FETCH {}:{} (FLAGS RFC822.HEADER)", std::cmp::max(exists.saturating_sub(10000), 1), exists).as_bytes()) - conn.read_response(&mut response) - ); - } + exit_on_error!(&tx, + conn.send_command(format!("UID FETCH {}:{} (FLAGS RFC822.HEADER)", std::cmp::max(exists.saturating_sub(20000), 1), exists).as_bytes()) + conn.read_response(&mut response) + ); debug!( "fetch response is {} bytes and {} lines", response.len(), @@ -145,10 +146,11 @@ impl MailBackend for ImapType { tx.send(AsyncStatus::Payload(Err(e))); } } - exists = std::cmp::max(exists.saturating_sub(10000), 1); + exists = std::cmp::max(exists.saturating_sub(20000), 1); debug!("sending payload"); tx.send(AsyncStatus::Payload(Ok(envelopes))); } + drop(conn); tx.send(AsyncStatus::Finished); }; Box::new(closure) @@ -157,286 +159,29 @@ impl MailBackend for ImapType { } fn watch(&self, sender: RefreshEventConsumer) -> Result<()> { - macro_rules! exit_on_error { - ($sender:expr, $folder_hash:ident, $($result:expr)+) => { - $(if let Err(e) = $result { - debug!("failure: {}", e.to_string()); - $sender.send(RefreshEvent { - hash: $folder_hash, - kind: RefreshEventKind::Failure(e), - }); - std::process::exit(1); - })+ - }; - }; let has_idle: bool = self.capabilities.contains(&b"IDLE"[0..]); - let sender = Arc::new(sender); - for f in self.folders.values() { - let mut conn = self.new_connection()?; - let main_conn = self.connection.clone(); - let f_path = f.path().to_string(); - let hash_index = self.hash_index.clone(); - let uid_index = self.uid_index.clone(); - let folder_hash = f.hash(); - let sender = sender.clone(); - std::thread::Builder::new() - .name(format!( - "{},{}: imap connection", - self.account_name.as_str(), - f_path.as_str() - )) - .spawn(move || { - let mut response = String::with_capacity(8 * 1024); - exit_on_error!( - sender.as_ref(), - folder_hash, - conn.read_response(&mut response) - conn.send_command(format!("SELECT {}", f_path).as_bytes()) - conn.read_response(&mut response) - ); - debug!("select response {}", &response); - let mut prev_exists = match protocol_parser::select_response(&response) - .to_full_result() - .map_err(MeliError::from) - { - Ok(SelectResponse::Bad(bad)) => { - debug!(bad); - panic!("could not select mailbox"); - } - Ok(SelectResponse::Ok(ok)) => { - debug!(&ok); - ok.exists - } - Err(e) => { - debug!("{:?}", e); - panic!("could not select mailbox"); - } - }; - if has_idle { - exit_on_error!(sender.as_ref(), folder_hash, conn.send_command(b"IDLE")); - let mut iter = ImapBlockingConnection::from(conn); - let mut beat = std::time::Instant::now(); - let _26_mins = std::time::Duration::from_secs(26 * 60); - while let Some(line) = iter.next() { - let now = std::time::Instant::now(); - if now.duration_since(beat) >= _26_mins { - exit_on_error!( - sender.as_ref(), - folder_hash, - iter.conn.set_nonblocking(true) - iter.conn.send_raw(b"DONE") - iter.conn.read_response(&mut response) - ); - exit_on_error!( - sender.as_ref(), - folder_hash, - iter.conn.send_command(b"IDLE") - iter.conn.set_nonblocking(false) - ); - { - exit_on_error!( - sender.as_ref(), - folder_hash, - main_conn.lock().unwrap().send_command(b"NOOP") - main_conn.lock().unwrap().read_response(&mut response) - ); - } - beat = now; - } - match protocol_parser::untagged_responses(line.as_slice()) - .to_full_result() - .map_err(MeliError::from) - { - Ok(Some(Recent(_))) => { - /* UID SEARCH RECENT */ - exit_on_error!( - sender.as_ref(), - folder_hash, - iter.conn.set_nonblocking(true) - iter.conn.send_raw(b"DONE") - iter.conn.read_response(&mut response) - iter.conn.send_command(b"UID SEARCH RECENT") - iter.conn.read_response(&mut response) - ); - match protocol_parser::search_results_raw(response.as_bytes()) - .to_full_result() - .map_err(MeliError::from) - { - Ok(&[]) => { - debug!("UID SEARCH RECENT returned no results"); - } - Ok(v) => { - exit_on_error!( - sender.as_ref(), - folder_hash, - iter.conn.send_command( - &[b"UID FETCH", v, b"(FLAGS RFC822.HEADER)"] - .join(&b' '), - ) - iter.conn.read_response(&mut response) - ); - debug!(&response); - match protocol_parser::uid_fetch_response( - response.as_bytes(), - ) - .to_full_result() - .map_err(MeliError::from) - { - Ok(v) => { - for (uid, flags, b) in v { - if let Ok(env) = - Envelope::from_bytes(&b, flags) - { - hash_index.lock().unwrap().insert( - env.hash(), - (uid, folder_hash), - ); - uid_index - .lock() - .unwrap() - .insert(uid, env.hash()); - debug!( - "Create event {} {} {}", - env.hash(), - env.subject(), - f_path.as_str() - ); - sender.send(RefreshEvent { - hash: folder_hash, - kind: Create(Box::new(env)), - }); - } - } - } - Err(e) => { - debug!(e); - } - } - } - Err(e) => { - debug!( - "UID SEARCH RECENT err: {}\nresp: {}", - e.to_string(), - &response - ); - } - } - exit_on_error!( - sender.as_ref(), - folder_hash, - iter.conn.send_command(b"IDLE") - iter.conn.set_nonblocking(false) - ); - } - Ok(Some(Expunge(n))) => { - debug!("expunge {}", n); - } - Ok(Some(Exists(n))) => { - exit_on_error!( - sender.as_ref(), - folder_hash, - iter.conn.set_nonblocking(true) - iter.conn.send_raw(b"DONE") - iter.conn.read_response(&mut response) - ); - /* UID FETCH ALL UID, cross-ref, then FETCH difference headers - * */ - debug!("exists {}", n); - if n > prev_exists { - exit_on_error!( - sender.as_ref(), - folder_hash, - iter.conn.send_command( - &[ - b"FETCH", - format!("{}:{}", prev_exists + 1, n).as_bytes(), - b"(UID FLAGS RFC822.HEADER)", - ] - .join(&b' '), - ) - iter.conn.read_response(&mut response) - ); - match protocol_parser::uid_fetch_response( - response.as_bytes(), - ) - .to_full_result() - .map_err(MeliError::from) - { - Ok(v) => { - for (uid, flags, b) in v { - if let Ok(env) = Envelope::from_bytes(&b, flags) - { - hash_index - .lock() - .unwrap() - .insert(env.hash(), (uid, folder_hash)); - uid_index - .lock() - .unwrap() - .insert(uid, env.hash()); - debug!( - "Create event {} {} {}", - env.hash(), - env.subject(), - f_path.as_str() - ); - sender.send(RefreshEvent { - hash: folder_hash, - kind: Create(Box::new(env)), - }); - } - } - } - Err(e) => { - debug!(e); - } - } - - prev_exists = n; - } else if n < prev_exists { - prev_exists = n; - } - exit_on_error!( - sender.as_ref(), - folder_hash, - iter.conn.send_command(b"IDLE") - iter.conn.set_nonblocking(false) - ); - } - Ok(None) | Err(_) => {} - } - } - debug!("failure"); - sender.send(RefreshEvent { - hash: folder_hash, - kind: RefreshEventKind::Failure(MeliError::new("conn_error")), - }); - return; - } else { - loop { - { - exit_on_error!( - sender.as_ref(), - folder_hash, - main_conn.lock().unwrap().send_command(b"NOOP") - main_conn.lock().unwrap().read_response(&mut response) - ); - } - exit_on_error!( - sender.as_ref(), - folder_hash, - conn.send_command(b"NOOP") - conn.read_response(&mut response) - ); - for r in response.lines() { - // FIXME mimic IDLE - debug!(&r); - } - std::thread::sleep(std::time::Duration::from_millis(10 * 1000)); - } - } - })?; - } + let folders = self.imap_folders(); + let conn = self.new_connection()?; + let main_conn = self.connection.clone(); + let hash_index = self.hash_index.clone(); + let uid_index = self.uid_index.clone(); + std::thread::Builder::new() + .name(format!("{} imap connection", self.account_name.as_str(),)) + .spawn(move || { + let kit = ImapWatchKit { + conn, + main_conn, + hash_index, + uid_index, + folders, + sender, + }; + if has_idle { + idle(kit); + } else { + poll_with_examine(kit); + } + })?; Ok(()) } diff --git a/melib/src/backends/imap/folder.rs b/melib/src/backends/imap/folder.rs index 285504a4..988fe27f 100644 --- a/melib/src/backends/imap/folder.rs +++ b/melib/src/backends/imap/folder.rs @@ -19,6 +19,7 @@ * along with meli. If not, see . */ use crate::backends::{BackendFolder, Folder, FolderHash}; +use std::sync::{Arc, Mutex}; #[derive(Debug, Default)] pub struct ImapFolder { @@ -27,6 +28,8 @@ pub struct ImapFolder { pub(super) name: String, pub(super) parent: Option, pub(super) children: Vec, + + pub exists: Arc>, } impl BackendFolder for ImapFolder { @@ -57,6 +60,7 @@ impl BackendFolder for ImapFolder { name: self.name.clone(), parent: self.parent, children: self.children.clone(), + exists: self.exists.clone(), }) } diff --git a/melib/src/backends/imap/watch.rs b/melib/src/backends/imap/watch.rs new file mode 100644 index 00000000..8528549f --- /dev/null +++ b/melib/src/backends/imap/watch.rs @@ -0,0 +1,479 @@ +/* + * meli - imap module. + * + * Copyright 2019 Manos Pitsidianakis + * + * This file is part of meli. + * + * meli is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * meli is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with meli. If not, see . + */ +use super::*; +use std::sync::{Arc, Mutex}; + +/// Arguments for IMAP watching functions +pub struct ImapWatchKit { + pub conn: ImapConnection, + pub main_conn: Arc>, + pub hash_index: Arc>>, + pub uid_index: Arc>>, + pub folders: FnvHashMap, + pub sender: RefreshEventConsumer, +} + +macro_rules! exit_on_error { + ($sender:expr, $folder_hash:ident, $($result:expr)+) => { + $(if let Err(e) = $result { + debug!("failure: {}", e.to_string()); + $sender.send(RefreshEvent { + hash: $folder_hash, + kind: RefreshEventKind::Failure(e), + }); + std::process::exit(1); + })+ + }; +} + +pub fn poll_with_examine(kit: ImapWatchKit) { + debug!("poll with examine"); + let ImapWatchKit { + mut conn, + main_conn, + hash_index, + uid_index, + folders, + sender, + } = kit; + let mut response = String::with_capacity(8 * 1024); + loop { + std::thread::sleep(std::time::Duration::from_millis(5 * 60 * 1000)); + for (hash, folder) in &folders { + examine_updates(folder, &sender, &mut conn, &hash_index, &uid_index); + } + let mut main_conn = main_conn.lock().unwrap(); + main_conn.send_command(b"NOOP").unwrap(); + main_conn.read_response(&mut response).unwrap(); + } +} + +pub fn idle(kit: ImapWatchKit) { + debug!("IDLE"); + /* IDLE only watches the connection's selected mailbox. We will IDLE on INBOX and every ~5 + * minutes wake up and poll the others */ + let ImapWatchKit { + mut conn, + main_conn, + hash_index, + uid_index, + folders, + sender, + } = kit; + let folder: &ImapFolder = folders + .values() + .find(|f| f.parent.is_none() && f.path().eq_ignore_ascii_case("INBOX")) + .unwrap(); + let folder_hash = folder.hash(); + let mut response = String::with_capacity(8 * 1024); + exit_on_error!( + sender, + folder_hash, + conn.read_response(&mut response) + conn.send_command(format!("SELECT {}", folder.path()).as_bytes()) + conn.read_response(&mut response) + ); + debug!("select response {}", &response); + { + let mut prev_exists = folder.exists.lock().unwrap(); + *prev_exists = match protocol_parser::select_response(&response) + .to_full_result() + .map_err(MeliError::from) + { + Ok(SelectResponse::Bad(bad)) => { + debug!(bad); + panic!("could not select mailbox"); + } + Ok(SelectResponse::Ok(ok)) => { + debug!(&ok); + ok.exists + } + Err(e) => { + debug!("{:?}", e); + panic!("could not select mailbox"); + } + }; + } + exit_on_error!(sender, folder_hash, conn.send_command(b"IDLE")); + let mut iter = ImapBlockingConnection::from(conn); + let mut beat = std::time::Instant::now(); + let mut watch = std::time::Instant::now(); + /* duration interval to send heartbeat */ + let _26_mins = std::time::Duration::from_secs(26 * 60); + /* duration interval to check other folders for changes */ + let _5_mins = std::time::Duration::from_secs(5 * 60); + loop { + while let Some(line) = iter.next() { + let now = std::time::Instant::now(); + if now.duration_since(beat) >= _26_mins { + exit_on_error!( + sender, + folder_hash, + iter.conn.set_nonblocking(true) + iter.conn.send_raw(b"DONE") + iter.conn.read_response(&mut response) + iter.conn.send_command(b"IDLE") + iter.conn.set_nonblocking(false) + main_conn.lock().unwrap().send_command(b"NOOP") + main_conn.lock().unwrap().read_response(&mut response) + ); + beat = now; + } + if now.duration_since(watch) >= _5_mins { + /* Time to poll the other inboxes */ + exit_on_error!( + sender, + folder_hash, + iter.conn.set_nonblocking(true) + iter.conn.send_raw(b"DONE") + iter.conn.read_response(&mut response) + ); + for (hash, folder) in &folders { + if *hash == folder_hash { + /* Skip INBOX */ + continue; + } + examine_updates(folder, &sender, &mut iter.conn, &hash_index, &uid_index); + } + exit_on_error!( + sender, + folder_hash, + iter.conn.send_command(b"IDLE") + iter.conn.set_nonblocking(false) + main_conn.lock().unwrap().send_command(b"NOOP") + main_conn.lock().unwrap().read_response(&mut response) + ); + watch = now; + } + match protocol_parser::untagged_responses(line.as_slice()) + .to_full_result() + .map_err(MeliError::from) + { + Ok(Some(Recent(_))) => { + /* UID SEARCH RECENT */ + exit_on_error!( + sender, + folder_hash, + iter.conn.set_nonblocking(true) + iter.conn.send_raw(b"DONE") + iter.conn.read_response(&mut response) + iter.conn.send_command(b"UID SEARCH RECENT") + iter.conn.read_response(&mut response) + ); + match protocol_parser::search_results_raw(response.as_bytes()) + .to_full_result() + .map_err(MeliError::from) + { + Ok(&[]) => { + debug!("UID SEARCH RECENT returned no results"); + } + Ok(v) => { + exit_on_error!( + sender, + folder_hash, + iter.conn.send_command( + &[b"UID FETCH", v, b"(FLAGS RFC822.HEADER)"] + .join(&b' '), + ) + iter.conn.read_response(&mut response) + ); + debug!(&response); + match protocol_parser::uid_fetch_response(response.as_bytes()) + .to_full_result() + .map_err(MeliError::from) + { + Ok(v) => { + for (uid, flags, b) in v { + if let Ok(env) = Envelope::from_bytes(&b, flags) { + hash_index + .lock() + .unwrap() + .insert(env.hash(), (uid, folder_hash)); + uid_index.lock().unwrap().insert(uid, env.hash()); + debug!( + "Create event {} {} {}", + env.hash(), + env.subject(), + folder.path(), + ); + sender.send(RefreshEvent { + hash: folder_hash, + kind: Create(Box::new(env)), + }); + } + } + } + Err(e) => { + debug!(e); + } + } + } + Err(e) => { + debug!( + "UID SEARCH RECENT err: {}\nresp: {}", + e.to_string(), + &response + ); + } + } + exit_on_error!( + sender, + folder_hash, + iter.conn.send_command(b"IDLE") + iter.conn.set_nonblocking(false) + ); + } + Ok(Some(Expunge(n))) => { + debug!("expunge {}", n); + } + Ok(Some(Exists(n))) => { + exit_on_error!( + sender, + folder_hash, + iter.conn.set_nonblocking(true) + iter.conn.send_raw(b"DONE") + iter.conn.read_response(&mut response) + ); + /* UID FETCH ALL UID, cross-ref, then FETCH difference headers + * */ + let mut prev_exists = folder.exists.lock().unwrap(); + debug!("exists {}", n); + if n > *prev_exists { + exit_on_error!( + sender, + folder_hash, + iter.conn.send_command( + &[ + b"FETCH", + format!("{}:{}", *prev_exists + 1, n).as_bytes(), + b"(UID FLAGS RFC822.HEADER)", + ] + .join(&b' '), + ) + iter.conn.read_response(&mut response) + ); + match protocol_parser::uid_fetch_response(response.as_bytes()) + .to_full_result() + .map_err(MeliError::from) + { + Ok(v) => { + for (uid, flags, b) in v { + if uid_index.lock().unwrap().contains_key(&uid) { + continue; + } + if let Ok(env) = Envelope::from_bytes(&b, flags) { + hash_index + .lock() + .unwrap() + .insert(env.hash(), (uid, folder_hash)); + uid_index.lock().unwrap().insert(uid, env.hash()); + debug!( + "Create event {} {} {}", + env.hash(), + env.subject(), + folder.path(), + ); + sender.send(RefreshEvent { + hash: folder_hash, + kind: Create(Box::new(env)), + }); + } + } + } + Err(e) => { + debug!(e); + } + } + + *prev_exists = n; + } else if n < *prev_exists { + *prev_exists = n; + } + exit_on_error!( + sender, + folder_hash, + iter.conn.send_command(b"IDLE") + iter.conn.set_nonblocking(false) + ); + } + Ok(None) | Err(_) => {} + } + } + } + debug!("failure"); + sender.send(RefreshEvent { + hash: folder_hash, + kind: RefreshEventKind::Failure(MeliError::new("conn_error")), + }); +} + +fn examine_updates( + folder: &ImapFolder, + sender: &RefreshEventConsumer, + conn: &mut ImapConnection, + hash_index: &Arc>>, + uid_index: &Arc>>, +) { + let folder_hash = folder.hash(); + debug!("examining folder {} {}", folder_hash, folder.path()); + let mut response = String::with_capacity(8 * 1024); + exit_on_error!( + sender, + folder_hash, + conn.send_command(format!("EXAMINE {}", folder.path()).as_bytes()) + conn.read_response(&mut response) + ); + match protocol_parser::select_response(&response) + .to_full_result() + .map_err(MeliError::from) + { + Ok(SelectResponse::Bad(bad)) => { + debug!(bad); + panic!("could not select mailbox"); + } + Ok(SelectResponse::Ok(ok)) => { + debug!(&ok); + let mut prev_exists = folder.exists.lock().unwrap(); + let n = ok.exists; + if ok.recent > 0 { + { + /* UID SEARCH RECENT */ + exit_on_error!( + sender, + folder_hash, + conn.send_command(b"UID SEARCH RECENT") + conn.read_response(&mut response) + ); + match protocol_parser::search_results_raw(response.as_bytes()) + .to_full_result() + .map_err(MeliError::from) + { + Ok(&[]) => { + debug!("UID SEARCH RECENT returned no results"); + } + Ok(v) => { + exit_on_error!( + sender, + folder_hash, + conn.send_command( + &[b"UID FETCH", v, b"(FLAGS RFC822.HEADER)"] + .join(&b' '), + ) + conn.read_response(&mut response) + ); + debug!(&response); + match protocol_parser::uid_fetch_response(response.as_bytes()) + .to_full_result() + .map_err(MeliError::from) + { + Ok(v) => { + for (uid, flags, b) in v { + if let Ok(env) = Envelope::from_bytes(&b, flags) { + hash_index + .lock() + .unwrap() + .insert(env.hash(), (uid, folder_hash)); + uid_index.lock().unwrap().insert(uid, env.hash()); + debug!( + "Create event {} {} {}", + env.hash(), + env.subject(), + folder.path(), + ); + sender.send(RefreshEvent { + hash: folder_hash, + kind: Create(Box::new(env)), + }); + } + } + } + Err(e) => { + debug!(e); + } + } + } + Err(e) => { + debug!( + "UID SEARCH RECENT err: {}\nresp: {}", + e.to_string(), + &response + ); + } + } + } + } else if n > *prev_exists { + /* UID FETCH ALL UID, cross-ref, then FETCH difference headers + * */ + debug!("exists {}", n); + exit_on_error!( + sender, + folder_hash, + conn.send_command( + &[ + b"FETCH", + format!("{}:{}", *prev_exists + 1, n).as_bytes(), + b"(UID FLAGS RFC822.HEADER)", + ] + .join(&b' '), + ) + conn.read_response(&mut response) + ); + match protocol_parser::uid_fetch_response(response.as_bytes()) + .to_full_result() + .map_err(MeliError::from) + { + Ok(v) => { + for (uid, flags, b) in v { + if let Ok(env) = Envelope::from_bytes(&b, flags) { + hash_index + .lock() + .unwrap() + .insert(env.hash(), (uid, folder_hash)); + uid_index.lock().unwrap().insert(uid, env.hash()); + debug!( + "Create event {} {} {}", + env.hash(), + env.subject(), + folder.path(), + ); + sender.send(RefreshEvent { + hash: folder_hash, + kind: Create(Box::new(env)), + }); + } + } + } + Err(e) => { + debug!(e); + } + } + + *prev_exists = n; + } else if n < *prev_exists { + *prev_exists = n; + } + } + Err(e) => { + debug!("{:?}", e); + panic!("could not select mailbox"); + } + }; +} diff --git a/ui/src/components/mail/view/thread.rs b/ui/src/components/mail/view/thread.rs index 0171bdc4..7c74c0a0 100644 --- a/ui/src/components/mail/view/thread.rs +++ b/ui/src/components/mail/view/thread.rs @@ -869,6 +869,9 @@ impl fmt::Display for ThreadView { impl Component for ThreadView { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { let total_cols = width!(area); + if self.entries.is_empty() { + return; + } /* If user has selected another mail to view, change to it */ if self.new_expanded_pos != self.expanded_pos { diff --git a/ui/src/conf/accounts.rs b/ui/src/conf/accounts.rs index 22748b86..4f84f8eb 100644 --- a/ui/src/conf/accounts.rs +++ b/ui/src/conf/accounts.rs @@ -40,7 +40,6 @@ use crate::types::UIEvent::{self, EnvelopeRemove, EnvelopeRename, EnvelopeUpdate use std::collections::VecDeque; use std::fs; use std::io; -use std::mem; use std::ops::{Index, IndexMut}; use std::result; use std::sync::Arc;