diff --git a/melib/src/backends.rs b/melib/src/backends.rs index a6d0b1ba..bfdc5a32 100644 --- a/melib/src/backends.rs +++ b/melib/src/backends.rs @@ -245,7 +245,7 @@ pub enum FolderOperation { type NewFolderName = String; pub trait MailBackend: ::std::fmt::Debug + Send + Sync { - fn is_online(&self) -> bool; + fn is_online(&self) -> Result<()>; fn get(&mut self, folder: &Folder) -> Async>>; fn watch( &self, diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index 7f92daa4..8b3fabec 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -45,6 +45,7 @@ use std::collections::{hash_map::DefaultHasher, BTreeMap}; use std::hash::Hasher; use std::str::FromStr; use std::sync::{Arc, Mutex, RwLock}; +use std::time::Instant; pub type UID = usize; pub static SUPPORTED_CAPABILITIES: &'static [&'static str] = @@ -123,7 +124,7 @@ pub struct UIDStore { #[derive(Debug)] pub struct ImapType { account_name: String, - online: Arc>, + online: Arc)>>, is_subscribed: Arc, connection: Arc>, server_conf: ImapServerConf, @@ -135,8 +136,8 @@ pub struct ImapType { } impl MailBackend for ImapType { - fn is_online(&self) -> bool { - *self.online.lock().unwrap() + fn is_online(&self) -> Result<()> { + self.online.lock().unwrap().1.clone() } fn get(&mut self, folder: &Folder) -> Async>> { macro_rules! exit_on_error { @@ -285,7 +286,7 @@ impl MailBackend for ImapType { ) -> Result { let folders = self.folders.clone(); let tag_index = self.tag_index.clone(); - let conn = ImapConnection::new_connection(&self.server_conf); + let conn = ImapConnection::new_connection(&self.server_conf, self.online.clone()); let main_conn = self.connection.clone(); let is_online = self.online.clone(); let uid_store = self.uid_store.clone(); @@ -342,7 +343,6 @@ impl MailBackend for ImapType { f.children.retain(|c| keys.contains(c)); } drop(uid_lock); - *self.online.lock().unwrap() = true; Ok(folders .iter() .map(|(h, f)| (*h, Box::new(Clone::clone(f)) as Folder)) @@ -510,11 +510,15 @@ impl ImapType { use_starttls, danger_accept_invalid_certs, }; - let connection = ImapConnection::new_connection(&server_conf); + let online = Arc::new(Mutex::new(( + Instant::now(), + Err(MeliError::new("Account is uninitialised.")), + ))); + let connection = ImapConnection::new_connection(&server_conf, online.clone()); Ok(Box::new(ImapType { account_name: s.name().to_string(), - online: Arc::new(Mutex::new(false)), + online, server_conf, is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)), @@ -532,7 +536,7 @@ impl ImapType { } pub fn shell(&mut self) { - let mut conn = ImapConnection::new_connection(&self.server_conf); + let mut conn = ImapConnection::new_connection(&self.server_conf, self.online.clone()); let mut res = String::with_capacity(8 * 1024); conn.send_command(b"NOOP").unwrap(); conn.read_response(&mut res).unwrap(); diff --git a/melib/src/backends/imap/connection.rs b/melib/src/backends/imap/connection.rs index 65fe835a..a2b23989 100644 --- a/melib/src/backends/imap/connection.rs +++ b/melib/src/backends/imap/connection.rs @@ -29,6 +29,8 @@ use fnv::FnvHashSet; use native_tls::TlsConnector; use std::iter::FromIterator; use std::net::SocketAddr; +use std::sync::{Arc, Mutex}; +use std::time::Instant; use super::protocol_parser; use super::{Capabilities, ImapServerConf}; @@ -44,6 +46,7 @@ pub struct ImapConnection { pub stream: Result, pub server_conf: ImapServerConf, pub capabilities: Capabilities, + pub online: Arc)>>, } impl Drop for ImapStream { @@ -317,11 +320,15 @@ impl ImapStream { } impl ImapConnection { - pub fn new_connection(server_conf: &ImapServerConf) -> ImapConnection { + pub fn new_connection( + server_conf: &ImapServerConf, + online: Arc)>>, + ) -> ImapConnection { ImapConnection { stream: Err(MeliError::new("Offline".to_string())), server_conf: server_conf.clone(), capabilities: Capabilities::default(), + online, } } @@ -331,7 +338,16 @@ impl ImapConnection { return Ok(()); } } - let (capabilities, mut stream) = ImapStream::new_connection(&self.server_conf)?; + let new_stream = ImapStream::new_connection(&self.server_conf); + if new_stream.is_err() { + *self.online.lock().unwrap() = ( + Instant::now(), + Err(new_stream.as_ref().unwrap_err().clone()), + ); + } else { + *self.online.lock().unwrap() = (Instant::now(), Ok(())); + } + let (capabilities, mut stream) = new_stream?; let ret = stream.read_response(ret); if ret.is_ok() { self.stream = Ok(stream); @@ -346,7 +362,16 @@ impl ImapConnection { return Ok(()); } } - let (capabilities, mut stream) = ImapStream::new_connection(&self.server_conf)?; + let new_stream = ImapStream::new_connection(&self.server_conf); + if new_stream.is_err() { + *self.online.lock().unwrap() = ( + Instant::now(), + Err(new_stream.as_ref().unwrap_err().clone()), + ); + } else { + *self.online.lock().unwrap() = (Instant::now(), Ok(())); + } + let (capabilities, mut stream) = new_stream?; let ret = stream.read_lines(ret, &termination_string); if ret.is_ok() { self.stream = Ok(stream); @@ -361,7 +386,16 @@ impl ImapConnection { return Ok(()); } } - let (capabilities, mut stream) = ImapStream::new_connection(&self.server_conf)?; + let new_stream = ImapStream::new_connection(&self.server_conf); + if new_stream.is_err() { + *self.online.lock().unwrap() = ( + Instant::now(), + Err(new_stream.as_ref().unwrap_err().clone()), + ); + } else { + *self.online.lock().unwrap() = (Instant::now(), Ok(())); + } + let (capabilities, mut stream) = new_stream?; let ret = stream.wait_for_continuation_request(); if ret.is_ok() { self.stream = Ok(stream); @@ -376,7 +410,16 @@ impl ImapConnection { return Ok(ret); } } - let (capabilities, mut stream) = ImapStream::new_connection(&self.server_conf)?; + let new_stream = ImapStream::new_connection(&self.server_conf); + if new_stream.is_err() { + *self.online.lock().unwrap() = ( + Instant::now(), + Err(new_stream.as_ref().unwrap_err().clone()), + ); + } else { + *self.online.lock().unwrap() = (Instant::now(), Ok(())); + } + let (capabilities, mut stream) = new_stream?; let ret = stream.send_command(command); if ret.is_ok() { self.stream = Ok(stream); @@ -391,7 +434,16 @@ impl ImapConnection { return Ok(()); } } - let (capabilities, mut stream) = ImapStream::new_connection(&self.server_conf)?; + let new_stream = ImapStream::new_connection(&self.server_conf); + if new_stream.is_err() { + *self.online.lock().unwrap() = ( + Instant::now(), + Err(new_stream.as_ref().unwrap_err().clone()), + ); + } else { + *self.online.lock().unwrap() = (Instant::now(), Ok(())); + } + let (capabilities, mut stream) = new_stream?; let ret = stream.send_literal(data); if ret.is_ok() { self.stream = Ok(stream); @@ -406,8 +458,16 @@ impl ImapConnection { return Ok(()); } } - let (capabilities, mut stream) = ImapStream::new_connection(&self.server_conf)?; - + let new_stream = ImapStream::new_connection(&self.server_conf); + if new_stream.is_err() { + *self.online.lock().unwrap() = ( + Instant::now(), + Err(new_stream.as_ref().unwrap_err().clone()), + ); + } else { + *self.online.lock().unwrap() = (Instant::now(), Ok(())); + } + let (capabilities, mut stream) = new_stream?; let ret = stream.send_raw(raw); if ret.is_ok() { self.stream = Ok(stream); @@ -422,7 +482,16 @@ impl ImapConnection { return Ok(()); } } - let (capabilities, mut stream) = ImapStream::new_connection(&self.server_conf)?; + let new_stream = ImapStream::new_connection(&self.server_conf); + if new_stream.is_err() { + *self.online.lock().unwrap() = ( + Instant::now(), + Err(new_stream.as_ref().unwrap_err().clone()), + ); + } else { + *self.online.lock().unwrap() = (Instant::now(), Ok(())); + } + let (capabilities, mut stream) = new_stream?; let ret = stream.set_nonblocking(val); if ret.is_ok() { self.stream = Ok(stream); diff --git a/melib/src/backends/imap/watch.rs b/melib/src/backends/imap/watch.rs index 1a92f433..87426cf9 100644 --- a/melib/src/backends/imap/watch.rs +++ b/melib/src/backends/imap/watch.rs @@ -25,7 +25,7 @@ use std::sync::{Arc, Mutex, RwLock}; /// Arguments for IMAP watching functions pub struct ImapWatchKit { pub conn: ImapConnection, - pub is_online: Arc>, + pub is_online: Arc)>>, pub main_conn: Arc>, pub uid_store: Arc, pub folders: Arc>>, @@ -62,7 +62,7 @@ pub fn poll_with_examine(kit: ImapWatchKit) -> Result<()> { tag_index, } = kit; loop { - if *is_online.lock().unwrap() { + if is_online.lock().unwrap().1.is_ok() { break; } std::thread::sleep(std::time::Duration::from_millis(100)); @@ -114,7 +114,7 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> { tag_index, } = kit; loop { - if *is_online.lock().unwrap() { + if is_online.lock().unwrap().1.is_ok() { break; } std::thread::sleep(std::time::Duration::from_millis(100)); diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs index 1537b338..4a574b43 100644 --- a/melib/src/backends/jmap.rs +++ b/melib/src/backends/jmap.rs @@ -31,6 +31,7 @@ use reqwest::blocking::Client; use std::collections::BTreeMap; use std::str::FromStr; use std::sync::{Arc, Mutex, RwLock}; +use std::time::Instant; #[macro_export] macro_rules! _impl { @@ -182,7 +183,7 @@ pub struct Store { #[derive(Debug)] pub struct JmapType { account_name: String, - online: Arc>, + online: Arc)>>, is_subscribed: Arc, server_conf: JmapServerConf, connection: Arc, @@ -192,8 +193,8 @@ pub struct JmapType { } impl MailBackend for JmapType { - fn is_online(&self) -> bool { - *self.online.lock().unwrap() + fn is_online(&self) -> Result<()> { + self.online.lock().unwrap().1.clone() } fn get(&mut self, folder: &Folder) -> Async>> { let mut w = AsyncBuilder::new(); @@ -277,7 +278,7 @@ impl JmapType { s: &AccountSettings, is_subscribed: Box bool + Send + Sync>, ) -> Result> { - let online = Arc::new(Mutex::new(false)); + let online = Arc::new(Mutex::new(Err(MeliError::new("Account is uninitialised.")))); let server_conf = JmapServerConf::new(s)?; Ok(Box::new(JmapType { diff --git a/melib/src/backends/jmap/connection.rs b/melib/src/backends/jmap/connection.rs index dfa0cfe4..ea520b3a 100644 --- a/melib/src/backends/jmap/connection.rs +++ b/melib/src/backends/jmap/connection.rs @@ -26,16 +26,17 @@ pub struct JmapConnection { pub session: JmapSession, pub request_no: Arc>, pub client: Arc>, - pub online_status: Arc>, + pub online_status: Arc)>>, pub server_conf: JmapServerConf, pub account_id: Arc>, pub method_call_states: Arc>>, } impl JmapConnection { - pub fn new(server_conf: &JmapServerConf, online_status: Arc>) -> Result { + pub fn new(server_conf: &JmapServerConf, online_status: Arc)>>) -> Result { use reqwest::header; let mut headers = header::HeaderMap::new(); + let connection_start = std::time::Instant::now(); headers.insert( header::ACCEPT, header::HeaderValue::from_static("application/json"), @@ -68,20 +69,29 @@ impl JmapConnection { .send()?; let res_text = req.text()?; - let session: JmapSession = serde_json::from_str(&res_text).map_err(|_| MeliError::new(format!("Could not connect to JMAP server endpoint for {}. Is your server hostname setting correct? (i.e. \"jmap.mailserver.org\") (Note: only session resource discovery via /.well-known/jmap is supported. DNS SRV records are not suppported.)", &server_conf.server_hostname)))?; + let session: JmapSession = serde_json::from_str(&res_text).map_err(|_| { + let err = MeliError::new(format!("Could not connect to JMAP server endpoint for {}. Is your server hostname setting correct? (i.e. \"jmap.mailserver.org\") (Note: only session resource discovery via /.well-known/jmap is supported. DNS SRV records are not suppported.)", &server_conf.server_hostname); + *online_status.lock().unwrap() = (Instant::new(), Err(err.clone())); + err + ))?; if !session .capabilities .contains_key("urn:ietf:params:jmap:core") { - return Err(MeliError::new(format!("Server {} did not return JMAP Core capability (urn:ietf:params:jmap:core). Returned capabilities were: {}", &server_conf.server_hostname, session.capabilities.keys().map(String::as_str).collect::>().join(", ")))); + let err = Err(MeliError::new(format!("Server {} did not return JMAP Core capability (urn:ietf:params:jmap:core). Returned capabilities were: {}", &server_conf.server_hostname, session.capabilities.keys().map(String::as_str).collect::>().join(", ")))); + *online_status.lock().unwrap() = (Instant::new(), Err(err.clone())); + return err; } if !session .capabilities .contains_key("urn:ietf:params:jmap:mail") { - return Err(MeliError::new(format!("Server {} does not support JMAP Mail capability (urn:ietf:params:jmap:mail). Returned capabilities were: {}", &server_conf.server_hostname, session.capabilities.keys().map(String::as_str).collect::>().join(", ")))); + let err = Err(MeliError::new(format!("Server {} does not support JMAP Mail capability (urn:ietf:params:jmap:mail). Returned capabilities were: {}", &server_conf.server_hostname, session.capabilities.keys().map(String::as_str).collect::>().join(", ")))); + *online_status.lock().unwrap() = (Instant::new(), Err(err.clone())); + return err; } + *online_status.lock().unwrap() = (Instant::new(), Ok(())); let server_conf = server_conf.clone(); Ok(JmapConnection { session, diff --git a/melib/src/backends/maildir/backend.rs b/melib/src/backends/maildir/backend.rs index e26ff9e6..288c7ce7 100644 --- a/melib/src/backends/maildir/backend.rs +++ b/melib/src/backends/maildir/backend.rs @@ -184,8 +184,8 @@ fn move_to_cur(p: PathBuf) -> Result { } impl MailBackend for MaildirType { - fn is_online(&self) -> bool { - true + fn is_online(&self) -> Result<()> { + Ok(()) } fn folders(&self) -> Result> { diff --git a/melib/src/backends/mbox.rs b/melib/src/backends/mbox.rs index f10674d4..c0a254c9 100644 --- a/melib/src/backends/mbox.rs +++ b/melib/src/backends/mbox.rs @@ -370,8 +370,8 @@ pub struct MboxType { } impl MailBackend for MboxType { - fn is_online(&self) -> bool { - true + fn is_online(&self) -> Result<()> { + Ok(()) } fn get(&mut self, folder: &Folder) -> Async>> { let mut w = AsyncBuilder::new(); diff --git a/melib/src/backends/notmuch.rs b/melib/src/backends/notmuch.rs index 95582d2b..708d022a 100644 --- a/melib/src/backends/notmuch.rs +++ b/melib/src/backends/notmuch.rs @@ -242,8 +242,8 @@ impl NotmuchDb { } impl MailBackend for NotmuchDb { - fn is_online(&self) -> bool { - true + fn is_online(&self) -> Result<()> { + Ok(()) } fn get(&mut self, folder: &Folder) -> Async>> { let mut w = AsyncBuilder::new(); diff --git a/ui/src/components/mail/listing.rs b/ui/src/components/mail/listing.rs index ed80ec08..bd705499 100644 --- a/ui/src/components/mail/listing.rs +++ b/ui/src/components/mail/listing.rs @@ -295,29 +295,10 @@ impl Component for Listing { } if right_component_width == total_cols { - if !context.is_online(self.cursor_pos.0) { + if let Err(err) = context.is_online(self.cursor_pos.0) { clear_area(grid, area); - write_string_to_grid( - "offline", - grid, - Color::Byte(243), - Color::Default, - Attr::Default, - area, - None, - ); - context.dirty_areas.push_back(area); - return; - } - self.component.draw(grid, area, context); - } else if right_component_width == 0 { - self.draw_menu(grid, area, context); - } else { - self.draw_menu(grid, (upper_left, (mid, get_y(bottom_right))), context); - if !context.is_online(self.cursor_pos.0) { - clear_area(grid, (set_x(upper_left, mid + 1), bottom_right)); - write_string_to_grid( - "offline", + let (x, _) = write_string_to_grid( + "offline: ", grid, Color::Byte(243), Color::Default, @@ -325,11 +306,49 @@ impl Component for Listing { (set_x(upper_left, mid + 1), bottom_right), None, ); + write_string_to_grid( + &err.to_string(), + grid, + Color::Red, + Color::Default, + Attr::Default, + (set_x(upper_left, x + 1), bottom_right), + None, + ); context.dirty_areas.push_back(area); return; + } else { + self.component.draw(grid, area, context); + } + } else if right_component_width == 0 { + self.draw_menu(grid, area, context); + } else { + self.draw_menu(grid, (upper_left, (mid, get_y(bottom_right))), context); + if let Err(err) = context.is_online(self.cursor_pos.0) { + clear_area(grid, (set_x(upper_left, mid + 1), bottom_right)); + let (x, _) = write_string_to_grid( + "offline: ", + grid, + Color::Byte(243), + Color::Default, + Attr::Default, + (set_x(upper_left, mid + 1), bottom_right), + None, + ); + write_string_to_grid( + &err.to_string(), + grid, + Color::Red, + Color::Default, + Attr::Default, + (set_x(upper_left, x + 1), bottom_right), + None, + ); + context.dirty_areas.push_back(area); + } else { + self.component + .draw(grid, (set_x(upper_left, mid + 1), bottom_right), context); } - self.component - .draw(grid, (set_x(upper_left, mid + 1), bottom_right), context); } self.dirty = false; } diff --git a/ui/src/conf/accounts.rs b/ui/src/conf/accounts.rs index bbe6cdef..145589ef 100644 --- a/ui/src/conf/accounts.rs +++ b/ui/src/conf/accounts.rs @@ -991,12 +991,12 @@ impl Account { /* Call only in Context::is_online, since only Context can launch the watcher threads if an * account goes from offline to online. */ - pub fn is_online(&mut self) -> bool { + pub fn is_online(&mut self) -> Result<()> { let ret = self.backend.read().unwrap().is_online(); - if ret != self.is_online && ret { + if ret.is_ok() != self.is_online && ret.is_ok() { self.init(); } - self.is_online = ret; + self.is_online = ret.is_ok(); ret } diff --git a/ui/src/state.rs b/ui/src/state.rs index c1bed88f..79c75679 100644 --- a/ui/src/state.rs +++ b/ui/src/state.rs @@ -135,14 +135,15 @@ impl Context { } } - pub fn is_online(&mut self, account_pos: usize) -> bool { + pub fn is_online(&mut self, account_pos: usize) -> Result<()> { let Context { ref mut accounts, ref mut mailbox_hashes, .. } = self; let was_online = accounts[account_pos].is_online; - if accounts[account_pos].is_online() { + let ret = accounts[account_pos].is_online(); + if ret.is_ok() { if !was_online { for folder in accounts[account_pos].list_folders() { debug!("hash & folder: {:?} {}", folder.hash(), folder.name()); @@ -157,10 +158,8 @@ impl Context { */ accounts[account_pos].watch(); } - true - } else { - false } + ret } pub fn work_controller(&self) -> &WorkController { @@ -291,7 +290,7 @@ impl State { s.switch_to_alternate_screen(); debug!("inserting mailbox hashes:"); for i in 0..s.context.accounts.len() { - if s.context.is_online(i) && s.context.accounts[i].is_empty() { + if s.context.is_online(i).is_ok() && s.context.accounts[i].is_empty() { return Err(MeliError::new(format!( "Account {} has no folders configured.", s.context.accounts[i].name() @@ -722,7 +721,7 @@ impl State { pub fn check_accounts(&mut self) { let mut ctr = 0; for i in 0..self.context.accounts.len() { - if self.context.is_online(i) { + if self.context.is_online(i).is_ok() { ctr += 1; } }