i hate code
This commit is contained in:
parent
de2336ab6d
commit
5ad3a844e1
5 changed files with 50 additions and 26 deletions
|
@ -3,14 +3,14 @@
|
||||||
//! One of the primary goals of panorama is to be able to always hot-reload configuration files.
|
//! One of the primary goals of panorama is to be able to always hot-reload configuration files.
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::sync::mpsc::{self, Receiver};
|
|
||||||
use std::time::Duration;
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::mpsc::{self, Receiver};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use anyhow::{Result, Context};
|
use anyhow::{Context, Result};
|
||||||
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use tokio::sync::watch;
|
use tokio::{sync::watch, task::JoinHandle};
|
||||||
use xdg::BaseDirectories;
|
use xdg::BaseDirectories;
|
||||||
|
|
||||||
/// Alias for a MailConfig receiver.
|
/// Alias for a MailConfig receiver.
|
||||||
|
@ -34,17 +34,16 @@ pub struct MailConfig {
|
||||||
|
|
||||||
/// Spawns a notify::RecommendedWatcher to watch the XDG config directory. Whenever the config file
|
/// Spawns a notify::RecommendedWatcher to watch the XDG config directory. Whenever the config file
|
||||||
/// is updated, the config file is parsed and sent to the receiver.
|
/// is updated, the config file is parsed and sent to the receiver.
|
||||||
fn start_watcher() -> Result<(
|
fn start_watcher() -> Result<(RecommendedWatcher, Receiver<DebouncedEvent>)> {
|
||||||
RecommendedWatcher,
|
|
||||||
Receiver<DebouncedEvent>,
|
|
||||||
)> {
|
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
let mut watcher = RecommendedWatcher::new(tx, Duration::from_secs(5))?;
|
let mut watcher = RecommendedWatcher::new(tx, Duration::from_secs(5))?;
|
||||||
|
|
||||||
let xdg = BaseDirectories::new()?;
|
let xdg = BaseDirectories::new()?;
|
||||||
let config_home = xdg.get_config_home();
|
let config_home = xdg.get_config_home();
|
||||||
debug!("config_home: {:?}", config_home);
|
debug!("config_home: {:?}", config_home);
|
||||||
watcher.watch(config_home.join("panorama"), RecursiveMode::Recursive).context("could not watch config_home")?;
|
watcher
|
||||||
|
.watch(config_home.join("panorama"), RecursiveMode::Recursive)
|
||||||
|
.context("could not watch config_home")?;
|
||||||
|
|
||||||
Ok((watcher, rx))
|
Ok((watcher, rx))
|
||||||
}
|
}
|
||||||
|
@ -85,11 +84,12 @@ async fn watcher_loop(
|
||||||
|
|
||||||
/// Start the entire config watcher system, and return a [ConfigWatcher][self::ConfigWatcher],
|
/// Start the entire config watcher system, and return a [ConfigWatcher][self::ConfigWatcher],
|
||||||
/// which is a cloneable receiver of config update events.
|
/// which is a cloneable receiver of config update events.
|
||||||
pub fn spawn_config_watcher() -> Result<ConfigWatcher> {
|
pub fn spawn_config_watcher() -> Result<(JoinHandle<()>, ConfigWatcher)> {
|
||||||
let (_watcher, config_rx) = start_watcher()?;
|
let (watcher, config_rx) = start_watcher()?;
|
||||||
let (config_tx, config_update) = watch::channel(None);
|
let (config_tx, config_update) = watch::channel(None);
|
||||||
|
|
||||||
tokio::spawn(async move {
|
let config_thread = tokio::spawn(async move {
|
||||||
|
let _watcher = watcher;
|
||||||
match watcher_loop(config_rx, config_tx).await {
|
match watcher_loop(config_rx, config_tx).await {
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -98,5 +98,5 @@ pub fn spawn_config_watcher() -> Result<ConfigWatcher> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(config_update)
|
Ok((config_thread, config_update))
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,4 +17,4 @@ pub mod mail;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
/// A cloneable type that allows sending an exit-"signal" to stop the application.
|
/// A cloneable type that allows sending an exit-"signal" to stop the application.
|
||||||
pub type ExitSender = tokio::sync::oneshot::Sender<()>;
|
pub type ExitSender = tokio::sync::mpsc::Sender<()>;
|
||||||
|
|
18
src/mail.rs
18
src/mail.rs
|
@ -19,12 +19,13 @@ use panorama_imap::{
|
||||||
use tokio::{
|
use tokio::{
|
||||||
net::TcpStream,
|
net::TcpStream,
|
||||||
sync::mpsc::{self, UnboundedReceiver},
|
sync::mpsc::{self, UnboundedReceiver},
|
||||||
|
task::JoinHandle,
|
||||||
};
|
};
|
||||||
use tokio_rustls::{rustls::ClientConfig, webpki::DNSNameRef, TlsConnector};
|
use tokio_rustls::{rustls::ClientConfig, webpki::DNSNameRef, TlsConnector};
|
||||||
use tokio_stream::wrappers::WatchStream;
|
use tokio_stream::wrappers::WatchStream;
|
||||||
use tokio_util::codec::{Decoder, LinesCodec, LinesCodecError};
|
use tokio_util::codec::{Decoder, LinesCodec, LinesCodecError};
|
||||||
|
|
||||||
use crate::config::{MailConfig, ConfigWatcher};
|
use crate::config::{ConfigWatcher, MailConfig};
|
||||||
|
|
||||||
/// 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 {
|
||||||
|
@ -40,7 +41,7 @@ pub async fn run_mail(
|
||||||
config_watcher: ConfigWatcher,
|
config_watcher: ConfigWatcher,
|
||||||
cmd_in: UnboundedReceiver<MailCommand>,
|
cmd_in: UnboundedReceiver<MailCommand>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut curr_conn = None;
|
let mut curr_conn: Option<JoinHandle<_>> = None;
|
||||||
|
|
||||||
let mut config_watcher = WatchStream::new(config_watcher);
|
let mut config_watcher = WatchStream::new(config_watcher);
|
||||||
loop {
|
loop {
|
||||||
|
@ -49,6 +50,13 @@ pub async fn run_mail(
|
||||||
_ => break,
|
_ => break,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// TODO: gracefully shut down connection
|
||||||
|
// just gonna drop the connection for now
|
||||||
|
if let Some(mut curr_conn) = curr_conn.take() {
|
||||||
|
debug!("dropping connection...");
|
||||||
|
curr_conn.abort();
|
||||||
|
}
|
||||||
|
|
||||||
let handle = tokio::spawn(open_imap_connection(config));
|
let handle = tokio::spawn(open_imap_connection(config));
|
||||||
curr_conn = Some(handle);
|
curr_conn = Some(handle);
|
||||||
}
|
}
|
||||||
|
@ -61,6 +69,7 @@ async fn open_imap_connection(config: MailConfig) -> Result<()> {
|
||||||
"Opening imap connection to {}:{}",
|
"Opening imap connection to {}:{}",
|
||||||
config.server, config.port
|
config.server, config.port
|
||||||
);
|
);
|
||||||
|
|
||||||
let server = config.server.as_str();
|
let server = config.server.as_str();
|
||||||
let port = config.port;
|
let port = config.port;
|
||||||
|
|
||||||
|
@ -71,6 +80,7 @@ async fn open_imap_connection(config: MailConfig) -> Result<()> {
|
||||||
let (sink, stream) = framed.split::<String>();
|
let (sink, stream) = framed.split::<String>();
|
||||||
|
|
||||||
let result = listen_loop(config.clone(), &mut state, sink, stream, false).await?;
|
let result = listen_loop(config.clone(), &mut state, sink, stream, false).await?;
|
||||||
|
|
||||||
if let LoopExit::NegotiateTls(stream, sink) = result {
|
if let LoopExit::NegotiateTls(stream, sink) = result {
|
||||||
debug!("negotiating tls");
|
debug!("negotiating tls");
|
||||||
let mut tls_config = ClientConfig::new();
|
let mut tls_config = ClientConfig::new();
|
||||||
|
@ -97,7 +107,9 @@ async fn open_imap_connection(config: MailConfig) -> Result<()> {
|
||||||
/// Action that should be taken after the connection loop quits.
|
/// Action that should be taken after the connection loop quits.
|
||||||
enum LoopExit<S, S2> {
|
enum LoopExit<S, S2> {
|
||||||
/// Used in case the STARTTLS command is issued; perform TLS negotiation on top of the current
|
/// Used in case the STARTTLS command is issued; perform TLS negotiation on top of the current
|
||||||
/// stream
|
/// stream.
|
||||||
|
///
|
||||||
|
/// S and S2 are stream and sink types, respectively.
|
||||||
NegotiateTls(S, S2),
|
NegotiateTls(S, S2),
|
||||||
Closed,
|
Closed,
|
||||||
}
|
}
|
||||||
|
|
27
src/main.rs
27
src/main.rs
|
@ -1,13 +1,19 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::future::TryFutureExt;
|
use futures::future::TryFutureExt;
|
||||||
|
use panorama::{
|
||||||
|
config::{spawn_config_watcher, MailConfig},
|
||||||
|
mail, ui,
|
||||||
|
};
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::{mpsc, oneshot};
|
||||||
use xdg::BaseDirectories;
|
use xdg::BaseDirectories;
|
||||||
use panorama::{config::{spawn_config_watcher, MailConfig}, mail, ui};
|
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
#[structopt(author, about)]
|
#[structopt(author, about)]
|
||||||
|
@ -30,7 +36,7 @@ async fn main() -> Result<()> {
|
||||||
setup_logger(&opt)?;
|
setup_logger(&opt)?;
|
||||||
|
|
||||||
let xdg = BaseDirectories::new()?;
|
let xdg = BaseDirectories::new()?;
|
||||||
let config_update = spawn_config_watcher()?;
|
let (config_thread, config_update) = spawn_config_watcher()?;
|
||||||
|
|
||||||
// let config: MailConfig = {
|
// let config: MailConfig = {
|
||||||
// let config_path = opt
|
// let config_path = opt
|
||||||
|
@ -44,17 +50,22 @@ async fn main() -> Result<()> {
|
||||||
// };
|
// };
|
||||||
|
|
||||||
// used to notify the runtime that the process should exit
|
// used to notify the runtime that the process should exit
|
||||||
let (exit_tx, exit_rx) = oneshot::channel::<()>();
|
let (exit_tx, mut exit_rx) = mpsc::channel::<()>(1);
|
||||||
|
|
||||||
// used to send commands to the mail service
|
// used to send commands to the mail service
|
||||||
let (mail_tx, mail_rx) = mpsc::unbounded_channel();
|
let (mail_tx, mail_rx) = mpsc::unbounded_channel();
|
||||||
|
|
||||||
tokio::spawn(mail::run_mail(config_update.clone(), mail_rx).unwrap_or_else(report_err));
|
let mail_thread = tokio::spawn(mail::run_mail(config_update.clone(), mail_rx).unwrap_or_else(report_err));
|
||||||
let stdout = std::io::stdout();
|
let stdout = std::io::stdout();
|
||||||
tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err));
|
let ui_thread = tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err));
|
||||||
|
|
||||||
exit_rx.await?;
|
exit_rx.recv().await;
|
||||||
Ok(())
|
|
||||||
|
// TODO: graceful shutdown
|
||||||
|
// yada yada create a background process and pass off the connections so they can be safely
|
||||||
|
// shutdown
|
||||||
|
std::process::exit(0);
|
||||||
|
// Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_logger(opt: &Opt) -> Result<()> {
|
fn setup_logger(opt: &Opt) -> Result<()> {
|
||||||
|
@ -79,5 +90,5 @@ fn setup_logger(opt: &Opt) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report_err(err: anyhow::Error) {
|
fn report_err(err: anyhow::Error) {
|
||||||
log::error!("error: {:?}", err);
|
error!("error: {:?}", err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ pub async fn run_ui(mut w: impl Write, exit: ExitSender) -> Result<()> {
|
||||||
)?;
|
)?;
|
||||||
terminal::disable_raw_mode()?;
|
terminal::disable_raw_mode()?;
|
||||||
|
|
||||||
exit.send(()).expect("fake news?");
|
exit.send(()).await?;
|
||||||
|
debug!("sent exit");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue