i hate code

This commit is contained in:
Michael Zhang 2021-02-14 07:20:35 -06:00
parent de2336ab6d
commit 5ad3a844e1
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
5 changed files with 50 additions and 26 deletions

View file

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

View file

@ -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<()>;

View file

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

View file

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

View file

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