melib: make MailBackend::is_online() return Result<()>

Return Result<()> instead of bool to indicate connection status in order
to be able to show errors to user.
memfd
Manos Pitsidianakis 2019-12-14 18:46:12 +02:00
parent 18a8d22b85
commit 2e38ea11e2
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
12 changed files with 172 additions and 70 deletions

View File

@ -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<Result<Vec<Envelope>>>;
fn watch(
&self,

View File

@ -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<Mutex<bool>>,
online: Arc<Mutex<(Instant, Result<()>)>>,
is_subscribed: Arc<IsSubscribedFn>,
connection: Arc<Mutex<ImapConnection>>,
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<Result<Vec<Envelope>>> {
macro_rules! exit_on_error {
@ -285,7 +286,7 @@ impl MailBackend for ImapType {
) -> Result<std::thread::ThreadId> {
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();

View File

@ -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<ImapStream>,
pub server_conf: ImapServerConf,
pub capabilities: Capabilities,
pub online: Arc<Mutex<(Instant, Result<()>)>>,
}
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<Mutex<(Instant, Result<()>)>>,
) -> 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);

View File

@ -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<Mutex<bool>>,
pub is_online: Arc<Mutex<(Instant, Result<()>)>>,
pub main_conn: Arc<Mutex<ImapConnection>>,
pub uid_store: Arc<UIDStore>,
pub folders: Arc<RwLock<FnvHashMap<FolderHash, ImapFolder>>>,
@ -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));

View File

@ -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<Mutex<bool>>,
online: Arc<Mutex<(Instant, Result<()>)>>,
is_subscribed: Arc<IsSubscribedFn>,
server_conf: JmapServerConf,
connection: Arc<JmapConnection>,
@ -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<Result<Vec<Envelope>>> {
let mut w = AsyncBuilder::new();
@ -277,7 +278,7 @@ impl JmapType {
s: &AccountSettings,
is_subscribed: Box<dyn Fn(&str) -> bool + Send + Sync>,
) -> Result<Box<dyn MailBackend>> {
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 {

View File

@ -26,16 +26,17 @@ pub struct JmapConnection {
pub session: JmapSession,
pub request_no: Arc<Mutex<usize>>,
pub client: Arc<Mutex<Client>>,
pub online_status: Arc<Mutex<bool>>,
pub online_status: Arc<Mutex<(Instant, Result<()>)>>,
pub server_conf: JmapServerConf,
pub account_id: Arc<Mutex<String>>,
pub method_call_states: Arc<Mutex<FnvHashMap<&'static str, String>>>,
}
impl JmapConnection {
pub fn new(server_conf: &JmapServerConf, online_status: Arc<Mutex<bool>>) -> Result<Self> {
pub fn new(server_conf: &JmapServerConf, online_status: Arc<Mutex<(Instant, Result<()>)>>) -> Result<Self> {
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::<Vec<&str>>().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::<Vec<&str>>().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::<Vec<&str>>().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::<Vec<&str>>().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,

View File

@ -184,8 +184,8 @@ fn move_to_cur(p: PathBuf) -> Result<PathBuf> {
}
impl MailBackend for MaildirType {
fn is_online(&self) -> bool {
true
fn is_online(&self) -> Result<()> {
Ok(())
}
fn folders(&self) -> Result<FnvHashMap<FolderHash, Folder>> {

View File

@ -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<Result<Vec<Envelope>>> {
let mut w = AsyncBuilder::new();

View File

@ -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<Result<Vec<Envelope>>> {
let mut w = AsyncBuilder::new();

View File

@ -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;
}

View File

@ -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
}

View File

@ -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;
}
}