diff --git a/melib/src/backends/imap.rs b/melib/src/backends/imap.rs index 6aa63351..0a5173a4 100644 --- a/melib/src/backends/imap.rs +++ b/melib/src/backends/imap.rs @@ -65,7 +65,6 @@ pub struct ImapType { danger_accept_invalid_certs: bool, connection: Arc>, - capabilities: Capabilities, folders: FnvHashMap, hash_index: Arc>>, uid_index: Arc>>, @@ -174,15 +173,19 @@ impl MailBackend for ImapType { sender: RefreshEventConsumer, work_context: WorkContext, ) -> Result { - let has_idle: bool = self.capabilities.contains(&b"IDLE"[0..]); + let has_idle: bool = self + .connection + .lock() + .unwrap() + .capabilities + .contains(&b"IDLE"[0..]); let folders = self.folders.clone(); let conn = ImapConnection::new_connection( self.server_hostname.clone(), self.server_username.clone(), self.server_password.clone(), self.danger_accept_invalid_certs, - )? - .1; + ); let main_conn = self.connection.clone(); let hash_index = self.hash_index.clone(); let uid_index = self.uid_index.clone(); @@ -385,31 +388,6 @@ macro_rules! get_conf_val { }; } -macro_rules! exit_on_error { - ($s:ident, $($result:expr)+) => { - $(if let Err(e) = $result { - eprintln!( - "IMAP error ({}): {}", - $s.name.as_str(), - e.to_string(), - ); - std::process::exit(1); - })+ - }; - ($s:ident returning $result:expr) => { - match $result { - Err(e) => { - eprintln!( - "IMAP error ({}): {}", - $s.name.as_str(), - e.to_string(), - ); - std::process::exit(1); - }, - Ok(v) => v, - } - } -} impl ImapType { pub fn new(s: &AccountSettings, is_subscribed: Box bool>) -> Self { debug!(s); @@ -418,7 +396,12 @@ impl ImapType { let server_password = get_conf_val!(s["server_password"]); let danger_accept_invalid_certs: bool = get_conf_val!(s["danger_accept_invalid_certs"], false); - let (capabilities, connection) = exit_on_error!(s returning ImapConnection::new_connection(server_hostname.to_string(), server_username.to_string(), server_password.to_string(), danger_accept_invalid_certs)); + let connection = ImapConnection::new_connection( + server_hostname.to_string(), + server_username.to_string(), + server_password.to_string(), + danger_accept_invalid_certs, + ); let mut m = ImapType { account_name: s.name().to_string(), @@ -430,16 +413,9 @@ impl ImapType { danger_accept_invalid_certs, hash_index: Default::default(), uid_index: Default::default(), - capabilities, byte_cache: Default::default(), }; - debug!(m - .capabilities - .iter() - .map(|s| String::from_utf8(s.to_vec()).unwrap()) - .collect::>()); - m.folders = m.imap_folders(); m.folders.retain(|_, f| is_subscribed(f.path())); let keys = m @@ -459,9 +435,7 @@ impl ImapType { self.server_username.clone(), self.server_password.clone(), self.danger_accept_invalid_certs, - ) - .unwrap() - .1; + ); let mut res = String::with_capacity(8 * 1024); conn.read_response(&mut res).unwrap(); debug!("out: {}", &res); diff --git a/melib/src/backends/imap/connection.rs b/melib/src/backends/imap/connection.rs index 6497afc0..9144a764 100644 --- a/melib/src/backends/imap/connection.rs +++ b/melib/src/backends/imap/connection.rs @@ -21,7 +21,6 @@ use crate::email::parser::BytesExt; use crate::error::*; -use crate::logging::{LoggingLevel::*, *}; use std::io::Read; use std::io::Write; extern crate native_tls; @@ -33,23 +32,29 @@ use std::net::SocketAddr; use super::protocol_parser; use super::Capabilities; +#[derive(Debug)] +pub struct ImapStream { + cmd_id: usize, + stream: native_tls::TlsStream, +} + #[derive(Debug)] pub struct ImapConnection { - pub cmd_id: usize, - pub stream: native_tls::TlsStream, + pub stream: Result, server_hostname: String, server_username: String, server_password: String, danger_accept_invalid_certs: bool, + pub capabilities: Capabilities, } -impl Drop for ImapConnection { +impl Drop for ImapStream { fn drop(&mut self) { self.send_command(b"LOGOUT").ok().take(); } } -impl ImapConnection { +impl ImapStream { pub fn read_response(&mut self, ret: &mut String) -> Result<()> { let id = format!("M{} ", self.cmd_id - 1); self.read_lines(ret, id) @@ -59,7 +64,6 @@ impl ImapConnection { let mut buf: [u8; 1024] = [0; 1024]; ret.clear(); let mut last_line_idx: usize = 0; - let mut connection_tries = 0; loop { match self.stream.read(&mut buf) { Ok(0) => break, @@ -92,34 +96,7 @@ impl ImapConnection { Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { continue; } - Err(_) if connection_tries == 0 => { - let server_hostname = - std::mem::replace(&mut self.server_hostname, String::new()); - let server_username = - std::mem::replace(&mut self.server_username, String::new()); - let server_password = - std::mem::replace(&mut self.server_password, String::new()); - log( - format!( - "IMAP connection to `{}` failed. Retrying one more time...", - &server_hostname - ), - ERROR, - ); - *self = ImapConnection::new_connection( - server_hostname, - server_username, - server_password, - self.danger_accept_invalid_certs, - )? - .1; - connection_tries += 1; - } Err(e) => { - log( - format!("IMAP connection to `{}` failed.", &self.server_hostname), - FATAL, - ); return Err(MeliError::from(e)); } } @@ -169,11 +146,11 @@ impl ImapConnection { } pub fn new_connection( - server_hostname: String, - server_username: String, - server_password: String, + server_hostname: &str, + server_username: &str, + server_password: &str, danger_accept_invalid_certs: bool, - ) -> Result<(Capabilities, ImapConnection)> { + ) -> Result<(Capabilities, ImapStream)> { use std::io::prelude::*; use std::net::TcpStream; let path = &server_hostname; @@ -249,20 +226,9 @@ impl ImapConnection { } conn_result? }; - let mut ret = ImapConnection { - cmd_id, - stream, - server_hostname, - server_username, - server_password, - danger_accept_invalid_certs, - }; + let mut ret = ImapStream { cmd_id, stream }; ret.send_command( - format!( - "LOGIN \"{}\" \"{}\"", - &ret.server_username, &ret.server_password - ) - .as_bytes(), + format!("LOGIN \"{}\" \"{}\"", &server_username, &server_password).as_bytes(), )?; let mut res = String::with_capacity(8 * 1024); ret.read_lines(&mut res, String::new())?; @@ -273,6 +239,151 @@ impl ImapConnection { } } +impl ImapConnection { + pub fn new_connection( + server_hostname: String, + server_username: String, + server_password: String, + danger_accept_invalid_certs: bool, + ) -> ImapConnection { + ImapConnection { + stream: Err(MeliError::new("Offline".to_string())), + server_hostname, + server_username, + server_password, + danger_accept_invalid_certs, + capabilities: Capabilities::default(), + } + } + + pub fn read_response(&mut self, ret: &mut String) -> Result<()> { + if let Ok(ref mut stream) = self.stream { + return stream.read_response(ret); + } + let (capabilities, mut stream) = ImapStream::new_connection( + &self.server_hostname, + &self.server_username, + &self.server_password, + self.danger_accept_invalid_certs, + )?; + let ret = stream.read_response(ret); + if ret.is_ok() { + self.stream = Ok(stream); + self.capabilities = capabilities; + } + ret + } + + pub fn read_lines(&mut self, ret: &mut String, termination_string: String) -> Result<()> { + if let Ok(ref mut stream) = self.stream { + return stream.read_lines(ret, termination_string); + } + let (capabilities, mut stream) = ImapStream::new_connection( + &self.server_hostname, + &self.server_username, + &self.server_password, + self.danger_accept_invalid_certs, + )?; + let ret = stream.read_lines(ret, termination_string); + if ret.is_ok() { + self.stream = Ok(stream); + self.capabilities = capabilities; + } + ret + } + + pub fn wait_for_continuation_request(&mut self) -> Result<()> { + if let Ok(ref mut stream) = self.stream { + return stream.wait_for_continuation_request(); + } + let (capabilities, mut stream) = ImapStream::new_connection( + &self.server_hostname, + &self.server_username, + &self.server_password, + self.danger_accept_invalid_certs, + )?; + let ret = stream.wait_for_continuation_request(); + if ret.is_ok() { + self.stream = Ok(stream); + self.capabilities = capabilities; + } + ret + } + + pub fn send_command(&mut self, command: &[u8]) -> Result { + if let Ok(ref mut stream) = self.stream { + return stream.send_command(command); + } + let (capabilities, mut stream) = ImapStream::new_connection( + &self.server_hostname, + &self.server_username, + &self.server_password, + self.danger_accept_invalid_certs, + )?; + let ret = stream.send_command(command); + if ret.is_ok() { + self.stream = Ok(stream); + self.capabilities = capabilities; + } + ret + } + + pub fn send_literal(&mut self, data: &[u8]) -> Result<()> { + if let Ok(ref mut stream) = self.stream { + return stream.send_literal(data); + } + let (capabilities, mut stream) = ImapStream::new_connection( + &self.server_hostname, + &self.server_username, + &self.server_password, + self.danger_accept_invalid_certs, + )?; + let ret = stream.send_literal(data); + if ret.is_ok() { + self.stream = Ok(stream); + self.capabilities = capabilities; + } + ret + } + + pub fn send_raw(&mut self, raw: &[u8]) -> Result<()> { + if let Ok(ref mut stream) = self.stream { + return stream.send_raw(raw); + } + let (capabilities, mut stream) = ImapStream::new_connection( + &self.server_hostname, + &self.server_username, + &self.server_password, + self.danger_accept_invalid_certs, + )?; + + let ret = stream.send_raw(raw); + if ret.is_ok() { + self.stream = Ok(stream); + self.capabilities = capabilities; + } + ret + } + + pub fn set_nonblocking(&mut self, val: bool) -> Result<()> { + if let Ok(ref mut stream) = self.stream { + return stream.set_nonblocking(val); + } + let (capabilities, mut stream) = ImapStream::new_connection( + &self.server_hostname, + &self.server_username, + &self.server_password, + self.danger_accept_invalid_certs, + )?; + let ret = stream.set_nonblocking(val); + if ret.is_ok() { + self.stream = Ok(stream); + self.capabilities = capabilities; + } + ret + } +} + pub struct ImapBlockingConnection { buf: [u8; 1024], result: Vec, @@ -282,9 +393,7 @@ pub struct ImapBlockingConnection { impl From for ImapBlockingConnection { fn from(mut conn: ImapConnection) -> Self { - conn.stream - .get_mut() - .set_nonblocking(false) + conn.set_nonblocking(false) .expect("set_nonblocking call failed"); ImapBlockingConnection { buf: [0; 1024], @@ -306,15 +415,34 @@ impl Iterator for ImapBlockingConnection { fn next(&mut self) -> Option { self.result.drain(0..self.prev_res_length); self.prev_res_length = 0; + let ImapBlockingConnection { + ref mut prev_res_length, + ref mut result, + ref mut conn, + ref mut buf, + } = self; loop { - match self.conn.stream.read(&mut self.buf) { + if conn.stream.is_err() { + if let Ok((_, stream)) = ImapStream::new_connection( + &conn.server_hostname, + &conn.server_username, + &conn.server_password, + conn.danger_accept_invalid_certs, + ) { + conn.stream = Ok(stream); + } else { + debug!(&conn.stream); + return None; + } + } + match conn.stream.as_mut().unwrap().stream.read(buf) { Ok(0) => continue, Ok(b) => { - self.result.extend_from_slice(&self.buf[0..b]); - debug!(unsafe { std::str::from_utf8_unchecked(&self.result) }); - if let Some(pos) = self.result.find(b"\r\n") { - self.prev_res_length = pos + b"\r\n".len(); - return Some(self.result[0..self.prev_res_length].to_vec()); + 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()); } } Err(e) => {