Nuke the original imap impls
This commit is contained in:
parent
18ca5e540d
commit
a5dc3eebcb
5 changed files with 45 additions and 448 deletions
|
@ -9,9 +9,13 @@ Goals:
|
||||||
|
|
||||||
- Never have to actually close the application.
|
- Never have to actually close the application.
|
||||||
- Handles email, calendar, and address books using open standards.
|
- Handles email, calendar, and address books using open standards.
|
||||||
- Unified "feed" that any app can submit to.
|
|
||||||
- Hot-reload on-disk config.
|
- Hot-reload on-disk config.
|
||||||
|
|
||||||
|
Stretch goals:
|
||||||
|
- Unified "feed" that any app can submit to.
|
||||||
- Submit notifications to gotify-shaped notification servers.
|
- Submit notifications to gotify-shaped notification servers.
|
||||||
|
- RSS aggregator.
|
||||||
|
- IRC client??
|
||||||
|
|
||||||
Credits
|
Credits
|
||||||
-------
|
-------
|
||||||
|
|
|
@ -10,6 +10,13 @@ pub enum Response {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl FromStr for Response {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub enum Capability {
|
pub enum Capability {
|
||||||
Imap4rev1,
|
Imap4rev1,
|
||||||
Auth(String),
|
Auth(String),
|
||||||
|
|
231
src/mail/imap.rs
231
src/mail/imap.rs
|
@ -1,231 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Display;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use futures::{
|
|
||||||
future::{self, Either},
|
|
||||||
pin_mut,
|
|
||||||
sink::{Sink, SinkExt},
|
|
||||||
stream::{Stream, StreamExt},
|
|
||||||
};
|
|
||||||
use panorama_imap::{
|
|
||||||
builders::command::Command,
|
|
||||||
parser::parse_response,
|
|
||||||
types::{Capability, RequestId, Response, ResponseCode, State, Status},
|
|
||||||
};
|
|
||||||
use tokio::{net::TcpStream, sync::mpsc};
|
|
||||||
use tokio_rustls::{rustls::ClientConfig, webpki::DNSNameRef, TlsConnector};
|
|
||||||
use tokio_util::codec::{Decoder, LinesCodec, LinesCodecError};
|
|
||||||
|
|
||||||
use crate::config::ImapConfig;
|
|
||||||
|
|
||||||
pub async fn open_imap_connection(config: ImapConfig) -> Result<()> {
|
|
||||||
debug!(
|
|
||||||
"Opening imap connection to {}:{}",
|
|
||||||
config.server, config.port
|
|
||||||
);
|
|
||||||
|
|
||||||
let server = config.server.as_str();
|
|
||||||
let port = config.port;
|
|
||||||
|
|
||||||
let client = TcpStream::connect((server, port)).await?;
|
|
||||||
let codec = LinesCodec::new();
|
|
||||||
let framed = codec.framed(client);
|
|
||||||
let mut state = State::NotAuthenticated;
|
|
||||||
let (sink, stream) = framed.split::<String>();
|
|
||||||
|
|
||||||
let result = listen_loop(config.clone(), &mut state, sink, stream, false).await?;
|
|
||||||
|
|
||||||
if let LoopExit::NegotiateTls(stream, sink) = result {
|
|
||||||
debug!("negotiating tls");
|
|
||||||
let mut tls_config = ClientConfig::new();
|
|
||||||
tls_config
|
|
||||||
.root_store
|
|
||||||
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
|
||||||
let tls_config = TlsConnector::from(Arc::new(tls_config));
|
|
||||||
let dnsname = DNSNameRef::try_from_ascii_str(server).unwrap();
|
|
||||||
|
|
||||||
// reconstruct the original stream
|
|
||||||
let stream = stream.reunite(sink)?.into_inner();
|
|
||||||
// let stream = TcpStream::connect((server, port)).await?;
|
|
||||||
let stream = tls_config.connect(dnsname, stream).await?;
|
|
||||||
|
|
||||||
let codec = LinesCodec::new();
|
|
||||||
let framed = codec.framed(stream);
|
|
||||||
let (sink, stream) = framed.split::<String>();
|
|
||||||
listen_loop(config.clone(), &mut state, sink, stream, true).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Action that should be taken after the connection loop quits.
|
|
||||||
enum LoopExit<S, S2> {
|
|
||||||
/// Used in case the STARTTLS command is issued; perform TLS negotiation on top of the current
|
|
||||||
/// stream.
|
|
||||||
///
|
|
||||||
/// S and S2 are stream and sink types, respectively.
|
|
||||||
NegotiateTls(S, S2),
|
|
||||||
Closed,
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn listen_loop<S, S2>(
|
|
||||||
config: ImapConfig,
|
|
||||||
st: &mut State,
|
|
||||||
sink: S2,
|
|
||||||
mut stream: S,
|
|
||||||
with_ssl: bool,
|
|
||||||
) -> Result<LoopExit<S, S2>>
|
|
||||||
where
|
|
||||||
S: Stream<Item = Result<String, LinesCodecError>> + Unpin,
|
|
||||||
S2: Sink<String> + Unpin,
|
|
||||||
S2::Error: Display,
|
|
||||||
{
|
|
||||||
let (tx, mut rx) = mpsc::unbounded_channel::<()>();
|
|
||||||
let mut cmd_mgr = CommandManager::new(sink);
|
|
||||||
|
|
||||||
if with_ssl {
|
|
||||||
let cmd = Command {
|
|
||||||
args: b"CAPABILITY".to_vec(),
|
|
||||||
next_state: Some(State::Authenticated),
|
|
||||||
};
|
|
||||||
cmd_mgr.send(cmd, |_| {}).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
let fut1 = stream.next();
|
|
||||||
let fut2 = rx.recv();
|
|
||||||
pin_mut!(fut1);
|
|
||||||
pin_mut!(fut2);
|
|
||||||
|
|
||||||
debug!("waiting for next select");
|
|
||||||
match future::select(fut1, fut2).await {
|
|
||||||
Either::Left((line, _)) => {
|
|
||||||
let mut line = match line {
|
|
||||||
Some(v) => v?,
|
|
||||||
None => break,
|
|
||||||
};
|
|
||||||
line += "\r\n";
|
|
||||||
let (_, resp) = match parse_response(line.as_bytes()) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => bail!(e.to_string()),
|
|
||||||
};
|
|
||||||
debug!("<<< {:?}", resp);
|
|
||||||
|
|
||||||
match st {
|
|
||||||
State::Authenticated => {}
|
|
||||||
State::NotAuthenticated => match resp {
|
|
||||||
Response::Data {
|
|
||||||
status: Status::Ok,
|
|
||||||
code: Some(ResponseCode::Capabilities(caps)),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
if !with_ssl {
|
|
||||||
// prepare to do TLS negotiation
|
|
||||||
let mut has_starttls = false;
|
|
||||||
for cap in caps {
|
|
||||||
if let Capability::Atom("STARTTLS") = cap {
|
|
||||||
has_starttls = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if has_starttls {
|
|
||||||
let cmd = Command {
|
|
||||||
args: b"STARTTLS".to_vec(),
|
|
||||||
next_state: None,
|
|
||||||
};
|
|
||||||
let tx = tx.clone();
|
|
||||||
cmd_mgr
|
|
||||||
.send(cmd, move |_| {
|
|
||||||
tx.send(()).unwrap();
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Response::Capabilities(_caps) => {
|
|
||||||
if with_ssl {
|
|
||||||
// send authentication information
|
|
||||||
let cmd = Command {
|
|
||||||
args: format!("LOGIN {} {}", config.username, config.password)
|
|
||||||
.as_bytes()
|
|
||||||
.to_vec(),
|
|
||||||
next_state: Some(State::Authenticated),
|
|
||||||
};
|
|
||||||
cmd_mgr.send(cmd, |_| {}).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Response::Done { tag, code, .. } => {
|
|
||||||
cmd_mgr.process_done(tag, code)?;
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Either::Right((_, _)) => {
|
|
||||||
debug!("ENCOUNTERED EXIT");
|
|
||||||
let sink = cmd_mgr.decompose();
|
|
||||||
return Ok(LoopExit::NegotiateTls(stream, sink));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(LoopExit::Closed)
|
|
||||||
}
|
|
||||||
|
|
||||||
type InFlightFunc = Box<dyn Fn(Option<ResponseCode>) + Send>;
|
|
||||||
|
|
||||||
/// A struct in charge of managing multiple in-flight commands.
|
|
||||||
struct CommandManager<S> {
|
|
||||||
tag_idx: usize,
|
|
||||||
in_flight: HashMap<String, InFlightFunc>,
|
|
||||||
sink: S,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> CommandManager<S>
|
|
||||||
where
|
|
||||||
S: Sink<String> + Unpin,
|
|
||||||
{
|
|
||||||
pub fn new(sink: S) -> Self {
|
|
||||||
CommandManager {
|
|
||||||
tag_idx: 0,
|
|
||||||
in_flight: HashMap::new(),
|
|
||||||
sink,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decompose(self) -> S {
|
|
||||||
self.sink
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn send(
|
|
||||||
&mut self,
|
|
||||||
cmd: Command,
|
|
||||||
cb: impl Fn(Option<ResponseCode>) + Send + 'static,
|
|
||||||
) -> Result<()> {
|
|
||||||
let tag_idx = self.tag_idx;
|
|
||||||
self.tag_idx += 1;
|
|
||||||
let cb = Box::new(cb);
|
|
||||||
let tag_str = format!("t{}", tag_idx);
|
|
||||||
let cmd_str = std::str::from_utf8(&cmd.args)?;
|
|
||||||
let full_str = format!("{} {}", tag_str, cmd_str);
|
|
||||||
self.in_flight.insert(tag_str.clone(), cb);
|
|
||||||
|
|
||||||
debug!(">>> {:?}", full_str);
|
|
||||||
self.sink
|
|
||||||
.send(full_str)
|
|
||||||
.await
|
|
||||||
.map_err(|_| anyhow!("failed to send command"))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_done(&mut self, id: RequestId, code: Option<ResponseCode>) -> Result<()> {
|
|
||||||
let name = std::str::from_utf8(id.as_bytes())?;
|
|
||||||
if let Some(cb) = self.in_flight.remove(name) {
|
|
||||||
cb(code);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,177 +0,0 @@
|
||||||
// let's try this again
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
|
||||||
use futures::{
|
|
||||||
future::{self, BoxFuture, Future, FutureExt, TryFuture},
|
|
||||||
sink::{Sink, SinkExt},
|
|
||||||
stream::{Stream, StreamExt},
|
|
||||||
};
|
|
||||||
use panorama_imap::builders::command::Command;
|
|
||||||
use parking_lot::Mutex;
|
|
||||||
use tokio::{
|
|
||||||
io::{AsyncRead, AsyncWrite},
|
|
||||||
net::TcpStream,
|
|
||||||
sync::{oneshot, Notify},
|
|
||||||
};
|
|
||||||
use tokio_rustls::{rustls::ClientConfig, webpki::DNSNameRef, TlsConnector};
|
|
||||||
use tokio_util::codec::{Decoder, Framed, FramedRead, FramedWrite, LinesCodec, LinesCodecError};
|
|
||||||
|
|
||||||
use crate::config::{ImapConfig, TlsMethod};
|
|
||||||
|
|
||||||
pub async fn open_imap_connection(config: ImapConfig) -> Result<()> {
|
|
||||||
let server = config.server.as_str();
|
|
||||||
let port = config.port;
|
|
||||||
|
|
||||||
let stream = TcpStream::connect((server, port)).await?;
|
|
||||||
|
|
||||||
debug!("hellosu");
|
|
||||||
match config.tls {
|
|
||||||
TlsMethod::Off => begin_authentication(config, stream).await,
|
|
||||||
TlsMethod::On => {
|
|
||||||
let stream = perform_tls_negotiation(server.to_owned(), stream).await?;
|
|
||||||
begin_authentication(config, stream).await
|
|
||||||
}
|
|
||||||
TlsMethod::Starttls => {
|
|
||||||
let (stream, cmd_mgr) = CommandManager::new(stream);
|
|
||||||
let flights = cmd_mgr.flights();
|
|
||||||
|
|
||||||
// listen(stream, flights).await?;
|
|
||||||
// async move {
|
|
||||||
// let mut cmd_mgr = cmd_mgr;
|
|
||||||
// cmd_mgr.capabilities().await;
|
|
||||||
// }
|
|
||||||
// .await;
|
|
||||||
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs TLS negotiation, using the webpki_roots and verifying the server name
|
|
||||||
#[instrument(skip(server_name, stream))]
|
|
||||||
async fn perform_tls_negotiation(
|
|
||||||
server_name: impl AsRef<str>,
|
|
||||||
stream: impl AsyncRead + AsyncWrite + Unpin,
|
|
||||||
) -> Result<impl AsyncRead + AsyncWrite> {
|
|
||||||
let server_name = server_name.as_ref();
|
|
||||||
|
|
||||||
let mut tls_config = ClientConfig::new();
|
|
||||||
tls_config
|
|
||||||
.root_store
|
|
||||||
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
|
|
||||||
let tls_config = TlsConnector::from(Arc::new(tls_config));
|
|
||||||
let dnsname = DNSNameRef::try_from_ascii_str(server_name).unwrap();
|
|
||||||
let stream = tls_config.connect(dnsname, stream).await?;
|
|
||||||
|
|
||||||
Ok(stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn fetch_capabilities(stream: impl AsyncRead + AsyncWrite) -> Result<Vec<String>> {
|
|
||||||
let codec = LinesCodec::new();
|
|
||||||
let framed = codec.framed(stream);
|
|
||||||
|
|
||||||
todo!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(config, stream))]
|
|
||||||
async fn begin_authentication(
|
|
||||||
config: ImapConfig,
|
|
||||||
stream: impl AsyncRead + AsyncWrite,
|
|
||||||
) -> Result<()> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn listen(
|
|
||||||
mut stream: impl Stream<Item = Result<String, LinesCodecError>> + Unpin,
|
|
||||||
in_flight: InFlight,
|
|
||||||
) -> Result<()> {
|
|
||||||
debug!("listening for messages from server");
|
|
||||||
loop {
|
|
||||||
let line = match stream.next().await {
|
|
||||||
Some(v) => v?,
|
|
||||||
None => break,
|
|
||||||
};
|
|
||||||
debug!("line: {:?}", line);
|
|
||||||
|
|
||||||
let mut parts = line.split(' ');
|
|
||||||
let tag = parts.next().unwrap().parse()?; // TODO: handle empty
|
|
||||||
|
|
||||||
{
|
|
||||||
let mut in_flight = in_flight.lock();
|
|
||||||
if let Some(sender) = in_flight.remove(&tag) {
|
|
||||||
sender.send(()).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// trait ImapStream: AsyncRead + AsyncWrite + Send + Unpin {}
|
|
||||||
// impl<T: AsyncRead + AsyncWrite + Send + Unpin> ImapStream for T {}
|
|
||||||
|
|
||||||
trait ImapSink: Sink<String, Error = LinesCodecError> + Unpin {}
|
|
||||||
impl<T: Sink<String, Error = LinesCodecError> + Unpin> ImapSink for T {}
|
|
||||||
|
|
||||||
type InFlightMap = HashMap<usize, oneshot::Sender<()>>;
|
|
||||||
type InFlight = Arc<Mutex<InFlightMap>>;
|
|
||||||
|
|
||||||
struct CommandManager<'a> {
|
|
||||||
id: usize,
|
|
||||||
in_flight: Arc<Mutex<HashMap<usize, oneshot::Sender<()>>>>,
|
|
||||||
sink: Box<dyn ImapSink + 'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> CommandManager<'a> {
|
|
||||||
pub fn new(
|
|
||||||
stream: impl AsyncRead + AsyncWrite + 'a,
|
|
||||||
) -> (impl Stream<Item = Result<String, LinesCodecError>>, Self) {
|
|
||||||
let codec = LinesCodec::new();
|
|
||||||
let framed = codec.framed(stream);
|
|
||||||
let (framed_sink, framed_stream) = framed.split();
|
|
||||||
|
|
||||||
let cmd_mgr = CommandManager {
|
|
||||||
id: 0,
|
|
||||||
in_flight: Arc::new(Mutex::new(HashMap::new())),
|
|
||||||
sink: Box::new(framed_sink),
|
|
||||||
};
|
|
||||||
(framed_stream, cmd_mgr)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn flights(&self) -> Arc<Mutex<HashMap<usize, oneshot::Sender<()>>>> {
|
|
||||||
self.in_flight.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decompose(self) -> impl ImapSink + Unpin + 'a {
|
|
||||||
self.sink
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn capabilities(&mut self) -> Result<Vec<String>> {
|
|
||||||
self.exec(Command {
|
|
||||||
args: b"CAPABILITY".to_vec(),
|
|
||||||
next_state: None,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
Ok(vec![])
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn exec(&mut self, command: Command) -> Result<()> {
|
|
||||||
let id = self.id;
|
|
||||||
self.id += 1;
|
|
||||||
|
|
||||||
let cmd_str = String::from_utf8(command.args)?;
|
|
||||||
self.sink.send(cmd_str).await?;
|
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
|
||||||
{
|
|
||||||
let mut in_flight = self.in_flight.lock();
|
|
||||||
in_flight.insert(id, tx);
|
|
||||||
}
|
|
||||||
|
|
||||||
rx.await?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +1,5 @@
|
||||||
//! Mail
|
//! Mail
|
||||||
|
|
||||||
mod imap;
|
|
||||||
mod imap2;
|
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::stream::StreamExt;
|
use futures::stream::StreamExt;
|
||||||
use panorama_imap::{
|
use panorama_imap::{
|
||||||
|
@ -14,8 +11,6 @@ use tokio_stream::wrappers::WatchStream;
|
||||||
|
|
||||||
use crate::config::{Config, ConfigWatcher, MailAccountConfig, TlsMethod};
|
use crate::config::{Config, ConfigWatcher, MailAccountConfig, TlsMethod};
|
||||||
|
|
||||||
use self::imap2::open_imap_connection;
|
|
||||||
|
|
||||||
/// Command sent to the mail thread by something else (i.e. UI)
|
/// Command sent to the mail thread by something else (i.e. UI)
|
||||||
pub enum MailCommand {
|
pub enum MailCommand {
|
||||||
/// Refresh the list
|
/// Refresh the list
|
||||||
|
@ -30,7 +25,7 @@ pub async fn run_mail(
|
||||||
mut config_watcher: ConfigWatcher,
|
mut config_watcher: ConfigWatcher,
|
||||||
_cmd_in: UnboundedReceiver<MailCommand>,
|
_cmd_in: UnboundedReceiver<MailCommand>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut curr_conn: Option<JoinHandle<_>> = None;
|
let mut curr_conn: Vec<JoinHandle<_>> = Vec::new();
|
||||||
|
|
||||||
// let mut config_watcher = WatchStream::new(config_watcher);
|
// let mut config_watcher = WatchStream::new(config_watcher);
|
||||||
loop {
|
loop {
|
||||||
|
@ -43,13 +38,13 @@ pub async fn run_mail(
|
||||||
|
|
||||||
// TODO: gracefully shut down connection
|
// TODO: gracefully shut down connection
|
||||||
// just gonna drop the connection for now
|
// just gonna drop the connection for now
|
||||||
if let Some(curr_conn) = curr_conn.take() {
|
debug!("dropping all connections...");
|
||||||
debug!("dropping connection...");
|
for conn in curr_conn.drain(0..) {
|
||||||
curr_conn.abort();
|
conn.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
let handle = tokio::spawn(async {
|
|
||||||
for acct in config.mail_accounts.into_iter() {
|
for acct in config.mail_accounts.into_iter() {
|
||||||
|
let handle = tokio::spawn(async move {
|
||||||
debug!("opening imap connection for {:?}", acct);
|
debug!("opening imap connection for {:?}", acct);
|
||||||
match imap_main(acct).await {
|
match imap_main(acct).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
|
@ -57,11 +52,9 @@ pub async fn run_mail(
|
||||||
error!("IMAP Error: {}", err);
|
error!("IMAP Error: {}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// open_imap_connection(acct.imap).await.unwrap();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
curr_conn.push(handle);
|
||||||
curr_conn = Some(handle);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -69,6 +62,8 @@ pub async fn run_mail(
|
||||||
|
|
||||||
/// The main sequence of steps for the IMAP thread to follow
|
/// The main sequence of steps for the IMAP thread to follow
|
||||||
async fn imap_main(acct: MailAccountConfig) -> Result<()> {
|
async fn imap_main(acct: MailAccountConfig) -> Result<()> {
|
||||||
|
// loop ensures that the connection is retried after it dies
|
||||||
|
loop {
|
||||||
let builder: ClientConfig = ClientBuilder::default()
|
let builder: ClientConfig = ClientBuilder::default()
|
||||||
.hostname(acct.imap.server.clone())
|
.hostname(acct.imap.server.clone())
|
||||||
.port(acct.imap.port)
|
.port(acct.imap.port)
|
||||||
|
@ -94,6 +89,5 @@ async fn imap_main(acct: MailAccountConfig) -> Result<()> {
|
||||||
|
|
||||||
// debug!("sending CAPABILITY");
|
// debug!("sending CAPABILITY");
|
||||||
// let result = unauth.capabilities().await?;
|
// let result = unauth.capabilities().await?;
|
||||||
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue