cargo clippy fixes + refactor
This commit is contained in:
parent
4ed955fb93
commit
706d397ad5
8 changed files with 108 additions and 83 deletions
|
@ -34,3 +34,6 @@ tokio-util = { version = "0.6.3", features = ["full"] }
|
||||||
toml = "0.5.8"
|
toml = "0.5.8"
|
||||||
webpki-roots = "0.21.0"
|
webpki-roots = "0.21.0"
|
||||||
xdg = "2.2.0"
|
xdg = "2.2.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
clippy = []
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt;
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::ops::{RangeFrom, RangeInclusive};
|
use std::ops::{RangeFrom, RangeInclusive};
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
|
@ -29,14 +29,14 @@ pub struct Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuration for a single mail account
|
/// Configuration for a single mail account
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct MailAccountConfig {
|
pub struct MailAccountConfig {
|
||||||
/// Imap
|
/// Imap
|
||||||
pub imap: ImapConfig,
|
pub imap: ImapConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Configuring an IMAP server
|
/// Configuring an IMAP server
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
pub struct ImapConfig {
|
pub struct ImapConfig {
|
||||||
/// Host of the IMAP server (needs to be hostname for TLS)
|
/// Host of the IMAP server (needs to be hostname for TLS)
|
||||||
pub server: String,
|
pub server: String,
|
||||||
|
@ -49,6 +49,25 @@ pub struct ImapConfig {
|
||||||
|
|
||||||
/// Password for authenticating to IMAP
|
/// Password for authenticating to IMAP
|
||||||
pub password: String,
|
pub password: String,
|
||||||
|
|
||||||
|
/// TLS
|
||||||
|
pub tls: TlsMethod,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Describes when to perform the TLS handshake
|
||||||
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
|
pub enum TlsMethod {
|
||||||
|
/// Perform TLS handshake immediately upon connection
|
||||||
|
#[serde(rename = "on")]
|
||||||
|
On,
|
||||||
|
|
||||||
|
/// Perform TLS handshake after issuing the STARTTLS command
|
||||||
|
#[serde(rename = "starttls")]
|
||||||
|
Starttls,
|
||||||
|
|
||||||
|
/// Don't perform TLS handshake at all (unsecured)
|
||||||
|
#[serde(rename = "off")]
|
||||||
|
Off,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
//! Mail
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -16,62 +14,13 @@ use panorama_imap::{
|
||||||
parser::parse_response,
|
parser::parse_response,
|
||||||
types::{Capability, RequestId, Response, ResponseCode, State, Status},
|
types::{Capability, RequestId, Response, ResponseCode, State, Status},
|
||||||
};
|
};
|
||||||
use tokio::{
|
use tokio::{net::TcpStream, sync::mpsc};
|
||||||
net::TcpStream,
|
|
||||||
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_util::codec::{Decoder, LinesCodec, LinesCodecError};
|
use tokio_util::codec::{Decoder, LinesCodec, LinesCodecError};
|
||||||
|
|
||||||
use crate::config::{Config, ConfigWatcher, ImapConfig};
|
use crate::config::ImapConfig;
|
||||||
|
|
||||||
/// Command sent to the mail thread by something else (i.e. UI)
|
pub async fn open_imap_connection(config: ImapConfig) -> Result<()> {
|
||||||
pub enum MailCommand {
|
|
||||||
/// Refresh the list
|
|
||||||
Refresh,
|
|
||||||
|
|
||||||
/// Send a raw command
|
|
||||||
Raw(Command),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Main entrypoint for the mail listener.
|
|
||||||
pub async fn run_mail(
|
|
||||||
config_watcher: ConfigWatcher,
|
|
||||||
cmd_in: UnboundedReceiver<MailCommand>,
|
|
||||||
) -> Result<()> {
|
|
||||||
let mut curr_conn: Option<JoinHandle<_>> = None;
|
|
||||||
|
|
||||||
let mut config_watcher = WatchStream::new(config_watcher);
|
|
||||||
loop {
|
|
||||||
debug!("listening for configs");
|
|
||||||
let a = config_watcher.next().await;
|
|
||||||
debug!("got config {:?}", a);
|
|
||||||
let config: Config = match a {
|
|
||||||
Some(Some(v)) => v,
|
|
||||||
_ => 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(async {
|
|
||||||
for acct in config.mail_accounts.into_iter() {
|
|
||||||
open_imap_connection(acct.imap);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
curr_conn = Some(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn open_imap_connection(config: ImapConfig) -> Result<()> {
|
|
||||||
debug!(
|
debug!(
|
||||||
"Opening imap connection to {}:{}",
|
"Opening imap connection to {}:{}",
|
||||||
config.server, config.port
|
config.server, config.port
|
||||||
|
@ -165,6 +114,7 @@ where
|
||||||
debug!("<<< {:?}", resp);
|
debug!("<<< {:?}", resp);
|
||||||
|
|
||||||
match st {
|
match st {
|
||||||
|
State::Authenticated => {}
|
||||||
State::NotAuthenticated => match resp {
|
State::NotAuthenticated => match resp {
|
||||||
Response::Data {
|
Response::Data {
|
||||||
status: Status::Ok,
|
status: Status::Ok,
|
||||||
|
@ -194,7 +144,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Response::Capabilities(caps) => {
|
Response::Capabilities(_caps) => {
|
||||||
if with_ssl {
|
if with_ssl {
|
||||||
// send authentication information
|
// send authentication information
|
||||||
let cmd = Command {
|
let cmd = Command {
|
||||||
|
@ -226,10 +176,12 @@ where
|
||||||
Ok(LoopExit::Closed)
|
Ok(LoopExit::Closed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InFlightFunc = Box<dyn Fn(Option<ResponseCode>) + Send>;
|
||||||
|
|
||||||
/// A struct in charge of managing multiple in-flight commands.
|
/// A struct in charge of managing multiple in-flight commands.
|
||||||
struct CommandManager<S> {
|
struct CommandManager<S> {
|
||||||
tag_idx: usize,
|
tag_idx: usize,
|
||||||
in_flight: HashMap<String, Box<dyn Fn(Option<ResponseCode>) + Send>>,
|
in_flight: HashMap<String, InFlightFunc>,
|
||||||
sink: S,
|
sink: S,
|
||||||
}
|
}
|
||||||
|
|
57
src/mail/mod.rs
Normal file
57
src/mail/mod.rs
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
//! Mail
|
||||||
|
|
||||||
|
mod imap;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use futures::stream::StreamExt;
|
||||||
|
use panorama_imap::builders::command::Command as ImapCommand;
|
||||||
|
use tokio::{sync::mpsc::UnboundedReceiver, task::JoinHandle};
|
||||||
|
use tokio_stream::wrappers::WatchStream;
|
||||||
|
|
||||||
|
use crate::config::{Config, ConfigWatcher};
|
||||||
|
|
||||||
|
use self::imap::open_imap_connection;
|
||||||
|
|
||||||
|
/// Command sent to the mail thread by something else (i.e. UI)
|
||||||
|
pub enum MailCommand {
|
||||||
|
/// Refresh the list
|
||||||
|
Refresh,
|
||||||
|
|
||||||
|
/// Send a raw command
|
||||||
|
Raw(ImapCommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Main entrypoint for the mail listener.
|
||||||
|
pub async fn run_mail(
|
||||||
|
config_watcher: ConfigWatcher,
|
||||||
|
_cmd_in: UnboundedReceiver<MailCommand>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let mut curr_conn: Option<JoinHandle<_>> = None;
|
||||||
|
|
||||||
|
let mut config_watcher = WatchStream::new(config_watcher);
|
||||||
|
loop {
|
||||||
|
debug!("listening for configs");
|
||||||
|
let a = config_watcher.next().await;
|
||||||
|
debug!("got config {:?}", a);
|
||||||
|
let config: Config = match a {
|
||||||
|
Some(Some(v)) => v,
|
||||||
|
_ => break,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: gracefully shut down connection
|
||||||
|
// just gonna drop the connection for now
|
||||||
|
if let Some(curr_conn) = curr_conn.take() {
|
||||||
|
debug!("dropping connection...");
|
||||||
|
curr_conn.abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
let handle = tokio::spawn(async {
|
||||||
|
for acct in config.mail_accounts.into_iter() {
|
||||||
|
open_imap_connection(acct.imap).await.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
curr_conn = Some(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
15
src/main.rs
15
src/main.rs
|
@ -1,18 +1,13 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
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::{
|
use panorama::{config::spawn_config_watcher, mail, ui};
|
||||||
config::{spawn_config_watcher, Config},
|
|
||||||
mail, ui,
|
|
||||||
};
|
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
use tokio::sync::{mpsc, oneshot};
|
use tokio::sync::mpsc;
|
||||||
use xdg::BaseDirectories;
|
use xdg::BaseDirectories;
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
|
@ -35,14 +30,14 @@ async fn main() -> Result<()> {
|
||||||
// print logs to file as directed by command line options
|
// print logs to file as directed by command line options
|
||||||
setup_logger(&opt)?;
|
setup_logger(&opt)?;
|
||||||
|
|
||||||
let xdg = BaseDirectories::new()?;
|
let _xdg = BaseDirectories::new()?;
|
||||||
let (config_thread, config_update) = spawn_config_watcher()?;
|
let (_config_thread, config_update) = spawn_config_watcher()?;
|
||||||
|
|
||||||
// used to notify the runtime that the process should exit
|
// used to notify the runtime that the process should exit
|
||||||
let (exit_tx, mut exit_rx) = mpsc::channel::<()>(1);
|
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));
|
tokio::spawn(mail::run_mail(config_update.clone(), mail_rx).unwrap_or_else(report_err));
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ use crossterm::{
|
||||||
cursor,
|
cursor,
|
||||||
event::{self, Event, KeyCode, KeyEvent},
|
event::{self, Event, KeyCode, KeyEvent},
|
||||||
style::{self, Color},
|
style::{self, Color},
|
||||||
terminal::{self, ClearType},
|
terminal,
|
||||||
};
|
};
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
|
|
||||||
|
@ -58,12 +58,13 @@ pub async fn run_ui(mut w: impl Write, exit: ExitSender) -> Result<()> {
|
||||||
if event::poll(FRAME)? {
|
if event::poll(FRAME)? {
|
||||||
let event = event::read()?;
|
let event = event::read()?;
|
||||||
table.update(&event);
|
table.update(&event);
|
||||||
match event {
|
|
||||||
Event::Key(KeyEvent {
|
if let Event::Key(KeyEvent {
|
||||||
code: KeyCode::Char('q'),
|
code: KeyCode::Char('q'),
|
||||||
..
|
..
|
||||||
}) => break,
|
}) = event
|
||||||
_ => {}
|
{
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ pub struct Table {
|
||||||
|
|
||||||
impl Table {
|
impl Table {
|
||||||
pub fn update(&mut self, event: &Event) {
|
pub fn update(&mut self, event: &Event) {
|
||||||
match event {
|
if let Event::Key(KeyEvent { code, .. }) = event {
|
||||||
Event::Key(KeyEvent { code, .. }) => match code {
|
match code {
|
||||||
KeyCode::Char('j') => {
|
KeyCode::Char('j') => {
|
||||||
if let Some(selected_row) = &mut self.selected_row {
|
if let Some(selected_row) = &mut self.selected_row {
|
||||||
*selected_row = (self.rows.len() as u16 - 1).min(*selected_row + 1);
|
*selected_row = (self.rows.len() as u16 - 1).min(*selected_row + 1);
|
||||||
|
@ -27,13 +27,12 @@ impl Table {
|
||||||
KeyCode::Char('k') => {
|
KeyCode::Char('k') => {
|
||||||
if let Some(selected_row) = &mut self.selected_row {
|
if let Some(selected_row) = &mut self.selected_row {
|
||||||
if *selected_row > 0 {
|
if *selected_row > 0 {
|
||||||
*selected_row = *selected_row - 1;
|
*selected_row -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
}
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +44,7 @@ impl Table {
|
||||||
let mut columns = Vec::new();
|
let mut columns = Vec::new();
|
||||||
for row in self.rows.iter() {
|
for row in self.rows.iter() {
|
||||||
for (i, cell) in row.iter().enumerate() {
|
for (i, cell) in row.iter().enumerate() {
|
||||||
if columns.len() == 0 || columns.len() - 1 < i {
|
if columns.is_empty() || columns.len() - 1 < i {
|
||||||
columns.push(0);
|
columns.push(0);
|
||||||
} else {
|
} else {
|
||||||
columns[i] = cell.len().max(columns[i]);
|
columns[i] = cell.len().max(columns[i]);
|
||||||
|
@ -105,7 +104,7 @@ impl Table {
|
||||||
|
|
||||||
pub fn push_row(&mut self, row: Vec<String>) {
|
pub fn push_row(&mut self, row: Vec<String>) {
|
||||||
self.rows.push(row);
|
self.rows.push(row);
|
||||||
if let None = self.selected_row {
|
if self.selected_row.is_none() {
|
||||||
self.selected_row = Some(0);
|
self.selected_row = Some(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue